diff options
69 files changed, 3860 insertions, 238 deletions
diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index 7efe31ed3a25..568103d3376e 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -253,6 +253,17 @@ Description: only if the system firmware is capable of describing the connection between a port and its connector. +What: /sys/bus/usb/devices/.../<hub_interface>/port<X>/disable +Date: June 2022 +Contact: Michael Grzeschik <m.grzeschik@pengutronix.de> +Description: + This file controls the state of a USB port, including + Vbus power output (but only on hubs that support + power switching -- most hubs don't support it). If + a port is disabled, the port is unusable: Devices + attached to the port will not be detected, initialized, + or enumerated. + What: /sys/bus/usb/devices/.../power/usb2_lpm_l1_timeout Date: May 2013 Contact: Mathias Nyman <mathias.nyman@linux.intel.com> diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec index 75088ecad202..281b995beb05 100644 --- a/Documentation/ABI/testing/sysfs-class-typec +++ b/Documentation/ABI/testing/sysfs-class-typec @@ -141,6 +141,14 @@ Description: - "reverse": CC2 orientation - "unknown": Orientation cannot be determined. +What: /sys/class/typec/<port>/select_usb_power_delivery +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Lists the USB Power Delivery Capabilities that the port can + advertise to the partner. The currently used capabilities are in + brackets. Selection happens by writing to the file. + USB Type-C partner devices (eg. /sys/class/typec/port0-partner/) What: /sys/class/typec/<port>-partner/accessory_mode diff --git a/Documentation/ABI/testing/sysfs-class-usb_power_delivery b/Documentation/ABI/testing/sysfs-class-usb_power_delivery new file mode 100644 index 000000000000..ce2b1b563cb3 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-usb_power_delivery @@ -0,0 +1,240 @@ +What: /sys/class/usb_power_delivery +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Directory for USB Power Delivery devices. + +What: /sys/class/usb_power_delivery/.../revision +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + File showing the USB Power Delivery Specification Revision used + in communication. + +What: /sys/class/usb_power_delivery/.../version +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + This is an optional attribute file showing the version of the + specific revision of the USB Power Delivery Specification. In + most cases the specification version is not known and the file + is not available. + +What: /sys/class/usb_power_delivery/.../source-capabilities +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + The source capabilities message "Source_Capabilities" contains a + set of Power Data Objects (PDO), each representing a type of + power supply. The order of the PDO objects is defined in the USB + Power Delivery Specification. Each PDO - power supply - will + have its own device, and the PDO device name will start with the + object position number as the first character followed by the + power supply type name (":" as delimiter). + + /sys/class/usb_power_delivery/.../source_capabilities/<position>:<type> + +What: /sys/class/usb_power_delivery/.../sink-capabilities +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + The sink capability message "Sink_Capabilities" contains a set + of Power Data Objects (PDO) just like with source capabilities, + but instead of describing the power capabilities, these objects + describe the power requirements. + + The order of the objects in the sink capability message is the + same as with the source capabilities message. + +Fixed Supplies + +What: /sys/class/usb_power_delivery/.../<capability>/<position>:fixed_supply +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Devices containing the attributes (the bit fields) defined for + Fixed Supplies. + + The device "1:fixed_supply" is special. USB Power Delivery + Specification dictates that the first PDO (at object position + 1), and the only mandatory PDO, is always the vSafe5V Fixed + Supply Object. vSafe5V Object has additional fields defined for + it that the other Fixed Supply Objects do not have and that are + related to the USB capabilities rather than power capabilities. + +What: /sys/class/usb_power_delivery/.../<capability>/1:fixed_supply/dual_role_power +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + This file contains boolean value that tells does the device + support both source and sink power roles. + +What: /sys/class/usb_power_delivery/.../<capability>/1:fixed_supply/usb_suspend_supported +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + This file shows the value of the USB Suspend Supported bit in + vSafe5V Fixed Supply Object. If the bit is set then the device + will follow the USB 2.0 and USB 3.2 rules for suspend and + resume. + +What: /sys/class/usb_power_delivery/.../<capability>/1:fixed_supply/unconstrained_power +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + This file shows the value of the Unconstrained Power bit in + vSafe5V Fixed Supply Object. The bit is set when an external + source of power, powerful enough to power the entire system on + its own, is available for the device. + +What: /sys/class/usb_power_delivery/.../<capability>/1:fixed_supply/usb_communication_capable +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + This file shows the value of the USB Communication Capable bit in + vSafe5V Fixed Supply Object. + +What: /sys/class/usb_power_delivery/.../<capability>/1:fixed_supply/dual_role_data +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + This file shows the value of the Dual-Role Data bit in vSafe5V + Fixed Supply Object. Dual role data means ability act as both + USB host and USB device. + +What: /sys/class/usb_power_delivery/.../<capability>/1:fixed_supply/unchunked_extended_messages_supported +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + This file shows the value of the Unchunked Extended Messages + Supported bit in vSafe5V Fixed Supply Object. + +What: /sys/class/usb_power_delivery/.../<capability>/<position>:fixed_supply/voltage +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + The voltage the supply supports in millivolts. + +What: /sys/class/usb_power_delivery/.../source-capabilities/<position>:fixed_supply/maximum_current +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Maximum current of the fixed source supply in milliamperes. + +What: /sys/class/usb_power_delivery/.../sink-capabilities/<position>:fixed_supply/operational_current +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Operational current of the sink in milliamperes. + +What: /sys/class/usb_power_delivery/.../sink-capabilities/<position>:fixed_supply/fast_role_swap_current +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + This file contains the value of the "Fast Role Swap USB Type-C + Current" field that tells the current level the sink requires + after a Fast Role Swap. + 0 - Fast Swap not supported" + 1 - Default USB Power" + 2 - 1.5A@5V" + 3 - 3.0A@5V" + +Variable Supplies + +What: /sys/class/usb_power_delivery/.../<capability>/<position>:variable_supply +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Variable Power Supply PDO. + +What: /sys/class/usb_power_delivery/.../<capability>/<position>:variable_supply/maximum_voltage +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Maximum Voltage in millivolts. + +What: /sys/class/usb_power_delivery/.../<capability>/<position>:variable_supply/minimum_voltage +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Minimum Voltage in millivolts. + +What: /sys/class/usb_power_delivery/.../source-capabilities/<position>:variable_supply/maximum_current +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + The maximum current in milliamperes that the source can supply + at the given Voltage range. + +What: /sys/class/usb_power_delivery/.../sink-capabilities/<position>:variable_supply/operational_current +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + The operational current in milliamperes that the sink requires + at the given Voltage range. + +Battery Supplies + +What: /sys/class/usb_power_delivery/.../<capability>/<position>:battery +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Battery PDO. + +What: /sys/class/usb_power_delivery/.../<capability>/<position>:battery/maximum_voltage +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Maximum Voltage in millivolts. + +What: /sys/class/usb_power_delivery/.../<capability>/<position>:battery/minimum_voltage +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Minimum Voltage in millivolts. + +What: /sys/class/usb_power_delivery/.../source-capabilities/<position>:battery/maximum_power +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Maximum allowable Power in milliwatts. + +What: /sys/class/usb_power_delivery/.../sink-capabilities/<position>:battery/operational_power +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + The operational power that the sink requires at the given + voltage range. + +Standard Power Range (SPR) Programmable Power Supplies + +What: /sys/class/usb_power_delivery/.../<capability>/<position>:programmable_supply +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Programmable Power Supply (PPS) Augmented PDO (APDO). + +What: /sys/class/usb_power_delivery/.../<capability>/<position>:programmable_supply/maximum_voltage +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Maximum Voltage in millivolts. + +What: /sys/class/usb_power_delivery/.../<capability>/<position>:programmable_supply/minimum_voltage +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Minimum Voltage in millivolts. + +What: /sys/class/usb_power_delivery/.../<capability>/<position>:programmable_supply/maximum_current +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Maximum Current in milliamperes. + +What: /sys/class/usb_power_delivery/.../source-capabilities/<position>:programmable_supply/pps_power_limited +Date: May 2022 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + The PPS Power Limited bit indicates whether or not the source + supply will exceed the rated output power if requested. diff --git a/Documentation/devicetree/bindings/usb/aspeed,ast2600-udc.yaml b/Documentation/devicetree/bindings/usb/aspeed,ast2600-udc.yaml new file mode 100644 index 000000000000..c3b6be3d8002 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/aspeed,ast2600-udc.yaml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (c) 2020 Facebook Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/aspeed,ast2600-udc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ASPEED USB 2.0 Device Controller + +maintainers: + - Neal Liu <neal_liu@aspeedtech.com> + +description: |+ + The ASPEED USB 2.0 Device Controller implements 1 control endpoint and + 4 generic endpoints for AST260x. + + Supports independent DMA channel for each generic endpoint. + Supports 32/256 stages descriptor mode for all generic endpoints. + +properties: + compatible: + enum: + - aspeed,ast2600-udc + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - clocks + - interrupts + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/aspeed-clock.h> + udc: usb@1e6a2000 { + compatible = "aspeed,ast2600-udc"; + reg = <0x1e6a2000 0x300>; + interrupts = <9>; + clocks = <&syscon ASPEED_CLK_GATE_USBPORT2CLK>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usb2bd_default>; + }; diff --git a/Documentation/devicetree/bindings/usb/dwc2.yaml b/Documentation/devicetree/bindings/usb/dwc2.yaml index 8d22a9843ba5..1bfbc6ef16eb 100644 --- a/Documentation/devicetree/bindings/usb/dwc2.yaml +++ b/Documentation/devicetree/bindings/usb/dwc2.yaml @@ -11,6 +11,7 @@ maintainers: allOf: - $ref: usb-drd.yaml# + - $ref: usb-hcd.yaml# properties: compatible: @@ -161,6 +162,8 @@ properties: property is used. $ref: /schemas/graph.yaml#/properties/port + tpl-support: true + dependencies: port: [ usb-role-switch ] role-switch-default-mode: [ usb-role-switch ] diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml index 084d7135b2d9..892718459d25 100644 --- a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml +++ b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml @@ -113,6 +113,9 @@ properties: vbus-supply: description: Regulator of USB VBUS5v + resets: + maxItems: 1 + usb3-lpm-capable: true usb2-lpm-disable: true diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml b/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml index 37b02a841dc4..e63b66545317 100644 --- a/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml +++ b/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml @@ -107,6 +107,9 @@ properties: maximum-speed: enum: [super-speed-plus, super-speed, high-speed, full-speed] + resets: + maxItems: 1 + "#address-cells": enum: [1, 2] diff --git a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml index d41265ba8ce2..1779d08ba1c0 100644 --- a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml @@ -343,6 +343,11 @@ properties: This port is used with the 'usb-role-switch' property to connect the dwc3 to type C connector. + wakeup-source: + $ref: /schemas/types.yaml#/definitions/flag + description: + Enable USB remote wakeup. + unevaluatedProperties: false required: diff --git a/MAINTAINERS b/MAINTAINERS index 3cf9842d9233..e73c77d479bb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3136,6 +3136,13 @@ S: Maintained F: Documentation/devicetree/bindings/media/aspeed-video.txt F: drivers/media/platform/aspeed/ +ASPEED USB UDC DRIVER +M: Neal Liu <neal_liu@aspeedtech.com> +L: linux-aspeed@lists.ozlabs.org (moderated for non-subscribers) +S: Maintained +F: Documentation/devicetree/bindings/usb/aspeed,ast2600-udc.yaml +F: drivers/usb/gadget/udc/aspeed_udc.c + ASUS NOTEBOOKS AND EEEPC ACPI/WMI EXTRAS DRIVERS M: Corentin Chary <corentin.chary@gmail.com> L: acpi4asus-user@lists.sourceforge.net @@ -17162,6 +17169,7 @@ S: Supported F: arch/riscv/boot/dts/microchip/ F: drivers/mailbox/mailbox-mpfs.c F: drivers/soc/microchip/ +F: drivers/usb/musb/mpfs.c F: include/soc/microchip/mpfs.h RNBD BLOCK DRIVERS diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c index 5c15c48952a6..555caafe4f04 100644 --- a/drivers/usb/cdns3/cdns3-gadget.c +++ b/drivers/usb/cdns3/cdns3-gadget.c @@ -220,7 +220,7 @@ int cdns3_allocate_trb_pool(struct cdns3_endpoint *priv_ep) if (!priv_ep->trb_pool) { priv_ep->trb_pool = dma_pool_alloc(priv_dev->eps_dma_pool, - GFP_DMA32 | GFP_ATOMIC, + GFP_ATOMIC, &priv_ep->trb_pool_dma); if (!priv_ep->trb_pool) @@ -625,9 +625,9 @@ static void cdns3_wa2_remove_old_request(struct cdns3_endpoint *priv_ep) trace_cdns3_wa2(priv_ep, "removes eldest request"); kfree(priv_req->request.buf); + list_del_init(&priv_req->list); cdns3_gadget_ep_free_request(&priv_ep->endpoint, &priv_req->request); - list_del_init(&priv_req->list); --priv_ep->wa2_counter; if (!chain) diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 99440baa6458..a4a3be049910 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -49,6 +49,7 @@ enum ci_hw_regs { OP_USBCMD, OP_USBSTS, OP_USBINTR, + OP_FRINDEX, OP_DEVICEADDR, OP_ENDPTLISTADDR, OP_TTCTRL, diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 097142ffb184..9ffcecd3058c 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -348,25 +348,18 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) data->pinctrl = devm_pinctrl_get(dev); if (PTR_ERR(data->pinctrl) == -ENODEV) data->pinctrl = NULL; - else if (IS_ERR(data->pinctrl)) { - if (PTR_ERR(data->pinctrl) != -EPROBE_DEFER) - dev_err(dev, "pinctrl get failed, err=%ld\n", - PTR_ERR(data->pinctrl)); - return PTR_ERR(data->pinctrl); - } + else if (IS_ERR(data->pinctrl)) + return dev_err_probe(dev, PTR_ERR(data->pinctrl), + "pinctrl get failed\n"); data->hsic_pad_regulator = devm_regulator_get_optional(dev, "hsic"); if (PTR_ERR(data->hsic_pad_regulator) == -ENODEV) { /* no pad regualator is needed */ data->hsic_pad_regulator = NULL; - } else if (IS_ERR(data->hsic_pad_regulator)) { - if (PTR_ERR(data->hsic_pad_regulator) != -EPROBE_DEFER) - dev_err(dev, - "Get HSIC pad regulator error: %ld\n", - PTR_ERR(data->hsic_pad_regulator)); - return PTR_ERR(data->hsic_pad_regulator); - } + } else if (IS_ERR(data->hsic_pad_regulator)) + return dev_err_probe(dev, PTR_ERR(data->hsic_pad_regulator), + "Get HSIC pad regulator error\n"); if (data->hsic_pad_regulator) { ret = regulator_enable(data->hsic_pad_regulator); @@ -458,9 +451,7 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) &pdata); if (IS_ERR(data->ci_pdev)) { ret = PTR_ERR(data->ci_pdev); - if (ret != -EPROBE_DEFER) - dev_err(dev, "ci_hdrc_add_device failed, err=%d\n", - ret); + dev_err_probe(dev, ret, "ci_hdrc_add_device failed\n"); goto err_clk; } diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h index 999c65390b7f..7daccb9c5006 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.h +++ b/drivers/usb/chipidea/ci_hdrc_imx.h @@ -21,7 +21,7 @@ struct imx_usbmisc_data { unsigned int pwr_pol:1; /* power polarity */ unsigned int evdo:1; /* set external vbus divider option */ unsigned int ulpi:1; /* connected to an ULPI phy */ - unsigned int hsic:1; /* HSIC controlller */ + unsigned int hsic:1; /* HSIC controller */ unsigned int ext_id:1; /* ID from exteranl event */ unsigned int ext_vbus:1; /* Vbus from exteranl event */ struct usb_phy *usb_phy; diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 5359b2a2e4d2..6330fa911792 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -53,6 +53,7 @@ static const u8 ci_regs_nolpm[] = { [OP_USBCMD] = 0x00U, [OP_USBSTS] = 0x04U, [OP_USBINTR] = 0x08U, + [OP_FRINDEX] = 0x0CU, [OP_DEVICEADDR] = 0x14U, [OP_ENDPTLISTADDR] = 0x18U, [OP_TTCTRL] = 0x1CU, @@ -78,6 +79,7 @@ static const u8 ci_regs_lpm[] = { [OP_USBCMD] = 0x00U, [OP_USBSTS] = 0x04U, [OP_USBINTR] = 0x08U, + [OP_FRINDEX] = 0x0CU, [OP_DEVICEADDR] = 0x14U, [OP_ENDPTLISTADDR] = 0x18U, [OP_TTCTRL] = 0x1CU, diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index dc6c96e04bcf..0c9ae9768a67 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -1651,6 +1651,19 @@ static const struct usb_ep_ops usb_ep_ops = { /****************************************************************************** * GADGET block *****************************************************************************/ + +static int ci_udc_get_frame(struct usb_gadget *_gadget) +{ + struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); + unsigned long flags; + int ret; + + spin_lock_irqsave(&ci->lock, flags); + ret = hw_read(ci, OP_FRINDEX, 0x3fff); + spin_unlock_irqrestore(&ci->lock, flags); + return ret >> 3; +} + /* * ci_hdrc_gadget_connect: caller makes sure gadget driver is binded */ @@ -1807,6 +1820,7 @@ static struct usb_ep *ci_udc_match_ep(struct usb_gadget *gadget, * Check "usb_gadget.h" for details */ static const struct usb_gadget_ops usb_gadget_ops = { + .get_frame = ci_udc_get_frame, .vbus_session = ci_udc_vbus_session, .wakeup = ci_udc_wakeup, .set_selfpowered = ci_udc_selfpowered, diff --git a/drivers/usb/common/usb-conn-gpio.c b/drivers/usb/common/usb-conn-gpio.c index 395f9bbe3056..b39c9f1c375d 100644 --- a/drivers/usb/common/usb-conn-gpio.c +++ b/drivers/usb/common/usb-conn-gpio.c @@ -257,6 +257,7 @@ static int usb_conn_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, info); + device_set_wakeup_capable(&pdev->dev, true); /* Perform initial detection */ usb_conn_queue_dwork(info, 0); @@ -286,6 +287,14 @@ static int __maybe_unused usb_conn_suspend(struct device *dev) { struct usb_conn_info *info = dev_get_drvdata(dev); + if (device_may_wakeup(dev)) { + if (info->id_gpiod) + enable_irq_wake(info->id_irq); + if (info->vbus_gpiod) + enable_irq_wake(info->vbus_irq); + return 0; + } + if (info->id_gpiod) disable_irq(info->id_irq); if (info->vbus_gpiod) @@ -300,6 +309,14 @@ static int __maybe_unused usb_conn_resume(struct device *dev) { struct usb_conn_info *info = dev_get_drvdata(dev); + if (device_may_wakeup(dev)) { + if (info->id_gpiod) + disable_irq_wake(info->id_irq); + if (info->vbus_gpiod) + disable_irq_wake(info->vbus_irq); + return 0; + } + pinctrl_pm_select_default_state(dev); if (info->id_gpiod) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 68e9121c1878..b7f66dcd1fe0 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -613,7 +613,7 @@ static int hub_ext_port_status(struct usb_hub *hub, int port1, int type, return ret; } -static int hub_port_status(struct usb_hub *hub, int port1, +int usb_hub_port_status(struct usb_hub *hub, int port1, u16 *status, u16 *change) { return hub_ext_port_status(hub, port1, HUB_PORT_STATUS, @@ -1126,7 +1126,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) u16 portstatus, portchange; portstatus = portchange = 0; - status = hub_port_status(hub, port1, &portstatus, &portchange); + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); if (status) goto abort; @@ -2855,7 +2855,7 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1, &portstatus, &portchange, &ext_portstatus); else - ret = hub_port_status(hub, port1, &portstatus, + ret = usb_hub_port_status(hub, port1, &portstatus, &portchange); if (ret < 0) return ret; @@ -2956,7 +2956,8 @@ static int hub_port_reset(struct usb_hub *hub, int port1, * If the caller hasn't explicitly requested a warm reset, * double check and see if one is needed. */ - if (hub_port_status(hub, port1, &portstatus, &portchange) == 0) + if (usb_hub_port_status(hub, port1, &portstatus, + &portchange) == 0) if (hub_port_warm_reset_required(hub, port1, portstatus)) warm = true; @@ -3008,7 +3009,7 @@ static int hub_port_reset(struct usb_hub *hub, int port1, * If a USB 3.0 device migrates from reset to an error * state, re-issue the warm reset. */ - if (hub_port_status(hub, port1, + if (usb_hub_port_status(hub, port1, &portstatus, &portchange) < 0) goto done; @@ -3074,7 +3075,7 @@ done: } /* Check if a port is power on */ -static int port_is_power_on(struct usb_hub *hub, unsigned portstatus) +int usb_port_is_power_on(struct usb_hub *hub, unsigned int portstatus) { int ret = 0; @@ -3140,13 +3141,13 @@ static int check_port_resume_type(struct usb_device *udev, } /* Is the device still present? */ else if (status || port_is_suspended(hub, portstatus) || - !port_is_power_on(hub, portstatus)) { + !usb_port_is_power_on(hub, portstatus)) { if (status >= 0) status = -ENODEV; } else if (!(portstatus & USB_PORT_STAT_CONNECTION)) { if (retries--) { usleep_range(200, 300); - status = hub_port_status(hub, port1, &portstatus, + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); goto retry; } @@ -3409,7 +3410,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) u16 portstatus, portchange; portstatus = portchange = 0; - ret = hub_port_status(hub, port1, &portstatus, + ret = usb_hub_port_status(hub, port1, &portstatus, &portchange); dev_dbg(&port_dev->dev, @@ -3587,13 +3588,13 @@ static int wait_for_connected(struct usb_device *udev, while (delay_ms < 2000) { if (status || *portstatus & USB_PORT_STAT_CONNECTION) break; - if (!port_is_power_on(hub, *portstatus)) { + if (!usb_port_is_power_on(hub, *portstatus)) { status = -ENODEV; break; } msleep(20); delay_ms += 20; - status = hub_port_status(hub, port1, portstatus, portchange); + status = usb_hub_port_status(hub, port1, portstatus, portchange); } dev_dbg(&udev->dev, "Waited %dms for CONNECT\n", delay_ms); return status; @@ -3653,7 +3654,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) usb_lock_port(port_dev); /* Skip the initial Clear-Suspend step for a remote wakeup */ - status = hub_port_status(hub, port1, &portstatus, &portchange); + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); if (status == 0 && !port_is_suspended(hub, portstatus)) { if (portchange & USB_PORT_STAT_C_SUSPEND) pm_wakeup_event(&udev->dev, 0); @@ -3678,7 +3679,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) * stop resume signaling. Then finish the resume * sequence. */ - status = hub_port_status(hub, port1, &portstatus, &portchange); + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); } SuspendCleared: @@ -3791,7 +3792,7 @@ static int check_ports_changed(struct usb_hub *hub) u16 portstatus, portchange; int status; - status = hub_port_status(hub, port1, &portstatus, &portchange); + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); if (!status && portchange) return 1; } @@ -3946,7 +3947,7 @@ static const char * const usb3_lpm_names[] = { * This function will fail if the SEL or PEL values for udev are greater than * the maximum allowed values for the link state to be enabled. */ -static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state) +static int usb_req_set_sel(struct usb_device *udev) { struct usb_set_sel_req *sel_values; unsigned long long u1_sel; @@ -3955,7 +3956,7 @@ static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state) unsigned long long u2_pel; int ret; - if (udev->state != USB_STATE_CONFIGURED) + if (!udev->parent || udev->speed < USB_SPEED_SUPER || !udev->lpm_capable) return 0; /* Convert SEL and PEL stored in ns to us */ @@ -3972,35 +3973,15 @@ static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state) * latency for the link state, and could start a device-initiated * U1/U2 when the exit latencies are too high. */ - if ((state == USB3_LPM_U1 && - (u1_sel > USB3_LPM_MAX_U1_SEL_PEL || - u1_pel > USB3_LPM_MAX_U1_SEL_PEL)) || - (state == USB3_LPM_U2 && - (u2_sel > USB3_LPM_MAX_U2_SEL_PEL || - u2_pel > USB3_LPM_MAX_U2_SEL_PEL))) { - dev_dbg(&udev->dev, "Device-initiated %s disabled due to long SEL %llu us or PEL %llu us\n", - usb3_lpm_names[state], u1_sel, u1_pel); + if (u1_sel > USB3_LPM_MAX_U1_SEL_PEL || + u1_pel > USB3_LPM_MAX_U1_SEL_PEL || + u2_sel > USB3_LPM_MAX_U2_SEL_PEL || + u2_pel > USB3_LPM_MAX_U2_SEL_PEL) { + dev_dbg(&udev->dev, "Device-initiated U1/U2 disabled due to long SEL or PEL\n"); return -EINVAL; } /* - * If we're enabling device-initiated LPM for one link state, - * but the other link state has a too high SEL or PEL value, - * just set those values to the max in the Set SEL request. - */ - if (u1_sel > USB3_LPM_MAX_U1_SEL_PEL) - u1_sel = USB3_LPM_MAX_U1_SEL_PEL; - - if (u1_pel > USB3_LPM_MAX_U1_SEL_PEL) - u1_pel = USB3_LPM_MAX_U1_SEL_PEL; - - if (u2_sel > USB3_LPM_MAX_U2_SEL_PEL) - u2_sel = USB3_LPM_MAX_U2_SEL_PEL; - - if (u2_pel > USB3_LPM_MAX_U2_SEL_PEL) - u2_pel = USB3_LPM_MAX_U2_SEL_PEL; - - /* * usb_enable_lpm() can be called as part of a failed device reset, * which may be initiated by an error path of a mass storage driver. * Therefore, use GFP_NOIO. @@ -4021,6 +4002,10 @@ static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state) sel_values, sizeof *(sel_values), USB_CTRL_SET_TIMEOUT); kfree(sel_values); + + if (ret > 0) + udev->lpm_devinit_allow = 1; + return ret; } @@ -4136,6 +4121,9 @@ static bool usb_device_may_initiate_lpm(struct usb_device *udev, unsigned int sel; /* us */ int i, j; + if (!udev->lpm_devinit_allow) + return false; + if (state == USB3_LPM_U1) sel = DIV_ROUND_UP(udev->u1_params.sel, 1000); else if (state == USB3_LPM_U2) @@ -4184,7 +4172,7 @@ static bool usb_device_may_initiate_lpm(struct usb_device *udev, static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, enum usb3_link_state state) { - int timeout, ret; + int timeout; __u8 u1_mel = udev->bos->ss_cap->bU1devExitLat; __le16 u2_mel = udev->bos->ss_cap->bU2DevExitLat; @@ -4196,17 +4184,6 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, (state == USB3_LPM_U2 && u2_mel == 0)) return; - /* - * First, let the device know about the exit latencies - * associated with the link state we're about to enable. - */ - ret = usb_req_set_sel(udev, state); - if (ret < 0) { - dev_warn(&udev->dev, "Set SEL for device-initiated %s failed.\n", - usb3_lpm_names[state]); - return; - } - /* We allow the host controller to set the U1/U2 timeout internally * first, so that it can change its schedule to account for the * additional latency to send data to a device in a lower power @@ -4486,6 +4463,11 @@ static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port, return 0; } +static int usb_req_set_sel(struct usb_device *udev) +{ + return 0; +} + #endif /* CONFIG_PM */ /* @@ -4554,7 +4536,7 @@ int hub_port_debounce(struct usb_hub *hub, int port1, bool must_be_connected) struct usb_port *port_dev = hub->ports[port1 - 1]; for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) { - ret = hub_port_status(hub, port1, &portstatus, &portchange); + ret = usb_hub_port_status(hub, port1, &portstatus, &portchange); if (ret < 0) return ret; @@ -5011,6 +4993,7 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, udev->lpm_capable = usb_device_supports_lpm(udev); udev->lpm_disable_count = 1; usb_set_lpm_parameters(udev); + usb_req_set_sel(udev); } } @@ -5240,7 +5223,7 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus, * but only if the port isn't owned by someone else. */ if (hub_is_port_power_switchable(hub) - && !port_is_power_on(hub, portstatus) + && !usb_port_is_power_on(hub, portstatus) && !port_dev->port_owner) set_port_feature(hdev, port1, USB_PORT_FEAT_POWER); @@ -5557,7 +5540,7 @@ static void port_event(struct usb_hub *hub, int port1) clear_bit(port1, hub->event_bits); clear_bit(port1, hub->wakeup_bits); - if (hub_port_status(hub, port1, &portstatus, &portchange) < 0) + if (usb_hub_port_status(hub, port1, &portstatus, &portchange) < 0) return; if (portchange & USB_PORT_STAT_C_CONNECTION) { @@ -5594,7 +5577,7 @@ static void port_event(struct usb_hub *hub, int port1) USB_PORT_FEAT_C_OVER_CURRENT); msleep(100); /* Cool down */ hub_power_on(hub, true); - hub_port_status(hub, port1, &status, &unused); + usb_hub_port_status(hub, port1, &status, &unused); if (status & USB_PORT_STAT_OVERCURRENT) dev_err(&port_dev->dev, "over-current condition\n"); } @@ -5638,7 +5621,7 @@ static void port_event(struct usb_hub *hub, int port1) u16 unused; msleep(20); - hub_port_status(hub, port1, &portstatus, &unused); + usb_hub_port_status(hub, port1, &portstatus, &unused); dev_dbg(&port_dev->dev, "Wait for inactive link disconnect detect\n"); continue; } else if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION) diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 22ea1f4f2d66..3fcb38099ce3 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -121,6 +121,9 @@ extern int hub_port_debounce(struct usb_hub *hub, int port1, bool must_be_connected); extern int usb_clear_port_feature(struct usb_device *hdev, int port1, int feature); +extern int usb_hub_port_status(struct usb_hub *hub, int port1, + u16 *status, u16 *change); +extern int usb_port_is_power_on(struct usb_hub *hub, unsigned int portstatus); static inline bool hub_is_port_power_switchable(struct usb_hub *hub) { diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index d5bc36ca5b1f..38c1a4f4fdea 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -17,6 +17,88 @@ static int usb_port_block_power_off; static const struct attribute_group *port_dev_group[]; +static ssize_t disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *port_dev = to_usb_port(dev); + struct usb_device *hdev = to_usb_device(dev->parent->parent); + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + struct usb_interface *intf = to_usb_interface(hub->intfdev); + int port1 = port_dev->portnum; + u16 portstatus, unused; + bool disabled; + int rc; + + rc = usb_autopm_get_interface(intf); + if (rc < 0) + return rc; + + usb_lock_device(hdev); + if (hub->disconnected) { + rc = -ENODEV; + goto out_hdev_lock; + } + + usb_hub_port_status(hub, port1, &portstatus, &unused); + disabled = !usb_port_is_power_on(hub, portstatus); + +out_hdev_lock: + usb_unlock_device(hdev); + usb_autopm_put_interface(intf); + + if (rc) + return rc; + + return sysfs_emit(buf, "%s\n", disabled ? "1" : "0"); +} + +static ssize_t disable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_port *port_dev = to_usb_port(dev); + struct usb_device *hdev = to_usb_device(dev->parent->parent); + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + struct usb_interface *intf = to_usb_interface(hub->intfdev); + int port1 = port_dev->portnum; + bool disabled; + int rc; + + rc = strtobool(buf, &disabled); + if (rc) + return rc; + + rc = usb_autopm_get_interface(intf); + if (rc < 0) + return rc; + + usb_lock_device(hdev); + if (hub->disconnected) { + rc = -ENODEV; + goto out_hdev_lock; + } + + if (disabled && port_dev->child) + usb_disconnect(&port_dev->child); + + rc = usb_hub_set_port_power(hdev, hub, port1, !disabled); + + if (disabled) { + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION); + if (!port_dev->is_superspeed) + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE); + } + + if (!rc) + rc = count; + +out_hdev_lock: + usb_unlock_device(hdev); + usb_autopm_put_interface(intf); + + return rc; +} +static DEVICE_ATTR_RW(disable); + static ssize_t location_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -153,6 +235,7 @@ static struct attribute *port_dev_attrs[] = { &dev_attr_location.attr, &dev_attr_quirks.attr, &dev_attr_over_current_count.attr, + &dev_attr_disable.attr, NULL, }; diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 3f107a06817d..2c9613d0d395 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -52,6 +52,7 @@ #include <linux/usb/hcd.h> #include <linux/usb/ch11.h> +#include <linux/usb/of.h> #include "core.h" #include "hcd.h" @@ -5339,6 +5340,8 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg) /* Don't support SG list at this point */ hcd->self.sg_tablesize = 0; + hcd->tpl_support = of_usb_host_tpl_support(hsotg->dev->of_node); + if (!IS_ERR_OR_NULL(hsotg->uphy)) otg_set_host(hsotg->uphy->otg, &hcd->self); diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index cd9a734522a7..03ededa86da1 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -9,7 +9,7 @@ config USB_DWC3 Say Y or M here if your system has a Dual Role SuperSpeed USB controller based on the DesignWare USB3 IP Core. - If you choose to build this driver is a dynamically linked + If you choose to build this driver as a dynamically linked module, the module will be called dwc3.ko. if USB_DWC3 @@ -165,7 +165,7 @@ config USB_DWC3_AM62 default USB_DWC3 help Support TI's AM62 platforms with DesignWare Core USB3 IP. - The Designware Core USB3 IP is progammed to operate in + The Designware Core USB3 IP is programmed to operate in in USB 2.0 mode only. Say 'Y' or 'M' here if you have one such device endif diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 573421984948..050b2ba5986d 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -159,7 +159,8 @@ static void __dwc3_set_mode(struct work_struct *work) } /* For DRD host or device mode only */ - if (dwc->desired_dr_role != DWC3_GCTL_PRTCAP_OTG) { + if ((DWC3_IP_IS(DWC3) || DWC3_VER_IS_PRIOR(DWC31, 190A)) && + dwc->desired_dr_role != DWC3_GCTL_PRTCAP_OTG) { reg = dwc3_readl(dwc->regs, DWC3_GCTL); reg |= DWC3_GCTL_CORESOFTRESET; dwc3_writel(dwc->regs, DWC3_GCTL, reg); @@ -426,7 +427,7 @@ static void dwc3_free_one_event_buffer(struct dwc3 *dwc, * otherwise ERR_PTR(errno). */ static struct dwc3_event_buffer *dwc3_alloc_one_event_buffer(struct dwc3 *dwc, - unsigned length) + unsigned int length) { struct dwc3_event_buffer *evt; @@ -469,7 +470,7 @@ static void dwc3_free_event_buffers(struct dwc3 *dwc) * Returns 0 on success otherwise negative errno. In the error case, dwc * may contain some buffers allocated but not all which were requested. */ -static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length) +static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned int length) { struct dwc3_event_buffer *evt; @@ -1029,6 +1030,37 @@ static void dwc3_set_incr_burst_type(struct dwc3 *dwc) dwc3_writel(dwc->regs, DWC3_GSBUSCFG0, cfg); } +static void dwc3_set_power_down_clk_scale(struct dwc3 *dwc) +{ + u32 scale; + u32 reg; + + if (!dwc->susp_clk) + return; + + /* + * The power down scale field specifies how many suspend_clk + * periods fit into a 16KHz clock period. When performing + * the division, round up the remainder. + * + * The power down scale value is calculated using the fastest + * frequency of the suspend_clk. If it isn't fixed (but within + * the accuracy requirement), the driver may not know the max + * rate of the suspend_clk, so only update the power down scale + * if the default is less than the calculated value from + * clk_get_rate() or if the default is questionably high + * (3x or more) to be within the requirement. + */ + scale = DIV_ROUND_UP(clk_get_rate(dwc->susp_clk), 16000); + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + if ((reg & DWC3_GCTL_PWRDNSCALE_MASK) < DWC3_GCTL_PWRDNSCALE(scale) || + (reg & DWC3_GCTL_PWRDNSCALE_MASK) > DWC3_GCTL_PWRDNSCALE(scale*3)) { + reg &= ~(DWC3_GCTL_PWRDNSCALE_MASK); + reg |= DWC3_GCTL_PWRDNSCALE(scale); + dwc3_writel(dwc->regs, DWC3_GCTL, reg); + } +} + /** * dwc3_core_init - Low-level initialization of DWC3 Core * @dwc: Pointer to our controller context structure @@ -1105,6 +1137,9 @@ static int dwc3_core_init(struct dwc3 *dwc) if (ret) goto err1; + /* Set power down scale of suspend_clk */ + dwc3_set_power_down_clk_scale(dwc); + /* Adjust Frame Length */ dwc3_frame_length_adjustment(dwc); @@ -1782,6 +1817,7 @@ static int dwc3_probe(struct platform_device *pdev) platform_set_drvdata(pdev, dwc); dwc3_cache_hwparams(dwc); + device_init_wakeup(&pdev->dev, of_property_read_bool(dev->of_node, "wakeup-source")); spin_lock_init(&dwc->lock); mutex_init(&dwc->mutex); @@ -1943,7 +1979,7 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg) dwc3_core_exit(dwc); break; case DWC3_GCTL_PRTCAP_HOST: - if (!PMSG_IS_AUTO(msg)) { + if (!PMSG_IS_AUTO(msg) && !device_can_wakeup(dwc->dev)) { dwc3_core_exit(dwc); break; } @@ -2004,7 +2040,7 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg) spin_unlock_irqrestore(&dwc->lock, flags); break; case DWC3_GCTL_PRTCAP_HOST: - if (!PMSG_IS_AUTO(msg)) { + if (!PMSG_IS_AUTO(msg) && !device_can_wakeup(dwc->dev)) { ret = dwc3_core_init_for_resume(dwc); if (ret) return ret; @@ -2081,8 +2117,6 @@ static int dwc3_runtime_suspend(struct device *dev) if (ret) return ret; - device_init_wakeup(dev, true); - return 0; } @@ -2091,8 +2125,6 @@ static int dwc3_runtime_resume(struct device *dev) struct dwc3 *dwc = dev_get_drvdata(dev); int ret; - device_init_wakeup(dev, false); - ret = dwc3_resume_common(dwc, PMSG_AUTO_RESUME); if (ret) return ret; diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 81c486b3941c..722808d8c0af 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -231,6 +231,7 @@ /* Global Configuration Register */ #define DWC3_GCTL_PWRDNSCALE(n) ((n) << 19) +#define DWC3_GCTL_PWRDNSCALE_MASK GENMASK(31, 19) #define DWC3_GCTL_U2RSTECN BIT(16) #define DWC3_GCTL_RAMCLKSEL(x) (((x) & DWC3_GCTL_CLK_MASK) << 6) #define DWC3_GCTL_CLK_BUS (0) diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 6cba990da32e..77036551987a 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -17,10 +17,12 @@ #include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/phy/phy.h> +#include <linux/pm_domain.h> #include <linux/usb/of.h> #include <linux/reset.h> #include <linux/iopoll.h> - +#include <linux/usb/hcd.h> +#include <linux/usb.h> #include "core.h" /* USB QSCRATCH Hardware registers */ @@ -76,6 +78,7 @@ struct dwc3_qcom { int dp_hs_phy_irq; int dm_hs_phy_irq; int ss_phy_irq; + enum usb_device_speed usb2_speed; struct extcon_dev *edev; struct extcon_dev *host_edev; @@ -296,50 +299,92 @@ static void dwc3_qcom_interconnect_exit(struct dwc3_qcom *qcom) icc_put(qcom->icc_path_apps); } -static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom) +static enum usb_device_speed dwc3_qcom_read_usb2_speed(struct dwc3_qcom *qcom) { - if (qcom->hs_phy_irq) { - disable_irq_wake(qcom->hs_phy_irq); - disable_irq_nosync(qcom->hs_phy_irq); - } + struct dwc3 *dwc = platform_get_drvdata(qcom->dwc3); + struct usb_hcd *hcd = platform_get_drvdata(dwc->xhci); + struct usb_device *udev; - if (qcom->dp_hs_phy_irq) { - disable_irq_wake(qcom->dp_hs_phy_irq); - disable_irq_nosync(qcom->dp_hs_phy_irq); - } + /* + * It is possible to query the speed of all children of + * USB2.0 root hub via usb_hub_for_each_child(). DWC3 code + * currently supports only 1 port per controller. So + * this is sufficient. + */ + udev = usb_hub_find_child(hcd->self.root_hub, 1); - if (qcom->dm_hs_phy_irq) { - disable_irq_wake(qcom->dm_hs_phy_irq); - disable_irq_nosync(qcom->dm_hs_phy_irq); - } + if (!udev) + return USB_SPEED_UNKNOWN; + + return udev->speed; +} + +static void dwc3_qcom_enable_wakeup_irq(int irq, unsigned int polarity) +{ + if (!irq) + return; + + if (polarity) + irq_set_irq_type(irq, polarity); + + enable_irq(irq); + enable_irq_wake(irq); +} + +static void dwc3_qcom_disable_wakeup_irq(int irq) +{ + if (!irq) + return; + + disable_irq_wake(irq); + disable_irq_nosync(irq); +} + +static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom) +{ + dwc3_qcom_disable_wakeup_irq(qcom->hs_phy_irq); - if (qcom->ss_phy_irq) { - disable_irq_wake(qcom->ss_phy_irq); - disable_irq_nosync(qcom->ss_phy_irq); + if (qcom->usb2_speed == USB_SPEED_LOW) { + dwc3_qcom_disable_wakeup_irq(qcom->dm_hs_phy_irq); + } else if ((qcom->usb2_speed == USB_SPEED_HIGH) || + (qcom->usb2_speed == USB_SPEED_FULL)) { + dwc3_qcom_disable_wakeup_irq(qcom->dp_hs_phy_irq); + } else { + dwc3_qcom_disable_wakeup_irq(qcom->dp_hs_phy_irq); + dwc3_qcom_disable_wakeup_irq(qcom->dm_hs_phy_irq); } + + dwc3_qcom_disable_wakeup_irq(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); - } + dwc3_qcom_enable_wakeup_irq(qcom->hs_phy_irq, 0); - if (qcom->dp_hs_phy_irq) { - enable_irq(qcom->dp_hs_phy_irq); - enable_irq_wake(qcom->dp_hs_phy_irq); - } + /* + * Configure DP/DM line interrupts based on the USB2 device attached to + * the root hub port. When HS/FS device is connected, configure the DP line + * as falling edge to detect both disconnect and remote wakeup scenarios. When + * LS device is connected, configure DM line as falling edge to detect both + * disconnect and remote wakeup. When no device is connected, configure both + * DP and DM lines as rising edge to detect HS/HS/LS device connect scenario. + */ - if (qcom->dm_hs_phy_irq) { - enable_irq(qcom->dm_hs_phy_irq); - enable_irq_wake(qcom->dm_hs_phy_irq); + if (qcom->usb2_speed == USB_SPEED_LOW) { + dwc3_qcom_enable_wakeup_irq(qcom->dm_hs_phy_irq, + IRQ_TYPE_EDGE_FALLING); + } else if ((qcom->usb2_speed == USB_SPEED_HIGH) || + (qcom->usb2_speed == USB_SPEED_FULL)) { + dwc3_qcom_enable_wakeup_irq(qcom->dp_hs_phy_irq, + IRQ_TYPE_EDGE_FALLING); + } else { + dwc3_qcom_enable_wakeup_irq(qcom->dp_hs_phy_irq, + IRQ_TYPE_EDGE_RISING); + dwc3_qcom_enable_wakeup_irq(qcom->dm_hs_phy_irq, + IRQ_TYPE_EDGE_RISING); } - if (qcom->ss_phy_irq) { - enable_irq(qcom->ss_phy_irq); - enable_irq_wake(qcom->ss_phy_irq); - } + dwc3_qcom_enable_wakeup_irq(qcom->ss_phy_irq, 0); } static int dwc3_qcom_suspend(struct dwc3_qcom *qcom) @@ -361,8 +406,10 @@ static int dwc3_qcom_suspend(struct dwc3_qcom *qcom) if (ret) dev_warn(qcom->dev, "failed to disable interconnect: %d\n", ret); - if (device_may_wakeup(qcom->dev)) + if (device_may_wakeup(qcom->dev)) { + qcom->usb2_speed = dwc3_qcom_read_usb2_speed(qcom); dwc3_qcom_enable_interrupts(qcom); + } qcom->is_suspended = true; @@ -710,12 +757,13 @@ dwc3_qcom_create_urs_usb_platdev(struct device *dev) static int dwc3_qcom_probe(struct platform_device *pdev) { - struct device_node *np = pdev->dev.of_node; - struct device *dev = &pdev->dev; - struct dwc3_qcom *qcom; - struct resource *res, *parent_res = NULL; - int ret, i; - bool ignore_pipe_clk; + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct dwc3_qcom *qcom; + struct resource *res, *parent_res = NULL; + int ret, i; + bool ignore_pipe_clk; + struct generic_pm_domain *genpd; qcom = devm_kzalloc(&pdev->dev, sizeof(*qcom), GFP_KERNEL); if (!qcom) @@ -724,6 +772,8 @@ static int dwc3_qcom_probe(struct platform_device *pdev) platform_set_drvdata(pdev, qcom); qcom->dev = &pdev->dev; + genpd = pd_to_genpd(qcom->dev->pm_domain); + if (has_acpi_companion(dev)) { qcom->acpi_pdata = acpi_device_get_match_data(dev); if (!qcom->acpi_pdata) { @@ -831,7 +881,17 @@ static int dwc3_qcom_probe(struct platform_device *pdev) if (ret) goto interconnect_exit; - device_init_wakeup(&pdev->dev, 1); + if (device_can_wakeup(&qcom->dwc3->dev)) { + /* + * Setting GENPD_FLAG_ALWAYS_ON flag takes care of keeping + * genpd on in both runtime suspend and system suspend cases. + */ + genpd->flags |= GENPD_FLAG_ALWAYS_ON; + device_init_wakeup(&pdev->dev, true); + } else { + genpd->flags |= GENPD_FLAG_RPM_ALWAYS_ON; + } + qcom->is_suspended = false; pm_runtime_set_active(dev); pm_runtime_enable(dev); diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 5d642660fd15..2a510e84eef4 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -473,7 +473,7 @@ static int dwc3_ep0_handle_device(struct dwc3 *dwc, case USB_DEVICE_REMOTE_WAKEUP: break; /* - * 9.4.1 says only only for SS, in AddressState only for + * 9.4.1 says only for SS, in AddressState only for * default control pipe */ case USB_DEVICE_U1_ENABLE: diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index d3feeeb50841..71669e0e4d00 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -141,7 +141,8 @@ static struct usb_endpoint_descriptor uvc_fs_streaming_ep = { .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_SYNC_ASYNC | USB_ENDPOINT_XFER_ISOC, - /* The wMaxPacketSize and bInterval values will be initialized from + /* + * The wMaxPacketSize and bInterval values will be initialized from * module parameters. */ }; @@ -152,7 +153,8 @@ static struct usb_endpoint_descriptor uvc_hs_streaming_ep = { .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_SYNC_ASYNC | USB_ENDPOINT_XFER_ISOC, - /* The wMaxPacketSize and bInterval values will be initialized from + /* + * The wMaxPacketSize and bInterval values will be initialized from * module parameters. */ }; @@ -164,7 +166,8 @@ static struct usb_endpoint_descriptor uvc_ss_streaming_ep = { .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_SYNC_ASYNC | USB_ENDPOINT_XFER_ISOC, - /* The wMaxPacketSize and bInterval values will be initialized from + /* + * The wMaxPacketSize and bInterval values will be initialized from * module parameters. */ }; @@ -172,7 +175,8 @@ static struct usb_endpoint_descriptor uvc_ss_streaming_ep = { static struct usb_ss_ep_comp_descriptor uvc_ss_streaming_comp = { .bLength = sizeof(uvc_ss_streaming_comp), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, - /* The bMaxBurst, bmAttributes and wBytesPerInterval values will be + /* + * The bMaxBurst, bmAttributes and wBytesPerInterval values will be * initialized from module parameters. */ }; @@ -234,7 +238,8 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) if (le16_to_cpu(ctrl->wLength) > UVC_MAX_REQUEST_SIZE) return -EINVAL; - /* Tell the complete callback to generate an event for the next request + /* + * Tell the complete callback to generate an event for the next request * that will be enqueued by UVCIOC_SEND_RESPONSE. */ uvc->event_setup_out = !(ctrl->bRequestType & USB_DIR_IN); @@ -500,7 +505,8 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) if (!uvc_control_desc || !uvc_streaming_cls) return ERR_PTR(-ENODEV); - /* Descriptors layout + /* + * Descriptors layout * * uvc_iad * uvc_control_intf @@ -597,8 +603,7 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) uvcg_info(f, "%s()\n", __func__); opts = fi_to_f_uvc_opts(f->fi); - /* Sanity check the streaming endpoint module parameters. - */ + /* Sanity check the streaming endpoint module parameters. */ opts->streaming_interval = clamp(opts->streaming_interval, 1U, 16U); opts->streaming_maxpacket = clamp(opts->streaming_maxpacket, 1U, 3072U); opts->streaming_maxburst = min(opts->streaming_maxburst, 15U); @@ -611,7 +616,8 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) opts->streaming_maxpacket); } - /* Fill in the FS/HS/SS Video Streaming specific descriptors from the + /* + * Fill in the FS/HS/SS Video Streaming specific descriptors from the * module parameters. * * NOTE: We assume that the user knows what they are doing and won't @@ -895,7 +901,8 @@ static void uvc_function_unbind(struct usb_configuration *c, uvcg_info(f, "%s()\n", __func__); - /* If we know we're connected via v4l2, then there should be a cleanup + /* + * If we know we're connected via v4l2, then there should be a cleanup * of the device from userspace either via UVC_EVENT_DISCONNECT or * though the video device removal uevent. Allow some time for the * application to close out before things get deleted. @@ -912,7 +919,8 @@ static void uvc_function_unbind(struct usb_configuration *c, v4l2_device_unregister(&uvc->v4l2_dev); if (uvc->func_connected) { - /* Wait for the release to occur to ensure there are no longer any + /* + * Wait for the release to occur to ensure there are no longer any * pending operations that may cause panics when resources are cleaned * up. */ diff --git a/drivers/usb/gadget/function/uvc_queue.c b/drivers/usb/gadget/function/uvc_queue.c index d25edc3d2174..ec500ee499ee 100644 --- a/drivers/usb/gadget/function/uvc_queue.c +++ b/drivers/usb/gadget/function/uvc_queue.c @@ -44,7 +44,8 @@ static int uvc_queue_setup(struct vb2_queue *vq, { struct uvc_video_queue *queue = vb2_get_drv_priv(vq); struct uvc_video *video = container_of(queue, struct uvc_video, queue); - struct usb_composite_dev *cdev = video->uvc->func.config->cdev; + unsigned int req_size; + unsigned int nreq; if (*nbuffers > UVC_MAX_VIDEO_BUFFERS) *nbuffers = UVC_MAX_VIDEO_BUFFERS; @@ -53,10 +54,16 @@ static int uvc_queue_setup(struct vb2_queue *vq, sizes[0] = video->imagesize; - if (cdev->gadget->speed < USB_SPEED_SUPER) - video->uvc_num_requests = 4; - else - video->uvc_num_requests = 64; + req_size = video->ep->maxpacket + * max_t(unsigned int, video->ep->maxburst, 1) + * (video->ep->mult); + + /* We divide by two, to increase the chance to run + * into fewer requests for smaller framesizes. + */ + nreq = DIV_ROUND_UP(DIV_ROUND_UP(sizes[0], 2), req_size); + nreq = clamp(nreq, 4U, 64U); + video->uvc_num_requests = nreq; return 0; } @@ -104,7 +111,8 @@ static void uvc_buffer_queue(struct vb2_buffer *vb) if (likely(!(queue->flags & UVC_QUEUE_DISCONNECTED))) { list_add_tail(&buf->queue, &queue->irqqueue); } else { - /* If the device is disconnected return the buffer to userspace + /* + * If the device is disconnected return the buffer to userspace * directly. The next QBUF call will fail with -ENODEV. */ buf->state = UVC_BUF_STATE_ERROR; @@ -255,7 +263,8 @@ void uvcg_queue_cancel(struct uvc_video_queue *queue, int disconnect) } queue->buf_used = 0; - /* This must be protected by the irqlock spinlock to avoid race + /* + * This must be protected by the irqlock spinlock to avoid race * conditions between uvc_queue_buffer and the disconnection event that * could result in an interruptible wait in uvc_dequeue_buffer. Do not * blindly replace this logic by checking for the UVC_DEV_DISCONNECTED diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index a9bb4553db84..93f42c7f800d 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -261,7 +261,7 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req) break; default: - uvcg_info(&video->uvc->func, + uvcg_warn(&video->uvc->func, "VS request completed with status %d.\n", req->status); uvcg_queue_cancel(queue, 0); @@ -378,7 +378,8 @@ static void uvcg_video_pump(struct work_struct *work) int ret; while (video->ep->enabled) { - /* Retrieve the first available USB request, protected by the + /* + * Retrieve the first available USB request, protected by the * request lock. */ spin_lock_irqsave(&video->req_lock, flags); @@ -391,7 +392,8 @@ static void uvcg_video_pump(struct work_struct *work) list_del(&req->list); spin_unlock_irqrestore(&video->req_lock, flags); - /* Retrieve the first available video buffer and fill the + /* + * Retrieve the first available video buffer and fill the * request, protected by the video queue irqlock. */ spin_lock_irqsave(&queue->irqlock, flags); @@ -403,9 +405,11 @@ static void uvcg_video_pump(struct work_struct *work) video->encode(req, video, buf); - /* With usb3 we have more requests. This will decrease the + /* + * With usb3 we have more requests. This will decrease the * interrupt load to a quarter but also catches the corner - * cases, which needs to be handled */ + * cases, which needs to be handled. + */ if (list_empty(&video->req_free) || buf->state == UVC_BUF_STATE_DONE || !(video->req_int_count % diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 69394dc1cdfb..03535f33511b 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -463,6 +463,19 @@ config USB_TEGRA_XUDC dynamically linked module called "tegra_xudc" and force all gadget drivers to also be dynamically linked. +config USB_ASPEED_UDC + tristate "Aspeed UDC driver support" + depends on ARCH_ASPEED || COMPILE_TEST + depends on USB_LIBCOMPOSITE + help + Enables Aspeed USB2.0 Device Controller driver for AST260x + family SoCs. The controller supports 1 control endpoint and + 4 programmable endpoints. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "aspeed_udc" and force all + gadget drivers to also be dynamically linked. + source "drivers/usb/gadget/udc/aspeed-vhub/Kconfig" # diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile index a21f2224e7eb..12f9e4c9eb0c 100644 --- a/drivers/usb/gadget/udc/Makefile +++ b/drivers/usb/gadget/udc/Makefile @@ -40,5 +40,6 @@ 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_ASPEED_UDC) += aspeed_udc.o obj-$(CONFIG_USB_BDC_UDC) += bdc/ obj-$(CONFIG_USB_MAX3420_UDC) += max3420_udc.o diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c new file mode 100644 index 000000000000..d75a4e070bf7 --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed_udc.c @@ -0,0 +1,1594 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2021 Aspeed Technology Inc. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/prefetch.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/slab.h> + +#define AST_UDC_NUM_ENDPOINTS (1 + 4) +#define AST_UDC_EP0_MAX_PACKET 64 /* EP0's max packet size */ +#define AST_UDC_EPn_MAX_PACKET 1024 /* Generic EPs max packet size */ +#define AST_UDC_DESCS_COUNT 256 /* Use 256 stages descriptor mode (32/256) */ +#define AST_UDC_DESC_MODE 1 /* Single/Multiple Stage(s) Descriptor Mode */ + +#define AST_UDC_EP_DMA_SIZE (AST_UDC_EPn_MAX_PACKET + 8 * AST_UDC_DESCS_COUNT) + +/***************************** + * * + * UDC register definitions * + * * + *****************************/ + +#define AST_UDC_FUNC_CTRL 0x00 /* Root Function Control & Status Register */ +#define AST_UDC_CONFIG 0x04 /* Root Configuration Setting Register */ +#define AST_UDC_IER 0x08 /* Interrupt Control Register */ +#define AST_UDC_ISR 0x0C /* Interrupt Status Register */ +#define AST_UDC_EP_ACK_IER 0x10 /* Programmable ep Pool ACK Interrupt Enable Reg */ +#define AST_UDC_EP_NAK_IER 0x14 /* Programmable ep Pool NAK Interrupt Enable Reg */ +#define AST_UDC_EP_ACK_ISR 0x18 /* Programmable ep Pool ACK Interrupt Status Reg */ +#define AST_UDC_EP_NAK_ISR 0x1C /* Programmable ep Pool NAK Interrupt Status Reg */ +#define AST_UDC_DEV_RESET 0x20 /* Device Controller Soft Reset Enable Register */ +#define AST_UDC_STS 0x24 /* USB Status Register */ +#define AST_VHUB_EP_DATA 0x28 /* Programmable ep Pool Data Toggle Value Set */ +#define AST_VHUB_ISO_TX_FAIL 0x2C /* Isochronous Transaction Fail Accumulator */ +#define AST_UDC_EP0_CTRL 0x30 /* Endpoint 0 Control/Status Register */ +#define AST_UDC_EP0_DATA_BUFF 0x34 /* Base Address of ep0 IN/OUT Data Buffer Reg */ +#define AST_UDC_SETUP0 0x80 /* Root Device Setup Data Buffer0 */ +#define AST_UDC_SETUP1 0x84 /* Root Device Setup Data Buffer1 */ + + +/* Main control reg */ +#define USB_PHY_CLK_EN BIT(31) +#define USB_FIFO_DYN_PWRD_EN BIT(19) +#define USB_EP_LONG_DESC BIT(18) +#define USB_BIST_TEST_PASS BIT(13) +#define USB_BIST_TURN_ON BIT(12) +#define USB_PHY_RESET_DIS BIT(11) +#define USB_TEST_MODE(x) ((x) << 8) +#define USB_FORCE_TIMER_HS BIT(7) +#define USB_FORCE_HS BIT(6) +#define USB_REMOTE_WAKEUP_12MS BIT(5) +#define USB_REMOTE_WAKEUP_EN BIT(4) +#define USB_AUTO_REMOTE_WAKEUP_EN BIT(3) +#define USB_STOP_CLK_IN_SUPEND BIT(2) +#define USB_UPSTREAM_FS BIT(1) +#define USB_UPSTREAM_EN BIT(0) + +/* Main config reg */ +#define UDC_CFG_SET_ADDR(x) ((x) & 0x3f) +#define UDC_CFG_ADDR_MASK (0x3f) + +/* Interrupt ctrl & status reg */ +#define UDC_IRQ_EP_POOL_NAK BIT(17) +#define UDC_IRQ_EP_POOL_ACK_STALL BIT(16) +#define UDC_IRQ_BUS_RESUME BIT(8) +#define UDC_IRQ_BUS_SUSPEND BIT(7) +#define UDC_IRQ_BUS_RESET BIT(6) +#define UDC_IRQ_EP0_IN_DATA_NAK BIT(4) +#define UDC_IRQ_EP0_IN_ACK_STALL BIT(3) +#define UDC_IRQ_EP0_OUT_NAK BIT(2) +#define UDC_IRQ_EP0_OUT_ACK_STALL BIT(1) +#define UDC_IRQ_EP0_SETUP BIT(0) +#define UDC_IRQ_ACK_ALL (0x1ff) + +/* EP isr reg */ +#define USB_EP3_ISR BIT(3) +#define USB_EP2_ISR BIT(2) +#define USB_EP1_ISR BIT(1) +#define USB_EP0_ISR BIT(0) +#define UDC_IRQ_EP_ACK_ALL (0xf) + +/*Soft reset reg */ +#define ROOT_UDC_SOFT_RESET BIT(0) + +/* USB status reg */ +#define UDC_STS_HIGHSPEED BIT(27) + +/* Programmable EP data toggle */ +#define EP_TOGGLE_SET_EPNUM(x) ((x) & 0x3) + +/* EP0 ctrl reg */ +#define EP0_GET_RX_LEN(x) ((x >> 16) & 0x7f) +#define EP0_TX_LEN(x) ((x & 0x7f) << 8) +#define EP0_RX_BUFF_RDY BIT(2) +#define EP0_TX_BUFF_RDY BIT(1) +#define EP0_STALL BIT(0) + +/************************************* + * * + * per-endpoint register definitions * + * * + *************************************/ + +#define AST_UDC_EP_CONFIG 0x00 /* Endpoint Configuration Register */ +#define AST_UDC_EP_DMA_CTRL 0x04 /* DMA Descriptor List Control/Status Register */ +#define AST_UDC_EP_DMA_BUFF 0x08 /* DMA Descriptor/Buffer Base Address */ +#define AST_UDC_EP_DMA_STS 0x0C /* DMA Descriptor List R/W Pointer and Status */ + +#define AST_UDC_EP_BASE 0x200 +#define AST_UDC_EP_OFFSET 0x10 + +/* EP config reg */ +#define EP_SET_MAX_PKT(x) ((x & 0x3ff) << 16) +#define EP_DATA_FETCH_CTRL(x) ((x & 0x3) << 14) +#define EP_AUTO_DATA_DISABLE (0x1 << 13) +#define EP_SET_EP_STALL (0x1 << 12) +#define EP_SET_EP_NUM(x) ((x & 0xf) << 8) +#define EP_SET_TYPE_MASK(x) ((x) << 5) +#define EP_TYPE_BULK (0x1) +#define EP_TYPE_INT (0x2) +#define EP_TYPE_ISO (0x3) +#define EP_DIR_OUT (0x1 << 4) +#define EP_ALLOCATED_MASK (0x7 << 1) +#define EP_ENABLE BIT(0) + +/* EP DMA ctrl reg */ +#define EP_DMA_CTRL_GET_PROC_STS(x) ((x >> 4) & 0xf) +#define EP_DMA_CTRL_STS_RX_IDLE 0x0 +#define EP_DMA_CTRL_STS_TX_IDLE 0x8 +#define EP_DMA_CTRL_IN_LONG_MODE (0x1 << 3) +#define EP_DMA_CTRL_RESET (0x1 << 2) +#define EP_DMA_SINGLE_STAGE (0x1 << 1) +#define EP_DMA_DESC_MODE (0x1 << 0) + +/* EP DMA status reg */ +#define EP_DMA_SET_TX_SIZE(x) ((x & 0x7ff) << 16) +#define EP_DMA_GET_TX_SIZE(x) (((x) >> 16) & 0x7ff) +#define EP_DMA_GET_RPTR(x) (((x) >> 8) & 0xff) +#define EP_DMA_GET_WPTR(x) ((x) & 0xff) +#define EP_DMA_SINGLE_KICK (1 << 0) /* WPTR = 1 for single mode */ + +/* EP desc reg */ +#define AST_EP_DMA_DESC_INTR_ENABLE BIT(31) +#define AST_EP_DMA_DESC_PID_DATA0 (0 << 14) +#define AST_EP_DMA_DESC_PID_DATA2 BIT(14) +#define AST_EP_DMA_DESC_PID_DATA1 (2 << 14) +#define AST_EP_DMA_DESC_PID_MDATA (3 << 14) +#define EP_DESC1_IN_LEN(x) ((x) & 0x1fff) +#define AST_EP_DMA_DESC_MAX_LEN (7680) /* Max packet length for trasmit in 1 desc */ + +struct ast_udc_request { + struct usb_request req; + struct list_head queue; + unsigned mapped:1; + unsigned int actual_dma_length; + u32 saved_dma_wptr; +}; + +#define to_ast_req(__req) container_of(__req, struct ast_udc_request, req) + +struct ast_dma_desc { + u32 des_0; + u32 des_1; +}; + +struct ast_udc_ep { + struct usb_ep ep; + + /* Request queue */ + struct list_head queue; + + struct ast_udc_dev *udc; + void __iomem *ep_reg; + void *epn_buf; + dma_addr_t epn_buf_dma; + const struct usb_endpoint_descriptor *desc; + + /* DMA Descriptors */ + struct ast_dma_desc *descs; + dma_addr_t descs_dma; + u32 descs_wptr; + u32 chunk_max; + + bool dir_in:1; + unsigned stopped:1; + bool desc_mode:1; +}; + +#define to_ast_ep(__ep) container_of(__ep, struct ast_udc_ep, ep) + +struct ast_udc_dev { + struct platform_device *pdev; + void __iomem *reg; + int irq; + spinlock_t lock; + struct clk *clk; + struct work_struct wake_work; + + /* EP0 DMA buffers allocated in one chunk */ + void *ep0_buf; + dma_addr_t ep0_buf_dma; + struct ast_udc_ep ep[AST_UDC_NUM_ENDPOINTS]; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + void __iomem *creq; + enum usb_device_state suspended_from; + int desc_mode; + + /* Force full speed only */ + bool force_usb1:1; + unsigned is_control_tx:1; + bool wakeup_en:1; +}; + +#define to_ast_dev(__g) container_of(__g, struct ast_udc_dev, gadget) + +static const char * const ast_ep_name[] = { + "ep0", "ep1", "ep2", "ep3", "ep4" +}; + +#ifdef AST_UDC_DEBUG_ALL +#define AST_UDC_DEBUG +#define AST_SETUP_DEBUG +#define AST_EP_DEBUG +#define AST_ISR_DEBUG +#endif + +#ifdef AST_SETUP_DEBUG +#define SETUP_DBG(u, fmt, ...) \ + dev_dbg(&(u)->pdev->dev, "%s() " fmt, __func__, ##__VA_ARGS__) +#else +#define SETUP_DBG(u, fmt, ...) +#endif + +#ifdef AST_EP_DEBUG +#define EP_DBG(e, fmt, ...) \ + dev_dbg(&(e)->udc->pdev->dev, "%s():%s " fmt, __func__, \ + (e)->ep.name, ##__VA_ARGS__) +#else +#define EP_DBG(ep, fmt, ...) ((void)(ep)) +#endif + +#ifdef AST_UDC_DEBUG +#define UDC_DBG(u, fmt, ...) \ + dev_dbg(&(u)->pdev->dev, "%s() " fmt, __func__, ##__VA_ARGS__) +#else +#define UDC_DBG(u, fmt, ...) +#endif + +#ifdef AST_ISR_DEBUG +#define ISR_DBG(u, fmt, ...) \ + dev_dbg(&(u)->pdev->dev, "%s() " fmt, __func__, ##__VA_ARGS__) +#else +#define ISR_DBG(u, fmt, ...) +#endif + +/*-------------------------------------------------------------------------*/ +#define ast_udc_read(udc, offset) \ + readl((udc)->reg + (offset)) +#define ast_udc_write(udc, val, offset) \ + writel((val), (udc)->reg + (offset)) + +#define ast_ep_read(ep, reg) \ + readl((ep)->ep_reg + (reg)) +#define ast_ep_write(ep, val, reg) \ + writel((val), (ep)->ep_reg + (reg)) + +/*-------------------------------------------------------------------------*/ + +static void ast_udc_done(struct ast_udc_ep *ep, struct ast_udc_request *req, + int status) +{ + struct ast_udc_dev *udc = ep->udc; + + EP_DBG(ep, "req @%p, len (%d/%d), buf:0x%x, dir:0x%x\n", + req, req->req.actual, req->req.length, + (u32)req->req.buf, ep->dir_in); + + list_del(&req->queue); + + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + if (status && status != -ESHUTDOWN) + EP_DBG(ep, "done req:%p, status:%d\n", req, status); + + spin_unlock(&udc->lock); + usb_gadget_giveback_request(&ep->ep, &req->req); + spin_lock(&udc->lock); +} + +static void ast_udc_nuke(struct ast_udc_ep *ep, int status) +{ + int count = 0; + + while (!list_empty(&ep->queue)) { + struct ast_udc_request *req; + + req = list_entry(ep->queue.next, struct ast_udc_request, + queue); + ast_udc_done(ep, req, status); + count++; + } + + if (count) + EP_DBG(ep, "Nuked %d request(s)\n", count); +} + +/* + * Stop activity on all endpoints. + * Device controller for which EP activity is to be stopped. + * + * All the endpoints are stopped and any pending transfer requests if any on + * the endpoint are terminated. + */ +static void ast_udc_stop_activity(struct ast_udc_dev *udc) +{ + struct ast_udc_ep *ep; + int i; + + for (i = 0; i < AST_UDC_NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + ep->stopped = 1; + ast_udc_nuke(ep, -ESHUTDOWN); + } +} + +static int ast_udc_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + u16 maxpacket = usb_endpoint_maxp(desc); + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_dev *udc = ep->udc; + u8 epnum = usb_endpoint_num(desc); + unsigned long flags; + u32 ep_conf = 0; + u8 dir_in; + u8 type; + + if (!_ep || !ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT || + maxpacket == 0 || maxpacket > ep->ep.maxpacket) { + EP_DBG(ep, "Failed, invalid EP enable param\n"); + return -EINVAL; + } + + if (!udc->driver) { + EP_DBG(ep, "bogus device state\n"); + return -ESHUTDOWN; + } + + EP_DBG(ep, "maxpacket:0x%x\n", maxpacket); + + spin_lock_irqsave(&udc->lock, flags); + + ep->desc = desc; + ep->stopped = 0; + ep->ep.maxpacket = maxpacket; + ep->chunk_max = AST_EP_DMA_DESC_MAX_LEN; + + if (maxpacket < AST_UDC_EPn_MAX_PACKET) + ep_conf = EP_SET_MAX_PKT(maxpacket); + + ep_conf |= EP_SET_EP_NUM(epnum); + + type = usb_endpoint_type(desc); + dir_in = usb_endpoint_dir_in(desc); + ep->dir_in = dir_in; + if (!ep->dir_in) + ep_conf |= EP_DIR_OUT; + + EP_DBG(ep, "type %d, dir_in %d\n", type, dir_in); + switch (type) { + case USB_ENDPOINT_XFER_ISOC: + ep_conf |= EP_SET_TYPE_MASK(EP_TYPE_ISO); + break; + + case USB_ENDPOINT_XFER_BULK: + ep_conf |= EP_SET_TYPE_MASK(EP_TYPE_BULK); + break; + + case USB_ENDPOINT_XFER_INT: + ep_conf |= EP_SET_TYPE_MASK(EP_TYPE_INT); + break; + } + + ep->desc_mode = udc->desc_mode && ep->descs_dma && ep->dir_in; + if (ep->desc_mode) { + ast_ep_write(ep, EP_DMA_CTRL_RESET, AST_UDC_EP_DMA_CTRL); + ast_ep_write(ep, 0, AST_UDC_EP_DMA_STS); + ast_ep_write(ep, ep->descs_dma, AST_UDC_EP_DMA_BUFF); + + /* Enable Long Descriptor Mode */ + ast_ep_write(ep, EP_DMA_CTRL_IN_LONG_MODE | EP_DMA_DESC_MODE, + AST_UDC_EP_DMA_CTRL); + + ep->descs_wptr = 0; + + } else { + ast_ep_write(ep, EP_DMA_CTRL_RESET, AST_UDC_EP_DMA_CTRL); + ast_ep_write(ep, EP_DMA_SINGLE_STAGE, AST_UDC_EP_DMA_CTRL); + ast_ep_write(ep, 0, AST_UDC_EP_DMA_STS); + } + + /* Cleanup data toggle just in case */ + ast_udc_write(udc, EP_TOGGLE_SET_EPNUM(epnum), AST_VHUB_EP_DATA); + + /* Enable EP */ + ast_ep_write(ep, ep_conf | EP_ENABLE, AST_UDC_EP_CONFIG); + + EP_DBG(ep, "ep_config: 0x%x\n", ast_ep_read(ep, AST_UDC_EP_CONFIG)); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int ast_udc_ep_disable(struct usb_ep *_ep) +{ + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_dev *udc = ep->udc; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + ep->ep.desc = NULL; + ep->stopped = 1; + + ast_udc_nuke(ep, -ESHUTDOWN); + ast_ep_write(ep, 0, AST_UDC_EP_CONFIG); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static struct usb_request *ast_udc_ep_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_request *req; + + req = kzalloc(sizeof(struct ast_udc_request), gfp_flags); + if (!req) { + EP_DBG(ep, "request allocation failed\n"); + return NULL; + } + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void ast_udc_ep_free_request(struct usb_ep *_ep, + struct usb_request *_req) +{ + struct ast_udc_request *req = to_ast_req(_req); + + kfree(req); +} + +static int ast_dma_descriptor_setup(struct ast_udc_ep *ep, u32 dma_buf, + u16 tx_len, struct ast_udc_request *req) +{ + struct ast_udc_dev *udc = ep->udc; + struct device *dev = &udc->pdev->dev; + int chunk, count; + u32 offset; + + if (!ep->descs) { + dev_warn(dev, "%s: Empty DMA descs list failure\n", + ep->ep.name); + return -EINVAL; + } + + chunk = tx_len; + offset = count = 0; + + EP_DBG(ep, "req @%p, %s:%d, %s:0x%x, %s:0x%x\n", req, + "wptr", ep->descs_wptr, "dma_buf", dma_buf, + "tx_len", tx_len); + + /* Create Descriptor Lists */ + while (chunk > 0 && count < AST_UDC_DESCS_COUNT) { + + ep->descs[ep->descs_wptr].des_0 = dma_buf + offset; + + if (chunk > ep->chunk_max) + ep->descs[ep->descs_wptr].des_1 = ep->chunk_max; + else + ep->descs[ep->descs_wptr].des_1 = chunk; + + chunk -= ep->chunk_max; + + EP_DBG(ep, "descs[%d]: 0x%x 0x%x\n", + ep->descs_wptr, + ep->descs[ep->descs_wptr].des_0, + ep->descs[ep->descs_wptr].des_1); + + if (count == 0) + req->saved_dma_wptr = ep->descs_wptr; + + ep->descs_wptr++; + count++; + + if (ep->descs_wptr >= AST_UDC_DESCS_COUNT) + ep->descs_wptr = 0; + + offset = ep->chunk_max * count; + } + + return 0; +} + +static void ast_udc_epn_kick(struct ast_udc_ep *ep, struct ast_udc_request *req) +{ + u32 tx_len; + u32 last; + + last = req->req.length - req->req.actual; + tx_len = last > ep->ep.maxpacket ? ep->ep.maxpacket : last; + + EP_DBG(ep, "kick req @%p, len:%d, dir:%d\n", + req, tx_len, ep->dir_in); + + ast_ep_write(ep, req->req.dma + req->req.actual, AST_UDC_EP_DMA_BUFF); + + /* Start DMA */ + ast_ep_write(ep, EP_DMA_SET_TX_SIZE(tx_len), AST_UDC_EP_DMA_STS); + ast_ep_write(ep, EP_DMA_SET_TX_SIZE(tx_len) | EP_DMA_SINGLE_KICK, + AST_UDC_EP_DMA_STS); +} + +static void ast_udc_epn_kick_desc(struct ast_udc_ep *ep, + struct ast_udc_request *req) +{ + u32 descs_max_size; + u32 tx_len; + u32 last; + + descs_max_size = AST_EP_DMA_DESC_MAX_LEN * AST_UDC_DESCS_COUNT; + + last = req->req.length - req->req.actual; + tx_len = last > descs_max_size ? descs_max_size : last; + + EP_DBG(ep, "kick req @%p, %s:%d, %s:0x%x, %s:0x%x (%d/%d), %s:0x%x\n", + req, "tx_len", tx_len, "dir_in", ep->dir_in, + "dma", req->req.dma + req->req.actual, + req->req.actual, req->req.length, + "descs_max_size", descs_max_size); + + if (!ast_dma_descriptor_setup(ep, req->req.dma + req->req.actual, + tx_len, req)) + req->actual_dma_length += tx_len; + + /* make sure CPU done everything before triggering DMA */ + mb(); + + ast_ep_write(ep, ep->descs_wptr, AST_UDC_EP_DMA_STS); + + EP_DBG(ep, "descs_wptr:%d, dstat:0x%x, dctrl:0x%x\n", + ep->descs_wptr, + ast_ep_read(ep, AST_UDC_EP_DMA_STS), + ast_ep_read(ep, AST_UDC_EP_DMA_CTRL)); +} + +static void ast_udc_ep0_queue(struct ast_udc_ep *ep, + struct ast_udc_request *req) +{ + struct ast_udc_dev *udc = ep->udc; + u32 tx_len; + u32 last; + + last = req->req.length - req->req.actual; + tx_len = last > ep->ep.maxpacket ? ep->ep.maxpacket : last; + + ast_udc_write(udc, req->req.dma + req->req.actual, + AST_UDC_EP0_DATA_BUFF); + + if (ep->dir_in) { + /* IN requests, send data */ + SETUP_DBG(udc, "IN: %s:0x%x, %s:0x%x, %s:%d (%d/%d), %s:%d\n", + "buf", (u32)req->req.buf, + "dma", req->req.dma + req->req.actual, + "tx_len", tx_len, + req->req.actual, req->req.length, + "dir_in", ep->dir_in); + + req->req.actual += tx_len; + ast_udc_write(udc, EP0_TX_LEN(tx_len), AST_UDC_EP0_CTRL); + ast_udc_write(udc, EP0_TX_LEN(tx_len) | EP0_TX_BUFF_RDY, + AST_UDC_EP0_CTRL); + + } else { + /* OUT requests, receive data */ + SETUP_DBG(udc, "OUT: %s:%x, %s:%x, %s:(%d/%d), %s:%d\n", + "buf", (u32)req->req.buf, + "dma", req->req.dma + req->req.actual, + "len", req->req.actual, req->req.length, + "dir_in", ep->dir_in); + + if (!req->req.length) { + /* 0 len request, send tx as completion */ + ast_udc_write(udc, EP0_TX_BUFF_RDY, AST_UDC_EP0_CTRL); + ep->dir_in = 0x1; + } else + ast_udc_write(udc, EP0_RX_BUFF_RDY, AST_UDC_EP0_CTRL); + } +} + +static int ast_udc_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct ast_udc_request *req = to_ast_req(_req); + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_dev *udc = ep->udc; + struct device *dev = &udc->pdev->dev; + unsigned long flags; + int rc; + + if (unlikely(!_req || !_req->complete || !_req->buf || !_ep)) { + dev_warn(dev, "Invalid EP request !\n"); + return -EINVAL; + } + + if (ep->stopped) { + dev_warn(dev, "%s is already stopped !\n", _ep->name); + return -ESHUTDOWN; + } + + spin_lock_irqsave(&udc->lock, flags); + + list_add_tail(&req->queue, &ep->queue); + + req->req.actual = 0; + req->req.status = -EINPROGRESS; + req->actual_dma_length = 0; + + rc = usb_gadget_map_request(&udc->gadget, &req->req, ep->dir_in); + if (rc) { + EP_DBG(ep, "Request mapping failure %d\n", rc); + dev_warn(dev, "Request mapping failure %d\n", rc); + goto end; + } + + EP_DBG(ep, "enqueue req @%p\n", req); + EP_DBG(ep, "l=%d, dma:0x%x, zero:%d, is_in:%d\n", + _req->length, _req->dma, _req->zero, ep->dir_in); + + /* EP0 request enqueue */ + if (ep->ep.desc == NULL) { + if ((req->req.dma % 4) != 0) { + dev_warn(dev, "EP0 req dma alignment error\n"); + rc = -ESHUTDOWN; + goto end; + } + + ast_udc_ep0_queue(ep, req); + goto end; + } + + /* EPn request enqueue */ + if (list_is_singular(&ep->queue)) { + if (ep->desc_mode) + ast_udc_epn_kick_desc(ep, req); + else + ast_udc_epn_kick(ep, req); + } + +end: + spin_unlock_irqrestore(&udc->lock, flags); + + return rc; +} + +static int ast_udc_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_dev *udc = ep->udc; + struct ast_udc_request *req; + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&udc->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) { + list_del_init(&req->queue); + ast_udc_done(ep, req, -ESHUTDOWN); + _req->status = -ECONNRESET; + break; + } + } + + /* dequeue request not found */ + if (&req->req != _req) + rc = -EINVAL; + + spin_unlock_irqrestore(&udc->lock, flags); + + return rc; +} + +static int ast_udc_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_dev *udc = ep->udc; + unsigned long flags; + int epnum; + u32 ctrl; + + EP_DBG(ep, "val:%d\n", value); + + spin_lock_irqsave(&udc->lock, flags); + + epnum = usb_endpoint_num(ep->desc); + + /* EP0 */ + if (epnum == 0) { + ctrl = ast_udc_read(udc, AST_UDC_EP0_CTRL); + if (value) + ctrl |= EP0_STALL; + else + ctrl &= ~EP0_STALL; + + ast_udc_write(udc, ctrl, AST_UDC_EP0_CTRL); + + } else { + /* EPn */ + ctrl = ast_udc_read(udc, AST_UDC_EP_CONFIG); + if (value) + ctrl |= EP_SET_EP_STALL; + else + ctrl &= ~EP_SET_EP_STALL; + + ast_ep_write(ep, ctrl, AST_UDC_EP_CONFIG); + + /* only epn is stopped and waits for clear */ + ep->stopped = value ? 1 : 0; + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static const struct usb_ep_ops ast_udc_ep_ops = { + .enable = ast_udc_ep_enable, + .disable = ast_udc_ep_disable, + .alloc_request = ast_udc_ep_alloc_request, + .free_request = ast_udc_ep_free_request, + .queue = ast_udc_ep_queue, + .dequeue = ast_udc_ep_dequeue, + .set_halt = ast_udc_ep_set_halt, + /* there's only imprecise fifo status reporting */ +}; + +static void ast_udc_ep0_rx(struct ast_udc_dev *udc) +{ + ast_udc_write(udc, udc->ep0_buf_dma, AST_UDC_EP0_DATA_BUFF); + ast_udc_write(udc, EP0_RX_BUFF_RDY, AST_UDC_EP0_CTRL); +} + +static void ast_udc_ep0_tx(struct ast_udc_dev *udc) +{ + ast_udc_write(udc, udc->ep0_buf_dma, AST_UDC_EP0_DATA_BUFF); + ast_udc_write(udc, EP0_TX_BUFF_RDY, AST_UDC_EP0_CTRL); +} + +static void ast_udc_ep0_out(struct ast_udc_dev *udc) +{ + struct device *dev = &udc->pdev->dev; + struct ast_udc_ep *ep = &udc->ep[0]; + struct ast_udc_request *req; + u16 rx_len; + + if (list_empty(&ep->queue)) + return; + + req = list_entry(ep->queue.next, struct ast_udc_request, queue); + + rx_len = EP0_GET_RX_LEN(ast_udc_read(udc, AST_UDC_EP0_CTRL)); + req->req.actual += rx_len; + + SETUP_DBG(udc, "req %p (%d/%d)\n", req, + req->req.actual, req->req.length); + + if ((rx_len < ep->ep.maxpacket) || + (req->req.actual == req->req.length)) { + ast_udc_ep0_tx(udc); + if (!ep->dir_in) + ast_udc_done(ep, req, 0); + + } else { + if (rx_len > req->req.length) { + // Issue Fix + dev_warn(dev, "Something wrong (%d/%d)\n", + req->req.actual, req->req.length); + ast_udc_ep0_tx(udc); + ast_udc_done(ep, req, 0); + return; + } + + ep->dir_in = 0; + + /* More works */ + ast_udc_ep0_queue(ep, req); + } +} + +static void ast_udc_ep0_in(struct ast_udc_dev *udc) +{ + struct ast_udc_ep *ep = &udc->ep[0]; + struct ast_udc_request *req; + + if (list_empty(&ep->queue)) { + if (udc->is_control_tx) { + ast_udc_ep0_rx(udc); + udc->is_control_tx = 0; + } + + return; + } + + req = list_entry(ep->queue.next, struct ast_udc_request, queue); + + SETUP_DBG(udc, "req %p (%d/%d)\n", req, + req->req.actual, req->req.length); + + if (req->req.length == req->req.actual) { + if (req->req.length) + ast_udc_ep0_rx(udc); + + if (ep->dir_in) + ast_udc_done(ep, req, 0); + + } else { + /* More works */ + ast_udc_ep0_queue(ep, req); + } +} + +static void ast_udc_epn_handle(struct ast_udc_dev *udc, u16 ep_num) +{ + struct ast_udc_ep *ep = &udc->ep[ep_num]; + struct ast_udc_request *req; + u16 len = 0; + + if (list_empty(&ep->queue)) + return; + + req = list_first_entry(&ep->queue, struct ast_udc_request, queue); + + len = EP_DMA_GET_TX_SIZE(ast_ep_read(ep, AST_UDC_EP_DMA_STS)); + req->req.actual += len; + + EP_DBG(ep, "req @%p, length:(%d/%d), %s:0x%x\n", req, + req->req.actual, req->req.length, "len", len); + + /* Done this request */ + if (req->req.length == req->req.actual) { + ast_udc_done(ep, req, 0); + req = list_first_entry_or_null(&ep->queue, + struct ast_udc_request, + queue); + + } else { + /* Check for short packet */ + if (len < ep->ep.maxpacket) { + ast_udc_done(ep, req, 0); + req = list_first_entry_or_null(&ep->queue, + struct ast_udc_request, + queue); + } + } + + /* More requests */ + if (req) + ast_udc_epn_kick(ep, req); +} + +static void ast_udc_epn_handle_desc(struct ast_udc_dev *udc, u16 ep_num) +{ + struct ast_udc_ep *ep = &udc->ep[ep_num]; + struct device *dev = &udc->pdev->dev; + struct ast_udc_request *req; + u32 proc_sts, wr_ptr, rd_ptr; + u32 len_in_desc, ctrl; + u16 total_len = 0; + int i; + + if (list_empty(&ep->queue)) { + dev_warn(dev, "%s request queue empty!\n", ep->ep.name); + return; + } + + req = list_first_entry(&ep->queue, struct ast_udc_request, queue); + + ctrl = ast_ep_read(ep, AST_UDC_EP_DMA_CTRL); + proc_sts = EP_DMA_CTRL_GET_PROC_STS(ctrl); + + /* Check processing status is idle */ + if (proc_sts != EP_DMA_CTRL_STS_RX_IDLE && + proc_sts != EP_DMA_CTRL_STS_TX_IDLE) { + dev_warn(dev, "EP DMA CTRL: 0x%x, PS:0x%x\n", + ast_ep_read(ep, AST_UDC_EP_DMA_CTRL), + proc_sts); + return; + } + + ctrl = ast_ep_read(ep, AST_UDC_EP_DMA_STS); + rd_ptr = EP_DMA_GET_RPTR(ctrl); + wr_ptr = EP_DMA_GET_WPTR(ctrl); + + if (rd_ptr != wr_ptr) { + dev_warn(dev, "desc list is not empty ! %s:%d, %s:%d\n", + "rptr", rd_ptr, "wptr", wr_ptr); + return; + } + + EP_DBG(ep, "rd_ptr:%d, wr_ptr:%d\n", rd_ptr, wr_ptr); + i = req->saved_dma_wptr; + + do { + len_in_desc = EP_DESC1_IN_LEN(ep->descs[i].des_1); + EP_DBG(ep, "desc[%d] len: %d\n", i, len_in_desc); + total_len += len_in_desc; + i++; + if (i >= AST_UDC_DESCS_COUNT) + i = 0; + + } while (i != wr_ptr); + + req->req.actual += total_len; + + EP_DBG(ep, "req @%p, length:(%d/%d), %s:0x%x\n", req, + req->req.actual, req->req.length, "len", total_len); + + /* Done this request */ + if (req->req.length == req->req.actual) { + ast_udc_done(ep, req, 0); + req = list_first_entry_or_null(&ep->queue, + struct ast_udc_request, + queue); + + } else { + /* Check for short packet */ + if (total_len < ep->ep.maxpacket) { + ast_udc_done(ep, req, 0); + req = list_first_entry_or_null(&ep->queue, + struct ast_udc_request, + queue); + } + } + + /* More requests & dma descs not setup yet */ + if (req && (req->actual_dma_length == req->req.actual)) { + EP_DBG(ep, "More requests\n"); + ast_udc_epn_kick_desc(ep, req); + } +} + +static void ast_udc_ep0_data_tx(struct ast_udc_dev *udc, u8 *tx_data, u32 len) +{ + if (len) { + memcpy(udc->ep0_buf, tx_data, len); + + ast_udc_write(udc, udc->ep0_buf_dma, AST_UDC_EP0_DATA_BUFF); + ast_udc_write(udc, EP0_TX_LEN(len), AST_UDC_EP0_CTRL); + ast_udc_write(udc, EP0_TX_LEN(len) | EP0_TX_BUFF_RDY, + AST_UDC_EP0_CTRL); + udc->is_control_tx = 1; + + } else + ast_udc_write(udc, EP0_TX_BUFF_RDY, AST_UDC_EP0_CTRL); +} + +static void ast_udc_getstatus(struct ast_udc_dev *udc) +{ + struct usb_ctrlrequest crq; + struct ast_udc_ep *ep; + u16 status = 0; + u16 epnum = 0; + + memcpy_fromio(&crq, udc->creq, sizeof(crq)); + + switch (crq.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + /* Get device status */ + status = 1 << USB_DEVICE_SELF_POWERED; + break; + case USB_RECIP_INTERFACE: + break; + case USB_RECIP_ENDPOINT: + epnum = crq.wIndex & USB_ENDPOINT_NUMBER_MASK; + status = udc->ep[epnum].stopped; + break; + default: + goto stall; + } + + ep = &udc->ep[epnum]; + EP_DBG(ep, "status: 0x%x\n", status); + ast_udc_ep0_data_tx(udc, (u8 *)&status, sizeof(status)); + + return; + +stall: + EP_DBG(ep, "Can't respond request\n"); + ast_udc_write(udc, ast_udc_read(udc, AST_UDC_EP0_CTRL) | EP0_STALL, + AST_UDC_EP0_CTRL); +} + +static void ast_udc_ep0_handle_setup(struct ast_udc_dev *udc) +{ + struct ast_udc_ep *ep = &udc->ep[0]; + struct ast_udc_request *req; + struct usb_ctrlrequest crq; + int req_num = 0; + int rc = 0; + u32 reg; + + memcpy_fromio(&crq, udc->creq, sizeof(crq)); + + SETUP_DBG(udc, "SETUP packet: %02x/%02x/%04x/%04x/%04x\n", + crq.bRequestType, crq.bRequest, le16_to_cpu(crq.wValue), + le16_to_cpu(crq.wIndex), le16_to_cpu(crq.wLength)); + + /* + * Cleanup ep0 request(s) in queue because + * there is a new control setup comes. + */ + list_for_each_entry(req, &udc->ep[0].queue, queue) { + req_num++; + EP_DBG(ep, "there is req %p in ep0 queue !\n", req); + } + + if (req_num) + ast_udc_nuke(&udc->ep[0], -ETIMEDOUT); + + udc->ep[0].dir_in = crq.bRequestType & USB_DIR_IN; + + if ((crq.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (crq.bRequest) { + case USB_REQ_SET_ADDRESS: + if (ast_udc_read(udc, AST_UDC_STS) & UDC_STS_HIGHSPEED) + udc->gadget.speed = USB_SPEED_HIGH; + else + udc->gadget.speed = USB_SPEED_FULL; + + SETUP_DBG(udc, "set addr: 0x%x\n", crq.wValue); + reg = ast_udc_read(udc, AST_UDC_CONFIG); + reg &= ~UDC_CFG_ADDR_MASK; + reg |= UDC_CFG_SET_ADDR(crq.wValue); + ast_udc_write(udc, reg, AST_UDC_CONFIG); + goto req_complete; + + case USB_REQ_CLEAR_FEATURE: + SETUP_DBG(udc, "ep0: CLEAR FEATURE\n"); + goto req_driver; + + case USB_REQ_SET_FEATURE: + SETUP_DBG(udc, "ep0: SET FEATURE\n"); + goto req_driver; + + case USB_REQ_GET_STATUS: + ast_udc_getstatus(udc); + return; + + default: + goto req_driver; + } + + } + +req_driver: + if (udc->driver) { + SETUP_DBG(udc, "Forwarding %s to gadget...\n", + udc->gadget.name); + + spin_unlock(&udc->lock); + rc = udc->driver->setup(&udc->gadget, &crq); + spin_lock(&udc->lock); + + } else { + SETUP_DBG(udc, "No gadget for request !\n"); + } + + if (rc >= 0) + return; + + /* Stall if gadget failed */ + SETUP_DBG(udc, "Stalling, rc:0x%x\n", rc); + ast_udc_write(udc, ast_udc_read(udc, AST_UDC_EP0_CTRL) | EP0_STALL, + AST_UDC_EP0_CTRL); + return; + +req_complete: + SETUP_DBG(udc, "ep0: Sending IN status without data\n"); + ast_udc_write(udc, EP0_TX_BUFF_RDY, AST_UDC_EP0_CTRL); +} + +static irqreturn_t ast_udc_isr(int irq, void *data) +{ + struct ast_udc_dev *udc = (struct ast_udc_dev *)data; + struct ast_udc_ep *ep; + u32 isr, ep_isr; + int i; + + spin_lock(&udc->lock); + + isr = ast_udc_read(udc, AST_UDC_ISR); + if (!isr) + goto done; + + /* Ack interrupts */ + ast_udc_write(udc, isr, AST_UDC_ISR); + + if (isr & UDC_IRQ_BUS_RESET) { + ISR_DBG(udc, "UDC_IRQ_BUS_RESET\n"); + udc->gadget.speed = USB_SPEED_UNKNOWN; + + ep = &udc->ep[1]; + EP_DBG(ep, "dctrl:0x%x\n", + ast_ep_read(ep, AST_UDC_EP_DMA_CTRL)); + + if (udc->driver && udc->driver->reset) { + spin_unlock(&udc->lock); + udc->driver->reset(&udc->gadget); + spin_lock(&udc->lock); + } + } + + if (isr & UDC_IRQ_BUS_SUSPEND) { + ISR_DBG(udc, "UDC_IRQ_BUS_SUSPEND\n"); + udc->suspended_from = udc->gadget.state; + usb_gadget_set_state(&udc->gadget, USB_STATE_SUSPENDED); + + if (udc->driver && udc->driver->suspend) { + spin_unlock(&udc->lock); + udc->driver->suspend(&udc->gadget); + spin_lock(&udc->lock); + } + } + + if (isr & UDC_IRQ_BUS_RESUME) { + ISR_DBG(udc, "UDC_IRQ_BUS_RESUME\n"); + usb_gadget_set_state(&udc->gadget, udc->suspended_from); + + if (udc->driver && udc->driver->resume) { + spin_unlock(&udc->lock); + udc->driver->resume(&udc->gadget); + spin_lock(&udc->lock); + } + } + + if (isr & UDC_IRQ_EP0_IN_ACK_STALL) { + ISR_DBG(udc, "UDC_IRQ_EP0_IN_ACK_STALL\n"); + ast_udc_ep0_in(udc); + } + + if (isr & UDC_IRQ_EP0_OUT_ACK_STALL) { + ISR_DBG(udc, "UDC_IRQ_EP0_OUT_ACK_STALL\n"); + ast_udc_ep0_out(udc); + } + + if (isr & UDC_IRQ_EP0_SETUP) { + ISR_DBG(udc, "UDC_IRQ_EP0_SETUP\n"); + ast_udc_ep0_handle_setup(udc); + } + + if (isr & UDC_IRQ_EP_POOL_ACK_STALL) { + ISR_DBG(udc, "UDC_IRQ_EP_POOL_ACK_STALL\n"); + ep_isr = ast_udc_read(udc, AST_UDC_EP_ACK_ISR); + + /* Ack EP interrupts */ + ast_udc_write(udc, ep_isr, AST_UDC_EP_ACK_ISR); + + /* Handle each EP */ + for (i = 0; i < AST_UDC_NUM_ENDPOINTS - 1; i++) { + if (ep_isr & (0x1 << i)) { + ep = &udc->ep[i + 1]; + if (ep->desc_mode) + ast_udc_epn_handle_desc(udc, i + 1); + else + ast_udc_epn_handle(udc, i + 1); + } + } + } + +done: + spin_unlock(&udc->lock); + return IRQ_HANDLED; +} + +static int ast_udc_gadget_getframe(struct usb_gadget *gadget) +{ + struct ast_udc_dev *udc = to_ast_dev(gadget); + + return (ast_udc_read(udc, AST_UDC_STS) >> 16) & 0x7ff; +} + +static void ast_udc_wake_work(struct work_struct *work) +{ + struct ast_udc_dev *udc = container_of(work, struct ast_udc_dev, + wake_work); + unsigned long flags; + u32 ctrl; + + spin_lock_irqsave(&udc->lock, flags); + + UDC_DBG(udc, "Wakeup Host !\n"); + ctrl = ast_udc_read(udc, AST_UDC_FUNC_CTRL); + ast_udc_write(udc, ctrl | USB_REMOTE_WAKEUP_EN, AST_UDC_FUNC_CTRL); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static void ast_udc_wakeup_all(struct ast_udc_dev *udc) +{ + /* + * A device is trying to wake the world, because this + * can recurse into the device, we break the call chain + * using a work queue + */ + schedule_work(&udc->wake_work); +} + +static int ast_udc_wakeup(struct usb_gadget *gadget) +{ + struct ast_udc_dev *udc = to_ast_dev(gadget); + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&udc->lock, flags); + + if (!udc->wakeup_en) { + UDC_DBG(udc, "Remote Wakeup is disabled\n"); + rc = -EINVAL; + goto err; + } + + UDC_DBG(udc, "Device initiated wakeup\n"); + ast_udc_wakeup_all(udc); + +err: + spin_unlock_irqrestore(&udc->lock, flags); + return rc; +} + +/* + * Activate/Deactivate link with host + */ +static int ast_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + struct ast_udc_dev *udc = to_ast_dev(gadget); + unsigned long flags; + u32 ctrl; + + spin_lock_irqsave(&udc->lock, flags); + + UDC_DBG(udc, "is_on: %d\n", is_on); + if (is_on) + ctrl = ast_udc_read(udc, AST_UDC_FUNC_CTRL) | USB_UPSTREAM_EN; + else + ctrl = ast_udc_read(udc, AST_UDC_FUNC_CTRL) & ~USB_UPSTREAM_EN; + + ast_udc_write(udc, ctrl, AST_UDC_FUNC_CTRL); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int ast_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct ast_udc_dev *udc = to_ast_dev(gadget); + struct ast_udc_ep *ep; + unsigned long flags; + int i; + + spin_lock_irqsave(&udc->lock, flags); + + UDC_DBG(udc, "\n"); + udc->driver = driver; + udc->gadget.dev.of_node = udc->pdev->dev.of_node; + + for (i = 0; i < AST_UDC_NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + ep->stopped = 0; + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int ast_udc_stop(struct usb_gadget *gadget) +{ + struct ast_udc_dev *udc = to_ast_dev(gadget); + unsigned long flags; + u32 ctrl; + + spin_lock_irqsave(&udc->lock, flags); + + UDC_DBG(udc, "\n"); + ctrl = ast_udc_read(udc, AST_UDC_FUNC_CTRL) & ~USB_UPSTREAM_EN; + ast_udc_write(udc, ctrl, AST_UDC_FUNC_CTRL); + + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->driver = NULL; + + ast_udc_stop_activity(udc); + usb_gadget_set_state(&udc->gadget, USB_STATE_NOTATTACHED); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static const struct usb_gadget_ops ast_udc_ops = { + .get_frame = ast_udc_gadget_getframe, + .wakeup = ast_udc_wakeup, + .pullup = ast_udc_pullup, + .udc_start = ast_udc_start, + .udc_stop = ast_udc_stop, +}; + +/* + * Support 1 Control Endpoint. + * Support multiple programmable endpoints that can be configured to + * Bulk IN/OUT, Interrupt IN/OUT, and Isochronous IN/OUT type endpoint. + */ +static void ast_udc_init_ep(struct ast_udc_dev *udc) +{ + struct ast_udc_ep *ep; + int i; + + for (i = 0; i < AST_UDC_NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + ep->ep.name = ast_ep_name[i]; + if (i == 0) { + ep->ep.caps.type_control = true; + } else { + ep->ep.caps.type_iso = true; + ep->ep.caps.type_bulk = true; + ep->ep.caps.type_int = true; + } + ep->ep.caps.dir_in = true; + ep->ep.caps.dir_out = true; + + ep->ep.ops = &ast_udc_ep_ops; + ep->udc = udc; + + INIT_LIST_HEAD(&ep->queue); + + if (i == 0) { + usb_ep_set_maxpacket_limit(&ep->ep, + AST_UDC_EP0_MAX_PACKET); + continue; + } + + ep->ep_reg = udc->reg + AST_UDC_EP_BASE + + (AST_UDC_EP_OFFSET * (i - 1)); + + ep->epn_buf = udc->ep0_buf + (i * AST_UDC_EP_DMA_SIZE); + ep->epn_buf_dma = udc->ep0_buf_dma + (i * AST_UDC_EP_DMA_SIZE); + usb_ep_set_maxpacket_limit(&ep->ep, AST_UDC_EPn_MAX_PACKET); + + ep->descs = ep->epn_buf + AST_UDC_EPn_MAX_PACKET; + ep->descs_dma = ep->epn_buf_dma + AST_UDC_EPn_MAX_PACKET; + ep->descs_wptr = 0; + + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + } +} + +static void ast_udc_init_dev(struct ast_udc_dev *udc) +{ + INIT_WORK(&udc->wake_work, ast_udc_wake_work); +} + +static void ast_udc_init_hw(struct ast_udc_dev *udc) +{ + u32 ctrl; + + /* Enable PHY */ + ctrl = USB_PHY_CLK_EN | USB_PHY_RESET_DIS; + ast_udc_write(udc, ctrl, AST_UDC_FUNC_CTRL); + + udelay(1); + ast_udc_write(udc, 0, AST_UDC_DEV_RESET); + + /* Set descriptor ring size */ + if (AST_UDC_DESCS_COUNT == 256) { + ctrl |= USB_EP_LONG_DESC; + ast_udc_write(udc, ctrl, AST_UDC_FUNC_CTRL); + } + + /* Mask & ack all interrupts before installing the handler */ + ast_udc_write(udc, 0, AST_UDC_IER); + ast_udc_write(udc, UDC_IRQ_ACK_ALL, AST_UDC_ISR); + + /* Enable some interrupts */ + ctrl = UDC_IRQ_EP_POOL_ACK_STALL | UDC_IRQ_BUS_RESUME | + UDC_IRQ_BUS_SUSPEND | UDC_IRQ_BUS_RESET | + UDC_IRQ_EP0_IN_ACK_STALL | UDC_IRQ_EP0_OUT_ACK_STALL | + UDC_IRQ_EP0_SETUP; + ast_udc_write(udc, ctrl, AST_UDC_IER); + + /* Cleanup and enable ep ACK interrupts */ + ast_udc_write(udc, UDC_IRQ_EP_ACK_ALL, AST_UDC_EP_ACK_IER); + ast_udc_write(udc, UDC_IRQ_EP_ACK_ALL, AST_UDC_EP_ACK_ISR); + + ast_udc_write(udc, 0, AST_UDC_EP0_CTRL); +} + +static int ast_udc_remove(struct platform_device *pdev) +{ + struct ast_udc_dev *udc = platform_get_drvdata(pdev); + unsigned long flags; + u32 ctrl; + + usb_del_gadget_udc(&udc->gadget); + if (udc->driver) + return -EBUSY; + + spin_lock_irqsave(&udc->lock, flags); + + /* Disable upstream port connection */ + ctrl = ast_udc_read(udc, AST_UDC_FUNC_CTRL) & ~USB_UPSTREAM_EN; + ast_udc_write(udc, ctrl, AST_UDC_FUNC_CTRL); + + clk_disable_unprepare(udc->clk); + + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->ep0_buf) + dma_free_coherent(&pdev->dev, + AST_UDC_EP_DMA_SIZE * AST_UDC_NUM_ENDPOINTS, + udc->ep0_buf, + udc->ep0_buf_dma); + + udc->ep0_buf = NULL; + + return 0; +} + +static int ast_udc_probe(struct platform_device *pdev) +{ + enum usb_device_speed max_speed; + struct device *dev = &pdev->dev; + struct ast_udc_dev *udc; + struct resource *res; + int rc; + + udc = devm_kzalloc(&pdev->dev, sizeof(struct ast_udc_dev), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + udc->gadget.dev.parent = dev; + udc->pdev = pdev; + spin_lock_init(&udc->lock); + + udc->gadget.ops = &ast_udc_ops; + udc->gadget.ep0 = &udc->ep[0].ep; + udc->gadget.name = "aspeed-udc"; + udc->gadget.dev.init_name = "gadget"; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + udc->reg = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(udc->reg)) { + dev_err(&pdev->dev, "Failed to map resources\n"); + return PTR_ERR(udc->reg); + } + + platform_set_drvdata(pdev, udc); + + udc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(udc->clk)) { + rc = PTR_ERR(udc->clk); + goto err; + } + rc = clk_prepare_enable(udc->clk); + if (rc) { + dev_err(&pdev->dev, "Failed to enable clock (0x%x)\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) + udc->force_usb1 = true; + + /* + * Allocate DMA buffers for all EPs in one chunk + */ + udc->ep0_buf = dma_alloc_coherent(&pdev->dev, + AST_UDC_EP_DMA_SIZE * + AST_UDC_NUM_ENDPOINTS, + &udc->ep0_buf_dma, GFP_KERNEL); + + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.max_speed = USB_SPEED_HIGH; + udc->creq = udc->reg + AST_UDC_SETUP0; + + /* + * Support single stage mode or 32/256 stages descriptor mode. + * Set default as Descriptor Mode. + */ + udc->desc_mode = AST_UDC_DESC_MODE; + + dev_info(&pdev->dev, "DMA %s\n", udc->desc_mode ? + "descriptor mode" : "single mode"); + + INIT_LIST_HEAD(&udc->gadget.ep_list); + INIT_LIST_HEAD(&udc->gadget.ep0->ep_list); + + /* Initialized udc ep */ + ast_udc_init_ep(udc); + + /* Initialized udc device */ + ast_udc_init_dev(udc); + + /* Initialized udc hardware */ + ast_udc_init_hw(udc); + + /* Find interrupt and install handler */ + udc->irq = platform_get_irq(pdev, 0); + if (udc->irq < 0) { + rc = udc->irq; + goto err; + } + + rc = devm_request_irq(&pdev->dev, udc->irq, ast_udc_isr, 0, + KBUILD_MODNAME, udc); + if (rc) { + dev_err(&pdev->dev, "Failed to request interrupt\n"); + goto err; + } + + rc = usb_add_gadget_udc(&pdev->dev, &udc->gadget); + if (rc) { + dev_err(&pdev->dev, "Failed to add gadget udc\n"); + goto err; + } + + dev_info(&pdev->dev, "Initialized udc in USB%s mode\n", + udc->force_usb1 ? "1" : "2"); + + return 0; + +err: + dev_err(&pdev->dev, "Failed to udc probe, rc:0x%x\n", rc); + ast_udc_remove(pdev); + + return rc; +} + +static const struct of_device_id ast_udc_of_dt_ids[] = { + { .compatible = "aspeed,ast2600-udc", }, + {} +}; + +MODULE_DEVICE_TABLE(of, ast_udc_of_dt_ids); + +static struct platform_driver ast_udc_driver = { + .probe = ast_udc_probe, + .remove = ast_udc_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = ast_udc_of_dt_ids, + }, +}; + +module_platform_driver(ast_udc_driver); + +MODULE_DESCRIPTION("ASPEED UDC driver"); +MODULE_AUTHOR("Neal Liu <neal_liu@aspeedtech.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/udc/bdc/bdc_cmd.c b/drivers/usb/gadget/udc/bdc/bdc_cmd.c index 67887316a1a6..1848ced073f8 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_cmd.c +++ b/drivers/usb/gadget/udc/bdc/bdc_cmd.c @@ -307,7 +307,7 @@ int bdc_ep_clear_stall(struct bdc *bdc, int epnum) * his will reset the seq number for non EP0. */ if (epnum != 1) { - /* if the endpoint it not stallled */ + /* if the endpoint it not stalled */ if (!(ep->flags & BDC_EP_STALL)) { ret = bdc_ep_set_stall(bdc, epnum); if (ret) diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index 6d31ccf6aee5..3c37effdfa64 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -3691,15 +3691,15 @@ static int tegra_xudc_powerdomain_init(struct tegra_xudc *xudc) int err; xudc->genpd_dev_device = dev_pm_domain_attach_by_name(dev, "dev"); - if (IS_ERR(xudc->genpd_dev_device)) { - err = PTR_ERR(xudc->genpd_dev_device); + if (IS_ERR_OR_NULL(xudc->genpd_dev_device)) { + err = PTR_ERR(xudc->genpd_dev_device) ? : -ENODATA; dev_err(dev, "failed to get device power domain: %d\n", err); return err; } xudc->genpd_dev_ss = dev_pm_domain_attach_by_name(dev, "ss"); - if (IS_ERR(xudc->genpd_dev_ss)) { - err = PTR_ERR(xudc->genpd_dev_ss); + if (IS_ERR_OR_NULL(xudc->genpd_dev_ss)) { + err = PTR_ERR(xudc->genpd_dev_ss) ? : -ENODATA; dev_err(dev, "failed to get SuperSpeed power domain: %d\n", err); return err; } diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index f343967443e2..6924f0316e9a 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -370,6 +370,8 @@ static int ehci_platform_probe(struct platform_device *dev) hcd->rsrc_start = res_mem->start; hcd->rsrc_len = resource_size(res_mem); + hcd->tpl_support = of_usb_host_tpl_support(dev->dev.of_node); + err = usb_add_hcd(hcd, irq, IRQF_SHARED); if (err) goto err_power; diff --git a/drivers/usb/host/ehci-ppc-of.c b/drivers/usb/host/ehci-ppc-of.c index 6bbaee74f7e7..28a19693c19f 100644 --- a/drivers/usb/host/ehci-ppc-of.c +++ b/drivers/usb/host/ehci-ppc-of.c @@ -148,6 +148,7 @@ static int ehci_hcd_ppc_of_probe(struct platform_device *op) } else { ehci->has_amcc_usb23 = 1; } + of_node_put(np); } if (of_get_property(dn, "big-endian", NULL)) { diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 1163af6fad77..807e64991e3e 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -1162,7 +1162,7 @@ submit_async ( * This is done in two parts: first SETUP req for GetDesc is sent then * 15 seconds later, the IN stage for GetDesc starts to req data from dev * - * is_setup : i/p arguement decides which of the two stage needs to be + * is_setup : i/p argument decides which of the two stage needs to be * performed; TRUE - SETUP and FALSE - IN+STATUS * Returns 0 if success */ diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index a24aea3d2759..98326465e2dc 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -13,6 +13,7 @@ * This file is licenced under the GPL. */ +#include <linux/arm-smccc.h> #include <linux/clk.h> #include <linux/dma-mapping.h> #include <linux/gpio/consumer.h> @@ -55,6 +56,7 @@ struct ohci_at91_priv { bool clocked; bool wakeup; /* Saved wake-up state for resume */ struct regmap *sfr_regmap; + u32 suspend_smc_id; }; /* interface and function clocks; sometimes also an AHB clock */ @@ -135,6 +137,19 @@ static void at91_stop_hc(struct platform_device *pdev) static void usb_hcd_at91_remove (struct usb_hcd *, struct platform_device *); +static u32 at91_dt_suspend_smc(struct device *dev) +{ + u32 suspend_smc_id; + + if (!dev->of_node) + return 0; + + if (of_property_read_u32(dev->of_node, "microchip,suspend-smc-id", &suspend_smc_id)) + return 0; + + return suspend_smc_id; +} + static struct regmap *at91_dt_syscon_sfr(void) { struct regmap *regmap; @@ -215,9 +230,13 @@ static int usb_hcd_at91_probe(const struct hc_driver *driver, goto err; } - ohci_at91->sfr_regmap = at91_dt_syscon_sfr(); - if (!ohci_at91->sfr_regmap) - dev_dbg(dev, "failed to find sfr node\n"); + ohci_at91->suspend_smc_id = at91_dt_suspend_smc(dev); + if (!ohci_at91->suspend_smc_id) { + dev_dbg(dev, "failed to find sfr suspend smc id, using regmap\n"); + ohci_at91->sfr_regmap = at91_dt_syscon_sfr(); + if (!ohci_at91->sfr_regmap) + dev_dbg(dev, "failed to find sfr node\n"); + } board = hcd->self.controller->platform_data; ohci = hcd_to_ohci(hcd); @@ -303,24 +322,30 @@ static int ohci_at91_hub_status_data(struct usb_hcd *hcd, char *buf) return length; } -static int ohci_at91_port_suspend(struct regmap *regmap, u8 set) +static int ohci_at91_port_suspend(struct ohci_at91_priv *ohci_at91, u8 set) { + struct regmap *regmap = ohci_at91->sfr_regmap; u32 regval; int ret; - if (!regmap) - return 0; + if (ohci_at91->suspend_smc_id) { + struct arm_smccc_res res; - ret = regmap_read(regmap, AT91_SFR_OHCIICR, ®val); - if (ret) - return ret; + arm_smccc_smc(ohci_at91->suspend_smc_id, set, 0, 0, 0, 0, 0, 0, &res); + if (res.a0) + return -EINVAL; + } else if (regmap) { + ret = regmap_read(regmap, AT91_SFR_OHCIICR, ®val); + if (ret) + return ret; - if (set) - regval |= AT91_OHCIICR_USB_SUSPEND; - else - regval &= ~AT91_OHCIICR_USB_SUSPEND; + if (set) + regval |= AT91_OHCIICR_USB_SUSPEND; + else + regval &= ~AT91_OHCIICR_USB_SUSPEND; - regmap_write(regmap, AT91_SFR_OHCIICR, regval); + regmap_write(regmap, AT91_SFR_OHCIICR, regval); + } return 0; } @@ -357,9 +382,8 @@ static int ohci_at91_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case USB_PORT_FEAT_SUSPEND: dev_dbg(hcd->self.controller, "SetPortFeat: SUSPEND\n"); - if (valid_port(wIndex) && ohci_at91->sfr_regmap) { - ohci_at91_port_suspend(ohci_at91->sfr_regmap, - 1); + if (valid_port(wIndex)) { + ohci_at91_port_suspend(ohci_at91, 1); return 0; } break; @@ -400,9 +424,8 @@ static int ohci_at91_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case USB_PORT_FEAT_SUSPEND: dev_dbg(hcd->self.controller, "ClearPortFeature: SUSPEND\n"); - if (valid_port(wIndex) && ohci_at91->sfr_regmap) { - ohci_at91_port_suspend(ohci_at91->sfr_regmap, - 0); + if (valid_port(wIndex)) { + ohci_at91_port_suspend(ohci_at91, 0); return 0; } break; @@ -630,10 +653,10 @@ ohci_hcd_at91_drv_suspend(struct device *dev) /* flush the writes */ (void) ohci_readl (ohci, &ohci->regs->control); msleep(1); - ohci_at91_port_suspend(ohci_at91->sfr_regmap, 1); + ohci_at91_port_suspend(ohci_at91, 1); at91_stop_clock(ohci_at91); } else { - ohci_at91_port_suspend(ohci_at91->sfr_regmap, 1); + ohci_at91_port_suspend(ohci_at91, 1); } return ret; @@ -645,7 +668,7 @@ ohci_hcd_at91_drv_resume(struct device *dev) struct usb_hcd *hcd = dev_get_drvdata(dev); struct ohci_at91_priv *ohci_at91 = hcd_to_ohci_at91_priv(hcd); - ohci_at91_port_suspend(ohci_at91->sfr_regmap, 0); + ohci_at91_port_suspend(ohci_at91, 0); if (ohci_at91->wakeup) disable_irq_wake(hcd->irq); diff --git a/drivers/usb/host/ohci-nxp.c b/drivers/usb/host/ohci-nxp.c index 85878e8ad331..106a6bcefb08 100644 --- a/drivers/usb/host/ohci-nxp.c +++ b/drivers/usb/host/ohci-nxp.c @@ -164,6 +164,7 @@ static int ohci_hcd_nxp_probe(struct platform_device *pdev) } isp1301_i2c_client = isp1301_get_client(isp1301_node); + of_node_put(isp1301_node); if (!isp1301_i2c_client) return -EPROBE_DEFER; diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index 47dfbfe9e519..0adae6265127 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -28,6 +28,7 @@ #include <linux/usb/ohci_pdriver.h> #include <linux/usb.h> #include <linux/usb/hcd.h> +#include <linux/usb/of.h> #include "ohci.h" @@ -210,6 +211,8 @@ static int ohci_platform_probe(struct platform_device *dev) hcd->rsrc_start = res_mem->start; hcd->rsrc_len = resource_size(res_mem); + hcd->tpl_support = of_usb_host_tpl_support(dev->dev.of_node); + err = usb_add_hcd(hcd, irq, IRQF_SHARED); if (err) goto err_power; diff --git a/drivers/usb/host/ohci-ppc-of.c b/drivers/usb/host/ohci-ppc-of.c index 1960b8dfdba5..591f675cc930 100644 --- a/drivers/usb/host/ohci-ppc-of.c +++ b/drivers/usb/host/ohci-ppc-of.c @@ -166,6 +166,7 @@ static int ohci_hcd_ppc_of_probe(struct platform_device *op) release_mem_region(res.start, 0x4); } else pr_debug("%s: cannot get ehci offset from fdt\n", __FILE__); + of_node_put(np); } irq_dispose_mapping(irq); diff --git a/drivers/usb/host/ohci-sm501.c b/drivers/usb/host/ohci-sm501.c index b91d50da6127..f5de586454e3 100644 --- a/drivers/usb/host/ohci-sm501.c +++ b/drivers/usb/host/ohci-sm501.c @@ -153,7 +153,7 @@ static int ohci_hcd_sm501_drv_probe(struct platform_device *pdev) * fine. This is however not always the case - buffers may be allocated * using kmalloc() - so the usb core needs to be told that it must copy * data into our local memory if the buffers happen to be placed in - * regular memory. A non-null hcd->localmem_pool initialized by the + * regular memory. A non-null hcd->localmem_pool initialized by * the call to usb_hcd_setup_local_mem() below does just that. */ diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c index b1045f534a4b..01705e559c42 100644 --- a/drivers/usb/host/xhci-mtk.c +++ b/drivers/usb/host/xhci-mtk.c @@ -18,6 +18,7 @@ #include <linux/pm_wakeirq.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> +#include <linux/reset.h> #include "xhci.h" #include "xhci-mtk.h" @@ -550,6 +551,12 @@ static int xhci_mtk_probe(struct platform_device *pdev) if (ret) goto disable_ldos; + ret = device_reset_optional(dev); + if (ret) { + dev_err_probe(dev, ret, "failed to reset controller\n"); + goto disable_clk; + } + hcd = usb_create_hcd(driver, dev, dev_name(dev)); if (!hcd) { ret = -ENOMEM; diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 996958a6565c..bdb776553826 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -1010,15 +1010,15 @@ static int tegra_xusb_powerdomain_init(struct device *dev, int err; tegra->genpd_dev_host = dev_pm_domain_attach_by_name(dev, "xusb_host"); - if (IS_ERR(tegra->genpd_dev_host)) { - err = PTR_ERR(tegra->genpd_dev_host); + if (IS_ERR_OR_NULL(tegra->genpd_dev_host)) { + err = PTR_ERR(tegra->genpd_dev_host) ? : -ENODATA; dev_err(dev, "failed to get host pm-domain: %d\n", err); return err; } tegra->genpd_dev_ss = dev_pm_domain_attach_by_name(dev, "xusb_ss"); - if (IS_ERR(tegra->genpd_dev_ss)) { - err = PTR_ERR(tegra->genpd_dev_ss); + if (IS_ERR_OR_NULL(tegra->genpd_dev_ss)) { + err = PTR_ERR(tegra->genpd_dev_ss) ? : -ENODATA; dev_err(dev, "failed to get superspeed pm-domain: %d\n", err); return err; } diff --git a/drivers/usb/mtu3/mtu3.h b/drivers/usb/mtu3/mtu3.h index 022bbdc54e68..8408e1b1a24a 100644 --- a/drivers/usb/mtu3/mtu3.h +++ b/drivers/usb/mtu3/mtu3.h @@ -357,6 +357,7 @@ struct mtu3 { unsigned delayed_status:1; unsigned gen2cp:1; unsigned connected:1; + unsigned async_callbacks:1; u8 address; u8 test_mode_nr; diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c index c4a2c37abf62..3c6a670efafa 100644 --- a/drivers/usb/mtu3/mtu3_core.c +++ b/drivers/usb/mtu3/mtu3_core.c @@ -965,7 +965,8 @@ int ssusb_gadget_init(struct ssusb_mtk *ssusb) goto dma_mask_err; } - ret = devm_request_irq(dev, mtu->irq, mtu3_irq, 0, dev_name(dev), mtu); + ret = devm_request_threaded_irq(dev, mtu->irq, NULL, mtu3_irq, + IRQF_ONESHOT, dev_name(dev), mtu); if (ret) { dev_err(dev, "request irq %d failed!\n", mtu->irq); goto irq_err; diff --git a/drivers/usb/mtu3/mtu3_gadget.c b/drivers/usb/mtu3/mtu3_gadget.c index 9977600616d7..30999b4debb8 100644 --- a/drivers/usb/mtu3/mtu3_gadget.c +++ b/drivers/usb/mtu3/mtu3_gadget.c @@ -592,6 +592,18 @@ mtu3_gadget_set_speed(struct usb_gadget *g, enum usb_device_speed speed) spin_unlock_irqrestore(&mtu->lock, flags); } +static void mtu3_gadget_async_callbacks(struct usb_gadget *g, bool enable) +{ + struct mtu3 *mtu = gadget_to_mtu3(g); + unsigned long flags; + + dev_dbg(mtu->dev, "%s %s\n", __func__, enable ? "en" : "dis"); + + spin_lock_irqsave(&mtu->lock, flags); + mtu->async_callbacks = enable; + spin_unlock_irqrestore(&mtu->lock, flags); +} + static const struct usb_gadget_ops mtu3_gadget_ops = { .get_frame = mtu3_gadget_get_frame, .wakeup = mtu3_gadget_wakeup, @@ -600,6 +612,7 @@ static const struct usb_gadget_ops mtu3_gadget_ops = { .udc_start = mtu3_gadget_start, .udc_stop = mtu3_gadget_stop, .udc_set_speed = mtu3_gadget_set_speed, + .udc_async_callbacks = mtu3_gadget_async_callbacks, }; static void mtu3_state_reset(struct mtu3 *mtu) @@ -680,6 +693,7 @@ int mtu3_gadget_setup(struct mtu3 *mtu) mtu->g.speed = USB_SPEED_UNKNOWN; mtu->g.sg_supported = 0; mtu->g.name = MTU3_DRIVER_NAME; + mtu->g.irq = mtu->irq; mtu->is_active = 0; mtu->delayed_status = false; @@ -696,7 +710,7 @@ void mtu3_gadget_cleanup(struct mtu3 *mtu) void mtu3_gadget_resume(struct mtu3 *mtu) { dev_dbg(mtu->dev, "gadget RESUME\n"); - if (mtu->gadget_driver && mtu->gadget_driver->resume) { + if (mtu->async_callbacks && mtu->gadget_driver && mtu->gadget_driver->resume) { spin_unlock(&mtu->lock); mtu->gadget_driver->resume(&mtu->g); spin_lock(&mtu->lock); @@ -707,7 +721,7 @@ void mtu3_gadget_resume(struct mtu3 *mtu) void mtu3_gadget_suspend(struct mtu3 *mtu) { dev_dbg(mtu->dev, "gadget SUSPEND\n"); - if (mtu->gadget_driver && mtu->gadget_driver->suspend) { + if (mtu->async_callbacks && mtu->gadget_driver && mtu->gadget_driver->suspend) { spin_unlock(&mtu->lock); mtu->gadget_driver->suspend(&mtu->g); spin_lock(&mtu->lock); @@ -718,7 +732,7 @@ void mtu3_gadget_suspend(struct mtu3 *mtu) void mtu3_gadget_disconnect(struct mtu3 *mtu) { dev_dbg(mtu->dev, "gadget DISCONNECT\n"); - if (mtu->gadget_driver && mtu->gadget_driver->disconnect) { + if (mtu->async_callbacks && mtu->gadget_driver && mtu->gadget_driver->disconnect) { spin_unlock(&mtu->lock); mtu->gadget_driver->disconnect(&mtu->g); spin_lock(&mtu->lock); diff --git a/drivers/usb/mtu3/mtu3_gadget_ep0.c b/drivers/usb/mtu3/mtu3_gadget_ep0.c index 0ca47212f1ec..7e7013508af6 100644 --- a/drivers/usb/mtu3/mtu3_gadget_ep0.c +++ b/drivers/usb/mtu3/mtu3_gadget_ep0.c @@ -66,7 +66,7 @@ __acquires(mtu->lock) { int ret; - if (!mtu->gadget_driver) + if (!mtu->gadget_driver || !mtu->async_callbacks) return -EOPNOTSUPP; spin_unlock(&mtu->lock); diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index 4309ed939178..d14494b30064 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -13,6 +13,7 @@ #include <linux/of_irq.h> #include <linux/platform_device.h> #include <linux/pm_wakeirq.h> +#include <linux/reset.h> #include "mtu3.h" #include "mtu3_dr.h" @@ -345,6 +346,12 @@ static int mtu3_probe(struct platform_device *pdev) dev_info(dev, "wakeup irq %d\n", ssusb->wakeup_irq); } + ret = device_reset_optional(dev); + if (ret) { + dev_err_probe(dev, ret, "failed to reset controller\n"); + goto comm_exit; + } + ssusb_ip_sw_reset(ssusb); if (IS_ENABLED(CONFIG_USB_MTU3_HOST)) diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index 4d61df6a9b5c..f906dfd360d3 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -123,6 +123,17 @@ config USB_MUSB_MEDIATEK select GENERIC_PHY select USB_ROLE_SWITCH +config USB_MUSB_POLARFIRE_SOC + tristate "Microchip PolarFire SoC platforms" + depends on SOC_MICROCHIP_POLARFIRE || COMPILE_TEST + depends on NOP_USB_XCEIV + select USB_MUSB_DUAL_ROLE + help + Say Y here to enable support for USB on Microchip's PolarFire SoC. + + This support is also available as a module. If so, the module + will be called mpfs. + comment "MUSB DMA mode" config MUSB_PIO_ONLY @@ -146,7 +157,7 @@ config USB_UX500_DMA config USB_INVENTRA_DMA bool 'Inventra' - depends on USB_MUSB_OMAP2PLUS || USB_MUSB_MEDIATEK || USB_MUSB_JZ4740 + depends on USB_MUSB_OMAP2PLUS || USB_MUSB_MEDIATEK || USB_MUSB_JZ4740 || USB_MUSB_POLARFIRE_SOC help Enable DMA transfers using Mentor's engine. diff --git a/drivers/usb/musb/Makefile b/drivers/usb/musb/Makefile index 932247360a9f..51dd54a8de49 100644 --- a/drivers/usb/musb/Makefile +++ b/drivers/usb/musb/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_USB_MUSB_UX500) += ux500.o obj-$(CONFIG_USB_MUSB_JZ4740) += jz4740.o obj-$(CONFIG_USB_MUSB_SUNXI) += sunxi.o obj-$(CONFIG_USB_MUSB_MEDIATEK) += mediatek.o +obj-$(CONFIG_USB_MUSB_POLARFIRE_SOC) += mpfs.o # the kconfig must guarantee that only one of the # possible I/O schemes will be enabled at a time ... diff --git a/drivers/usb/musb/mpfs.c b/drivers/usb/musb/mpfs.c new file mode 100644 index 000000000000..99666ef8af06 --- /dev/null +++ b/drivers/usb/musb/mpfs.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PolarFire SoC (MPFS) MUSB Glue Layer + * + * Copyright (c) 2020-2022 Microchip Corporation. All rights reserved. + * Based on {omap2430,tusb6010,ux500}.c + * + */ + +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/usb/usb_phy_generic.h> +#include "musb_core.h" +#include "musb_dma.h" + +#define MPFS_MUSB_MAX_EP_NUM 8 +#define MPFS_MUSB_RAM_BITS 12 + +struct mpfs_glue { + struct device *dev; + struct platform_device *musb; + struct platform_device *phy; + struct clk *clk; +}; + +static struct musb_fifo_cfg mpfs_musb_mode_cfg[] = { + { .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 3, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 3, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 4, .style = FIFO_TX, .maxpacket = 1024, }, + { .hw_ep_num = 4, .style = FIFO_RX, .maxpacket = 4096, }, +}; + +static const struct musb_hdrc_config mpfs_musb_hdrc_config = { + .fifo_cfg = mpfs_musb_mode_cfg, + .fifo_cfg_size = ARRAY_SIZE(mpfs_musb_mode_cfg), + .multipoint = true, + .dyn_fifo = true, + .num_eps = MPFS_MUSB_MAX_EP_NUM, + .ram_bits = MPFS_MUSB_RAM_BITS, +}; + +static irqreturn_t mpfs_musb_interrupt(int irq, void *__hci) +{ + unsigned long flags; + irqreturn_t ret = IRQ_NONE; + struct musb *musb = __hci; + + spin_lock_irqsave(&musb->lock, flags); + + musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB); + musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX); + musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX); + + if (musb->int_usb || musb->int_tx || musb->int_rx) { + musb_writeb(musb->mregs, MUSB_INTRUSB, musb->int_usb); + musb_writew(musb->mregs, MUSB_INTRTX, musb->int_tx); + musb_writew(musb->mregs, MUSB_INTRRX, musb->int_rx); + ret = musb_interrupt(musb); + } + + spin_unlock_irqrestore(&musb->lock, flags); + + return ret; +} + +static void mpfs_musb_set_vbus(struct musb *musb, int is_on) +{ + u8 devctl; + + /* + * HDRC controls CPEN, but beware current surges during device + * connect. They can trigger transient overcurrent conditions + * that must be ignored. + */ + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + if (is_on) { + musb->is_active = 1; + musb->xceiv->otg->default_a = 1; + musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE; + devctl |= MUSB_DEVCTL_SESSION; + MUSB_HST_MODE(musb); + } else { + musb->is_active = 0; + + /* + * NOTE: skipping A_WAIT_VFALL -> A_IDLE and + * jumping right to B_IDLE... + */ + musb->xceiv->otg->default_a = 0; + musb->xceiv->otg->state = OTG_STATE_B_IDLE; + devctl &= ~MUSB_DEVCTL_SESSION; + + MUSB_DEV_MODE(musb); + } + + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + dev_dbg(musb->controller, "VBUS %s, devctl %02x\n", + usb_otg_state_string(musb->xceiv->otg->state), + musb_readb(musb->mregs, MUSB_DEVCTL)); +} + +static int mpfs_musb_init(struct musb *musb) +{ + struct device *dev = musb->controller; + + musb->xceiv = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + if (IS_ERR(musb->xceiv)) { + dev_err(dev, "HS UDC: no transceiver configured\n"); + return PTR_ERR(musb->xceiv); + } + + musb->dyn_fifo = true; + musb->isr = mpfs_musb_interrupt; + + musb_platform_set_vbus(musb, 1); + + return 0; +} + +static const struct musb_platform_ops mpfs_ops = { + .quirks = MUSB_DMA_INVENTRA, + .init = mpfs_musb_init, + .fifo_mode = 2, +#ifdef CONFIG_USB_INVENTRA_DMA + .dma_init = musbhs_dma_controller_create, + .dma_exit = musbhs_dma_controller_destroy, +#endif + .set_vbus = mpfs_musb_set_vbus +}; + +static int mpfs_probe(struct platform_device *pdev) +{ + struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct mpfs_glue *glue; + struct platform_device *musb_pdev; + struct device *dev = &pdev->dev; + struct clk *clk; + int ret; + + glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL); + if (!glue) + return -ENOMEM; + + musb_pdev = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); + if (!musb_pdev) { + dev_err(dev, "failed to allocate musb device\n"); + return -ENOMEM; + } + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + ret = PTR_ERR(clk); + goto err_phy_release; + } + + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable clock\n"); + goto err_phy_release; + } + + musb_pdev->dev.parent = dev; + musb_pdev->dev.coherent_dma_mask = DMA_BIT_MASK(39); + musb_pdev->dev.dma_mask = &musb_pdev->dev.coherent_dma_mask; + device_set_of_node_from_dev(&musb_pdev->dev, dev); + + glue->dev = dev; + glue->musb = musb_pdev; + glue->clk = clk; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + goto err_clk_disable; + + pdata->config = &mpfs_musb_hdrc_config; + pdata->platform_ops = &mpfs_ops; + + pdata->mode = usb_get_dr_mode(dev); + if (pdata->mode == USB_DR_MODE_UNKNOWN) { + dev_info(dev, "No dr_mode property found, defaulting to otg\n"); + pdata->mode = USB_DR_MODE_OTG; + } + + glue->phy = usb_phy_generic_register(); + if (IS_ERR(glue->phy)) { + dev_err(dev, "failed to register usb-phy %ld\n", + PTR_ERR(glue->phy)); + goto err_clk_disable; + } + + platform_set_drvdata(pdev, glue); + + ret = platform_device_add_resources(musb_pdev, pdev->resource, pdev->num_resources); + if (ret) { + dev_err(dev, "failed to add resources\n"); + goto err_clk_disable; + } + + ret = platform_device_add_data(musb_pdev, pdata, sizeof(*pdata)); + if (ret) { + dev_err(dev, "failed to add platform_data\n"); + goto err_clk_disable; + } + + ret = platform_device_add(musb_pdev); + if (ret) { + dev_err(dev, "failed to register musb device\n"); + goto err_clk_disable; + } + + dev_info(&pdev->dev, "Registered MPFS MUSB driver\n"); + return 0; + +err_clk_disable: + clk_disable_unprepare(clk); + +err_phy_release: + usb_phy_generic_unregister(glue->phy); + platform_device_put(musb_pdev); + return ret; +} + +static int mpfs_remove(struct platform_device *pdev) +{ + struct mpfs_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->musb); + usb_phy_generic_unregister(pdev); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id mpfs_id_table[] = { + { .compatible = "microchip,mpfs-musb" }, + { } +}; +MODULE_DEVICE_TABLE(of, mpfs_id_table); +#endif + +static struct platform_driver mpfs_musb_driver = { + .probe = mpfs_probe, + .remove = mpfs_remove, + .driver = { + .name = "mpfs-musb", + .of_match_table = of_match_ptr(mpfs_id_table) + }, +}; + +module_platform_driver(mpfs_musb_driver); + +MODULE_DESCRIPTION("PolarFire SoC MUSB Glue Layer"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index f7b1d5993f8c..bbbcfd49fb35 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -2684,13 +2684,7 @@ static void musb_save_context(struct musb *musb) musb->context.devctl = musb_readb(musb_base, MUSB_DEVCTL); for (i = 0; i < musb->config->num_eps; ++i) { - struct musb_hw_ep *hw_ep; - - hw_ep = &musb->endpoints[i]; - if (!hw_ep) - continue; - - epio = hw_ep->regs; + epio = musb->endpoints[i].regs; if (!epio) continue; @@ -2765,13 +2759,7 @@ static void musb_restore_context(struct musb *musb) musb_writeb(musb_base, MUSB_DEVCTL, musb->context.devctl); for (i = 0; i < musb->config->num_eps; ++i) { - struct musb_hw_ep *hw_ep; - - hw_ep = &musb->endpoints[i]; - if (!hw_ep) - continue; - - epio = hw_ep->regs; + epio = musb->endpoints[i].regs; if (!epio) continue; diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index 51274b87f46c..daada4b66a92 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -1910,8 +1910,6 @@ static int musb_gadget_stop(struct usb_gadget *g) */ /* Force check of devctl register for PM runtime */ - schedule_delayed_work(&musb->irq_work, 0); - pm_runtime_mark_last_busy(musb->controller); pm_runtime_put_autosuspend(musb->controller); diff --git a/drivers/usb/renesas_usbhs/rza.c b/drivers/usb/renesas_usbhs/rza.c index 24de64edb674..2d77edefb4b3 100644 --- a/drivers/usb/renesas_usbhs/rza.c +++ b/drivers/usb/renesas_usbhs/rza.c @@ -23,6 +23,10 @@ static int usbhs_rza1_hardware_init(struct platform_device *pdev) extal_clk = of_find_node_by_name(NULL, "extal"); of_property_read_u32(usb_x1_clk, "clock-frequency", &freq_usb); of_property_read_u32(extal_clk, "clock-frequency", &freq_extal); + + of_node_put(usb_x1_clk); + of_node_put(extal_clk); + if (freq_usb == 0) { if (freq_extal == 12000000) { /* Select 12MHz XTAL */ diff --git a/drivers/usb/storage/transport.c b/drivers/usb/storage/transport.c index 64d96d210e02..7449e379077a 100644 --- a/drivers/usb/storage/transport.c +++ b/drivers/usb/storage/transport.c @@ -1178,7 +1178,7 @@ int usb_stor_Bulk_transport(struct scsi_cmnd *srb, struct us_data *us) /* * If the device tried to send back more data than the * amount requested, the spec requires us to transfer - * the CSW anyway. Since there's no point retrying the + * the CSW anyway. Since there's no point retrying * the command, we'll return fake sense data indicating * Illegal Request, Invalid Field in CDB. */ diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index 43626acc0aaf..2f174cd3e5df 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_TYPEC) += typec.o -typec-y := class.o mux.o bus.o +typec-y := class.o mux.o bus.o pd.o typec-$(CONFIG_ACPI) += port-mapper.o obj-$(CONFIG_TYPEC) += altmodes/ obj-$(CONFIG_TYPEC_TCPM) += tcpm/ diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index ee0e520707dd..bbc46b14f99a 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -15,6 +15,7 @@ #include "bus.h" #include "class.h" +#include "pd.h" static DEFINE_IDA(typec_index_ida); @@ -721,6 +722,39 @@ void typec_partner_set_pd_revision(struct typec_partner *partner, u16 pd_revisio EXPORT_SYMBOL_GPL(typec_partner_set_pd_revision); /** + * typec_partner_set_usb_power_delivery - Declare USB Power Delivery Contract. + * @partner: The partner device. + * @pd: The USB PD instance. + * + * This routine can be used to declare USB Power Delivery Contract with @partner + * by linking @partner to @pd which contains the objects that were used during the + * negotiation of the contract. + * + * If @pd is NULL, the link is removed and the contract with @partner has ended. + */ +int typec_partner_set_usb_power_delivery(struct typec_partner *partner, + struct usb_power_delivery *pd) +{ + int ret; + + if (IS_ERR_OR_NULL(partner) || partner->pd == pd) + return 0; + + if (pd) { + ret = usb_power_delivery_link_device(pd, &partner->dev); + if (ret) + return ret; + } else { + usb_power_delivery_unlink_device(partner->pd, &partner->dev); + } + + partner->pd = pd; + + return 0; +} +EXPORT_SYMBOL_GPL(typec_partner_set_usb_power_delivery); + +/** * typec_partner_set_num_altmodes - Set the number of available partner altmodes * @partner: The partner to be updated. * @num_altmodes: The number of altmodes we want to specify as available. @@ -1170,6 +1204,104 @@ EXPORT_SYMBOL_GPL(typec_unregister_cable); /* ------------------------------------------------------------------------- */ /* USB Type-C ports */ +/** + * typec_port_set_usb_power_delivery - Assign USB PD for port. + * @port: USB Type-C port. + * @pd: USB PD instance. + * + * This routine can be used to set the USB Power Delivery Capabilities for @port + * that it will advertise to the partner. + * + * If @pd is NULL, the assignment is removed. + */ +int typec_port_set_usb_power_delivery(struct typec_port *port, struct usb_power_delivery *pd) +{ + int ret; + + if (IS_ERR_OR_NULL(port) || port->pd == pd) + return 0; + + if (pd) { + ret = usb_power_delivery_link_device(pd, &port->dev); + if (ret) + return ret; + } else { + usb_power_delivery_unlink_device(port->pd, &port->dev); + } + + port->pd = pd; + + return 0; +} +EXPORT_SYMBOL_GPL(typec_port_set_usb_power_delivery); + +static ssize_t select_usb_power_delivery_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + struct usb_power_delivery *pd; + + if (!port->ops || !port->ops->pd_set) + return -EOPNOTSUPP; + + pd = usb_power_delivery_find(buf); + if (!pd) + return -EINVAL; + + return port->ops->pd_set(port, pd); +} + +static ssize_t select_usb_power_delivery_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_port *port = to_typec_port(dev); + struct usb_power_delivery **pds; + struct usb_power_delivery *pd; + int ret = 0; + + if (!port->ops || !port->ops->pd_get) + return -EOPNOTSUPP; + + pds = port->ops->pd_get(port); + if (!pds) + return 0; + + for (pd = pds[0]; pd; pd++) { + if (pd == port->pd) + ret += sysfs_emit(buf + ret, "[%s] ", dev_name(&pd->dev)); + else + ret += sysfs_emit(buf + ret, "%s ", dev_name(&pd->dev)); + } + + buf[ret - 1] = '\n'; + + return ret; +} +static DEVICE_ATTR_RW(select_usb_power_delivery); + +static struct attribute *port_attrs[] = { + &dev_attr_select_usb_power_delivery.attr, + NULL +}; + +static umode_t port_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct typec_port *port = to_typec_port(kobj_to_dev(kobj)); + + if (!port->pd || !port->ops || !port->ops->pd_get) + return 0; + if (!port->ops->pd_set) + return 0444; + + return attr->mode; +} + +static const struct attribute_group pd_group = { + .is_visible = port_attr_is_visible, + .attrs = port_attrs, +}; + static const char * const typec_orientations[] = { [TYPEC_ORIENTATION_NONE] = "unknown", [TYPEC_ORIENTATION_NORMAL] = "normal", @@ -1581,6 +1713,7 @@ static const struct attribute_group typec_group = { static const struct attribute_group *typec_groups[] = { &typec_group, + &pd_group, NULL }; @@ -2123,6 +2256,13 @@ struct typec_port *typec_register_port(struct device *parent, return ERR_PTR(ret); } + ret = typec_port_set_usb_power_delivery(port, cap->pd); + if (ret) { + dev_err(&port->dev, "failed to link pd\n"); + device_unregister(&port->dev); + return ERR_PTR(ret); + } + ret = typec_link_ports(port); if (ret) dev_warn(&port->dev, "failed to create symlinks (%d)\n", ret); @@ -2141,6 +2281,7 @@ void typec_unregister_port(struct typec_port *port) { if (!IS_ERR_OR_NULL(port)) { typec_unlink_ports(port); + typec_port_set_usb_power_delivery(port, NULL); device_unregister(&port->dev); } } @@ -2162,8 +2303,15 @@ static int __init typec_init(void) if (ret) goto err_unregister_mux_class; + ret = usb_power_delivery_init(); + if (ret) + goto err_unregister_class; + return 0; +err_unregister_class: + class_unregister(&typec_class); + err_unregister_mux_class: class_unregister(&typec_mux_class); @@ -2176,6 +2324,7 @@ subsys_initcall(typec_init); static void __exit typec_exit(void) { + usb_power_delivery_exit(); class_unregister(&typec_class); ida_destroy(&typec_index_ida); bus_unregister(&typec_bus); diff --git a/drivers/usb/typec/class.h b/drivers/usb/typec/class.h index 0f1bd6d19d67..b531f9853bc0 100644 --- a/drivers/usb/typec/class.h +++ b/drivers/usb/typec/class.h @@ -33,6 +33,8 @@ struct typec_partner { int num_altmodes; u16 pd_revision; /* 0300H = "3.0" */ enum usb_pd_svdm_ver svdm_version; + + struct usb_power_delivery *pd; }; struct typec_port { @@ -40,6 +42,8 @@ struct typec_port { struct device dev; struct ida mode_ids; + struct usb_power_delivery *pd; + int prefer_role; enum typec_data_role data_role; enum typec_role pwr_role; diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index fd55c2c516a5..464330776cd6 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -281,9 +281,13 @@ static void *typec_mux_match(struct fwnode_handle *fwnode, const char *id, if (match) goto find_mux; - /* Accessory Mode muxes */ if (!desc) { - match = fwnode_property_present(fwnode, "accessory"); + /* + * Accessory Mode muxes & muxes which explicitly specify + * the required identifier can avoid SVID matching. + */ + match = fwnode_property_present(fwnode, "accessory") || + fwnode_property_present(fwnode, id); if (match) goto find_mux; return NULL; diff --git a/drivers/usb/typec/pd.c b/drivers/usb/typec/pd.c new file mode 100644 index 000000000000..dc72005d68db --- /dev/null +++ b/drivers/usb/typec/pd.c @@ -0,0 +1,708 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Power Delivery sysfs entries + * + * Copyright (C) 2022, Intel Corporation + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + */ + +#include <linux/slab.h> +#include <linux/usb/pd.h> + +#include "pd.h" + +static DEFINE_IDA(pd_ida); + +static struct class pd_class = { + .name = "usb_power_delivery", + .owner = THIS_MODULE, +}; + +#define to_pdo(o) container_of(o, struct pdo, dev) + +struct pdo { + struct device dev; + int object_position; + u32 pdo; +}; + +static void pdo_release(struct device *dev) +{ + kfree(to_pdo(dev)); +} + +/* -------------------------------------------------------------------------- */ +/* Fixed Supply */ + +static ssize_t +dual_role_power_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_DUAL_ROLE)); +} +static DEVICE_ATTR_RO(dual_role_power); + +static ssize_t +usb_suspend_supported_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_SUSPEND)); +} +static DEVICE_ATTR_RO(usb_suspend_supported); + +static ssize_t +unconstrained_power_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_EXTPOWER)); +} +static DEVICE_ATTR_RO(unconstrained_power); + +static ssize_t +usb_communication_capable_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_USB_COMM)); +} +static DEVICE_ATTR_RO(usb_communication_capable); + +static ssize_t +dual_role_data_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_DATA_SWAP)); +} +static DEVICE_ATTR_RO(dual_role_data); + +static ssize_t +unchunked_extended_messages_supported_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_UNCHUNK_EXT)); +} +static DEVICE_ATTR_RO(unchunked_extended_messages_supported); + +/* + * REVISIT: Peak Current requires access also to the RDO. +static ssize_t +peak_current_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + ... +} +*/ + +static ssize_t +fast_role_swap_current_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", to_pdo(dev)->pdo >> PDO_FIXED_FRS_CURR_SHIFT) & 3; +} +static DEVICE_ATTR_RO(fast_role_swap_current); + +static ssize_t voltage_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umV\n", pdo_fixed_voltage(to_pdo(dev)->pdo)); +} +static DEVICE_ATTR_RO(voltage); + +/* Shared with Variable supplies, both source and sink */ +static ssize_t current_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umA\n", pdo_max_current(to_pdo(dev)->pdo)); +} + +/* Shared with Variable type supplies */ +static struct device_attribute maximum_current_attr = { + .attr = { + .name = "maximum_current", + .mode = 0444, + }, + .show = current_show, +}; + +static struct device_attribute operational_current_attr = { + .attr = { + .name = "operational_current", + .mode = 0444, + }, + .show = current_show, +}; + +static struct attribute *source_fixed_supply_attrs[] = { + &dev_attr_dual_role_power.attr, + &dev_attr_usb_suspend_supported.attr, + &dev_attr_unconstrained_power.attr, + &dev_attr_usb_communication_capable.attr, + &dev_attr_dual_role_data.attr, + &dev_attr_unchunked_extended_messages_supported.attr, + /*&dev_attr_peak_current.attr,*/ + &dev_attr_voltage.attr, + &maximum_current_attr.attr, + NULL +}; + +static umode_t fixed_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + if (to_pdo(kobj_to_dev(kobj))->object_position && + /*attr != &dev_attr_peak_current.attr &&*/ + attr != &dev_attr_voltage.attr && + attr != &maximum_current_attr.attr && + attr != &operational_current_attr.attr) + return 0; + + return attr->mode; +} + +static const struct attribute_group source_fixed_supply_group = { + .is_visible = fixed_attr_is_visible, + .attrs = source_fixed_supply_attrs, +}; +__ATTRIBUTE_GROUPS(source_fixed_supply); + +static struct device_type source_fixed_supply_type = { + .name = "pdo", + .release = pdo_release, + .groups = source_fixed_supply_groups, +}; + +static struct attribute *sink_fixed_supply_attrs[] = { + &dev_attr_dual_role_power.attr, + &dev_attr_usb_suspend_supported.attr, + &dev_attr_unconstrained_power.attr, + &dev_attr_usb_communication_capable.attr, + &dev_attr_dual_role_data.attr, + &dev_attr_unchunked_extended_messages_supported.attr, + &dev_attr_fast_role_swap_current.attr, + &dev_attr_voltage.attr, + &operational_current_attr.attr, + NULL +}; + +static const struct attribute_group sink_fixed_supply_group = { + .is_visible = fixed_attr_is_visible, + .attrs = sink_fixed_supply_attrs, +}; +__ATTRIBUTE_GROUPS(sink_fixed_supply); + +static struct device_type sink_fixed_supply_type = { + .name = "pdo", + .release = pdo_release, + .groups = sink_fixed_supply_groups, +}; + +/* -------------------------------------------------------------------------- */ +/* Variable Supply */ + +static ssize_t +maximum_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umV\n", pdo_max_voltage(to_pdo(dev)->pdo)); +} +static DEVICE_ATTR_RO(maximum_voltage); + +static ssize_t +minimum_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umV\n", pdo_min_voltage(to_pdo(dev)->pdo)); +} +static DEVICE_ATTR_RO(minimum_voltage); + +static struct attribute *source_variable_supply_attrs[] = { + &dev_attr_maximum_voltage.attr, + &dev_attr_minimum_voltage.attr, + &maximum_current_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(source_variable_supply); + +static struct device_type source_variable_supply_type = { + .name = "pdo", + .release = pdo_release, + .groups = source_variable_supply_groups, +}; + +static struct attribute *sink_variable_supply_attrs[] = { + &dev_attr_maximum_voltage.attr, + &dev_attr_minimum_voltage.attr, + &operational_current_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(sink_variable_supply); + +static struct device_type sink_variable_supply_type = { + .name = "pdo", + .release = pdo_release, + .groups = sink_variable_supply_groups, +}; + +/* -------------------------------------------------------------------------- */ +/* Battery */ + +static ssize_t +maximum_power_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umW\n", pdo_max_power(to_pdo(dev)->pdo)); +} +static DEVICE_ATTR_RO(maximum_power); + +static ssize_t +operational_power_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umW\n", pdo_max_power(to_pdo(dev)->pdo)); +} +static DEVICE_ATTR_RO(operational_power); + +static struct attribute *source_battery_attrs[] = { + &dev_attr_maximum_voltage.attr, + &dev_attr_minimum_voltage.attr, + &dev_attr_maximum_power.attr, + NULL +}; +ATTRIBUTE_GROUPS(source_battery); + +static struct device_type source_battery_type = { + .name = "pdo", + .release = pdo_release, + .groups = source_battery_groups, +}; + +static struct attribute *sink_battery_attrs[] = { + &dev_attr_maximum_voltage.attr, + &dev_attr_minimum_voltage.attr, + &dev_attr_operational_power.attr, + NULL +}; +ATTRIBUTE_GROUPS(sink_battery); + +static struct device_type sink_battery_type = { + .name = "pdo", + .release = pdo_release, + .groups = sink_battery_groups, +}; + +/* -------------------------------------------------------------------------- */ +/* Standard Power Range (SPR) Programmable Power Supply (PPS) */ + +static ssize_t +pps_power_limited_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & BIT(27))); +} +static DEVICE_ATTR_RO(pps_power_limited); + +static ssize_t +pps_max_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umV\n", pdo_pps_apdo_max_voltage(to_pdo(dev)->pdo)); +} + +static ssize_t +pps_min_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umV\n", pdo_pps_apdo_min_voltage(to_pdo(dev)->pdo)); +} + +static ssize_t +pps_max_current_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umA\n", pdo_pps_apdo_max_current(to_pdo(dev)->pdo)); +} + +static struct device_attribute pps_max_voltage_attr = { + .attr = { + .name = "maximum_voltage", + .mode = 0444, + }, + .show = pps_max_voltage_show, +}; + +static struct device_attribute pps_min_voltage_attr = { + .attr = { + .name = "minimum_voltage", + .mode = 0444, + }, + .show = pps_min_voltage_show, +}; + +static struct device_attribute pps_max_current_attr = { + .attr = { + .name = "maximum_current", + .mode = 0444, + }, + .show = pps_max_current_show, +}; + +static struct attribute *source_pps_attrs[] = { + &dev_attr_pps_power_limited.attr, + &pps_max_voltage_attr.attr, + &pps_min_voltage_attr.attr, + &pps_max_current_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(source_pps); + +static struct device_type source_pps_type = { + .name = "pdo", + .release = pdo_release, + .groups = source_pps_groups, +}; + +static struct attribute *sink_pps_attrs[] = { + &pps_max_voltage_attr.attr, + &pps_min_voltage_attr.attr, + &pps_max_current_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(sink_pps); + +static struct device_type sink_pps_type = { + .name = "pdo", + .release = pdo_release, + .groups = sink_pps_groups, +}; + +/* -------------------------------------------------------------------------- */ + +static const char * const supply_name[] = { + [PDO_TYPE_FIXED] = "fixed_supply", + [PDO_TYPE_BATT] = "battery", + [PDO_TYPE_VAR] = "variable_supply", +}; + +static const char * const apdo_supply_name[] = { + [APDO_TYPE_PPS] = "programmable_supply", +}; + +static struct device_type *source_type[] = { + [PDO_TYPE_FIXED] = &source_fixed_supply_type, + [PDO_TYPE_BATT] = &source_battery_type, + [PDO_TYPE_VAR] = &source_variable_supply_type, +}; + +static struct device_type *source_apdo_type[] = { + [APDO_TYPE_PPS] = &source_pps_type, +}; + +static struct device_type *sink_type[] = { + [PDO_TYPE_FIXED] = &sink_fixed_supply_type, + [PDO_TYPE_BATT] = &sink_battery_type, + [PDO_TYPE_VAR] = &sink_variable_supply_type, +}; + +static struct device_type *sink_apdo_type[] = { + [APDO_TYPE_PPS] = &sink_pps_type, +}; + +/* REVISIT: Export when EPR_*_Capabilities need to be supported. */ +static int add_pdo(struct usb_power_delivery_capabilities *cap, u32 pdo, int position) +{ + struct device_type *type; + const char *name; + struct pdo *p; + int ret; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + + p->pdo = pdo; + p->object_position = position; + + if (pdo_type(pdo) == PDO_TYPE_APDO) { + /* FIXME: Only PPS supported for now! Skipping others. */ + if (pdo_apdo_type(pdo) > APDO_TYPE_PPS) { + dev_warn(&cap->dev, "Unknown APDO type. PDO 0x%08x\n", pdo); + kfree(p); + return 0; + } + + if (is_source(cap->role)) + type = source_apdo_type[pdo_apdo_type(pdo)]; + else + type = sink_apdo_type[pdo_apdo_type(pdo)]; + + name = apdo_supply_name[pdo_apdo_type(pdo)]; + } else { + if (is_source(cap->role)) + type = source_type[pdo_type(pdo)]; + else + type = sink_type[pdo_type(pdo)]; + + name = supply_name[pdo_type(pdo)]; + } + + p->dev.parent = &cap->dev; + p->dev.type = type; + dev_set_name(&p->dev, "%u:%s", position + 1, name); + + ret = device_register(&p->dev); + if (ret) { + put_device(&p->dev); + return ret; + } + + return 0; +} + +static int remove_pdo(struct device *dev, void *data) +{ + device_unregister(dev); + return 0; +} + +/* -------------------------------------------------------------------------- */ + +static const char * const cap_name[] = { + [TYPEC_SINK] = "sink-capabilities", + [TYPEC_SOURCE] = "source-capabilities", +}; + +static void pd_capabilities_release(struct device *dev) +{ + kfree(to_usb_power_delivery_capabilities(dev)); +} + +static struct device_type pd_capabilities_type = { + .name = "capabilities", + .release = pd_capabilities_release, +}; + +/** + * usb_power_delivery_register_capabilities - Register a set of capabilities. + * @pd: The USB PD instance that the capabilities belong to. + * @desc: Description of the Capablities Message. + * + * This function registers a Capabilities Message described in @desc. The + * capabilities will have their own sub-directory under @pd in sysfs. + * + * The function returns pointer to struct usb_power_delivery_capabilities, or + * ERR_PRT(errno). + */ +struct usb_power_delivery_capabilities * +usb_power_delivery_register_capabilities(struct usb_power_delivery *pd, + struct usb_power_delivery_capabilities_desc *desc) +{ + struct usb_power_delivery_capabilities *cap; + int ret; + int i; + + cap = kzalloc(sizeof(*cap), GFP_KERNEL); + if (!cap) + return ERR_PTR(-ENOMEM); + + cap->pd = pd; + cap->role = desc->role; + + cap->dev.parent = &pd->dev; + cap->dev.type = &pd_capabilities_type; + dev_set_name(&cap->dev, "%s", cap_name[cap->role]); + + ret = device_register(&cap->dev); + if (ret) { + put_device(&cap->dev); + return ERR_PTR(ret); + } + + for (i = 0; i < PDO_MAX_OBJECTS && desc->pdo[i]; i++) { + ret = add_pdo(cap, desc->pdo[i], i); + if (ret) { + usb_power_delivery_unregister_capabilities(cap); + return ERR_PTR(ret); + } + } + + return cap; +} +EXPORT_SYMBOL_GPL(usb_power_delivery_register_capabilities); + +/** + * usb_power_delivery_unregister_capabilities - Unregister a set of capabilities + * @cap: The capabilities + */ +void usb_power_delivery_unregister_capabilities(struct usb_power_delivery_capabilities *cap) +{ + if (!cap) + return; + + device_for_each_child(&cap->dev, NULL, remove_pdo); + device_unregister(&cap->dev); +} +EXPORT_SYMBOL_GPL(usb_power_delivery_unregister_capabilities); + +/* -------------------------------------------------------------------------- */ + +static ssize_t revision_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_power_delivery *pd = to_usb_power_delivery(dev); + + return sysfs_emit(buf, "%u.%u\n", (pd->revision >> 8) & 0xff, (pd->revision >> 4) & 0xf); +} +static DEVICE_ATTR_RO(revision); + +static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_power_delivery *pd = to_usb_power_delivery(dev); + + return sysfs_emit(buf, "%u.%u\n", (pd->version >> 8) & 0xff, (pd->version >> 4) & 0xf); +} +static DEVICE_ATTR_RO(version); + +static struct attribute *pd_attrs[] = { + &dev_attr_revision.attr, + &dev_attr_version.attr, + NULL +}; + +static umode_t pd_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct usb_power_delivery *pd = to_usb_power_delivery(kobj_to_dev(kobj)); + + if (attr == &dev_attr_version.attr && !pd->version) + return 0; + + return attr->mode; +} + +static const struct attribute_group pd_group = { + .is_visible = pd_attr_is_visible, + .attrs = pd_attrs, +}; +__ATTRIBUTE_GROUPS(pd); + +static void pd_release(struct device *dev) +{ + struct usb_power_delivery *pd = to_usb_power_delivery(dev); + + ida_simple_remove(&pd_ida, pd->id); + kfree(pd); +} + +static struct device_type pd_type = { + .name = "usb_power_delivery", + .release = pd_release, + .groups = pd_groups, +}; + +struct usb_power_delivery *usb_power_delivery_find(const char *name) +{ + struct device *dev; + + dev = class_find_device_by_name(&pd_class, name); + + return dev ? to_usb_power_delivery(dev) : NULL; +} + +/** + * usb_power_delivery_register - Register USB Power Delivery Support. + * @parent: Parent device. + * @desc: Description of the USB PD contract. + * + * This routine can be used to register USB Power Delivery capabilities that a + * device or devices can support. These capabilities represent all the + * capabilities that can be negotiated with a partner, so not only the Power + * Capabilities that are negotiated using the USB PD Capabilities Message. + * + * The USB Power Delivery Support object that this routine generates can be used + * as the parent object for all the actual USB Power Delivery Messages and + * objects that can be negotiated with the partner. + * + * Returns handle to struct usb_power_delivery or ERR_PTR. + */ +struct usb_power_delivery * +usb_power_delivery_register(struct device *parent, struct usb_power_delivery_desc *desc) +{ + struct usb_power_delivery *pd; + int ret; + + pd = kzalloc(sizeof(*pd), GFP_KERNEL); + if (!pd) + return ERR_PTR(-ENOMEM); + + ret = ida_simple_get(&pd_ida, 0, 0, GFP_KERNEL); + if (ret < 0) { + kfree(pd); + return ERR_PTR(ret); + } + + pd->id = ret; + pd->revision = desc->revision; + pd->version = desc->version; + + pd->dev.parent = parent; + pd->dev.type = &pd_type; + pd->dev.class = &pd_class; + dev_set_name(&pd->dev, "pd%d", pd->id); + + ret = device_register(&pd->dev); + if (ret) { + put_device(&pd->dev); + return ERR_PTR(ret); + } + + return pd; +} +EXPORT_SYMBOL_GPL(usb_power_delivery_register); + +/** + * usb_power_delivery_unregister - Unregister USB Power Delivery Support. + * @pd: The USB PD contract. + */ +void usb_power_delivery_unregister(struct usb_power_delivery *pd) +{ + if (IS_ERR_OR_NULL(pd)) + return; + + device_unregister(&pd->dev); +} +EXPORT_SYMBOL_GPL(usb_power_delivery_unregister); + +/** + * usb_power_delivery_link_device - Link device to its USB PD object. + * @pd: The USB PD instance. + * @dev: The device. + * + * This function can be used to create a symlink named "usb_power_delivery" for + * @dev that points to @pd. + */ +int usb_power_delivery_link_device(struct usb_power_delivery *pd, struct device *dev) +{ + int ret; + + if (IS_ERR_OR_NULL(pd) || !dev) + return 0; + + ret = sysfs_create_link(&dev->kobj, &pd->dev.kobj, "usb_power_delivery"); + if (ret) + return ret; + + get_device(&pd->dev); + get_device(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(usb_power_delivery_link_device); + +/** + * usb_power_delivery_unlink_device - Unlink device from its USB PD object. + * @pd: The USB PD instance. + * @dev: The device. + * + * Remove the symlink that was previously created with pd_link_device(). + */ +void usb_power_delivery_unlink_device(struct usb_power_delivery *pd, struct device *dev) +{ + if (IS_ERR_OR_NULL(pd) || !dev) + return; + + sysfs_remove_link(&dev->kobj, "usb_power_delivery"); + put_device(&pd->dev); + put_device(dev); +} +EXPORT_SYMBOL_GPL(usb_power_delivery_unlink_device); + +/* -------------------------------------------------------------------------- */ + +int __init usb_power_delivery_init(void) +{ + return class_register(&pd_class); +} + +void __exit usb_power_delivery_exit(void) +{ + ida_destroy(&pd_ida); + class_unregister(&pd_class); +} diff --git a/drivers/usb/typec/pd.h b/drivers/usb/typec/pd.h new file mode 100644 index 000000000000..049a1aad440a --- /dev/null +++ b/drivers/usb/typec/pd.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __USB_POWER_DELIVERY__ +#define __USB_POWER_DELIVERY__ + +#include <linux/device.h> +#include <linux/usb/typec.h> + +struct usb_power_delivery { + struct device dev; + int id; + u16 revision; + u16 version; +}; + +struct usb_power_delivery_capabilities { + struct device dev; + struct usb_power_delivery *pd; + enum typec_role role; +}; + +#define to_usb_power_delivery_capabilities(o) container_of(o, struct usb_power_delivery_capabilities, dev) +#define to_usb_power_delivery(o) container_of(o, struct usb_power_delivery, dev) + +struct usb_power_delivery *usb_power_delivery_find(const char *name); + +int usb_power_delivery_init(void); +void usb_power_delivery_exit(void); + +#endif /* __USB_POWER_DELIVERY__ */ diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 3bc2f4ebd1fe..e1126a6c8e46 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -394,6 +394,14 @@ struct tcpm_port { bool explicit_contract; unsigned int rx_msgid; + /* USB PD objects */ + struct usb_power_delivery *pd; + struct usb_power_delivery_capabilities *port_source_caps; + struct usb_power_delivery_capabilities *port_sink_caps; + struct usb_power_delivery *partner_pd; + struct usb_power_delivery_capabilities *partner_source_caps; + struct usb_power_delivery_capabilities *partner_sink_caps; + /* Partner capabilities/requests */ u32 sink_request; u32 source_caps[PDO_MAX_OBJECTS]; @@ -471,7 +479,7 @@ struct tcpm_port { /* * When set, port requests PD_P_SNK_STDBY_MW upon entering SNK_DISCOVERY and - * the actual currrent limit after RX of PD_CTRL_PSRDY for PD link, + * the actual current limit after RX of PD_CTRL_PSRDY for PD link, * SNK_READY for non-pd link. */ bool slow_charger_loop; @@ -2352,6 +2360,52 @@ static void tcpm_pd_handle_msg(struct tcpm_port *port, } } +static int tcpm_register_source_caps(struct tcpm_port *port) +{ + struct usb_power_delivery_desc desc = { port->negotiated_rev }; + struct usb_power_delivery_capabilities_desc caps = { }; + struct usb_power_delivery_capabilities *cap; + + if (!port->partner_pd) + port->partner_pd = usb_power_delivery_register(NULL, &desc); + if (IS_ERR(port->partner_pd)) + return PTR_ERR(port->partner_pd); + + memcpy(caps.pdo, port->source_caps, sizeof(u32) * port->nr_source_caps); + caps.role = TYPEC_SOURCE; + + cap = usb_power_delivery_register_capabilities(port->partner_pd, &caps); + if (IS_ERR(cap)) + return PTR_ERR(cap); + + port->partner_source_caps = cap; + + return 0; +} + +static int tcpm_register_sink_caps(struct tcpm_port *port) +{ + struct usb_power_delivery_desc desc = { port->negotiated_rev }; + struct usb_power_delivery_capabilities_desc caps = { }; + struct usb_power_delivery_capabilities *cap; + + if (!port->partner_pd) + port->partner_pd = usb_power_delivery_register(NULL, &desc); + if (IS_ERR(port->partner_pd)) + return PTR_ERR(port->partner_pd); + + memcpy(caps.pdo, port->sink_caps, sizeof(u32) * port->nr_sink_caps); + caps.role = TYPEC_SINK; + + cap = usb_power_delivery_register_capabilities(port->partner_pd, &caps); + if (IS_ERR(cap)) + return PTR_ERR(cap); + + port->partner_sink_caps = cap; + + return 0; +} + static void tcpm_pd_data_request(struct tcpm_port *port, const struct pd_message *msg) { @@ -2381,6 +2435,8 @@ static void tcpm_pd_data_request(struct tcpm_port *port, tcpm_validate_caps(port, port->source_caps, port->nr_source_caps); + tcpm_register_source_caps(port); + /* * Adjust revision in subsequent message headers, as required, * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't @@ -2488,6 +2544,8 @@ static void tcpm_pd_data_request(struct tcpm_port *port, port->nr_sink_caps = cnt; port->sink_cap_done = true; + tcpm_register_sink_caps(port); + if (port->ams == GET_SINK_CAPABILITIES) tcpm_set_state(port, ready_state(port), 0); /* Unexpected Sink Capabilities */ @@ -3554,6 +3612,7 @@ static void tcpm_typec_connect(struct tcpm_port *port) port->partner = typec_register_partner(port->typec_port, &port->partner_desc); port->connected = true; + typec_partner_set_usb_power_delivery(port->partner, port->partner_pd); } } @@ -3622,6 +3681,7 @@ out_disable_mux: static void tcpm_typec_disconnect(struct tcpm_port *port) { if (port->connected) { + typec_partner_set_usb_power_delivery(port->partner, NULL); typec_unregister_partner(port->partner); port->partner = NULL; port->connected = false; @@ -3684,6 +3744,13 @@ static void tcpm_reset_port(struct tcpm_port *port) port->sink_cap_done = false; if (port->tcpc->enable_frs) port->tcpc->enable_frs(port->tcpc, false); + + usb_power_delivery_unregister_capabilities(port->partner_sink_caps); + port->partner_sink_caps = NULL; + usb_power_delivery_unregister_capabilities(port->partner_source_caps); + port->partner_source_caps = NULL; + usb_power_delivery_unregister(port->partner_pd); + port->partner_pd = NULL; } static void tcpm_detach(struct tcpm_port *port) @@ -5924,6 +5991,68 @@ void tcpm_tcpc_reset(struct tcpm_port *port) } EXPORT_SYMBOL_GPL(tcpm_tcpc_reset); +static void tcpm_port_unregister_pd(struct tcpm_port *port) +{ + usb_power_delivery_unregister_capabilities(port->port_sink_caps); + port->port_sink_caps = NULL; + usb_power_delivery_unregister_capabilities(port->port_source_caps); + port->port_source_caps = NULL; + usb_power_delivery_unregister(port->pd); + port->pd = NULL; +} + +static int tcpm_port_register_pd(struct tcpm_port *port) +{ + struct usb_power_delivery_desc desc = { port->typec_caps.pd_revision }; + struct usb_power_delivery_capabilities_desc caps = { }; + struct usb_power_delivery_capabilities *cap; + int ret; + + if (!port->nr_src_pdo && !port->nr_snk_pdo) + return 0; + + port->pd = usb_power_delivery_register(port->dev, &desc); + if (IS_ERR(port->pd)) { + ret = PTR_ERR(port->pd); + goto err_unregister; + } + + if (port->nr_src_pdo) { + memcpy_and_pad(caps.pdo, sizeof(caps.pdo), port->src_pdo, + port->nr_src_pdo * sizeof(u32), 0); + caps.role = TYPEC_SOURCE; + + cap = usb_power_delivery_register_capabilities(port->pd, &caps); + if (IS_ERR(cap)) { + ret = PTR_ERR(cap); + goto err_unregister; + } + + port->port_source_caps = cap; + } + + if (port->nr_snk_pdo) { + memcpy_and_pad(caps.pdo, sizeof(caps.pdo), port->snk_pdo, + port->nr_snk_pdo * sizeof(u32), 0); + caps.role = TYPEC_SINK; + + cap = usb_power_delivery_register_capabilities(port->pd, &caps); + if (IS_ERR(cap)) { + ret = PTR_ERR(cap); + goto err_unregister; + } + + port->port_sink_caps = cap; + } + + return 0; + +err_unregister: + tcpm_port_unregister_pd(port); + + return ret; +} + static int tcpm_fw_get_caps(struct tcpm_port *port, struct fwnode_handle *fwnode) { @@ -6382,10 +6511,16 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) goto out_role_sw_put; power_supply_changed(port->psy); + err = tcpm_port_register_pd(port); + if (err) + goto out_role_sw_put; + + port->typec_caps.pd = port->pd; + port->typec_port = typec_register_port(port->dev, &port->typec_caps); if (IS_ERR(port->typec_port)) { err = PTR_ERR(port->typec_port); - goto out_role_sw_put; + goto out_unregister_pd; } typec_port_register_altmodes(port->typec_port, @@ -6400,6 +6535,8 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) tcpm_log(port, "%s: registered", dev_name(dev)); return port; +out_unregister_pd: + tcpm_port_unregister_pd(port); out_role_sw_put: usb_role_switch_put(port->role_sw); out_destroy_wq: @@ -6422,6 +6559,9 @@ void tcpm_unregister_port(struct tcpm_port *port) hrtimer_cancel(&port->state_machine_timer); tcpm_reset_port(port); + + tcpm_port_unregister_pd(port); + for (i = 0; i < ARRAY_SIZE(port->port_altmode); i++) typec_unregister_altmode(port->port_altmode[i]); typec_unregister_port(port->typec_port); diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index 6db7c8ddd51c..5c0bf48be766 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -627,6 +627,16 @@ err_clear_irq: return IRQ_HANDLED; } +static int ccg_request_irq(struct ucsi_ccg *uc) +{ + unsigned long flags = IRQF_ONESHOT; + + if (!has_acpi_companion(uc->dev)) + flags |= IRQF_TRIGGER_HIGH; + + return request_threaded_irq(uc->irq, NULL, ccg_irq_handler, flags, dev_name(uc->dev), uc); +} + static void ccg_pm_workaround_work(struct work_struct *pm_work) { ccg_irq_handler(0, container_of(pm_work, struct ucsi_ccg, pm_work)); @@ -1250,9 +1260,7 @@ static int ccg_restart(struct ucsi_ccg *uc) return status; } - status = request_threaded_irq(uc->irq, NULL, ccg_irq_handler, - IRQF_ONESHOT | IRQF_TRIGGER_HIGH, - dev_name(dev), uc); + status = ccg_request_irq(uc); if (status < 0) { dev_err(dev, "request_threaded_irq failed - %d\n", status); return status; @@ -1331,6 +1339,7 @@ static int ucsi_ccg_probe(struct i2c_client *client, uc->dev = dev; uc->client = client; + uc->irq = client->irq; mutex_init(&uc->lock); init_completion(&uc->complete); INIT_WORK(&uc->work, ccg_update_firmware); @@ -1366,16 +1375,12 @@ static int ucsi_ccg_probe(struct i2c_client *client, ucsi_set_drvdata(uc->ucsi, uc); - status = request_threaded_irq(client->irq, NULL, ccg_irq_handler, - IRQF_ONESHOT | IRQF_TRIGGER_HIGH, - dev_name(dev), uc); + status = ccg_request_irq(uc); if (status < 0) { dev_err(uc->dev, "request_threaded_irq failed - %d\n", status); goto out_ucsi_destroy; } - uc->irq = client->irq; - status = ucsi_register(uc->ucsi); if (status) goto out_free_irq; @@ -1418,6 +1423,12 @@ static const struct i2c_device_id ucsi_ccg_device_id[] = { }; MODULE_DEVICE_TABLE(i2c, ucsi_ccg_device_id); +static const struct acpi_device_id amd_i2c_ucsi_match[] = { + {"AMDI0042"}, + {} +}; +MODULE_DEVICE_TABLE(acpi, amd_i2c_ucsi_match); + static int ucsi_ccg_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); @@ -1459,6 +1470,7 @@ static struct i2c_driver ucsi_ccg_driver = { .name = "ucsi_ccg", .pm = &ucsi_ccg_pm, .dev_groups = ucsi_ccg_groups, + .acpi_match_table = amd_i2c_ucsi_match, }, .probe = ucsi_ccg_probe, .remove = ucsi_ccg_remove, diff --git a/drivers/usb/usbip/vudc_rx.c b/drivers/usb/usbip/vudc_rx.c index 1e8a23d92cb4..d4a2f30a7580 100644 --- a/drivers/usb/usbip/vudc_rx.c +++ b/drivers/usb/usbip/vudc_rx.c @@ -104,18 +104,18 @@ static int v_recv_cmd_submit(struct vudc *udc, if (pdu->base.direction == USBIP_DIR_IN) address |= USB_DIR_IN; - spin_lock_irq(&udc->lock); + spin_lock_irqsave(&udc->lock, flags); urb_p->ep = vudc_find_endpoint(udc, address); if (!urb_p->ep) { /* we don't know the type, there may be isoc data! */ dev_err(&udc->pdev->dev, "request to nonexistent endpoint"); - spin_unlock_irq(&udc->lock); + spin_unlock_irqrestore(&udc->lock, flags); usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_TCP); ret = -EPIPE; goto free_urbp; } urb_p->type = urb_p->ep->type; - spin_unlock_irq(&udc->lock); + spin_unlock_irqrestore(&udc->lock, flags); urb_p->new = 1; urb_p->seqnum = pdu->base.seqnum; diff --git a/drivers/usb/usbip/vudc_sysfs.c b/drivers/usb/usbip/vudc_sysfs.c index d1cf6b51bf85..c95e6b2bfd32 100644 --- a/drivers/usb/usbip/vudc_sysfs.c +++ b/drivers/usb/usbip/vudc_sysfs.c @@ -128,7 +128,7 @@ static ssize_t usbip_sockfd_store(struct device *dev, goto unlock; } - spin_lock_irq(&udc->ud.lock); + spin_lock(&udc->ud.lock); if (udc->ud.status != SDEV_ST_AVAILABLE) { ret = -EINVAL; @@ -150,7 +150,7 @@ static ssize_t usbip_sockfd_store(struct device *dev, } /* unlock and create threads and get tasks */ - spin_unlock_irq(&udc->ud.lock); + spin_unlock(&udc->ud.lock); spin_unlock_irqrestore(&udc->lock, flags); tcp_rx = kthread_create(&v_rx_loop, &udc->ud, "vudc_rx"); @@ -173,14 +173,14 @@ static ssize_t usbip_sockfd_store(struct device *dev, /* lock and update udc->ud state */ spin_lock_irqsave(&udc->lock, flags); - spin_lock_irq(&udc->ud.lock); + spin_lock(&udc->ud.lock); udc->ud.tcp_socket = socket; udc->ud.tcp_rx = tcp_rx; udc->ud.tcp_tx = tcp_tx; udc->ud.status = SDEV_ST_USED; - spin_unlock_irq(&udc->ud.lock); + spin_unlock(&udc->ud.lock); ktime_get_ts64(&udc->start_time); v_start_timer(udc); @@ -201,12 +201,12 @@ static ssize_t usbip_sockfd_store(struct device *dev, goto unlock; } - spin_lock_irq(&udc->ud.lock); + spin_lock(&udc->ud.lock); if (udc->ud.status != SDEV_ST_USED) { ret = -EINVAL; goto unlock_ud; } - spin_unlock_irq(&udc->ud.lock); + spin_unlock(&udc->ud.lock); usbip_event_add(&udc->ud, VUDC_EVENT_DOWN); } @@ -219,7 +219,7 @@ static ssize_t usbip_sockfd_store(struct device *dev, sock_err: sockfd_put(socket); unlock_ud: - spin_unlock_irq(&udc->ud.lock); + spin_unlock(&udc->ud.lock); unlock: spin_unlock_irqrestore(&udc->lock, flags); mutex_unlock(&udc->ud.sysfs_lock); diff --git a/include/linux/usb.h b/include/linux/usb.h index 60bee864d897..f7a9914fc97f 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -584,6 +584,7 @@ struct usb3_lpm_parameters { * @authenticated: Crypto authentication passed * @wusb: device is Wireless USB * @lpm_capable: device supports LPM + * @lpm_devinit_allow: Allow USB3 device initiated LPM, exit latency is in range * @usb2_hw_lpm_capable: device can perform USB2 hardware LPM * @usb2_hw_lpm_besl_capable: device can perform USB2 hardware BESL LPM * @usb2_hw_lpm_enabled: USB2 hardware LPM is enabled @@ -666,6 +667,7 @@ struct usb_device { unsigned authenticated:1; unsigned wusb:1; unsigned lpm_capable:1; + unsigned lpm_devinit_allow:1; unsigned usb2_hw_lpm_capable:1; unsigned usb2_hw_lpm_besl_capable:1; unsigned usb2_hw_lpm_enabled:1; diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index 96b7ff66f074..c59fb79a42e8 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -495,4 +495,42 @@ static inline unsigned int rdo_max_power(u32 rdo) #define PD_P_SNK_STDBY_MW 2500 /* 2500 mW */ +#if IS_ENABLED(CONFIG_TYPEC) + +struct usb_power_delivery; + +/** + * usb_power_delivery_desc - USB Power Delivery Descriptor + * @revision: USB Power Delivery Specification Revision + * @version: USB Power Delivery Specicication Version - optional + */ +struct usb_power_delivery_desc { + u16 revision; + u16 version; +}; + +/** + * usb_power_delivery_capabilities_desc - Description of USB Power Delivery Capabilities Message + * @pdo: The Power Data Objects in the Capability Message + * @role: Power role of the capabilities + */ +struct usb_power_delivery_capabilities_desc { + u32 pdo[PDO_MAX_OBJECTS]; + enum typec_role role; +}; + +struct usb_power_delivery_capabilities * +usb_power_delivery_register_capabilities(struct usb_power_delivery *pd, + struct usb_power_delivery_capabilities_desc *desc); +void usb_power_delivery_unregister_capabilities(struct usb_power_delivery_capabilities *cap); + +struct usb_power_delivery *usb_power_delivery_register(struct device *parent, + struct usb_power_delivery_desc *desc); +void usb_power_delivery_unregister(struct usb_power_delivery *pd); + +int usb_power_delivery_link_device(struct usb_power_delivery *pd, struct device *dev); +void usb_power_delivery_unlink_device(struct usb_power_delivery *pd, struct device *dev); + +#endif /* CONFIG_TYPEC */ + #endif /* __LINUX_USB_PD_H */ diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index fdf737d48b3b..7751bedcae5d 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -22,6 +22,8 @@ struct typec_altmode_ops; struct fwnode_handle; struct device; +struct usb_power_delivery; + enum typec_port_type { TYPEC_PORT_SRC, TYPEC_PORT_SNK, @@ -52,6 +54,16 @@ enum typec_role { TYPEC_SOURCE, }; +static inline int is_sink(enum typec_role role) +{ + return role == TYPEC_SINK; +} + +static inline int is_source(enum typec_role role) +{ + return role == TYPEC_SOURCE; +} + enum typec_pwr_opmode { TYPEC_PWR_MODE_USB, TYPEC_PWR_MODE_1_5A, @@ -213,6 +225,8 @@ struct typec_partner_desc { * @pr_set: Set Power Role * @vconn_set: Source VCONN * @port_type_set: Set port type + * @pd_get: Get available USB Power Delivery Capabilities. + * @pd_set: Set USB Power Delivery Capabilities. */ struct typec_operations { int (*try_role)(struct typec_port *port, int role); @@ -221,6 +235,8 @@ struct typec_operations { int (*vconn_set)(struct typec_port *port, enum typec_role role); int (*port_type_set)(struct typec_port *port, enum typec_port_type type); + struct usb_power_delivery **(*pd_get)(struct typec_port *port); + int (*pd_set)(struct typec_port *port, struct usb_power_delivery *pd); }; enum usb_pd_svdm_ver { @@ -240,6 +256,7 @@ enum usb_pd_svdm_ver { * @accessory: Supported Accessory Modes * @fwnode: Optional fwnode of the port * @driver_data: Private pointer for driver specific info + * @pd: Optional USB Power Delivery Support * @ops: Port operations vector * * Static capabilities of a single USB Type-C port. @@ -257,6 +274,8 @@ struct typec_capability { struct fwnode_handle *fwnode; void *driver_data; + struct usb_power_delivery *pd; + const struct typec_operations *ops; }; @@ -308,4 +327,8 @@ void typec_partner_set_svdm_version(struct typec_partner *partner, enum usb_pd_svdm_ver svdm_version); int typec_get_negotiated_svdm_version(struct typec_port *port); +int typec_port_set_usb_power_delivery(struct typec_port *port, struct usb_power_delivery *pd); +int typec_partner_set_usb_power_delivery(struct typec_partner *partner, + struct usb_power_delivery *pd); + #endif /* __LINUX_USB_TYPEC_H */ diff --git a/include/linux/usb/typec_mux.h b/include/linux/usb/typec_mux.h index ee57781dcf28..9292f0e07846 100644 --- a/include/linux/usb/typec_mux.h +++ b/include/linux/usb/typec_mux.h @@ -58,17 +58,13 @@ struct typec_mux_desc { void *drvdata; }; +#if IS_ENABLED(CONFIG_TYPEC) + struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode, const struct typec_altmode_desc *desc); void typec_mux_put(struct typec_mux *mux); int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state); -static inline struct typec_mux * -typec_mux_get(struct device *dev, const struct typec_altmode_desc *desc) -{ - return fwnode_typec_mux_get(dev_fwnode(dev), desc); -} - struct typec_mux_dev * typec_mux_register(struct device *parent, const struct typec_mux_desc *desc); void typec_mux_unregister(struct typec_mux_dev *mux); @@ -76,4 +72,40 @@ void typec_mux_unregister(struct typec_mux_dev *mux); void typec_mux_set_drvdata(struct typec_mux_dev *mux, void *data); void *typec_mux_get_drvdata(struct typec_mux_dev *mux); +#else + +static inline struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode, + const struct typec_altmode_desc *desc) +{ + return NULL; +} + +static inline void typec_mux_put(struct typec_mux *mux) {} + +static inline int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state) +{ + return 0; +} + +static inline struct typec_mux_dev * +typec_mux_register(struct device *parent, const struct typec_mux_desc *desc) +{ + return ERR_PTR(-EOPNOTSUPP); +} +static inline void typec_mux_unregister(struct typec_mux_dev *mux) {} + +static inline void typec_mux_set_drvdata(struct typec_mux_dev *mux, void *data) {} +static inline void *typec_mux_get_drvdata(struct typec_mux_dev *mux) +{ + return ERR_PTR(-EOPNOTSUPP); +} + +#endif /* CONFIG_TYPEC */ + +static inline struct typec_mux * +typec_mux_get(struct device *dev, const struct typec_altmode_desc *desc) +{ + return fwnode_typec_mux_get(dev_fwnode(dev), desc); +} + #endif /* __USB_TYPEC_MUX */ |