提交 109e37a6 编写于 作者: G Greg Kroah-Hartman

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

usb: changes for v4.18 merge window

A total of 98 non-merge commits, the biggest part being in dwc3 this
time around with a large refactoring of dwc3's transfer handling code.

We also have a new driver for Aspeed virtual hub controller.

Apart from that, just a list of miscellaneous fixes all over the place.
......@@ -7,6 +7,26 @@ Required properties:
- compatible: must be "snps,dwc3"
- reg : Address and length of the register set for the device
- interrupts: Interrupts used by the dwc3 controller.
- clock-names: should contain "ref", "bus_early", "suspend"
- clocks: list of phandle and clock specifier pairs corresponding to
entries in the clock-names property.
Exception for clocks:
clocks are optional if the parent node (i.e. glue-layer) is compatible to
one of the following:
"amlogic,meson-axg-dwc3"
"amlogic,meson-gxl-dwc3"
"cavium,octeon-7130-usb-uctl"
"qcom,dwc3"
"samsung,exynos5250-dwusb3"
"samsung,exynos7-dwusb3"
"sprd,sc9860-dwc3"
"st,stih407-dwc3"
"ti,am437x-dwc3"
"ti,dwc3"
"ti,keystone-dwc3"
"rockchip,rk3399-dwc3"
"xlnx,zynqmp-dwc3"
Optional properties:
- usb-phy : array of phandle for the PHY device. The first element
......@@ -15,6 +35,7 @@ Optional properties:
- phys: from the *Generic PHY* bindings
- phy-names: from the *Generic PHY* bindings; supported names are "usb2-phy"
or "usb3-phy".
- resets: a single pair of phandle and reset specifier
- snps,usb3_lpm_capable: determines if platform is USB3 LPM capable
- snps,disable_scramble_quirk: true when SW should disable data scrambling.
Only really useful for FPGA builds.
......
Qualcomm SuperSpeed DWC3 USB SoC controller
Required properties:
- compatible: should contain "qcom,dwc3"
- compatible: Compatible list, contains
"qcom,dwc3"
"qcom,msm8996-dwc3" for msm8996 SOC.
"qcom,sdm845-dwc3" for sdm845 SOC.
- reg: Offset and length of register set for QSCRATCH wrapper
- power-domains: specifies a phandle to PM domain provider node
- clocks: A list of phandle + clock-specifier pairs for the
clocks listed in clock-names
- clock-names: Should contain the following:
- clock-names: Should contain the following:
"core" Master/Core clock, have to be >= 125 MHz for SS
operation and >= 60MHz for HS operation
"mock_utmi" Mock utmi clock needed for ITP/SOF generation in
host mode. Its frequency should be 19.2MHz.
"sleep" Sleep clock, used for wakeup when USB3 core goes
into low power mode (U3).
Optional clocks:
"iface" System bus AXI clock. Not present on all platforms
"sleep" Sleep clock, used when USB3 core goes into low
power mode (U3).
"iface" System bus AXI clock.
Not present on "qcom,msm8996-dwc3" compatible.
"cfg_noc" System Config NOC clock.
Not present on "qcom,msm8996-dwc3" compatible.
- assigned-clocks: Should be:
MOCK_UTMI_CLK
MASTER_CLK
- assigned-clock-rates: Should be:
19.2Mhz (192000000) for MOCK_UTMI_CLK
>=125Mhz (125000000) for MASTER_CLK in SS mode
>=60Mhz (60000000) for MASTER_CLK in HS mode
Optional properties:
- resets: Phandle to reset control that resets core and wrapper.
- interrupts: specifies interrupts from controller wrapper used
to wakeup from low power/susepnd state. Must contain
one or more entry for interrupt-names property
- interrupt-names: Must include the following entries:
- "hs_phy_irq": The interrupt that is asserted when a
wakeup event is received on USB2 bus
- "ss_phy_irq": The interrupt that is asserted when a
wakeup event is received on USB3 bus
- "dm_hs_phy_irq" and "dp_hs_phy_irq": Separate
interrupts for any wakeup event on DM and DP lines
- qcom,select-utmi-as-pipe-clk: if present, disable USB3 pipe_clk requirement.
Used when dwc3 operates without SSPHY and only
HS/FS/LS modes are supported.
Required child node:
A child node must exist to represent the core DWC3 IP block. The name of
the node is not important. The content of the node is defined in dwc3.txt.
Phy documentation is provided in the following places:
Documentation/devicetree/bindings/phy/qcom-dwc3-usb-phy.txt
Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt - USB3 QMP PHY
Documentation/devicetree/bindings/phy/qcom-qusb2-phy.txt - USB2 QUSB2 PHY
Example device nodes:
hs_phy: phy@100f8800 {
compatible = "qcom,dwc3-hs-usb-phy";
reg = <0x100f8800 0x30>;
clocks = <&gcc USB30_0_UTMI_CLK>;
clock-names = "ref";
#phy-cells = <0>;
compatible = "qcom,qusb2-v2-phy";
...
};
ss_phy: phy@100f8830 {
compatible = "qcom,dwc3-ss-usb-phy";
reg = <0x100f8830 0x30>;
clocks = <&gcc USB30_0_MASTER_CLK>;
clock-names = "ref";
#phy-cells = <0>;
compatible = "qcom,qmp-v3-usb3-phy";
...
};
usb3_0: usb30@0 {
usb3_0: usb30@a6f8800 {
compatible = "qcom,dwc3";
reg = <0xa6f8800 0x400>;
#address-cells = <1>;
#size-cells = <1>;
clocks = <&gcc USB30_0_MASTER_CLK>;
clock-names = "core";
ranges;
interrupts = <0 131 0>, <0 486 0>, <0 488 0>, <0 489 0>;
interrupt-names = "hs_phy_irq", "ss_phy_irq",
"dm_hs_phy_irq", "dp_hs_phy_irq";
clocks = <&gcc GCC_USB30_PRIM_MASTER_CLK>,
<&gcc GCC_USB30_PRIM_MOCK_UTMI_CLK>,
<&gcc GCC_USB30_PRIM_SLEEP_CLK>;
clock-names = "core", "mock_utmi", "sleep";
assigned-clocks = <&gcc GCC_USB30_PRIM_MOCK_UTMI_CLK>,
<&gcc GCC_USB30_PRIM_MASTER_CLK>;
assigned-clock-rates = <19200000>, <133000000>;
resets = <&gcc GCC_USB30_PRIM_BCR>;
reset-names = "core_reset";
power-domains = <&gcc USB30_PRIM_GDSC>;
qcom,select-utmi-as-pipe-clk;
dwc3@10000000 {
compatible = "snps,dwc3";
......
......@@ -674,9 +674,8 @@ operations, both of which can be traced. Format is::
__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_TRANSFER_STARTED ? '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 ? '<' : '>'
)
......
......@@ -419,6 +419,8 @@ static void dwc2_wait_for_mode(struct dwc2_hsotg *hsotg,
/**
* dwc2_iddig_filter_enabled() - Returns true if the IDDIG debounce
* filter is enabled.
*
* @hsotg: Programming view of DWC_otg controller
*/
static bool dwc2_iddig_filter_enabled(struct dwc2_hsotg *hsotg)
{
......@@ -564,6 +566,9 @@ int dwc2_core_reset(struct dwc2_hsotg *hsotg, bool skip_wait)
* If a force is done, it requires a IDDIG debounce filter delay if
* the filter is configured and enabled. We poll the current mode of
* the controller to account for this delay.
*
* @hsotg: Programming view of DWC_otg controller
* @host: Host mode flag
*/
void dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host)
{
......@@ -610,6 +615,8 @@ void dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host)
* or not because the value of the connector ID status is affected by
* the force mode. We only need to call this once during probe if
* dr_mode == OTG.
*
* @hsotg: Programming view of DWC_otg controller
*/
static void dwc2_clear_force_mode(struct dwc2_hsotg *hsotg)
{
......
......@@ -164,12 +164,11 @@ struct dwc2_hsotg_req;
* and has yet to be completed (maybe due to data move, or simply
* awaiting an ack from the core all the data has been completed).
* @debugfs: File entry for debugfs file for this endpoint.
* @lock: State lock to protect contents of endpoint.
* @dir_in: Set to true if this endpoint is of the IN direction, which
* means that it is sending data to the Host.
* @index: The index for the endpoint registers.
* @mc: Multi Count - number of transactions per microframe
* @interval - Interval for periodic endpoints, in frames or microframes.
* @interval: Interval for periodic endpoints, in frames or microframes.
* @name: The name array passed to the USB core.
* @halted: Set if the endpoint has been halted.
* @periodic: Set if this is a periodic ep, such as Interrupt
......@@ -178,10 +177,11 @@ struct dwc2_hsotg_req;
* @desc_list_dma: The DMA address of descriptor chain currently in use.
* @desc_list: Pointer to descriptor DMA chain head currently in use.
* @desc_count: Count of entries within the DMA descriptor chain of EP.
* @isoc_chain_num: Number of ISOC chain currently in use - either 0 or 1.
* @next_desc: index of next free descriptor in the ISOC chain under SW control.
* @compl_desc: index of next descriptor to be completed by xFerComplete
* @total_data: The total number of data bytes done.
* @fifo_size: The size of the FIFO (for periodic IN endpoints)
* @fifo_index: For Dedicated FIFO operation, only FIFO0 can be used for EP0.
* @fifo_load: The amount of data loaded into the FIFO (periodic IN)
* @last_load: The offset of data for the last start of request.
* @size_loaded: The last loaded size for DxEPTSIZE for periodic IN
......@@ -231,8 +231,8 @@ struct dwc2_hsotg_ep {
struct dwc2_dma_desc *desc_list;
u8 desc_count;
unsigned char isoc_chain_num;
unsigned int next_desc;
unsigned int compl_desc;
char name[10];
};
......@@ -380,6 +380,12 @@ enum dwc2_ep0_state {
* is FS.
* 0 - No (default)
* 1 - Yes
* @ipg_isoc_en: Indicates the IPG supports is enabled or disabled.
* 0 - Disable (default)
* 1 - Enable
* @acg_enable: For enabling Active Clock Gating in the controller
* 0 - No
* 1 - Yes
* @ulpi_fs_ls: Make ULPI phy operate in FS/LS mode only
* 0 - No (default)
* 1 - Yes
......@@ -511,6 +517,7 @@ struct dwc2_core_params {
bool hird_threshold_en;
u8 hird_threshold;
bool activate_stm_fs_transceiver;
bool ipg_isoc_en;
u16 max_packet_count;
u32 max_transfer_size;
u32 ahbcfg;
......@@ -548,7 +555,7 @@ struct dwc2_core_params {
*
* The values that are not in dwc2_core_params are documented below.
*
* @op_mode Mode of Operation
* @op_mode: Mode of Operation
* 0 - HNP- and SRP-Capable OTG (Host & Device)
* 1 - SRP-Capable OTG (Host & Device)
* 2 - Non-HNP and Non-SRP Capable OTG (Host & Device)
......@@ -556,43 +563,102 @@ struct dwc2_core_params {
* 4 - Non-OTG Device
* 5 - SRP-Capable Host
* 6 - Non-OTG Host
* @arch Architecture
* @arch: Architecture
* 0 - Slave only
* 1 - External DMA
* 2 - Internal DMA
* @power_optimized Are power optimizations enabled?
* @num_dev_ep Number of device endpoints available
* @num_dev_in_eps Number of device IN endpoints available
* @num_dev_perio_in_ep Number of device periodic IN endpoints
* available
* @dev_token_q_depth Device Mode IN Token Sequence Learning Queue
* @ipg_isoc_en: This feature indicates that the controller supports
* the worst-case scenario of Rx followed by Rx
* Interpacket Gap (IPG) (32 bitTimes) as per the utmi
* specification for any token following ISOC OUT token.
* 0 - Don't support
* 1 - Support
* @power_optimized: Are power optimizations enabled?
* @num_dev_ep: Number of device endpoints available
* @num_dev_in_eps: Number of device IN endpoints available
* @num_dev_perio_in_ep: Number of device periodic IN endpoints
* available
* @dev_token_q_depth: Device Mode IN Token Sequence Learning Queue
* Depth
* 0 to 30
* @host_perio_tx_q_depth
* @host_perio_tx_q_depth:
* Host Mode Periodic Request Queue Depth
* 2, 4 or 8
* @nperio_tx_q_depth
* @nperio_tx_q_depth:
* Non-Periodic Request Queue Depth
* 2, 4 or 8
* @hs_phy_type High-speed PHY interface type
* @hs_phy_type: High-speed PHY interface type
* 0 - High-speed interface not supported
* 1 - UTMI+
* 2 - ULPI
* 3 - UTMI+ and ULPI
* @fs_phy_type Full-speed PHY interface type
* @fs_phy_type: Full-speed PHY interface type
* 0 - Full speed interface not supported
* 1 - Dedicated full speed interface
* 2 - FS pins shared with UTMI+ pins
* 3 - FS pins shared with ULPI pins
* @total_fifo_size: Total internal RAM for FIFOs (bytes)
* @hibernation Is hibernation enabled?
* @utmi_phy_data_width UTMI+ PHY data width
* @hibernation: Is hibernation enabled?
* @utmi_phy_data_width: UTMI+ PHY data width
* 0 - 8 bits
* 1 - 16 bits
* 2 - 8 or 16 bits
* @snpsid: Value from SNPSID register
* @dev_ep_dirs: Direction of device endpoints (GHWCFG1)
* @g_tx_fifo_size[] Power-on values of TxFIFO sizes
* @g_tx_fifo_size: Power-on values of TxFIFO sizes
* @dma_desc_enable: When DMA mode is enabled, specifies whether to use
* address DMA mode or descriptor DMA mode for accessing
* the data FIFOs. The driver will automatically detect the
* value for this if none is specified.
* 0 - Address DMA
* 1 - Descriptor DMA (default, if available)
* @enable_dynamic_fifo: 0 - Use coreConsultant-specified FIFO size parameters
* 1 - Allow dynamic FIFO sizing (default, if available)
* @en_multiple_tx_fifo: Specifies whether dedicated per-endpoint transmit FIFOs
* are enabled for non-periodic IN endpoints in device
* mode.
* @host_nperio_tx_fifo_size: Number of 4-byte words in the non-periodic Tx FIFO
* in host mode when dynamic FIFO sizing is enabled
* 16 to 32768
* Actual maximum value is autodetected and also
* the default.
* @host_perio_tx_fifo_size: Number of 4-byte words in the periodic Tx FIFO in
* host mode when dynamic FIFO sizing is enabled
* 16 to 32768
* Actual maximum value is autodetected and also
* the default.
* @max_transfer_size: The maximum transfer size supported, in bytes
* 2047 to 65,535
* Actual maximum value is autodetected and also
* the default.
* @max_packet_count: The maximum number of packets in a transfer
* 15 to 511
* Actual maximum value is autodetected and also
* the default.
* @host_channels: The number of host channel registers to use
* 1 to 16
* Actual maximum value is autodetected and also
* the default.
* @dev_nperio_tx_fifo_size: Number of 4-byte words in the non-periodic Tx FIFO
* in device mode when dynamic FIFO sizing is enabled
* 16 to 32768
* Actual maximum value is autodetected and also
* the default.
* @i2c_enable: Specifies whether to use the I2Cinterface for a full
* speed PHY. This parameter is only applicable if phy_type
* is FS.
* 0 - No (default)
* 1 - Yes
* @acg_enable: For enabling Active Clock Gating in the controller
* 0 - Disable
* 1 - Enable
* @lpm_mode: For enabling Link Power Management in the controller
* 0 - Disable
* 1 - Enable
* @rx_fifo_size: Number of 4-byte words in the Rx FIFO when dynamic
* FIFO sizing is enabled 16 to 32768
* Actual maximum value is autodetected and also
* the default.
*/
struct dwc2_hw_params {
unsigned op_mode:3;
......@@ -622,6 +688,7 @@ struct dwc2_hw_params {
unsigned hibernation:1;
unsigned utmi_phy_data_width:2;
unsigned lpm_mode:1;
unsigned ipg_isoc_en:1;
u32 snpsid;
u32 dev_ep_dirs;
u32 g_tx_fifo_size[MAX_EPS_CHANNELS];
......@@ -642,7 +709,11 @@ struct dwc2_hw_params {
* @gi2cctl: Backup of GI2CCTL register
* @glpmcfg: Backup of GLPMCFG register
* @gdfifocfg: Backup of GDFIFOCFG register
* @pcgcctl: Backup of PCGCCTL register
* @pcgcctl1: Backup of PCGCCTL1 register
* @dtxfsiz: Backup of DTXFSIZ registers for each endpoint
* @gpwrdn: Backup of GPWRDN register
* @valid: True if registers values backuped.
*/
struct dwc2_gregs_backup {
u32 gotgctl;
......@@ -675,6 +746,7 @@ struct dwc2_gregs_backup {
* @doeptsiz: Backup of DOEPTSIZ register
* @doepdma: Backup of DOEPDMA register
* @dtxfsiz: Backup of DTXFSIZ registers for each endpoint
* @valid: True if registers values backuped.
*/
struct dwc2_dregs_backup {
u32 dcfg;
......@@ -698,9 +770,10 @@ struct dwc2_dregs_backup {
* @hcfg: Backup of HCFG register
* @haintmsk: Backup of HAINTMSK register
* @hcintmsk: Backup of HCINTMSK register
* @hptr0: Backup of HPTR0 register
* @hprt0: Backup of HPTR0 register
* @hfir: Backup of HFIR register
* @hptxfsiz: Backup of HPTXFSIZ register
* @valid: True if registers values backuped.
*/
struct dwc2_hregs_backup {
u32 hcfg;
......@@ -800,7 +873,7 @@ struct dwc2_hregs_backup {
* @regs: Pointer to controller regs
* @hw_params: Parameters that were autodetected from the
* hardware registers
* @core_params: Parameters that define how the core should be configured
* @params: Parameters that define how the core should be configured
* @op_state: The operational State, during transitions (a_host=>
* a_peripheral and b_device=>b_host) this may not match
* the core, but allows the software to determine
......@@ -809,10 +882,13 @@ struct dwc2_hregs_backup {
* - USB_DR_MODE_PERIPHERAL
* - USB_DR_MODE_HOST
* - USB_DR_MODE_OTG
* @hcd_enabled Host mode sub-driver initialization indicator.
* @gadget_enabled Peripheral mode sub-driver initialization indicator.
* @ll_hw_enabled Status of low-level hardware resources.
* @hcd_enabled: Host mode sub-driver initialization indicator.
* @gadget_enabled: Peripheral mode sub-driver initialization indicator.
* @ll_hw_enabled: Status of low-level hardware resources.
* @hibernated: True if core is hibernated
* @frame_number: Frame number read from the core. For both device
* and host modes. The value ranges are from 0
* to HFNUM_MAX_FRNUM.
* @phy: The otg phy transceiver structure for phy control.
* @uphy: The otg phy transceiver structure for old USB phy
* control.
......@@ -832,13 +908,25 @@ struct dwc2_hregs_backup {
* interrupt
* @wkp_timer: Timer object for handling Wakeup Detected interrupt
* @lx_state: Lx state of connected device
* @gregs_backup: Backup of global registers during suspend
* @dregs_backup: Backup of device registers during suspend
* @hregs_backup: Backup of host registers during suspend
* @gr_backup: Backup of global registers during suspend
* @dr_backup: Backup of device registers during suspend
* @hr_backup: Backup of host registers during suspend
*
* These are for host mode:
*
* @flags: Flags for handling root port state changes
* @flags.d32: Contain all root port flags
* @flags.b: Separate root port flags from each other
* @flags.b.port_connect_status_change: True if root port connect status
* changed
* @flags.b.port_connect_status: True if device connected to root port
* @flags.b.port_reset_change: True if root port reset status changed
* @flags.b.port_enable_change: True if root port enable status changed
* @flags.b.port_suspend_change: True if root port suspend status changed
* @flags.b.port_over_current_change: True if root port over current state
* changed.
* @flags.b.port_l1_change: True if root port l1 status changed
* @flags.b.reserved: Reserved bits of root port register
* @non_periodic_sched_inactive: Inactive QHs in the non-periodic schedule.
* Transfers associated with these QHs are not currently
* assigned to a host channel.
......@@ -847,6 +935,9 @@ struct dwc2_hregs_backup {
* assigned to a host channel.
* @non_periodic_qh_ptr: Pointer to next QH to process in the active
* non-periodic schedule
* @non_periodic_sched_waiting: Waiting QHs in the non-periodic schedule.
* Transfers associated with these QHs are not currently
* assigned to a host channel.
* @periodic_sched_inactive: Inactive QHs in the periodic schedule. This is a
* list of QHs for periodic transfers that are _not_
* scheduled for the next frame. Each QH in the list has an
......@@ -886,8 +977,6 @@ struct dwc2_hregs_backup {
* @hs_periodic_bitmap: Bitmap used by the microframe scheduler any time the
* host is in high speed mode; low speed schedules are
* stored elsewhere since we need one per TT.
* @frame_number: Frame number read from the core at SOF. The value ranges
* from 0 to HFNUM_MAX_FRNUM.
* @periodic_qh_count: Count of periodic QHs, if using several eps. Used for
* SOF enable/disable.
* @free_hc_list: Free host channels in the controller. This is a list of
......@@ -898,8 +987,8 @@ struct dwc2_hregs_backup {
* host channel is available for non-periodic transactions.
* @non_periodic_channels: Number of host channels assigned to non-periodic
* transfers
* @available_host_channels Number of host channels available for the microframe
* scheduler to use
* @available_host_channels: Number of host channels available for the
* microframe scheduler to use
* @hc_ptr_array: Array of pointers to the host channel descriptors.
* Allows accessing a host channel descriptor given the
* host channel number. This is useful in interrupt
......@@ -922,9 +1011,6 @@ struct dwc2_hregs_backup {
* @dedicated_fifos: Set if the hardware has dedicated IN-EP fifos.
* @num_of_eps: Number of available EPs (excluding EP0)
* @debug_root: Root directrory for debugfs.
* @debug_file: Main status file for debugfs.
* @debug_testmode: Testmode status file for debugfs.
* @debug_fifo: FIFO status file for debugfs.
* @ep0_reply: Request used for ep0 reply.
* @ep0_buff: Buffer for EP0 reply data, if needed.
* @ctrl_buff: Buffer for EP0 control requests.
......@@ -939,7 +1025,37 @@ struct dwc2_hregs_backup {
* @ctrl_in_desc: EP0 IN data phase desc chain pointer
* @ctrl_out_desc_dma: EP0 OUT data phase desc chain DMA address
* @ctrl_out_desc: EP0 OUT data phase desc chain pointer
* @eps: The endpoints being supplied to the gadget framework
* @irq: Interrupt request line number
* @clk: Pointer to otg clock
* @reset: Pointer to dwc2 reset controller
* @reset_ecc: Pointer to dwc2 optional reset controller in Stratix10.
* @regset: A pointer to a struct debugfs_regset32, which contains
* a pointer to an array of register definitions, the
* array size and the base address where the register bank
* is to be found.
* @bus_suspended: True if bus is suspended
* @last_frame_num: Number of last frame. Range from 0 to 32768
* @frame_num_array: Used only if CONFIG_USB_DWC2_TRACK_MISSED_SOFS is
* defined, for missed SOFs tracking. Array holds that
* frame numbers, which not equal to last_frame_num +1
* @last_frame_num_array: Used only if CONFIG_USB_DWC2_TRACK_MISSED_SOFS is
* defined, for missed SOFs tracking.
* If current_frame_number != last_frame_num+1
* then last_frame_num added to this array
* @frame_num_idx: Actual size of frame_num_array and last_frame_num_array
* @dumped_frame_num_array: 1 - if missed SOFs frame numbers dumbed
* 0 - if missed SOFs frame numbers not dumbed
* @fifo_mem: Total internal RAM for FIFOs (bytes)
* @fifo_map: Each bit intend for concrete fifo. If that bit is set,
* then that fifo is used
* @gadget: Represents a usb slave device
* @connected: Used in slave mode. True if device connected with host
* @eps_in: The IN endpoints being supplied to the gadget framework
* @eps_out: The OUT endpoints being supplied to the gadget framework
* @new_connection: Used in host mode. True if there are new connected
* device
* @enabled: Indicates the enabling state of controller
*
*/
struct dwc2_hsotg {
struct device *dev;
......@@ -954,6 +1070,7 @@ struct dwc2_hsotg {
unsigned int gadget_enabled:1;
unsigned int ll_hw_enabled:1;
unsigned int hibernated:1;
u16 frame_number;
struct phy *phy;
struct usb_phy *uphy;
......@@ -1029,7 +1146,6 @@ struct dwc2_hsotg {
u16 periodic_usecs;
unsigned long hs_periodic_bitmap[
DIV_ROUND_UP(DWC2_HS_SCHEDULE_US, BITS_PER_LONG)];
u16 frame_number;
u16 periodic_qh_count;
bool bus_suspended;
bool new_connection;
......
......@@ -778,6 +778,14 @@ irqreturn_t dwc2_handle_common_intr(int irq, void *dev)
goto out;
}
/* Reading current frame number value in device or host modes. */
if (dwc2_is_device_mode(hsotg))
hsotg->frame_number = (dwc2_readl(hsotg->regs + DSTS)
& DSTS_SOFFN_MASK) >> DSTS_SOFFN_SHIFT;
else
hsotg->frame_number = (dwc2_readl(hsotg->regs + HFNUM)
& HFNUM_FRNUM_MASK) >> HFNUM_FRNUM_SHIFT;
gintsts = dwc2_read_common_intr(hsotg);
if (gintsts & ~GINTSTS_PRTINT)
retval = IRQ_HANDLED;
......
// SPDX-License-Identifier: GPL-2.0
/**
/*
* debug.h - Designware USB2 DRD controller debug header
*
* Copyright (C) 2015 Intel Corporation
......
// SPDX-License-Identifier: GPL-2.0
/**
/*
* debugfs.c - Designware USB2 DRD controller debugfs
*
* Copyright (C) 2015 Intel Corporation
......@@ -16,12 +16,13 @@
#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \
IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
/**
* testmode_write - debugfs: change usb test mode
* @seq: The seq file to write to.
* @v: Unused parameter.
*
* This debugfs entry modify the current usb test mode.
* testmode_write() - change usb test mode state.
* @file: The file to write to.
* @ubuf: The buffer where user wrote.
* @count: The ubuf size.
* @ppos: Unused parameter.
*/
static ssize_t testmode_write(struct file *file, const char __user *ubuf, size_t
count, loff_t *ppos)
......@@ -55,9 +56,9 @@ static ssize_t testmode_write(struct file *file, const char __user *ubuf, size_t
}
/**
* testmode_show - debugfs: show usb test mode state
* @seq: The seq file to write to.
* @v: Unused parameter.
* testmode_show() - debugfs: show usb test mode state
* @s: The seq file to write to.
* @unused: Unused parameter.
*
* This debugfs entry shows which usb test mode is currently enabled.
*/
......@@ -368,7 +369,7 @@ static const struct debugfs_reg32 dwc2_regs[] = {
dump_register(GINTSTS),
dump_register(GINTMSK),
dump_register(GRXSTSR),
dump_register(GRXSTSP),
/* Omit GRXSTSP */
dump_register(GRXFSIZ),
dump_register(GNPTXFSIZ),
dump_register(GNPTXSTS),
......@@ -710,6 +711,7 @@ static int params_show(struct seq_file *seq, void *v)
print_param(seq, p, phy_ulpi_ddr);
print_param(seq, p, phy_ulpi_ext_vbus);
print_param(seq, p, i2c_enable);
print_param(seq, p, ipg_isoc_en);
print_param(seq, p, ulpi_fs_ls);
print_param(seq, p, host_support_fs_ls_low_power);
print_param(seq, p, host_ls_low_power_phy_clk);
......
此差异已折叠。
......@@ -597,7 +597,7 @@ u32 dwc2_calc_frame_interval(struct dwc2_hsotg *hsotg)
* dwc2_read_packet() - Reads a packet from the Rx FIFO into the destination
* buffer
*
* @core_if: Programming view of DWC_otg controller
* @hsotg: Programming view of DWC_otg controller
* @dest: Destination buffer for the packet
* @bytes: Number of bytes to copy to the destination
*/
......@@ -4087,7 +4087,6 @@ static struct dwc2_hsotg *dwc2_hcd_to_hsotg(struct usb_hcd *hcd)
* then the refcount for the structure will go to 0 and we'll free it.
*
* @hsotg: The HCD state structure for the DWC OTG controller.
* @qh: The QH structure.
* @context: The priv pointer from a struct dwc2_hcd_urb.
* @mem_flags: Flags for allocating memory.
* @ttport: We'll return this device's port number here. That's used to
......
......@@ -80,7 +80,7 @@ struct dwc2_qh;
* @xfer_count: Number of bytes transferred so far
* @start_pkt_count: Packet count at start of transfer
* @xfer_started: True if the transfer has been started
* @ping: True if a PING request should be issued on this channel
* @do_ping: True if a PING request should be issued on this channel
* @error_state: True if the error count for this transaction is non-zero
* @halt_on_queue: True if this channel should be halted the next time a
* request is queued for the channel. This is necessary in
......@@ -102,7 +102,7 @@ struct dwc2_qh;
* @schinfo: Scheduling micro-frame bitmap
* @ntd: Number of transfer descriptors for the transfer
* @halt_status: Reason for halting the host channel
* @hcint Contents of the HCINT register when the interrupt came
* @hcint: Contents of the HCINT register when the interrupt came
* @qh: QH for the transfer being processed by this channel
* @hc_list_entry: For linking to list of host channels
* @desc_list_addr: Current QH's descriptor list DMA address
......@@ -237,7 +237,7 @@ struct dwc2_tt {
/**
* struct dwc2_hs_transfer_time - Info about a transfer on the high speed bus.
*
* @start_schedule_usecs: The start time on the main bus schedule. Note that
* @start_schedule_us: The start time on the main bus schedule. Note that
* the main bus schedule is tightly packed and this
* time should be interpreted as tightly packed (so
* uFrame 0 starts at 0 us, uFrame 1 starts at 100 us
......@@ -301,7 +301,6 @@ struct dwc2_hs_transfer_time {
* "struct dwc2_tt". Not used if this device is high
* speed. Note that this is in "schedule slice" which
* is tightly packed.
* @ls_duration_us: Duration on the low speed bus schedule.
* @ntd: Actual number of transfer descriptors in a list
* @qtd_list: List of QTDs for this QH
* @channel: Host channel currently processing transfers for this QH
......@@ -315,7 +314,7 @@ struct dwc2_hs_transfer_time {
* descriptor
* @unreserve_timer: Timer for releasing periodic reservation.
* @wait_timer: Timer used to wait before re-queuing.
* @dwc2_tt: Pointer to our tt info (or NULL if no tt).
* @dwc_tt: Pointer to our tt info (or NULL if no tt).
* @ttport: Port number within our tt.
* @tt_buffer_dirty True if clear_tt_buffer_complete is pending
* @unreserve_pending: True if we planned to unreserve but haven't yet.
......@@ -325,6 +324,7 @@ struct dwc2_hs_transfer_time {
* periodic transfers and is ignored for periodic ones.
* @wait_timer_cancel: Set to true to cancel the wait_timer.
*
* @tt_buffer_dirty: True if EP's TT buffer is not clean.
* A Queue Head (QH) holds the static characteristics of an endpoint and
* maintains a list of transfers (QTDs) for that endpoint. A QH structure may
* be entered in either the non-periodic or periodic schedule.
......@@ -400,6 +400,10 @@ struct dwc2_qh {
* @urb: URB for this transfer
* @qh: Queue head for this QTD
* @qtd_list_entry: For linking to the QH's list of QTDs
* @isoc_td_first: Index of first activated isochronous transfer
* descriptor in Descriptor DMA mode
* @isoc_td_last: Index of last activated isochronous transfer
* descriptor in Descriptor DMA mode
*
* A Queue Transfer Descriptor (QTD) holds the state of a bulk, control,
* interrupt, or isochronous transfer. A single QTD is created for each URB
......
......@@ -332,6 +332,7 @@ static void dwc2_release_channel_ddma(struct dwc2_hsotg *hsotg,
*
* @hsotg: The HCD state structure for the DWC OTG controller
* @qh: The QH to init
* @mem_flags: Indicates the type of memory allocation
*
* Return: 0 if successful, negative error code otherwise
*
......
......@@ -478,6 +478,12 @@ static u32 dwc2_get_actual_xfer_length(struct dwc2_hsotg *hsotg,
* of the URB based on the number of bytes transferred via the host channel.
* Sets the URB status if the data transfer is finished.
*
* @hsotg: Programming view of the DWC_otg controller
* @chan: Programming view of host channel
* @chnum: Channel number
* @urb: Processing URB
* @qtd: Queue transfer descriptor
*
* Return: 1 if the data transfer specified by the URB is completely finished,
* 0 otherwise
*/
......@@ -566,6 +572,12 @@ void dwc2_hcd_save_data_toggle(struct dwc2_hsotg *hsotg,
* halt_status. Completes the Isochronous URB if all the URB frames have been
* completed.
*
* @hsotg: Programming view of the DWC_otg controller
* @chan: Programming view of host channel
* @chnum: Channel number
* @halt_status: Reason for halting a host channel
* @qtd: Queue transfer descriptor
*
* Return: DWC2_HC_XFER_COMPLETE if there are more frames remaining to be
* transferred in the URB. Otherwise return DWC2_HC_XFER_URB_COMPLETE.
*/
......
......@@ -679,6 +679,7 @@ static int dwc2_hs_pmap_schedule(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh,
*
* @hsotg: The HCD state structure for the DWC OTG controller.
* @qh: QH for the periodic transfer.
* @index: Transfer index
*/
static void dwc2_hs_pmap_unschedule(struct dwc2_hsotg *hsotg,
struct dwc2_qh *qh, int index)
......@@ -1276,7 +1277,7 @@ static void dwc2_do_unreserve(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
* release the reservation. This worker is called after the appropriate
* delay.
*
* @work: Pointer to a qh unreserve_work.
* @t: Address to a qh unreserve_work.
*/
static void dwc2_unreserve_timer_fn(struct timer_list *t)
{
......@@ -1631,7 +1632,7 @@ static void dwc2_qh_init(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh,
* @hsotg: The HCD state structure for the DWC OTG controller
* @urb: Holds the information about the device/endpoint needed
* to initialize the QH
* @atomic_alloc: Flag to do atomic allocation if needed
* @mem_flags: Flags for allocating memory.
*
* Return: Pointer to the newly allocated QH, or NULL on error
*/
......
......@@ -311,6 +311,7 @@
#define GHWCFG4_UTMI_PHY_DATA_WIDTH_MASK (0x3 << 14)
#define GHWCFG4_UTMI_PHY_DATA_WIDTH_SHIFT 14
#define GHWCFG4_ACG_SUPPORTED BIT(12)
#define GHWCFG4_IPG_ISOC_SUPPORTED BIT(11)
#define GHWCFG4_UTMI_PHY_DATA_WIDTH_8 0
#define GHWCFG4_UTMI_PHY_DATA_WIDTH_16 1
#define GHWCFG4_UTMI_PHY_DATA_WIDTH_8_OR_16 2
......@@ -424,6 +425,7 @@
#define DCFG_EPMISCNT_SHIFT 18
#define DCFG_EPMISCNT_LIMIT 0x1f
#define DCFG_EPMISCNT(_x) ((_x) << 18)
#define DCFG_IPG_ISOC_SUPPORDED BIT(17)
#define DCFG_PERFRINT_MASK (0x3 << 11)
#define DCFG_PERFRINT_SHIFT 11
#define DCFG_PERFRINT_LIMIT 0x3
......
......@@ -70,6 +70,7 @@ static void dwc2_set_his_params(struct dwc2_hsotg *hsotg)
GAHBCFG_HBSTLEN_SHIFT;
p->uframe_sched = false;
p->change_speed_quirk = true;
p->power_down = false;
}
static void dwc2_set_rk_params(struct dwc2_hsotg *hsotg)
......@@ -269,6 +270,9 @@ static void dwc2_set_param_power_down(struct dwc2_hsotg *hsotg)
/**
* dwc2_set_default_params() - Set all core parameters to their
* auto-detected default values.
*
* @hsotg: Programming view of the DWC_otg controller
*
*/
static void dwc2_set_default_params(struct dwc2_hsotg *hsotg)
{
......@@ -298,6 +302,7 @@ static void dwc2_set_default_params(struct dwc2_hsotg *hsotg)
p->besl = true;
p->hird_threshold_en = true;
p->hird_threshold = 4;
p->ipg_isoc_en = false;
p->max_packet_count = hw->max_packet_count;
p->max_transfer_size = hw->max_transfer_size;
p->ahbcfg = GAHBCFG_HBSTLEN_INCR << GAHBCFG_HBSTLEN_SHIFT;
......@@ -338,6 +343,8 @@ static void dwc2_set_default_params(struct dwc2_hsotg *hsotg)
/**
* dwc2_get_device_properties() - Read in device properties.
*
* @hsotg: Programming view of the DWC_otg controller
*
* Read in the device properties and adjust core parameters if needed.
*/
static void dwc2_get_device_properties(struct dwc2_hsotg *hsotg)
......@@ -549,7 +556,7 @@ static void dwc2_check_param_tx_fifo_sizes(struct dwc2_hsotg *hsotg)
}
#define CHECK_RANGE(_param, _min, _max, _def) do { \
if ((hsotg->params._param) < (_min) || \
if ((int)(hsotg->params._param) < (_min) || \
(hsotg->params._param) > (_max)) { \
dev_warn(hsotg->dev, "%s: Invalid parameter %s=%d\n", \
__func__, #_param, hsotg->params._param); \
......@@ -579,6 +586,7 @@ static void dwc2_check_params(struct dwc2_hsotg *hsotg)
CHECK_BOOL(enable_dynamic_fifo, hw->enable_dynamic_fifo);
CHECK_BOOL(en_multiple_tx_fifo, hw->en_multiple_tx_fifo);
CHECK_BOOL(i2c_enable, hw->i2c_enable);
CHECK_BOOL(ipg_isoc_en, hw->ipg_isoc_en);
CHECK_BOOL(acg_enable, hw->acg_enable);
CHECK_BOOL(reload_ctl, (hsotg->hw_params.snpsid > DWC2_CORE_REV_2_92a));
CHECK_BOOL(lpm, (hsotg->hw_params.snpsid >= DWC2_CORE_REV_2_80a));
......@@ -688,6 +696,9 @@ static void dwc2_get_dev_hwparams(struct dwc2_hsotg *hsotg)
/**
* During device initialization, read various hardware configuration
* registers and interpret the contents.
*
* @hsotg: Programming view of the DWC_otg controller
*
*/
int dwc2_get_hwparams(struct dwc2_hsotg *hsotg)
{
......@@ -772,6 +783,7 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg)
hw->utmi_phy_data_width = (hwcfg4 & GHWCFG4_UTMI_PHY_DATA_WIDTH_MASK) >>
GHWCFG4_UTMI_PHY_DATA_WIDTH_SHIFT;
hw->acg_enable = !!(hwcfg4 & GHWCFG4_ACG_SUPPORTED);
hw->ipg_isoc_en = !!(hwcfg4 & GHWCFG4_IPG_ISOC_SUPPORTED);
/* fifo sizes */
hw->rx_fifo_size = (grxfsiz & GRXFSIZ_DEPTH_MASK) >>
......
......@@ -77,6 +77,12 @@ static int dwc2_pci_quirks(struct pci_dev *pdev, struct platform_device *dwc2)
return 0;
}
/**
* dwc2_pci_probe() - Provides the cleanup entry points for the DWC_otg PCI
* driver
*
* @pci: The programming view of DWC_otg PCI
*/
static void dwc2_pci_remove(struct pci_dev *pci)
{
struct dwc2_pci_glue *glue = pci_get_drvdata(pci);
......
......@@ -106,4 +106,16 @@ config USB_DWC3_ST
inside (i.e. STiH407).
Say 'Y' or 'M' if you have one such device.
config USB_DWC3_QCOM
tristate "Qualcomm Platform"
depends on ARCH_QCOM || COMPILE_TEST
depends on OF
default USB_DWC3
help
Some Qualcomm SoCs use DesignWare Core IP for USB2/3
functionality.
This driver also handles Qscratch wrapper which is needed
for peripheral mode support.
Say 'Y' or 'M' if you have one such device.
endif
......@@ -48,3 +48,4 @@ obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o
obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o
obj-$(CONFIG_USB_DWC3_OF_SIMPLE) += dwc3-of-simple.o
obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o
obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o
......@@ -8,6 +8,7 @@
* Sebastian Andrzej Siewior <bigeasy@linutronix.de>
*/
#include <linux/clk.h>
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
......@@ -24,6 +25,7 @@
#include <linux/of.h>
#include <linux/acpi.h>
#include <linux/pinctrl/consumer.h>
#include <linux/reset.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
......@@ -266,6 +268,12 @@ static int dwc3_core_soft_reset(struct dwc3 *dwc)
return 0;
}
static const struct clk_bulk_data dwc3_core_clks[] = {
{ .id = "ref" },
{ .id = "bus_early" },
{ .id = "suspend" },
};
/*
* dwc3_frame_length_adjustment - Adjusts frame length if required
* @dwc3: Pointer to our controller context structure
......@@ -667,6 +675,9 @@ static void dwc3_core_exit(struct dwc3 *dwc)
usb_phy_set_suspend(dwc->usb3_phy, 1);
phy_power_off(dwc->usb2_generic_phy);
phy_power_off(dwc->usb3_generic_phy);
clk_bulk_disable(dwc->num_clks, dwc->clks);
clk_bulk_unprepare(dwc->num_clks, dwc->clks);
reset_control_assert(dwc->reset);
}
static bool dwc3_core_is_valid(struct dwc3 *dwc)
......@@ -1245,7 +1256,7 @@ static void dwc3_check_params(struct dwc3 *dwc)
static int dwc3_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res;
struct resource *res, dwc_res;
struct dwc3 *dwc;
int ret;
......@@ -1256,6 +1267,12 @@ static int dwc3_probe(struct platform_device *pdev)
if (!dwc)
return -ENOMEM;
dwc->clks = devm_kmemdup(dev, dwc3_core_clks, sizeof(dwc3_core_clks),
GFP_KERNEL);
if (!dwc->clks)
return -ENOMEM;
dwc->num_clks = ARRAY_SIZE(dwc3_core_clks);
dwc->dev = dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
......@@ -1270,23 +1287,48 @@ static int dwc3_probe(struct platform_device *pdev)
dwc->xhci_resources[0].flags = res->flags;
dwc->xhci_resources[0].name = res->name;
res->start += DWC3_GLOBALS_REGS_START;
/*
* Request memory region but exclude xHCI regs,
* since it will be requested by the xhci-plat driver.
*/
regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs)) {
ret = PTR_ERR(regs);
goto err0;
}
dwc_res = *res;
dwc_res.start += DWC3_GLOBALS_REGS_START;
regs = devm_ioremap_resource(dev, &dwc_res);
if (IS_ERR(regs))
return PTR_ERR(regs);
dwc->regs = regs;
dwc->regs_size = resource_size(res);
dwc->regs_size = resource_size(&dwc_res);
dwc3_get_properties(dwc);
dwc->reset = devm_reset_control_get_optional_shared(dev, NULL);
if (IS_ERR(dwc->reset))
return PTR_ERR(dwc->reset);
ret = clk_bulk_get(dev, dwc->num_clks, dwc->clks);
if (ret == -EPROBE_DEFER)
return ret;
/*
* Clocks are optional, but new DT platforms should support all clocks
* as required by the DT-binding.
*/
if (ret)
dwc->num_clks = 0;
ret = reset_control_deassert(dwc->reset);
if (ret)
goto put_clks;
ret = clk_bulk_prepare(dwc->num_clks, dwc->clks);
if (ret)
goto assert_reset;
ret = clk_bulk_enable(dwc->num_clks, dwc->clks);
if (ret)
goto unprepare_clks;
platform_set_drvdata(pdev, dwc);
dwc3_cache_hwparams(dwc);
......@@ -1350,13 +1392,13 @@ static int dwc3_probe(struct platform_device *pdev)
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
err0:
/*
* restore res->start back to its original value so that, in case the
* probe is deferred, we don't end up getting error in request the
* memory region the next time probe is called.
*/
res->start -= DWC3_GLOBALS_REGS_START;
clk_bulk_disable(dwc->num_clks, dwc->clks);
unprepare_clks:
clk_bulk_unprepare(dwc->num_clks, dwc->clks);
assert_reset:
reset_control_assert(dwc->reset);
put_clks:
clk_bulk_put(dwc->num_clks, dwc->clks);
return ret;
}
......@@ -1364,15 +1406,8 @@ static int dwc3_probe(struct platform_device *pdev)
static int dwc3_remove(struct platform_device *pdev)
{
struct dwc3 *dwc = platform_get_drvdata(pdev);
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pm_runtime_get_sync(&pdev->dev);
/*
* restore res->start back to its original value so that, in case the
* probe is deferred, we don't end up getting error in request the
* memory region the next time probe is called.
*/
res->start -= DWC3_GLOBALS_REGS_START;
dwc3_debugfs_exit(dwc);
dwc3_core_exit_mode(dwc);
......@@ -1386,14 +1421,48 @@ static int dwc3_remove(struct platform_device *pdev)
dwc3_free_event_buffers(dwc);
dwc3_free_scratch_buffers(dwc);
clk_bulk_put(dwc->num_clks, dwc->clks);
return 0;
}
#ifdef CONFIG_PM
static int dwc3_core_init_for_resume(struct dwc3 *dwc)
{
int ret;
ret = reset_control_deassert(dwc->reset);
if (ret)
return ret;
ret = clk_bulk_prepare(dwc->num_clks, dwc->clks);
if (ret)
goto assert_reset;
ret = clk_bulk_enable(dwc->num_clks, dwc->clks);
if (ret)
goto unprepare_clks;
ret = dwc3_core_init(dwc);
if (ret)
goto disable_clks;
return 0;
disable_clks:
clk_bulk_disable(dwc->num_clks, dwc->clks);
unprepare_clks:
clk_bulk_unprepare(dwc->num_clks, dwc->clks);
assert_reset:
reset_control_assert(dwc->reset);
return ret;
}
static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
{
unsigned long flags;
u32 reg;
switch (dwc->current_dr_role) {
case DWC3_GCTL_PRTCAP_DEVICE:
......@@ -1403,9 +1472,25 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
dwc3_core_exit(dwc);
break;
case DWC3_GCTL_PRTCAP_HOST:
/* do nothing during host runtime_suspend */
if (!PMSG_IS_AUTO(msg))
if (!PMSG_IS_AUTO(msg)) {
dwc3_core_exit(dwc);
break;
}
/* Let controller to suspend HSPHY before PHY driver suspends */
if (dwc->dis_u2_susphy_quirk ||
dwc->dis_enblslpm_quirk) {
reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
reg |= DWC3_GUSB2PHYCFG_ENBLSLPM |
DWC3_GUSB2PHYCFG_SUSPHY;
dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
/* Give some time for USB2 PHY to suspend */
usleep_range(5000, 6000);
}
phy_pm_runtime_put_sync(dwc->usb2_generic_phy);
phy_pm_runtime_put_sync(dwc->usb3_generic_phy);
break;
case DWC3_GCTL_PRTCAP_OTG:
/* do nothing during runtime_suspend */
......@@ -1433,10 +1518,11 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg)
{
unsigned long flags;
int ret;
u32 reg;
switch (dwc->current_dr_role) {
case DWC3_GCTL_PRTCAP_DEVICE:
ret = dwc3_core_init(dwc);
ret = dwc3_core_init_for_resume(dwc);
if (ret)
return ret;
......@@ -1446,13 +1532,25 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg)
spin_unlock_irqrestore(&dwc->lock, flags);
break;
case DWC3_GCTL_PRTCAP_HOST:
/* nothing to do on host runtime_resume */
if (!PMSG_IS_AUTO(msg)) {
ret = dwc3_core_init(dwc);
ret = dwc3_core_init_for_resume(dwc);
if (ret)
return ret;
dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST);
break;
}
/* Restore GUSB2PHYCFG bits that were modified in suspend */
reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
if (dwc->dis_u2_susphy_quirk)
reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
if (dwc->dis_enblslpm_quirk)
reg &= ~DWC3_GUSB2PHYCFG_ENBLSLPM;
dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
phy_pm_runtime_get_sync(dwc->usb2_generic_phy);
phy_pm_runtime_get_sync(dwc->usb3_generic_phy);
break;
case DWC3_GCTL_PRTCAP_OTG:
/* nothing to do on runtime_resume */
......
......@@ -639,8 +639,6 @@ struct dwc3_event_buffer {
* @resource_index: Resource transfer index
* @frame_number: set to the frame number we want this transfer to start (ISOC)
* @interval: the interval on which the ISOC transfer is started
* @allocated_requests: number of requests allocated
* @queued_requests: number of requests queued for transfer
* @name: a human readable name e.g. ep1out-bulk
* @direction: true for TX, false for RX
* @stream_capable: true when streams are enabled
......@@ -664,11 +662,9 @@ struct dwc3_ep {
#define DWC3_EP_ENABLED BIT(0)
#define DWC3_EP_STALL BIT(1)
#define DWC3_EP_WEDGE BIT(2)
#define DWC3_EP_BUSY BIT(4)
#define DWC3_EP_TRANSFER_STARTED BIT(3)
#define DWC3_EP_PENDING_REQUEST BIT(5)
#define DWC3_EP_MISSED_ISOC BIT(6)
#define DWC3_EP_END_TRANSFER_PENDING BIT(7)
#define DWC3_EP_TRANSFER_STARTED BIT(8)
/* This last one is specific to EP0 */
#define DWC3_EP0_DIR_IN BIT(31)
......@@ -688,8 +684,6 @@ struct dwc3_ep {
u8 number;
u8 type;
u8 resource_index;
u32 allocated_requests;
u32 queued_requests;
u32 frame_number;
u32 interval;
......@@ -832,7 +826,9 @@ struct dwc3_hwparams {
* @list: a list_head used for request queueing
* @dep: struct dwc3_ep owning this request
* @sg: pointer to first incomplete sg
* @start_sg: pointer to the sg which should be queued next
* @num_pending_sgs: counter to pending sgs
* @num_queued_sgs: counter to the number of sgs which already got queued
* @remaining: amount of data remaining
* @epnum: endpoint number to which this request refers
* @trb: pointer to struct dwc3_trb
......@@ -848,8 +844,10 @@ struct dwc3_request {
struct list_head list;
struct dwc3_ep *dep;
struct scatterlist *sg;
struct scatterlist *start_sg;
unsigned num_pending_sgs;
unsigned int num_queued_sgs;
unsigned remaining;
u8 epnum;
struct dwc3_trb *trb;
......@@ -891,6 +889,9 @@ struct dwc3_scratchpad_array {
* @eps: endpoint array
* @gadget: device side representation of the peripheral controller
* @gadget_driver: pointer to the gadget driver
* @clks: array of clocks
* @num_clks: number of clocks
* @reset: reset control
* @regs: base address for our registers
* @regs_size: address space size
* @fladj: frame length adjustment
......@@ -1013,6 +1014,11 @@ struct dwc3 {
struct usb_gadget gadget;
struct usb_gadget_driver *gadget_driver;
struct clk_bulk_data *clks;
int num_clks;
struct reset_control *reset;
struct usb_phy *usb2_phy;
struct usb_phy *usb3_phy;
......@@ -1197,11 +1203,12 @@ struct dwc3_event_depevt {
/* Within XferNotReady */
#define DEPEVT_STATUS_TRANSFER_ACTIVE BIT(3)
/* Within XferComplete */
/* Within XferComplete or XferInProgress */
#define DEPEVT_STATUS_BUSERR BIT(0)
#define DEPEVT_STATUS_SHORT BIT(1)
#define DEPEVT_STATUS_IOC BIT(2)
#define DEPEVT_STATUS_LST BIT(3)
#define DEPEVT_STATUS_LST BIT(3) /* XferComplete */
#define DEPEVT_STATUS_MISSED_ISOC BIT(3) /* XferInProgress */
/* Stream event only */
#define DEPEVT_STREAMEVT_FOUND 1
......
......@@ -475,21 +475,37 @@ dwc3_ep_event_string(char *str, const struct dwc3_event_depevt *event,
if (ret < 0)
return "UNKNOWN";
status = event->status;
switch (event->endpoint_event) {
case DWC3_DEPEVT_XFERCOMPLETE:
strcat(str, "Transfer Complete");
len = strlen(str);
sprintf(str + len, "Transfer Complete (%c%c%c)",
status & DEPEVT_STATUS_SHORT ? 'S' : 's',
status & DEPEVT_STATUS_IOC ? 'I' : 'i',
status & DEPEVT_STATUS_LST ? 'L' : 'l');
len = strlen(str);
if (epnum <= 1)
sprintf(str + len, " [%s]", dwc3_ep0_state_string(ep0state));
break;
case DWC3_DEPEVT_XFERINPROGRESS:
strcat(str, "Transfer In-Progress");
len = strlen(str);
sprintf(str + len, "Transfer In Progress [%d] (%c%c%c)",
event->parameters,
status & DEPEVT_STATUS_SHORT ? 'S' : 's',
status & DEPEVT_STATUS_IOC ? 'I' : 'i',
status & DEPEVT_STATUS_LST ? 'M' : 'm');
break;
case DWC3_DEPEVT_XFERNOTREADY:
strcat(str, "Transfer Not Ready");
status = event->status & DEPEVT_STATUS_TRANSFER_ACTIVE;
strcat(str, status ? " (Active)" : " (Not Active)");
len = strlen(str);
sprintf(str + len, "Transfer Not Ready [%d]%s",
event->parameters,
status & DEPEVT_STATUS_TRANSFER_ACTIVE ?
" (Active)" : " (Not Active)");
/* Control Endpoints */
if (epnum <= 1) {
......
......@@ -8,6 +8,7 @@
*/
#include <linux/extcon.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include "debug.h"
......@@ -439,17 +440,38 @@ static int dwc3_drd_notifier(struct notifier_block *nb,
return NOTIFY_DONE;
}
static struct extcon_dev *dwc3_get_extcon(struct dwc3 *dwc)
{
struct device *dev = dwc->dev;
struct device_node *np_phy, *np_conn;
struct extcon_dev *edev;
if (of_property_read_bool(dev->of_node, "extcon"))
return extcon_get_edev_by_phandle(dwc->dev, 0);
np_phy = of_parse_phandle(dev->of_node, "phys", 0);
np_conn = of_graph_get_remote_node(np_phy, -1, -1);
if (np_conn)
edev = extcon_find_edev_by_node(np_conn);
else
edev = NULL;
of_node_put(np_conn);
of_node_put(np_phy);
return edev;
}
int dwc3_drd_init(struct dwc3 *dwc)
{
int ret, irq;
if (dwc->dev->of_node &&
of_property_read_bool(dwc->dev->of_node, "extcon")) {
dwc->edev = extcon_get_edev_by_phandle(dwc->dev, 0);
if (IS_ERR(dwc->edev))
return PTR_ERR(dwc->edev);
dwc->edev = dwc3_get_extcon(dwc);
if (IS_ERR(dwc->edev))
return PTR_ERR(dwc->edev);
if (dwc->edev) {
dwc->edev_nb.notifier_call = dwc3_drd_notifier;
ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST,
&dwc->edev_nb);
......
......@@ -208,13 +208,13 @@ static const struct dev_pm_ops dwc3_of_simple_dev_pm_ops = {
};
static const struct of_device_id of_dwc3_simple_match[] = {
{ .compatible = "qcom,dwc3" },
{ .compatible = "rockchip,rk3399-dwc3" },
{ .compatible = "xlnx,zynqmp-dwc3" },
{ .compatible = "cavium,octeon-7130-usb-uctl" },
{ .compatible = "sprd,sc9860-dwc3" },
{ .compatible = "amlogic,meson-axg-dwc3" },
{ .compatible = "amlogic,meson-gxl-dwc3" },
{ .compatible = "allwinner,sun50i-h6-dwc3" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, of_dwc3_simple_match);
......
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
*
* Inspired by dwc3-of-simple.c
*/
#define DEBUG
#include <linux/io.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/irq.h>
#include <linux/clk-provider.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/extcon.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/usb/of.h>
#include <linux/reset.h>
#include <linux/iopoll.h>
#include "core.h"
/* USB QSCRATCH Hardware registers */
#define QSCRATCH_HS_PHY_CTRL 0x10
#define UTMI_OTG_VBUS_VALID BIT(20)
#define SW_SESSVLD_SEL BIT(28)
#define QSCRATCH_SS_PHY_CTRL 0x30
#define LANE0_PWR_PRESENT BIT(24)
#define QSCRATCH_GENERAL_CFG 0x08
#define PIPE_UTMI_CLK_SEL BIT(0)
#define PIPE3_PHYSTATUS_SW BIT(3)
#define PIPE_UTMI_CLK_DIS BIT(8)
#define PWR_EVNT_IRQ_STAT_REG 0x58
#define PWR_EVNT_LPM_IN_L2_MASK BIT(4)
#define PWR_EVNT_LPM_OUT_L2_MASK BIT(5)
struct dwc3_qcom {
struct device *dev;
void __iomem *qscratch_base;
struct platform_device *dwc3;
struct clk **clks;
int num_clocks;
struct reset_control *resets;
int hs_phy_irq;
int dp_hs_phy_irq;
int dm_hs_phy_irq;
int ss_phy_irq;
struct extcon_dev *edev;
struct extcon_dev *host_edev;
struct notifier_block vbus_nb;
struct notifier_block host_nb;
enum usb_dr_mode mode;
bool is_suspended;
bool pm_suspended;
};
static inline void dwc3_qcom_setbits(void __iomem *base, u32 offset, u32 val)
{
u32 reg;
reg = readl(base + offset);
reg |= val;
writel(reg, base + offset);
/* ensure that above write is through */
readl(base + offset);
}
static inline void dwc3_qcom_clrbits(void __iomem *base, u32 offset, u32 val)
{
u32 reg;
reg = readl(base + offset);
reg &= ~val;
writel(reg, base + offset);
/* ensure that above write is through */
readl(base + offset);
}
static void dwc3_qcom_vbus_overrride_enable(struct dwc3_qcom *qcom, bool enable)
{
if (enable) {
dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL,
LANE0_PWR_PRESENT);
dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_HS_PHY_CTRL,
UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL);
} else {
dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL,
LANE0_PWR_PRESENT);
dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_HS_PHY_CTRL,
UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL);
}
}
static int dwc3_qcom_vbus_notifier(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, vbus_nb);
/* enable vbus override for device mode */
dwc3_qcom_vbus_overrride_enable(qcom, event);
qcom->mode = event ? USB_DR_MODE_PERIPHERAL : USB_DR_MODE_HOST;
return NOTIFY_DONE;
}
static int dwc3_qcom_host_notifier(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, host_nb);
/* disable vbus override in host mode */
dwc3_qcom_vbus_overrride_enable(qcom, !event);
qcom->mode = event ? USB_DR_MODE_HOST : USB_DR_MODE_PERIPHERAL;
return NOTIFY_DONE;
}
static int dwc3_qcom_register_extcon(struct dwc3_qcom *qcom)
{
struct device *dev = qcom->dev;
struct extcon_dev *host_edev;
int ret;
if (!of_property_read_bool(dev->of_node, "extcon"))
return 0;
qcom->edev = extcon_get_edev_by_phandle(dev, 0);
if (IS_ERR(qcom->edev))
return PTR_ERR(qcom->edev);
qcom->vbus_nb.notifier_call = dwc3_qcom_vbus_notifier;
qcom->host_edev = extcon_get_edev_by_phandle(dev, 1);
if (IS_ERR(qcom->host_edev))
qcom->host_edev = NULL;
ret = devm_extcon_register_notifier(dev, qcom->edev, EXTCON_USB,
&qcom->vbus_nb);
if (ret < 0) {
dev_err(dev, "VBUS notifier register failed\n");
return ret;
}
if (qcom->host_edev)
host_edev = qcom->host_edev;
else
host_edev = qcom->edev;
qcom->host_nb.notifier_call = dwc3_qcom_host_notifier;
ret = devm_extcon_register_notifier(dev, host_edev, EXTCON_USB_HOST,
&qcom->host_nb);
if (ret < 0) {
dev_err(dev, "Host notifier register failed\n");
return ret;
}
/* Update initial VBUS override based on extcon state */
if (extcon_get_state(qcom->edev, EXTCON_USB) ||
!extcon_get_state(host_edev, EXTCON_USB_HOST))
dwc3_qcom_vbus_notifier(&qcom->vbus_nb, true, qcom->edev);
else
dwc3_qcom_vbus_notifier(&qcom->vbus_nb, false, qcom->edev);
return 0;
}
static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom)
{
if (qcom->hs_phy_irq) {
disable_irq_wake(qcom->hs_phy_irq);
disable_irq_nosync(qcom->hs_phy_irq);
}
if (qcom->dp_hs_phy_irq) {
disable_irq_wake(qcom->dp_hs_phy_irq);
disable_irq_nosync(qcom->dp_hs_phy_irq);
}
if (qcom->dm_hs_phy_irq) {
disable_irq_wake(qcom->dm_hs_phy_irq);
disable_irq_nosync(qcom->dm_hs_phy_irq);
}
if (qcom->ss_phy_irq) {
disable_irq_wake(qcom->ss_phy_irq);
disable_irq_nosync(qcom->ss_phy_irq);
}
}
static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom)
{
if (qcom->hs_phy_irq) {
enable_irq(qcom->hs_phy_irq);
enable_irq_wake(qcom->hs_phy_irq);
}
if (qcom->dp_hs_phy_irq) {
enable_irq(qcom->dp_hs_phy_irq);
enable_irq_wake(qcom->dp_hs_phy_irq);
}
if (qcom->dm_hs_phy_irq) {
enable_irq(qcom->dm_hs_phy_irq);
enable_irq_wake(qcom->dm_hs_phy_irq);
}
if (qcom->ss_phy_irq) {
enable_irq(qcom->ss_phy_irq);
enable_irq_wake(qcom->ss_phy_irq);
}
}
static int dwc3_qcom_suspend(struct dwc3_qcom *qcom)
{
u32 val;
int i;
if (qcom->is_suspended)
return 0;
val = readl(qcom->qscratch_base + PWR_EVNT_IRQ_STAT_REG);
if (!(val & PWR_EVNT_LPM_IN_L2_MASK))
dev_err(qcom->dev, "HS-PHY not in L2\n");
for (i = qcom->num_clocks - 1; i >= 0; i--)
clk_disable_unprepare(qcom->clks[i]);
qcom->is_suspended = true;
dwc3_qcom_enable_interrupts(qcom);
return 0;
}
static int dwc3_qcom_resume(struct dwc3_qcom *qcom)
{
int ret;
int i;
if (!qcom->is_suspended)
return 0;
dwc3_qcom_disable_interrupts(qcom);
for (i = 0; i < qcom->num_clocks; i++) {
ret = clk_prepare_enable(qcom->clks[i]);
if (ret < 0) {
while (--i >= 0)
clk_disable_unprepare(qcom->clks[i]);
return ret;
}
}
/* Clear existing events from PHY related to L2 in/out */
dwc3_qcom_setbits(qcom->qscratch_base, PWR_EVNT_IRQ_STAT_REG,
PWR_EVNT_LPM_IN_L2_MASK | PWR_EVNT_LPM_OUT_L2_MASK);
qcom->is_suspended = false;
return 0;
}
static irqreturn_t qcom_dwc3_resume_irq(int irq, void *data)
{
struct dwc3_qcom *qcom = data;
struct dwc3 *dwc = platform_get_drvdata(qcom->dwc3);
/* If pm_suspended then let pm_resume take care of resuming h/w */
if (qcom->pm_suspended)
return IRQ_HANDLED;
if (dwc->xhci)
pm_runtime_resume(&dwc->xhci->dev);
return IRQ_HANDLED;
}
static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom)
{
/* Configure dwc3 to use UTMI clock as PIPE clock not present */
dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
PIPE_UTMI_CLK_DIS);
usleep_range(100, 1000);
dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
PIPE_UTMI_CLK_SEL | PIPE3_PHYSTATUS_SW);
usleep_range(100, 1000);
dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
PIPE_UTMI_CLK_DIS);
}
static int dwc3_qcom_setup_irq(struct platform_device *pdev)
{
struct dwc3_qcom *qcom = platform_get_drvdata(pdev);
int irq, ret;
irq = platform_get_irq_byname(pdev, "hs_phy_irq");
if (irq > 0) {
/* Keep wakeup interrupts disabled until suspend */
irq_set_status_flags(irq, IRQ_NOAUTOEN);
ret = devm_request_threaded_irq(qcom->dev, irq, NULL,
qcom_dwc3_resume_irq,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"qcom_dwc3 HS", qcom);
if (ret) {
dev_err(qcom->dev, "hs_phy_irq failed: %d\n", ret);
return ret;
}
qcom->hs_phy_irq = irq;
}
irq = platform_get_irq_byname(pdev, "dp_hs_phy_irq");
if (irq > 0) {
irq_set_status_flags(irq, IRQ_NOAUTOEN);
ret = devm_request_threaded_irq(qcom->dev, irq, NULL,
qcom_dwc3_resume_irq,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"qcom_dwc3 DP_HS", qcom);
if (ret) {
dev_err(qcom->dev, "dp_hs_phy_irq failed: %d\n", ret);
return ret;
}
qcom->dp_hs_phy_irq = irq;
}
irq = platform_get_irq_byname(pdev, "dm_hs_phy_irq");
if (irq > 0) {
irq_set_status_flags(irq, IRQ_NOAUTOEN);
ret = devm_request_threaded_irq(qcom->dev, irq, NULL,
qcom_dwc3_resume_irq,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"qcom_dwc3 DM_HS", qcom);
if (ret) {
dev_err(qcom->dev, "dm_hs_phy_irq failed: %d\n", ret);
return ret;
}
qcom->dm_hs_phy_irq = irq;
}
irq = platform_get_irq_byname(pdev, "ss_phy_irq");
if (irq > 0) {
irq_set_status_flags(irq, IRQ_NOAUTOEN);
ret = devm_request_threaded_irq(qcom->dev, irq, NULL,
qcom_dwc3_resume_irq,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"qcom_dwc3 SS", qcom);
if (ret) {
dev_err(qcom->dev, "ss_phy_irq failed: %d\n", ret);
return ret;
}
qcom->ss_phy_irq = irq;
}
return 0;
}
static int dwc3_qcom_clk_init(struct dwc3_qcom *qcom, int count)
{
struct device *dev = qcom->dev;
struct device_node *np = dev->of_node;
int i;
qcom->num_clocks = count;
if (!count)
return 0;
qcom->clks = devm_kcalloc(dev, qcom->num_clocks,
sizeof(struct clk *), GFP_KERNEL);
if (!qcom->clks)
return -ENOMEM;
for (i = 0; i < qcom->num_clocks; i++) {
struct clk *clk;
int ret;
clk = of_clk_get(np, i);
if (IS_ERR(clk)) {
while (--i >= 0)
clk_put(qcom->clks[i]);
return PTR_ERR(clk);
}
ret = clk_prepare_enable(clk);
if (ret < 0) {
while (--i >= 0) {
clk_disable_unprepare(qcom->clks[i]);
clk_put(qcom->clks[i]);
}
clk_put(clk);
return ret;
}
qcom->clks[i] = clk;
}
return 0;
}
static int dwc3_qcom_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node, *dwc3_np;
struct device *dev = &pdev->dev;
struct dwc3_qcom *qcom;
struct resource *res;
int ret, i;
bool ignore_pipe_clk;
qcom = devm_kzalloc(&pdev->dev, sizeof(*qcom), GFP_KERNEL);
if (!qcom)
return -ENOMEM;
platform_set_drvdata(pdev, qcom);
qcom->dev = &pdev->dev;
qcom->resets = devm_reset_control_array_get_optional_exclusive(dev);
if (IS_ERR(qcom->resets)) {
ret = PTR_ERR(qcom->resets);
dev_err(&pdev->dev, "failed to get resets, err=%d\n", ret);
return ret;
}
ret = reset_control_assert(qcom->resets);
if (ret) {
dev_err(&pdev->dev, "failed to assert resets, err=%d\n", ret);
return ret;
}
usleep_range(10, 1000);
ret = reset_control_deassert(qcom->resets);
if (ret) {
dev_err(&pdev->dev, "failed to deassert resets, err=%d\n", ret);
goto reset_assert;
}
ret = dwc3_qcom_clk_init(qcom, of_count_phandle_with_args(np,
"clocks", "#clock-cells"));
if (ret) {
dev_err(dev, "failed to get clocks\n");
goto reset_assert;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
qcom->qscratch_base = devm_ioremap_resource(dev, res);
if (IS_ERR(qcom->qscratch_base)) {
dev_err(dev, "failed to map qscratch, err=%d\n", ret);
ret = PTR_ERR(qcom->qscratch_base);
goto clk_disable;
}
ret = dwc3_qcom_setup_irq(pdev);
if (ret)
goto clk_disable;
dwc3_np = of_get_child_by_name(np, "dwc3");
if (!dwc3_np) {
dev_err(dev, "failed to find dwc3 core child\n");
ret = -ENODEV;
goto clk_disable;
}
/*
* Disable pipe_clk requirement if specified. Used when dwc3
* operates without SSPHY and only HS/FS/LS modes are supported.
*/
ignore_pipe_clk = device_property_read_bool(dev,
"qcom,select-utmi-as-pipe-clk");
if (ignore_pipe_clk)
dwc3_qcom_select_utmi_clk(qcom);
ret = of_platform_populate(np, NULL, NULL, dev);
if (ret) {
dev_err(dev, "failed to register dwc3 core - %d\n", ret);
goto clk_disable;
}
qcom->dwc3 = of_find_device_by_node(dwc3_np);
if (!qcom->dwc3) {
dev_err(&pdev->dev, "failed to get dwc3 platform device\n");
goto depopulate;
}
qcom->mode = usb_get_dr_mode(&qcom->dwc3->dev);
/* enable vbus override for device mode */
if (qcom->mode == USB_DR_MODE_PERIPHERAL)
dwc3_qcom_vbus_overrride_enable(qcom, true);
/* register extcon to override sw_vbus on Vbus change later */
ret = dwc3_qcom_register_extcon(qcom);
if (ret)
goto depopulate;
device_init_wakeup(&pdev->dev, 1);
qcom->is_suspended = false;
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
pm_runtime_forbid(dev);
return 0;
depopulate:
of_platform_depopulate(&pdev->dev);
clk_disable:
for (i = qcom->num_clocks - 1; i >= 0; i--) {
clk_disable_unprepare(qcom->clks[i]);
clk_put(qcom->clks[i]);
}
reset_assert:
reset_control_assert(qcom->resets);
return ret;
}
static int dwc3_qcom_remove(struct platform_device *pdev)
{
struct dwc3_qcom *qcom = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
int i;
of_platform_depopulate(dev);
for (i = qcom->num_clocks - 1; i >= 0; i--) {
clk_disable_unprepare(qcom->clks[i]);
clk_put(qcom->clks[i]);
}
qcom->num_clocks = 0;
reset_control_assert(qcom->resets);
pm_runtime_allow(dev);
pm_runtime_disable(dev);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int dwc3_qcom_pm_suspend(struct device *dev)
{
struct dwc3_qcom *qcom = dev_get_drvdata(dev);
int ret = 0;
ret = dwc3_qcom_suspend(qcom);
if (!ret)
qcom->pm_suspended = true;
return ret;
}
static int dwc3_qcom_pm_resume(struct device *dev)
{
struct dwc3_qcom *qcom = dev_get_drvdata(dev);
int ret;
ret = dwc3_qcom_resume(qcom);
if (!ret)
qcom->pm_suspended = false;
return ret;
}
#endif
#ifdef CONFIG_PM
static int dwc3_qcom_runtime_suspend(struct device *dev)
{
struct dwc3_qcom *qcom = dev_get_drvdata(dev);
return dwc3_qcom_suspend(qcom);
}
static int dwc3_qcom_runtime_resume(struct device *dev)
{
struct dwc3_qcom *qcom = dev_get_drvdata(dev);
return dwc3_qcom_resume(qcom);
}
#endif
static const struct dev_pm_ops dwc3_qcom_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(dwc3_qcom_pm_suspend, dwc3_qcom_pm_resume)
SET_RUNTIME_PM_OPS(dwc3_qcom_runtime_suspend, dwc3_qcom_runtime_resume,
NULL)
};
static const struct of_device_id dwc3_qcom_of_match[] = {
{ .compatible = "qcom,dwc3" },
{ .compatible = "qcom,msm8996-dwc3" },
{ .compatible = "qcom,sdm845-dwc3" },
{ }
};
MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match);
static struct platform_driver dwc3_qcom_driver = {
.probe = dwc3_qcom_probe,
.remove = dwc3_qcom_remove,
.driver = {
.name = "dwc3-qcom",
.pm = &dwc3_qcom_dev_pm_ops,
.of_match_table = dwc3_qcom_of_match,
},
};
module_platform_driver(dwc3_qcom_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("DesignWare DWC3 QCOM Glue Driver");
......@@ -66,7 +66,7 @@ static int dwc3_ep0_start_trans(struct dwc3_ep *dep)
struct dwc3 *dwc;
int ret;
if (dep->flags & DWC3_EP_BUSY)
if (dep->flags & DWC3_EP_TRANSFER_STARTED)
return 0;
dwc = dep->dwc;
......@@ -79,8 +79,6 @@ static int dwc3_ep0_start_trans(struct dwc3_ep *dep)
if (ret < 0)
return ret;
dep->flags |= DWC3_EP_BUSY;
dep->resource_index = dwc3_gadget_ep_get_transfer_index(dep);
dwc->ep0_next_event = DWC3_EP0_COMPLETE;
return 0;
......@@ -913,7 +911,7 @@ static void dwc3_ep0_xfer_complete(struct dwc3 *dwc,
{
struct dwc3_ep *dep = dwc->eps[event->endpoint_number];
dep->flags &= ~DWC3_EP_BUSY;
dep->flags &= ~DWC3_EP_TRANSFER_STARTED;
dep->resource_index = 0;
dwc->setup_packet_pending = false;
......
此差异已折叠。
......@@ -98,13 +98,12 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol);
* 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)
static inline void dwc3_gadget_ep_get_transfer_index(struct dwc3_ep *dep)
{
u32 res_id;
res_id = dwc3_readl(dep->regs, DWC3_DEPCMD);
return DWC3_DEPCMD_GET_RSC_IDX(res_id);
dep->resource_index = DWC3_DEPCMD_GET_RSC_IDX(res_id);
}
#endif /* __DRIVERS_USB_DWC3_GADGET_H */
......@@ -230,17 +230,14 @@ DECLARE_EVENT_CLASS(dwc3_log_trb,
TP_fast_assign(
__assign_str(name, dep->name);
__entry->trb = trb;
__entry->allocated = dep->allocated_requests;
__entry->queued = dep->queued_requests;
__entry->bpl = trb->bpl;
__entry->bph = trb->bph;
__entry->size = trb->size;
__entry->ctrl = trb->ctrl;
__entry->type = usb_endpoint_type(dep->endpoint.desc);
),
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,
TP_printk("%s: trb %p buf %08x%08x size %s%d ctrl %08x (%c%c%c%c:%c%c:%s)",
__get_str(name), __entry->trb, __entry->bph, __entry->bpl,
({char *s;
int pcm = ((__entry->size >> 24) & 3) + 1;
switch (__entry->type) {
......@@ -306,7 +303,7 @@ DECLARE_EVENT_CLASS(dwc3_log_ep,
__entry->trb_enqueue = dep->trb_enqueue;
__entry->trb_dequeue = dep->trb_dequeue;
),
TP_printk("%s: mps %d/%d streams %d burst %d ring %d/%d flags %c:%c%c%c%c%c:%c:%c",
TP_printk("%s: mps %d/%d streams %d burst %d ring %d/%d flags %c:%c%c%c%c:%c:%c",
__get_str(name), __entry->maxpacket,
__entry->maxpacket_limit, __entry->max_streams,
__entry->maxburst, __entry->trb_enqueue,
......@@ -314,9 +311,8 @@ DECLARE_EVENT_CLASS(dwc3_log_ep,
__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_TRANSFER_STARTED ? '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 ? '<' : '>'
)
......
......@@ -1601,7 +1601,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
cdev->gadget->ep0->maxpacket;
if (gadget_is_superspeed(gadget)) {
if (gadget->speed >= USB_SPEED_SUPER) {
cdev->desc.bcdUSB = cpu_to_le16(0x0310);
cdev->desc.bcdUSB = cpu_to_le16(0x0320);
cdev->desc.bMaxPacketSize0 = 9;
} else {
cdev->desc.bcdUSB = cpu_to_le16(0x0210);
......
......@@ -705,6 +705,8 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f)
ecm_opts->bound = true;
}
ecm_string_defs[1].s = ecm->ethaddr;
us = usb_gstrings_attach(cdev, ecm_strings,
ARRAY_SIZE(ecm_string_defs));
if (IS_ERR(us))
......@@ -928,7 +930,6 @@ static struct usb_function *ecm_alloc(struct usb_function_instance *fi)
mutex_unlock(&opts->lock);
return ERR_PTR(-EINVAL);
}
ecm_string_defs[1].s = ecm->ethaddr;
ecm->port.ioport = netdev_priv(opts->net);
mutex_unlock(&opts->lock);
......
......@@ -1266,6 +1266,14 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
return ret;
}
#ifdef CONFIG_COMPAT
static long ffs_epfile_compat_ioctl(struct file *file, unsigned code,
unsigned long value)
{
return ffs_epfile_ioctl(file, code, value);
}
#endif
static const struct file_operations ffs_epfile_operations = {
.llseek = no_llseek,
......@@ -1274,6 +1282,9 @@ static const struct file_operations ffs_epfile_operations = {
.read_iter = ffs_epfile_read_iter,
.release = ffs_epfile_release,
.unlocked_ioctl = ffs_epfile_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = ffs_epfile_compat_ioctl,
#endif
};
......
......@@ -109,6 +109,7 @@ static inline struct f_midi *func_to_midi(struct usb_function *f)
static void f_midi_transmit(struct f_midi *midi);
static void f_midi_rmidi_free(struct snd_rawmidi *rmidi);
static void f_midi_free_inst(struct usb_function_instance *f);
DECLARE_UAC_AC_HEADER_DESCRIPTOR(1);
DECLARE_USB_MIDI_OUT_JACK_DESCRIPTOR(1);
......@@ -1102,7 +1103,7 @@ static ssize_t f_midi_opts_##name##_store(struct config_item *item, \
u32 num; \
\
mutex_lock(&opts->lock); \
if (opts->refcnt) { \
if (opts->refcnt > 1) { \
ret = -EBUSY; \
goto end; \
} \
......@@ -1157,7 +1158,7 @@ static ssize_t f_midi_opts_id_store(struct config_item *item,
char *c;
mutex_lock(&opts->lock);
if (opts->refcnt) {
if (opts->refcnt > 1) {
ret = -EBUSY;
goto end;
}
......@@ -1198,13 +1199,21 @@ static const struct config_item_type midi_func_type = {
static void f_midi_free_inst(struct usb_function_instance *f)
{
struct f_midi_opts *opts;
bool free = false;
opts = container_of(f, struct f_midi_opts, func_inst);
if (opts->id_allocated)
kfree(opts->id);
mutex_lock(&opts->lock);
if (!--opts->refcnt) {
free = true;
}
mutex_unlock(&opts->lock);
kfree(opts);
if (free) {
if (opts->id_allocated)
kfree(opts->id);
kfree(opts);
}
}
static struct usb_function_instance *f_midi_alloc_inst(void)
......@@ -1223,6 +1232,7 @@ static struct usb_function_instance *f_midi_alloc_inst(void)
opts->qlen = 32;
opts->in_ports = 1;
opts->out_ports = 1;
opts->refcnt = 1;
config_group_init_type_name(&opts->func_inst.group, "",
&midi_func_type);
......@@ -1234,6 +1244,7 @@ static void f_midi_free(struct usb_function *f)
{
struct f_midi *midi;
struct f_midi_opts *opts;
bool free = false;
midi = func_to_midi(f);
opts = container_of(f->fi, struct f_midi_opts, func_inst);
......@@ -1242,9 +1253,12 @@ static void f_midi_free(struct usb_function *f)
kfree(midi->id);
kfifo_free(&midi->in_req_fifo);
kfree(midi);
--opts->refcnt;
free = true;
}
mutex_unlock(&opts->lock);
if (free)
f_midi_free_inst(&opts->func_inst);
}
static void f_midi_rmidi_free(struct snd_rawmidi *rmidi)
......
......@@ -851,6 +851,9 @@ int rndis_msg_parser(struct rndis_params *params, u8 *buf)
*/
pr_warn("%s: unknown RNDIS message 0x%08X len %d\n",
__func__, MsgType, MsgLength);
/* Garbled message can be huge, so limit what we display */
if (MsgLength > 16)
MsgLength = 16;
print_hex_dump_bytes(__func__, DUMP_PREFIX_OFFSET,
buf, MsgLength);
break;
......
......@@ -844,6 +844,10 @@ struct net_device *gether_setup_name_default(const char *netname)
net->ethtool_ops = &ops;
SET_NETDEV_DEVTYPE(net, &gadget_type);
/* MTU range: 14 - 15412 */
net->min_mtu = ETH_HLEN;
net->max_mtu = GETHER_MAX_ETH_FRAME_LEN;
return net;
}
EXPORT_SYMBOL_GPL(gether_setup_name_default);
......
......@@ -438,6 +438,8 @@ config USB_GADGET_XILINX
dynamically linked module called "udc-xilinx" and force all
gadget drivers to also be dynamically linked.
source "drivers/usb/gadget/udc/aspeed-vhub/Kconfig"
#
# LAST -- dummy/emulated controller
#
......
......@@ -39,4 +39,5 @@ 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_ASPEED_VHUB) += aspeed-vhub/
obj-$(CONFIG_USB_BDC_UDC) += bdc/
# SPDX-License-Identifier: GPL-2.0+
config USB_ASPEED_VHUB
tristate "Aspeed vHub UDC driver"
depends on ARCH_ASPEED || COMPILE_TEST
help
USB peripheral controller for the Aspeed AST2500 family
SoCs supporting the "vHub" functionality and USB2.0
# SPDX-License-Identifier: GPL-2.0+
obj-$(CONFIG_USB_ASPEED_VHUB) += aspeed-vhub.o
aspeed-vhub-y := core.o ep0.o epn.o dev.o hub.o
// SPDX-License-Identifier: GPL-2.0+
/*
* aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget
*
* core.c - Top level support
*
* Copyright 2017 IBM Corporation
*
* 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.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/proc_fs.h>
#include <linux/prefetch.h>
#include <linux/clk.h>
#include <linux/usb/gadget.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/regmap.h>
#include <linux/dma-mapping.h>
#include "vhub.h"
void ast_vhub_done(struct ast_vhub_ep *ep, struct ast_vhub_req *req,
int status)
{
bool internal = req->internal;
EPVDBG(ep, "completing request @%p, status %d\n", req, status);
list_del_init(&req->queue);
if (req->req.status == -EINPROGRESS)
req->req.status = status;
if (req->req.dma) {
if (!WARN_ON(!ep->dev))
usb_gadget_unmap_request(&ep->dev->gadget,
&req->req, ep->epn.is_in);
req->req.dma = 0;
}
/*
* If this isn't an internal EP0 request, call the core
* to call the gadget completion.
*/
if (!internal) {
spin_unlock(&ep->vhub->lock);
usb_gadget_giveback_request(&ep->ep, &req->req);
spin_lock(&ep->vhub->lock);
}
}
void ast_vhub_nuke(struct ast_vhub_ep *ep, int status)
{
struct ast_vhub_req *req;
EPDBG(ep, "Nuking\n");
/* Beware, lock will be dropped & req-acquired by done() */
while (!list_empty(&ep->queue)) {
req = list_first_entry(&ep->queue, struct ast_vhub_req, queue);
ast_vhub_done(ep, req, status);
}
}
struct usb_request *ast_vhub_alloc_request(struct usb_ep *u_ep,
gfp_t gfp_flags)
{
struct ast_vhub_req *req;
req = kzalloc(sizeof(*req), gfp_flags);
if (!req)
return NULL;
return &req->req;
}
void ast_vhub_free_request(struct usb_ep *u_ep, struct usb_request *u_req)
{
struct ast_vhub_req *req = to_ast_req(u_req);
kfree(req);
}
static irqreturn_t ast_vhub_irq(int irq, void *data)
{
struct ast_vhub *vhub = data;
irqreturn_t iret = IRQ_NONE;
u32 istat;
/* Stale interrupt while tearing down */
if (!vhub->ep0_bufs)
return IRQ_NONE;
spin_lock(&vhub->lock);
/* Read and ACK interrupts */
istat = readl(vhub->regs + AST_VHUB_ISR);
if (!istat)
goto bail;
writel(istat, vhub->regs + AST_VHUB_ISR);
iret = IRQ_HANDLED;
UDCVDBG(vhub, "irq status=%08x, ep_acks=%08x ep_nacks=%08x\n",
istat,
readl(vhub->regs + AST_VHUB_EP_ACK_ISR),
readl(vhub->regs + AST_VHUB_EP_NACK_ISR));
/* Handle generic EPs first */
if (istat & VHUB_IRQ_EP_POOL_ACK_STALL) {
u32 i, ep_acks = readl(vhub->regs + AST_VHUB_EP_ACK_ISR);
writel(ep_acks, vhub->regs + AST_VHUB_EP_ACK_ISR);
for (i = 0; ep_acks && i < AST_VHUB_NUM_GEN_EPs; i++) {
u32 mask = VHUB_EP_IRQ(i);
if (ep_acks & mask) {
ast_vhub_epn_ack_irq(&vhub->epns[i]);
ep_acks &= ~mask;
}
}
}
/* Handle device interrupts */
if (istat & (VHUB_IRQ_DEVICE1 |
VHUB_IRQ_DEVICE2 |
VHUB_IRQ_DEVICE3 |
VHUB_IRQ_DEVICE4 |
VHUB_IRQ_DEVICE5)) {
if (istat & VHUB_IRQ_DEVICE1)
ast_vhub_dev_irq(&vhub->ports[0].dev);
if (istat & VHUB_IRQ_DEVICE2)
ast_vhub_dev_irq(&vhub->ports[1].dev);
if (istat & VHUB_IRQ_DEVICE3)
ast_vhub_dev_irq(&vhub->ports[2].dev);
if (istat & VHUB_IRQ_DEVICE4)
ast_vhub_dev_irq(&vhub->ports[3].dev);
if (istat & VHUB_IRQ_DEVICE5)
ast_vhub_dev_irq(&vhub->ports[4].dev);
}
/* Handle top-level vHub EP0 interrupts */
if (istat & (VHUB_IRQ_HUB_EP0_OUT_ACK_STALL |
VHUB_IRQ_HUB_EP0_IN_ACK_STALL |
VHUB_IRQ_HUB_EP0_SETUP)) {
if (istat & VHUB_IRQ_HUB_EP0_IN_ACK_STALL)
ast_vhub_ep0_handle_ack(&vhub->ep0, true);
if (istat & VHUB_IRQ_HUB_EP0_OUT_ACK_STALL)
ast_vhub_ep0_handle_ack(&vhub->ep0, false);
if (istat & VHUB_IRQ_HUB_EP0_SETUP)
ast_vhub_ep0_handle_setup(&vhub->ep0);
}
/* Various top level bus events */
if (istat & (VHUB_IRQ_BUS_RESUME |
VHUB_IRQ_BUS_SUSPEND |
VHUB_IRQ_BUS_RESET)) {
if (istat & VHUB_IRQ_BUS_RESUME)
ast_vhub_hub_resume(vhub);
if (istat & VHUB_IRQ_BUS_SUSPEND)
ast_vhub_hub_suspend(vhub);
if (istat & VHUB_IRQ_BUS_RESET)
ast_vhub_hub_reset(vhub);
}
bail:
spin_unlock(&vhub->lock);
return iret;
}
void ast_vhub_init_hw(struct ast_vhub *vhub)
{
u32 ctrl;
UDCDBG(vhub,"(Re)Starting HW ...\n");
/* Enable PHY */
ctrl = VHUB_CTRL_PHY_CLK |
VHUB_CTRL_PHY_RESET_DIS;
/*
* We do *NOT* set the VHUB_CTRL_CLK_STOP_SUSPEND bit
* to stop the logic clock during suspend because
* it causes the registers to become inaccessible and
* we haven't yet figured out a good wayt to bring the
* controller back into life to issue a wakeup.
*/
/*
* Set some ISO & split control bits according to Aspeed
* recommendation
*
* VHUB_CTRL_ISO_RSP_CTRL: When set tells the HW to respond
* with 0 bytes data packet to ISO IN endpoints when no data
* is available.
*
* VHUB_CTRL_SPLIT_IN: This makes a SOF complete a split IN
* transaction.
*/
ctrl |= VHUB_CTRL_ISO_RSP_CTRL | VHUB_CTRL_SPLIT_IN;
writel(ctrl, vhub->regs + AST_VHUB_CTRL);
udelay(1);
/* Set descriptor ring size */
if (AST_VHUB_DESCS_COUNT == 256) {
ctrl |= VHUB_CTRL_LONG_DESC;
writel(ctrl, vhub->regs + AST_VHUB_CTRL);
} else {
BUILD_BUG_ON(AST_VHUB_DESCS_COUNT != 32);
}
/* Reset all devices */
writel(VHUB_SW_RESET_ALL, vhub->regs + AST_VHUB_SW_RESET);
udelay(1);
writel(0, vhub->regs + AST_VHUB_SW_RESET);
/* Disable and cleanup EP ACK/NACK interrupts */
writel(0, vhub->regs + AST_VHUB_EP_ACK_IER);
writel(0, vhub->regs + AST_VHUB_EP_NACK_IER);
writel(VHUB_EP_IRQ_ALL, vhub->regs + AST_VHUB_EP_ACK_ISR);
writel(VHUB_EP_IRQ_ALL, vhub->regs + AST_VHUB_EP_NACK_ISR);
/* Default settings for EP0, enable HW hub EP1 */
writel(0, vhub->regs + AST_VHUB_EP0_CTRL);
writel(VHUB_EP1_CTRL_RESET_TOGGLE |
VHUB_EP1_CTRL_ENABLE,
vhub->regs + AST_VHUB_EP1_CTRL);
writel(0, vhub->regs + AST_VHUB_EP1_STS_CHG);
/* Configure EP0 DMA buffer */
writel(vhub->ep0.buf_dma, vhub->regs + AST_VHUB_EP0_DATA);
/* Clear address */
writel(0, vhub->regs + AST_VHUB_CONF);
/* Pullup hub (activate on host) */
if (vhub->force_usb1)
ctrl |= VHUB_CTRL_FULL_SPEED_ONLY;
ctrl |= VHUB_CTRL_UPSTREAM_CONNECT;
writel(ctrl, vhub->regs + AST_VHUB_CTRL);
/* Enable some interrupts */
writel(VHUB_IRQ_HUB_EP0_IN_ACK_STALL |
VHUB_IRQ_HUB_EP0_OUT_ACK_STALL |
VHUB_IRQ_HUB_EP0_SETUP |
VHUB_IRQ_EP_POOL_ACK_STALL |
VHUB_IRQ_BUS_RESUME |
VHUB_IRQ_BUS_SUSPEND |
VHUB_IRQ_BUS_RESET,
vhub->regs + AST_VHUB_IER);
}
static int ast_vhub_remove(struct platform_device *pdev)
{
struct ast_vhub *vhub = platform_get_drvdata(pdev);
unsigned long flags;
int i;
if (!vhub || !vhub->regs)
return 0;
/* Remove devices */
for (i = 0; i < AST_VHUB_NUM_PORTS; i++)
ast_vhub_del_dev(&vhub->ports[i].dev);
spin_lock_irqsave(&vhub->lock, flags);
/* Mask & ack all interrupts */
writel(0, vhub->regs + AST_VHUB_IER);
writel(VHUB_IRQ_ACK_ALL, vhub->regs + AST_VHUB_ISR);
/* Pull device, leave PHY enabled */
writel(VHUB_CTRL_PHY_CLK |
VHUB_CTRL_PHY_RESET_DIS,
vhub->regs + AST_VHUB_CTRL);
if (vhub->clk)
clk_disable_unprepare(vhub->clk);
spin_unlock_irqrestore(&vhub->lock, flags);
if (vhub->ep0_bufs)
dma_free_coherent(&pdev->dev,
AST_VHUB_EP0_MAX_PACKET *
(AST_VHUB_NUM_PORTS + 1),
vhub->ep0_bufs,
vhub->ep0_bufs_dma);
vhub->ep0_bufs = NULL;
return 0;
}
static int ast_vhub_probe(struct platform_device *pdev)
{
enum usb_device_speed max_speed;
struct ast_vhub *vhub;
struct resource *res;
int i, rc = 0;
vhub = devm_kzalloc(&pdev->dev, sizeof(*vhub), GFP_KERNEL);
if (!vhub)
return -ENOMEM;
spin_lock_init(&vhub->lock);
vhub->pdev = pdev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
vhub->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(vhub->regs)) {
dev_err(&pdev->dev, "Failed to map resources\n");
return PTR_ERR(vhub->regs);
}
UDCDBG(vhub, "vHub@%pR mapped @%p\n", res, vhub->regs);
platform_set_drvdata(pdev, vhub);
vhub->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(vhub->clk)) {
rc = PTR_ERR(vhub->clk);
goto err;
}
rc = clk_prepare_enable(vhub->clk);
if (rc) {
dev_err(&pdev->dev, "Error couldn't enable clock (%d)\n", rc);
goto err;
}
/* Check if we need to limit the HW to USB1 */
max_speed = usb_get_maximum_speed(&pdev->dev);
if (max_speed != USB_SPEED_UNKNOWN && max_speed < USB_SPEED_HIGH)
vhub->force_usb1 = true;
/* Mask & ack all interrupts before installing the handler */
writel(0, vhub->regs + AST_VHUB_IER);
writel(VHUB_IRQ_ACK_ALL, vhub->regs + AST_VHUB_ISR);
/* Find interrupt and install handler */
vhub->irq = platform_get_irq(pdev, 0);
if (vhub->irq < 0) {
dev_err(&pdev->dev, "Failed to get interrupt\n");
rc = vhub->irq;
goto err;
}
rc = devm_request_irq(&pdev->dev, vhub->irq, ast_vhub_irq, 0,
KBUILD_MODNAME, vhub);
if (rc) {
dev_err(&pdev->dev, "Failed to request interrupt\n");
goto err;
}
/*
* Allocate DMA buffers for all EP0s in one chunk,
* one per port and one for the vHub itself
*/
vhub->ep0_bufs = dma_alloc_coherent(&pdev->dev,
AST_VHUB_EP0_MAX_PACKET *
(AST_VHUB_NUM_PORTS + 1),
&vhub->ep0_bufs_dma, GFP_KERNEL);
if (!vhub->ep0_bufs) {
dev_err(&pdev->dev, "Failed to allocate EP0 DMA buffers\n");
rc = -ENOMEM;
goto err;
}
UDCVDBG(vhub, "EP0 DMA buffers @%p (DMA 0x%08x)\n",
vhub->ep0_bufs, (u32)vhub->ep0_bufs_dma);
/* Init vHub EP0 */
ast_vhub_init_ep0(vhub, &vhub->ep0, NULL);
/* Init devices */
for (i = 0; i < AST_VHUB_NUM_PORTS && rc == 0; i++)
rc = ast_vhub_init_dev(vhub, i);
if (rc)
goto err;
/* Init hub emulation */
ast_vhub_init_hub(vhub);
/* Initialize HW */
ast_vhub_init_hw(vhub);
dev_info(&pdev->dev, "Initialized virtual hub in USB%d mode\n",
vhub->force_usb1 ? 1 : 2);
return 0;
err:
ast_vhub_remove(pdev);
return rc;
}
static const struct of_device_id ast_vhub_dt_ids[] = {
{
.compatible = "aspeed,ast2400-usb-vhub",
},
{
.compatible = "aspeed,ast2500-usb-vhub",
},
{ }
};
MODULE_DEVICE_TABLE(of, ast_vhub_dt_ids);
static struct platform_driver ast_vhub_driver = {
.probe = ast_vhub_probe,
.remove = ast_vhub_remove,
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = ast_vhub_dt_ids,
},
};
module_platform_driver(ast_vhub_driver);
MODULE_DESCRIPTION("Aspeed vHub udc driver");
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_LICENSE("GPL");
// SPDX-License-Identifier: GPL-2.0+
/*
* aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget
*
* dev.c - Individual device/gadget management (ie, a port = a gadget)
*
* Copyright 2017 IBM Corporation
*
* 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.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/proc_fs.h>
#include <linux/prefetch.h>
#include <linux/clk.h>
#include <linux/usb/gadget.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/regmap.h>
#include <linux/dma-mapping.h>
#include <linux/usb.h>
#include <linux/usb/hcd.h>
#include "vhub.h"
void ast_vhub_dev_irq(struct ast_vhub_dev *d)
{
u32 istat = readl(d->regs + AST_VHUB_DEV_ISR);
writel(istat, d->regs + AST_VHUB_DEV_ISR);
if (istat & VHUV_DEV_IRQ_EP0_IN_ACK_STALL)
ast_vhub_ep0_handle_ack(&d->ep0, true);
if (istat & VHUV_DEV_IRQ_EP0_OUT_ACK_STALL)
ast_vhub_ep0_handle_ack(&d->ep0, false);
if (istat & VHUV_DEV_IRQ_EP0_SETUP)
ast_vhub_ep0_handle_setup(&d->ep0);
}
static void ast_vhub_dev_enable(struct ast_vhub_dev *d)
{
u32 reg, hmsk;
if (d->enabled)
return;
/* Enable device and its EP0 interrupts */
reg = VHUB_DEV_EN_ENABLE_PORT |
VHUB_DEV_EN_EP0_IN_ACK_IRQEN |
VHUB_DEV_EN_EP0_OUT_ACK_IRQEN |
VHUB_DEV_EN_EP0_SETUP_IRQEN;
if (d->gadget.speed == USB_SPEED_HIGH)
reg |= VHUB_DEV_EN_SPEED_SEL_HIGH;
writel(reg, d->regs + AST_VHUB_DEV_EN_CTRL);
/* Enable device interrupt in the hub as well */
hmsk = VHUB_IRQ_DEVICE1 << d->index;
reg = readl(d->vhub->regs + AST_VHUB_IER);
reg |= hmsk;
writel(reg, d->vhub->regs + AST_VHUB_IER);
/* Set EP0 DMA buffer address */
writel(d->ep0.buf_dma, d->regs + AST_VHUB_DEV_EP0_DATA);
d->enabled = true;
}
static void ast_vhub_dev_disable(struct ast_vhub_dev *d)
{
u32 reg, hmsk;
if (!d->enabled)
return;
/* Disable device interrupt in the hub */
hmsk = VHUB_IRQ_DEVICE1 << d->index;
reg = readl(d->vhub->regs + AST_VHUB_IER);
reg &= ~hmsk;
writel(reg, d->vhub->regs + AST_VHUB_IER);
/* Then disable device */
writel(0, d->regs + AST_VHUB_DEV_EN_CTRL);
d->gadget.speed = USB_SPEED_UNKNOWN;
d->enabled = false;
d->suspended = false;
}
static int ast_vhub_dev_feature(struct ast_vhub_dev *d,
u16 wIndex, u16 wValue,
bool is_set)
{
DDBG(d, "%s_FEATURE(dev val=%02x)\n",
is_set ? "SET" : "CLEAR", wValue);
if (wValue != USB_DEVICE_REMOTE_WAKEUP)
return std_req_driver;
d->wakeup_en = is_set;
return std_req_complete;
}
static int ast_vhub_ep_feature(struct ast_vhub_dev *d,
u16 wIndex, u16 wValue, bool is_set)
{
struct ast_vhub_ep *ep;
int ep_num;
ep_num = wIndex & USB_ENDPOINT_NUMBER_MASK;
DDBG(d, "%s_FEATURE(ep%d val=%02x)\n",
is_set ? "SET" : "CLEAR", ep_num, wValue);
if (ep_num == 0)
return std_req_complete;
if (ep_num >= AST_VHUB_NUM_GEN_EPs || !d->epns[ep_num - 1])
return std_req_stall;
if (wValue != USB_ENDPOINT_HALT)
return std_req_driver;
ep = d->epns[ep_num - 1];
if (WARN_ON(!ep))
return std_req_stall;
if (!ep->epn.enabled || !ep->ep.desc || ep->epn.is_iso ||
ep->epn.is_in != !!(wIndex & USB_DIR_IN))
return std_req_stall;
DDBG(d, "%s stall on EP %d\n",
is_set ? "setting" : "clearing", ep_num);
ep->epn.stalled = is_set;
ast_vhub_update_epn_stall(ep);
return std_req_complete;
}
static int ast_vhub_dev_status(struct ast_vhub_dev *d,
u16 wIndex, u16 wValue)
{
u8 st0;
DDBG(d, "GET_STATUS(dev)\n");
st0 = d->gadget.is_selfpowered << USB_DEVICE_SELF_POWERED;
if (d->wakeup_en)
st0 |= 1 << USB_DEVICE_REMOTE_WAKEUP;
return ast_vhub_simple_reply(&d->ep0, st0, 0);
}
static int ast_vhub_ep_status(struct ast_vhub_dev *d,
u16 wIndex, u16 wValue)
{
int ep_num = wIndex & USB_ENDPOINT_NUMBER_MASK;
struct ast_vhub_ep *ep;
u8 st0 = 0;
DDBG(d, "GET_STATUS(ep%d)\n", ep_num);
if (ep_num >= AST_VHUB_NUM_GEN_EPs)
return std_req_stall;
if (ep_num != 0) {
ep = d->epns[ep_num - 1];
if (!ep)
return std_req_stall;
if (!ep->epn.enabled || !ep->ep.desc || ep->epn.is_iso ||
ep->epn.is_in != !!(wIndex & USB_DIR_IN))
return std_req_stall;
if (ep->epn.stalled)
st0 |= 1 << USB_ENDPOINT_HALT;
}
return ast_vhub_simple_reply(&d->ep0, st0, 0);
}
static void ast_vhub_dev_set_address(struct ast_vhub_dev *d, u8 addr)
{
u32 reg;
DDBG(d, "SET_ADDRESS: Got address %x\n", addr);
reg = readl(d->regs + AST_VHUB_DEV_EN_CTRL);
reg &= ~VHUB_DEV_EN_ADDR_MASK;
reg |= VHUB_DEV_EN_SET_ADDR(addr);
writel(reg, d->regs + AST_VHUB_DEV_EN_CTRL);
}
int ast_vhub_std_dev_request(struct ast_vhub_ep *ep,
struct usb_ctrlrequest *crq)
{
struct ast_vhub_dev *d = ep->dev;
u16 wValue, wIndex;
/* No driver, we shouldn't be enabled ... */
if (!d->driver || !d->enabled || d->suspended) {
EPDBG(ep,
"Device is wrong state driver=%p enabled=%d"
" suspended=%d\n",
d->driver, d->enabled, d->suspended);
return std_req_stall;
}
/* First packet, grab speed */
if (d->gadget.speed == USB_SPEED_UNKNOWN) {
d->gadget.speed = ep->vhub->speed;
if (d->gadget.speed > d->driver->max_speed)
d->gadget.speed = d->driver->max_speed;
DDBG(d, "fist packet, captured speed %d\n",
d->gadget.speed);
}
wValue = le16_to_cpu(crq->wValue);
wIndex = le16_to_cpu(crq->wIndex);
switch ((crq->bRequestType << 8) | crq->bRequest) {
/* SET_ADDRESS */
case DeviceOutRequest | USB_REQ_SET_ADDRESS:
ast_vhub_dev_set_address(d, wValue);
return std_req_complete;
/* GET_STATUS */
case DeviceRequest | USB_REQ_GET_STATUS:
return ast_vhub_dev_status(d, wIndex, wValue);
case InterfaceRequest | USB_REQ_GET_STATUS:
return ast_vhub_simple_reply(ep, 0, 0);
case EndpointRequest | USB_REQ_GET_STATUS:
return ast_vhub_ep_status(d, wIndex, wValue);
/* SET/CLEAR_FEATURE */
case DeviceOutRequest | USB_REQ_SET_FEATURE:
return ast_vhub_dev_feature(d, wIndex, wValue, true);
case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
return ast_vhub_dev_feature(d, wIndex, wValue, false);
case EndpointOutRequest | USB_REQ_SET_FEATURE:
return ast_vhub_ep_feature(d, wIndex, wValue, true);
case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
return ast_vhub_ep_feature(d, wIndex, wValue, false);
}
return std_req_driver;
}
static int ast_vhub_udc_wakeup(struct usb_gadget* gadget)
{
struct ast_vhub_dev *d = to_ast_dev(gadget);
unsigned long flags;
int rc = -EINVAL;
spin_lock_irqsave(&d->vhub->lock, flags);
if (!d->wakeup_en)
goto err;
DDBG(d, "Device initiated wakeup\n");
/* Wakeup the host */
ast_vhub_hub_wake_all(d->vhub);
rc = 0;
err:
spin_unlock_irqrestore(&d->vhub->lock, flags);
return rc;
}
static int ast_vhub_udc_get_frame(struct usb_gadget* gadget)
{
struct ast_vhub_dev *d = to_ast_dev(gadget);
return (readl(d->vhub->regs + AST_VHUB_USBSTS) >> 16) & 0x7ff;
}
static void ast_vhub_dev_nuke(struct ast_vhub_dev *d)
{
unsigned int i;
for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++) {
if (!d->epns[i])
continue;
ast_vhub_nuke(d->epns[i], -ESHUTDOWN);
}
}
static int ast_vhub_udc_pullup(struct usb_gadget* gadget, int on)
{
struct ast_vhub_dev *d = to_ast_dev(gadget);
unsigned long flags;
spin_lock_irqsave(&d->vhub->lock, flags);
DDBG(d, "pullup(%d)\n", on);
/* Mark disconnected in the hub */
ast_vhub_device_connect(d->vhub, d->index, on);
/*
* If enabled, nuke all requests if any (there shouldn't be)
* and disable the port. This will clear the address too.
*/
if (d->enabled) {
ast_vhub_dev_nuke(d);
ast_vhub_dev_disable(d);
}
spin_unlock_irqrestore(&d->vhub->lock, flags);
return 0;
}
static int ast_vhub_udc_start(struct usb_gadget *gadget,
struct usb_gadget_driver *driver)
{
struct ast_vhub_dev *d = to_ast_dev(gadget);
unsigned long flags;
spin_lock_irqsave(&d->vhub->lock, flags);
DDBG(d, "start\n");
/* We don't do much more until the hub enables us */
d->driver = driver;
d->gadget.is_selfpowered = 1;
spin_unlock_irqrestore(&d->vhub->lock, flags);
return 0;
}
static struct usb_ep *ast_vhub_udc_match_ep(struct usb_gadget *gadget,
struct usb_endpoint_descriptor *desc,
struct usb_ss_ep_comp_descriptor *ss)
{
struct ast_vhub_dev *d = to_ast_dev(gadget);
struct ast_vhub_ep *ep;
struct usb_ep *u_ep;
unsigned int max, addr, i;
DDBG(d, "Match EP type %d\n", usb_endpoint_type(desc));
/*
* First we need to look for an existing unclaimed EP as another
* configuration may have already associated a bunch of EPs with
* this gadget. This duplicates the code in usb_ep_autoconfig_ss()
* unfortunately.
*/
list_for_each_entry(u_ep, &gadget->ep_list, ep_list) {
if (usb_gadget_ep_match_desc(gadget, u_ep, desc, ss)) {
DDBG(d, " -> using existing EP%d\n",
to_ast_ep(u_ep)->d_idx);
return u_ep;
}
}
/*
* We didn't find one, we need to grab one from the pool.
*
* First let's do some sanity checking
*/
switch(usb_endpoint_type(desc)) {
case USB_ENDPOINT_XFER_CONTROL:
/* Only EP0 can be a control endpoint */
return NULL;
case USB_ENDPOINT_XFER_ISOC:
/* ISO: limit 1023 bytes full speed, 1024 high/super speed */
if (gadget_is_dualspeed(gadget))
max = 1024;
else
max = 1023;
break;
case USB_ENDPOINT_XFER_BULK:
if (gadget_is_dualspeed(gadget))
max = 512;
else
max = 64;
break;
case USB_ENDPOINT_XFER_INT:
if (gadget_is_dualspeed(gadget))
max = 1024;
else
max = 64;
break;
}
if (usb_endpoint_maxp(desc) > max)
return NULL;
/*
* Find a free EP address for that device. We can't
* let the generic code assign these as it would
* create overlapping numbers for IN and OUT which
* we don't support, so also create a suitable name
* that will allow the generic code to use our
* assigned address.
*/
for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++)
if (d->epns[i] == NULL)
break;
if (i >= AST_VHUB_NUM_GEN_EPs)
return NULL;
addr = i + 1;
/*
* Now grab an EP from the shared pool and associate
* it with our device
*/
ep = ast_vhub_alloc_epn(d, addr);
if (!ep)
return NULL;
DDBG(d, "Allocated epn#%d for port EP%d\n",
ep->epn.g_idx, addr);
return &ep->ep;
}
static int ast_vhub_udc_stop(struct usb_gadget *gadget)
{
struct ast_vhub_dev *d = to_ast_dev(gadget);
unsigned long flags;
spin_lock_irqsave(&d->vhub->lock, flags);
DDBG(d, "stop\n");
d->driver = NULL;
d->gadget.speed = USB_SPEED_UNKNOWN;
ast_vhub_dev_nuke(d);
if (d->enabled)
ast_vhub_dev_disable(d);
spin_unlock_irqrestore(&d->vhub->lock, flags);
return 0;
}
static struct usb_gadget_ops ast_vhub_udc_ops = {
.get_frame = ast_vhub_udc_get_frame,
.wakeup = ast_vhub_udc_wakeup,
.pullup = ast_vhub_udc_pullup,
.udc_start = ast_vhub_udc_start,
.udc_stop = ast_vhub_udc_stop,
.match_ep = ast_vhub_udc_match_ep,
};
void ast_vhub_dev_suspend(struct ast_vhub_dev *d)
{
d->suspended = true;
if (d->driver) {
spin_unlock(&d->vhub->lock);
d->driver->suspend(&d->gadget);
spin_lock(&d->vhub->lock);
}
}
void ast_vhub_dev_resume(struct ast_vhub_dev *d)
{
d->suspended = false;
if (d->driver) {
spin_unlock(&d->vhub->lock);
d->driver->resume(&d->gadget);
spin_lock(&d->vhub->lock);
}
}
void ast_vhub_dev_reset(struct ast_vhub_dev *d)
{
/*
* If speed is not set, we enable the port. If it is,
* send reset to the gadget and reset "speed".
*
* Speed is an indication that we have got the first
* setup packet to the device.
*/
if (d->gadget.speed == USB_SPEED_UNKNOWN && !d->enabled) {
DDBG(d, "Reset at unknown speed of disabled device, enabling...\n");
ast_vhub_dev_enable(d);
d->suspended = false;
}
if (d->gadget.speed != USB_SPEED_UNKNOWN && d->driver) {
unsigned int i;
DDBG(d, "Reset at known speed of bound device, resetting...\n");
spin_unlock(&d->vhub->lock);
d->driver->reset(&d->gadget);
spin_lock(&d->vhub->lock);
/*
* Disable/re-enable HW, this will clear the address
* and speed setting.
*/
ast_vhub_dev_disable(d);
ast_vhub_dev_enable(d);
/* Clear stall on all EPs */
for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++) {
struct ast_vhub_ep *ep = d->epns[i];
if (ep && ep->epn.stalled) {
ep->epn.stalled = false;
ast_vhub_update_epn_stall(ep);
}
}
/* Additional cleanups */
d->wakeup_en = false;
d->suspended = false;
}
}
void ast_vhub_del_dev(struct ast_vhub_dev *d)
{
unsigned long flags;
spin_lock_irqsave(&d->vhub->lock, flags);
if (!d->registered) {
spin_unlock_irqrestore(&d->vhub->lock, flags);
return;
}
d->registered = false;
spin_unlock_irqrestore(&d->vhub->lock, flags);
usb_del_gadget_udc(&d->gadget);
device_unregister(d->port_dev);
}
static void ast_vhub_dev_release(struct device *dev)
{
kfree(dev);
}
int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx)
{
struct ast_vhub_dev *d = &vhub->ports[idx].dev;
struct device *parent = &vhub->pdev->dev;
int rc;
d->vhub = vhub;
d->index = idx;
d->name = devm_kasprintf(parent, GFP_KERNEL, "port%d", idx+1);
d->regs = vhub->regs + 0x100 + 0x10 * idx;
ast_vhub_init_ep0(vhub, &d->ep0, d);
/*
* The UDC core really needs us to have separate and uniquely
* named "parent" devices for each port so we create a sub device
* here for that purpose
*/
d->port_dev = kzalloc(sizeof(struct device), GFP_KERNEL);
if (!d->port_dev)
return -ENOMEM;
device_initialize(d->port_dev);
d->port_dev->release = ast_vhub_dev_release;
d->port_dev->parent = parent;
dev_set_name(d->port_dev, "%s:p%d", dev_name(parent), idx + 1);
rc = device_add(d->port_dev);
if (rc)
goto fail_add;
/* Populate gadget */
INIT_LIST_HEAD(&d->gadget.ep_list);
d->gadget.ops = &ast_vhub_udc_ops;
d->gadget.ep0 = &d->ep0.ep;
d->gadget.name = KBUILD_MODNAME;
if (vhub->force_usb1)
d->gadget.max_speed = USB_SPEED_FULL;
else
d->gadget.max_speed = USB_SPEED_HIGH;
d->gadget.speed = USB_SPEED_UNKNOWN;
d->gadget.dev.of_node = vhub->pdev->dev.of_node;
rc = usb_add_gadget_udc(d->port_dev, &d->gadget);
if (rc != 0)
goto fail_udc;
d->registered = true;
return 0;
fail_udc:
device_del(d->port_dev);
fail_add:
put_device(d->port_dev);
return rc;
}
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
......@@ -326,7 +326,6 @@ struct usba_udc {
const struct usba_udc_errata *errata;
int irq;
struct gpio_desc *vbus_pin;
int vbus_pin_inverted;
int num_ep;
int configured_ep;
struct usba_fifo_cfg *fifo_cfg;
......
......@@ -244,6 +244,12 @@ EXPORT_SYMBOL_GPL(usb_ep_free_request);
* Returns zero, or a negative error code. Endpoints that are not enabled
* report errors; errors will also be
* reported when the usb peripheral is disconnected.
*
* If and only if @req is successfully queued (the return value is zero),
* @req->complete() will be called exactly once, when the Gadget core and
* UDC are finished with the request. When the completion function is called,
* control of the request is returned to the device driver which submitted it.
* The completion handler may then immediately free or reuse @req.
*/
int usb_ep_queue(struct usb_ep *ep,
struct usb_request *req, gfp_t gfp_flags)
......
......@@ -253,6 +253,7 @@ static int dr_controller_setup(struct fsl_udc *udc)
portctrl |= PORTSCX_PTW_16BIT;
/* fall through */
case FSL_USB2_PHY_UTMI:
case FSL_USB2_PHY_UTMI_DUAL:
if (udc->pdata->have_sysif_regs) {
if (udc->pdata->controller_ver) {
/* controller version 1.6 or above */
......
......@@ -33,7 +33,7 @@
* characters (which are also widely used in C strings).
*/
int
usb_gadget_get_string (struct usb_gadget_strings *table, int id, u8 *buf)
usb_gadget_get_string (const struct usb_gadget_strings *table, int id, u8 *buf)
{
struct usb_string *s;
int len;
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册