diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2020-10-15 09:51:18 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2020-10-15 09:51:18 -0700 |
commit | c6dbef7307629cce855aa6b482b60cbf7777ed88 (patch) | |
tree | d8d13630bd57090173ad9f0c5f8e3bfb392964c9 /drivers/usb | |
parent | ade7afe3e606f9f6ff0e6deefce140157f75540b (diff) | |
parent | 93578a25d4e21603518daf27a5f9caa4bf79de68 (diff) | |
download | linux-c6dbef7307629cce855aa6b482b60cbf7777ed88.tar.bz2 |
Merge tag 'usb-5.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
Pull USB/PHY/Thunderbolt driver updates from Greg KH:
"Here is the big set of USB, PHY, and Thunderbolt driver updates for
5.10-rc1.
Lots of tiny different things for these subsystems are in here,
including:
- phy driver updates
- thunderbolt / USB 4 updates and additions
- USB gadget driver updates
- xhci fixes and updates
- typec driver additions and updates
- api conversions to various drivers for core kernel api changes
- new USB control message functions to make it harder to get wrong,
as found by syzbot (took 2 tries to get it right)
- lots of tiny USB driver fixes and updates all over the place
All of these have been in linux-next for a while, with the exception
of the last "obviously correct" patch that updated a FALLTHROUGH
comment that got merged last weekend"
* tag 'usb-5.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (374 commits)
usb: musb: gadget: Use fallthrough pseudo-keyword
usb: typec: Add QCOM PMIC typec detection driver
USB: serial: option: add Cellient MPL200 card
usb: typec: tcpci_maxim: Add support for Sink FRS
usb: typec: tcpci: Implement callbacks for FRS
usb: typec: tcpm: Add support for Sink Fast Role SWAP(FRS)
usb: typec: tcpci_maxim: Chip level TCPC driver
usb: typec: tcpci: Add set_vbus tcpci callback
usb: typec: tcpci: Add a getter method to retrieve tcpm_port reference
usbip: vhci_hcd: fix calling usb_hcd_giveback_urb() with irqs enabled
usb: cdc-acm: add quirk to blacklist ETAS ES58X devices
USB: serial: ftdi_sio: use cur_altsetting for consistency
USB: serial: option: Add Telit FT980-KS composition
USB: core: remove polling for /sys/kernel/debug/usb/devices
usb: typec: add support for STUSB160x Type-C controller family
usb: typec: add typec_find_pwr_opmode
usb: typec: hd3ss3220: Use OF graph API to get the connector fwnode
dt-bindings: usb: renesas,usb3-peri: Document HS and SS data bus
dt-bindings: usb: convert ti,hd3ss3220 bindings to json-schema
usb: dwc2: Fix INTR OUT transfers in DDMA mode.
...
Diffstat (limited to 'drivers/usb')
139 files changed, 5265 insertions, 1386 deletions
diff --git a/drivers/usb/atm/cxacru.c b/drivers/usb/atm/cxacru.c index ea66f8f385ba..e62a770a5d3b 100644 --- a/drivers/usb/atm/cxacru.c +++ b/drivers/usb/atm/cxacru.c @@ -230,12 +230,12 @@ CXACRU__ATTR_INIT(_name) static ssize_t cxacru_sysfs_showattr_u32(u32 value, char *buf) { - return snprintf(buf, PAGE_SIZE, "%u\n", value); + return sprintf(buf, "%u\n", value); } static ssize_t cxacru_sysfs_showattr_s8(s8 value, char *buf) { - return snprintf(buf, PAGE_SIZE, "%d\n", value); + return sprintf(buf, "%d\n", value); } static ssize_t cxacru_sysfs_showattr_dB(s16 value, char *buf) @@ -255,8 +255,8 @@ static ssize_t cxacru_sysfs_showattr_bool(u32 value, char *buf) static char *str[] = { "no", "yes" }; if (unlikely(value >= ARRAY_SIZE(str))) - return snprintf(buf, PAGE_SIZE, "%u\n", value); - return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); + return sprintf(buf, "%u\n", value); + return sprintf(buf, "%s\n", str[value]); } static ssize_t cxacru_sysfs_showattr_LINK(u32 value, char *buf) @@ -264,8 +264,8 @@ static ssize_t cxacru_sysfs_showattr_LINK(u32 value, char *buf) static char *str[] = { NULL, "not connected", "connected", "lost" }; if (unlikely(value >= ARRAY_SIZE(str) || str[value] == NULL)) - return snprintf(buf, PAGE_SIZE, "%u\n", value); - return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); + return sprintf(buf, "%u\n", value); + return sprintf(buf, "%s\n", str[value]); } static ssize_t cxacru_sysfs_showattr_LINE(u32 value, char *buf) @@ -275,8 +275,8 @@ static ssize_t cxacru_sysfs_showattr_LINE(u32 value, char *buf) "waiting", "initialising" }; if (unlikely(value >= ARRAY_SIZE(str))) - return snprintf(buf, PAGE_SIZE, "%u\n", value); - return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); + return sprintf(buf, "%u\n", value); + return sprintf(buf, "%s\n", str[value]); } static ssize_t cxacru_sysfs_showattr_MODU(u32 value, char *buf) @@ -288,8 +288,8 @@ static ssize_t cxacru_sysfs_showattr_MODU(u32 value, char *buf) "ITU-T G.992.2 (G.LITE)" }; if (unlikely(value >= ARRAY_SIZE(str))) - return snprintf(buf, PAGE_SIZE, "%u\n", value); - return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); + return sprintf(buf, "%u\n", value); + return sprintf(buf, "%s\n", str[value]); } /* @@ -309,8 +309,7 @@ static ssize_t mac_address_show(struct device *dev, if (instance == NULL || instance->usbatm->atm_dev == NULL) return -ENODEV; - return snprintf(buf, PAGE_SIZE, "%pM\n", - instance->usbatm->atm_dev->esi); + return sprintf(buf, "%pM\n", instance->usbatm->atm_dev->esi); } static ssize_t adsl_state_show(struct device *dev, @@ -326,8 +325,8 @@ static ssize_t adsl_state_show(struct device *dev, value = instance->card_info[CXINF_LINE_STARTABLE]; if (unlikely(value >= ARRAY_SIZE(str))) - return snprintf(buf, PAGE_SIZE, "%u\n", value); - return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); + return sprintf(buf, "%u\n", value); + return sprintf(buf, "%s\n", str[value]); } static ssize_t adsl_state_store(struct device *dev, diff --git a/drivers/usb/atm/usbatm.c b/drivers/usb/atm/usbatm.c index 4e12a32ca392..56fe30d247da 100644 --- a/drivers/usb/atm/usbatm.c +++ b/drivers/usb/atm/usbatm.c @@ -511,9 +511,10 @@ static unsigned int usbatm_write_cells(struct usbatm_data *instance, ** receive ** **************/ -static void usbatm_rx_process(unsigned long data) +static void usbatm_rx_process(struct tasklet_struct *t) { - struct usbatm_data *instance = (struct usbatm_data *)data; + struct usbatm_data *instance = from_tasklet(instance, t, + rx_channel.tasklet); struct urb *urb; while ((urb = usbatm_pop_urb(&instance->rx_channel))) { @@ -564,9 +565,10 @@ static void usbatm_rx_process(unsigned long data) ** send ** ***********/ -static void usbatm_tx_process(unsigned long data) +static void usbatm_tx_process(struct tasklet_struct *t) { - struct usbatm_data *instance = (struct usbatm_data *)data; + struct usbatm_data *instance = from_tasklet(instance, t, + tx_channel.tasklet); struct sk_buff *skb = instance->current_skb; struct urb *urb = NULL; const unsigned int buf_size = instance->tx_channel.buf_size; @@ -1069,8 +1071,8 @@ int usbatm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id, usbatm_init_channel(&instance->rx_channel); usbatm_init_channel(&instance->tx_channel); - tasklet_init(&instance->rx_channel.tasklet, usbatm_rx_process, (unsigned long)instance); - tasklet_init(&instance->tx_channel.tasklet, usbatm_tx_process, (unsigned long)instance); + tasklet_setup(&instance->rx_channel.tasklet, usbatm_rx_process); + tasklet_setup(&instance->tx_channel.tasklet, usbatm_tx_process); instance->rx_channel.stride = ATM_CELL_SIZE + driver->rx_padding; instance->tx_channel.stride = ATM_CELL_SIZE + driver->tx_padding; instance->rx_channel.usbatm = instance->tx_channel.usbatm = instance; diff --git a/drivers/usb/c67x00/c67x00-sched.c b/drivers/usb/c67x00/c67x00-sched.c index 60f4711717d2..e65f1a0ae80b 100644 --- a/drivers/usb/c67x00/c67x00-sched.c +++ b/drivers/usb/c67x00/c67x00-sched.c @@ -1123,9 +1123,9 @@ static void c67x00_do_work(struct c67x00_hcd *c67x00) /* -------------------------------------------------------------------------- */ -static void c67x00_sched_tasklet(unsigned long __c67x00) +static void c67x00_sched_tasklet(struct tasklet_struct *t) { - struct c67x00_hcd *c67x00 = (struct c67x00_hcd *)__c67x00; + struct c67x00_hcd *c67x00 = from_tasklet(c67x00, t, tasklet); c67x00_do_work(c67x00); } @@ -1136,8 +1136,7 @@ void c67x00_sched_kick(struct c67x00_hcd *c67x00) int c67x00_sched_start_scheduler(struct c67x00_hcd *c67x00) { - tasklet_init(&c67x00->tasklet, c67x00_sched_tasklet, - (unsigned long)c67x00); + tasklet_setup(&c67x00->tasklet, c67x00_sched_tasklet); return 0; } diff --git a/drivers/usb/cdns3/cdns3-imx.c b/drivers/usb/cdns3/cdns3-imx.c index aba988e71958..54a2d70a9c73 100644 --- a/drivers/usb/cdns3/cdns3-imx.c +++ b/drivers/usb/cdns3/cdns3-imx.c @@ -15,6 +15,8 @@ #include <linux/io.h> #include <linux/of_platform.h> #include <linux/iopoll.h> +#include <linux/pm_runtime.h> +#include "core.h" #define USB3_CORE_CTRL1 0x00 #define USB3_CORE_CTRL2 0x04 @@ -32,7 +34,7 @@ /* Register bits definition */ /* USB3_CORE_CTRL1 */ -#define SW_RESET_MASK (0x3f << 26) +#define SW_RESET_MASK GENMASK(31, 26) #define PWR_SW_RESET BIT(31) #define APB_SW_RESET BIT(30) #define AXI_SW_RESET BIT(29) @@ -53,8 +55,8 @@ #define LPM_CLK_REQ BIT(28) #define DEVU3_WAEKUP_EN BIT(14) #define OTG_WAKEUP_EN BIT(12) -#define DEV_INT_EN (3 << 8) /* DEV INT b9:8 */ -#define HOST_INT1_EN (1 << 0) /* HOST INT b7:0 */ +#define DEV_INT_EN (3 << 8) /* DEV INT b9:8 */ +#define HOST_INT1_EN (1 << 0) /* HOST INT b7:0 */ /* USB3_CORE_STATUS */ #define MDCTRL_CLK_STATUS BIT(15) @@ -66,11 +68,30 @@ #define CLK_VALID_COMPARE_BITS (0xf << 28) #define PHY_REFCLK_REQ (1 << 0) +/* OTG registers definition */ +#define OTGSTS 0x4 +/* OTGSTS */ +#define OTG_NRDY BIT(11) + +/* xHCI registers definition */ +#define XECP_PM_PMCSR 0x8018 +#define XECP_AUX_CTRL_REG1 0x8120 + +/* Register bits definition */ +/* XECP_AUX_CTRL_REG1 */ +#define CFG_RXDET_P3_EN BIT(15) + +/* XECP_PM_PMCSR */ +#define PS_MASK GENMASK(1, 0) +#define PS_D0 0 +#define PS_D1 1 + struct cdns_imx { struct device *dev; void __iomem *noncore; struct clk_bulk_data *clks; int num_clks; + struct platform_device *cdns3_pdev; }; static inline u32 cdns_imx_readl(struct cdns_imx *data, u32 offset) @@ -126,6 +147,20 @@ static int cdns_imx_noncore_init(struct cdns_imx *data) return ret; } +static int cdns_imx_platform_suspend(struct device *dev, + bool suspend, bool wakeup); +static struct cdns3_platform_data cdns_imx_pdata = { + .platform_suspend = cdns_imx_platform_suspend, +}; + +static const struct of_dev_auxdata cdns_imx_auxdata[] = { + { + .compatible = "cdns,usb3", + .platform_data = &cdns_imx_pdata, + }, + {}, +}; + static int cdns_imx_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -162,14 +197,18 @@ static int cdns_imx_probe(struct platform_device *pdev) if (ret) goto err; - ret = of_platform_populate(node, NULL, NULL, dev); + ret = of_platform_populate(node, NULL, cdns_imx_auxdata, dev); if (ret) { dev_err(dev, "failed to create children: %d\n", ret); goto err; } - return ret; + device_set_wakeup_capable(dev, true); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_forbid(dev); + return ret; err: clk_bulk_disable_unprepare(data->num_clks, data->clks); return ret; @@ -194,6 +233,147 @@ static int cdns_imx_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +static void cdns3_set_wakeup(struct cdns_imx *data, bool enable) +{ + u32 value; + + value = cdns_imx_readl(data, USB3_INT_REG); + if (enable) + value |= OTG_WAKEUP_EN | DEVU3_WAEKUP_EN; + else + value &= ~(OTG_WAKEUP_EN | DEVU3_WAEKUP_EN); + + cdns_imx_writel(data, USB3_INT_REG, value); +} + +static int cdns_imx_platform_suspend(struct device *dev, + bool suspend, bool wakeup) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + struct device *parent = dev->parent; + struct cdns_imx *data = dev_get_drvdata(parent); + void __iomem *otg_regs = (void __iomem *)(cdns->otg_regs); + void __iomem *xhci_regs = cdns->xhci_regs; + u32 value; + int ret = 0; + + if (cdns->role != USB_ROLE_HOST) + return 0; + + if (suspend) { + /* SW request low power when all usb ports allow to it ??? */ + value = readl(xhci_regs + XECP_PM_PMCSR); + value &= ~PS_MASK; + value |= PS_D1; + writel(value, xhci_regs + XECP_PM_PMCSR); + + /* mdctrl_clk_sel */ + value = cdns_imx_readl(data, USB3_CORE_CTRL1); + value |= MDCTRL_CLK_SEL; + cdns_imx_writel(data, USB3_CORE_CTRL1, value); + + /* wait for mdctrl_clk_status */ + value = cdns_imx_readl(data, USB3_CORE_STATUS); + ret = readl_poll_timeout(data->noncore + USB3_CORE_STATUS, value, + (value & MDCTRL_CLK_STATUS) == MDCTRL_CLK_STATUS, + 10, 100000); + if (ret) + dev_warn(parent, "wait mdctrl_clk_status timeout\n"); + + /* wait lpm_clk_req to be 0 */ + value = cdns_imx_readl(data, USB3_INT_REG); + ret = readl_poll_timeout(data->noncore + USB3_INT_REG, value, + (value & LPM_CLK_REQ) != LPM_CLK_REQ, + 10, 100000); + if (ret) + dev_warn(parent, "wait lpm_clk_req timeout\n"); + + /* wait phy_refclk_req to be 0 */ + value = cdns_imx_readl(data, USB3_SSPHY_STATUS); + ret = readl_poll_timeout(data->noncore + USB3_SSPHY_STATUS, value, + (value & PHY_REFCLK_REQ) != PHY_REFCLK_REQ, + 10, 100000); + if (ret) + dev_warn(parent, "wait phy_refclk_req timeout\n"); + + cdns3_set_wakeup(data, wakeup); + } else { + cdns3_set_wakeup(data, false); + + /* SW request D0 */ + value = readl(xhci_regs + XECP_PM_PMCSR); + value &= ~PS_MASK; + value |= PS_D0; + writel(value, xhci_regs + XECP_PM_PMCSR); + + /* clr CFG_RXDET_P3_EN */ + value = readl(xhci_regs + XECP_AUX_CTRL_REG1); + value &= ~CFG_RXDET_P3_EN; + writel(value, xhci_regs + XECP_AUX_CTRL_REG1); + + /* clear mdctrl_clk_sel */ + value = cdns_imx_readl(data, USB3_CORE_CTRL1); + value &= ~MDCTRL_CLK_SEL; + cdns_imx_writel(data, USB3_CORE_CTRL1, value); + + /* wait CLK_125_REQ to be 1 */ + value = cdns_imx_readl(data, USB3_INT_REG); + ret = readl_poll_timeout(data->noncore + USB3_INT_REG, value, + (value & CLK_125_REQ) == CLK_125_REQ, + 10, 100000); + if (ret) + dev_warn(parent, "wait CLK_125_REQ timeout\n"); + + /* wait for mdctrl_clk_status is cleared */ + value = cdns_imx_readl(data, USB3_CORE_STATUS); + ret = readl_poll_timeout(data->noncore + USB3_CORE_STATUS, value, + (value & MDCTRL_CLK_STATUS) != MDCTRL_CLK_STATUS, + 10, 100000); + if (ret) + dev_warn(parent, "wait mdctrl_clk_status cleared timeout\n"); + + /* Wait until OTG_NRDY is 0 */ + value = readl(otg_regs + OTGSTS); + ret = readl_poll_timeout(otg_regs + OTGSTS, value, + (value & OTG_NRDY) != OTG_NRDY, + 10, 100000); + if (ret) + dev_warn(parent, "wait OTG ready timeout\n"); + } + + return ret; + +} + +static int cdns_imx_resume(struct device *dev) +{ + struct cdns_imx *data = dev_get_drvdata(dev); + + return clk_bulk_prepare_enable(data->num_clks, data->clks); +} + +static int cdns_imx_suspend(struct device *dev) +{ + struct cdns_imx *data = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(data->num_clks, data->clks); + + return 0; +} +#else +static int cdns_imx_platform_suspend(struct device *dev, + bool suspend, bool wakeup) +{ + return 0; +} + +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops cdns_imx_pm_ops = { + SET_RUNTIME_PM_OPS(cdns_imx_suspend, cdns_imx_resume, NULL) +}; + static const struct of_device_id cdns_imx_of_match[] = { { .compatible = "fsl,imx8qm-usb3", }, {}, @@ -206,6 +386,7 @@ static struct platform_driver cdns_imx_driver = { .driver = { .name = "cdns3-imx", .of_match_table = cdns_imx_of_match, + .pm = &cdns_imx_pm_ops, }, }; module_platform_driver(cdns_imx_driver); diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index 5c1586ec7824..a0f73d4711ae 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -280,6 +280,10 @@ int cdns3_hw_role_switch(struct cdns3 *cdns) enum usb_role real_role, current_role; int ret = 0; + /* Depends on role switch class */ + if (cdns->role_sw) + return 0; + pm_runtime_get_sync(cdns->dev); current_role = cdns->role; @@ -371,6 +375,50 @@ pm_put: return ret; } +static int set_phy_power_on(struct cdns3 *cdns) +{ + int ret; + + ret = phy_power_on(cdns->usb2_phy); + if (ret) + return ret; + + ret = phy_power_on(cdns->usb3_phy); + if (ret) + phy_power_off(cdns->usb2_phy); + + return ret; +} + +static void set_phy_power_off(struct cdns3 *cdns) +{ + phy_power_off(cdns->usb3_phy); + phy_power_off(cdns->usb2_phy); +} + +/** + * cdns3_wakeup_irq - interrupt handler for wakeup events + * @irq: irq number for cdns3 core device + * @data: structure of cdns3 + * + * Returns IRQ_HANDLED or IRQ_NONE + */ +static irqreturn_t cdns3_wakeup_irq(int irq, void *data) +{ + struct cdns3 *cdns = data; + + if (cdns->in_lpm) { + disable_irq_nosync(irq); + cdns->wakeup_pending = true; + if ((cdns->role == USB_ROLE_HOST) && cdns->host_dev) + pm_request_resume(&cdns->host_dev->dev); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + /** * cdns3_probe - probe for cdns3 core device * @pdev: Pointer to cdns3 core platform device @@ -397,6 +445,7 @@ static int cdns3_probe(struct platform_device *pdev) return -ENOMEM; cdns->dev = dev; + cdns->pdata = dev_get_platdata(dev); platform_set_drvdata(pdev, cdns); @@ -443,8 +492,21 @@ static int cdns3_probe(struct platform_device *pdev) return -ENXIO; } + cdns->phyrst_a_enable = device_property_read_bool(dev, "cdns,phyrst-a-enable"); + cdns->otg_res = *res; + cdns->wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup"); + if (cdns->wakeup_irq == -EPROBE_DEFER) + return cdns->wakeup_irq; + else if (cdns->wakeup_irq == 0) + return -EINVAL; + + if (cdns->wakeup_irq < 0) { + dev_dbg(dev, "couldn't get wakeup irq\n"); + cdns->wakeup_irq = 0x0; + } + mutex_init(&cdns->mutex); cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy"); @@ -463,14 +525,10 @@ static int cdns3_probe(struct platform_device *pdev) if (ret) goto err1; - ret = phy_power_on(cdns->usb2_phy); + ret = set_phy_power_on(cdns); if (ret) goto err2; - ret = phy_power_on(cdns->usb3_phy); - if (ret) - goto err3; - sw_desc.set = cdns3_role_set; sw_desc.get = cdns3_role_get; sw_desc.allow_userspace_control = true; @@ -482,20 +540,34 @@ static int cdns3_probe(struct platform_device *pdev) if (IS_ERR(cdns->role_sw)) { ret = PTR_ERR(cdns->role_sw); dev_warn(dev, "Unable to register Role Switch\n"); - goto err4; + goto err3; + } + + if (cdns->wakeup_irq) { + ret = devm_request_irq(cdns->dev, cdns->wakeup_irq, + cdns3_wakeup_irq, + IRQF_SHARED, + dev_name(cdns->dev), cdns); + + if (ret) { + dev_err(cdns->dev, "couldn't register wakeup irq handler\n"); + goto err3; + } } ret = cdns3_drd_init(cdns); if (ret) - goto err5; + goto err4; ret = cdns3_core_init_role(cdns); if (ret) - goto err5; + goto err4; + spin_lock_init(&cdns->lock); device_set_wakeup_capable(dev, true); pm_runtime_set_active(dev); pm_runtime_enable(dev); + pm_runtime_forbid(dev); /* * The controller needs less time between bus and controller suspend, @@ -508,14 +580,11 @@ static int cdns3_probe(struct platform_device *pdev) dev_dbg(dev, "Cadence USB3 core: probe succeed\n"); return 0; -err5: +err4: cdns3_drd_exit(cdns); usb_role_switch_unregister(cdns->role_sw); -err4: - phy_power_off(cdns->usb3_phy); - err3: - phy_power_off(cdns->usb2_phy); + set_phy_power_off(cdns); err2: phy_exit(cdns->usb3_phy); err1: @@ -539,59 +608,128 @@ static int cdns3_remove(struct platform_device *pdev) pm_runtime_put_noidle(&pdev->dev); cdns3_exit_roles(cdns); usb_role_switch_unregister(cdns->role_sw); - phy_power_off(cdns->usb2_phy); - phy_power_off(cdns->usb3_phy); + set_phy_power_off(cdns); phy_exit(cdns->usb2_phy); phy_exit(cdns->usb3_phy); return 0; } -#ifdef CONFIG_PM_SLEEP +#ifdef CONFIG_PM -static int cdns3_suspend(struct device *dev) +static int cdns3_set_platform_suspend(struct device *dev, + bool suspend, bool wakeup) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + int ret = 0; + + if (cdns->pdata && cdns->pdata->platform_suspend) + ret = cdns->pdata->platform_suspend(dev, suspend, wakeup); + + return ret; +} + +static int cdns3_controller_suspend(struct device *dev, pm_message_t msg) { struct cdns3 *cdns = dev_get_drvdata(dev); + bool wakeup; unsigned long flags; - if (cdns->role == USB_ROLE_HOST) + if (cdns->in_lpm) return 0; - if (pm_runtime_status_suspended(dev)) - pm_runtime_resume(dev); + if (PMSG_IS_AUTO(msg)) + wakeup = true; + else + wakeup = device_may_wakeup(dev); - if (cdns->roles[cdns->role]->suspend) { - spin_lock_irqsave(&cdns->gadget_dev->lock, flags); - cdns->roles[cdns->role]->suspend(cdns, false); - spin_unlock_irqrestore(&cdns->gadget_dev->lock, flags); - } + cdns3_set_platform_suspend(cdns->dev, true, wakeup); + set_phy_power_off(cdns); + spin_lock_irqsave(&cdns->lock, flags); + cdns->in_lpm = true; + spin_unlock_irqrestore(&cdns->lock, flags); + dev_dbg(cdns->dev, "%s ends\n", __func__); return 0; } -static int cdns3_resume(struct device *dev) +static int cdns3_controller_resume(struct device *dev, pm_message_t msg) { struct cdns3 *cdns = dev_get_drvdata(dev); + int ret; unsigned long flags; - if (cdns->role == USB_ROLE_HOST) + if (!cdns->in_lpm) return 0; - if (cdns->roles[cdns->role]->resume) { - spin_lock_irqsave(&cdns->gadget_dev->lock, flags); + ret = set_phy_power_on(cdns); + if (ret) + return ret; + + cdns3_set_platform_suspend(cdns->dev, false, false); + + spin_lock_irqsave(&cdns->lock, flags); + if (cdns->roles[cdns->role]->resume && !PMSG_IS_AUTO(msg)) cdns->roles[cdns->role]->resume(cdns, false); - spin_unlock_irqrestore(&cdns->gadget_dev->lock, flags); + + cdns->in_lpm = false; + spin_unlock_irqrestore(&cdns->lock, flags); + if (cdns->wakeup_pending) { + cdns->wakeup_pending = false; + enable_irq(cdns->wakeup_irq); + } + dev_dbg(cdns->dev, "%s ends\n", __func__); + + return ret; +} + +static int cdns3_runtime_suspend(struct device *dev) +{ + return cdns3_controller_suspend(dev, PMSG_AUTO_SUSPEND); +} + +static int cdns3_runtime_resume(struct device *dev) +{ + return cdns3_controller_resume(dev, PMSG_AUTO_RESUME); +} +#ifdef CONFIG_PM_SLEEP + +static int cdns3_suspend(struct device *dev) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + unsigned long flags; + + if (pm_runtime_status_suspended(dev)) + pm_runtime_resume(dev); + + if (cdns->roles[cdns->role]->suspend) { + spin_lock_irqsave(&cdns->lock, flags); + cdns->roles[cdns->role]->suspend(cdns, false); + spin_unlock_irqrestore(&cdns->lock, flags); } + return cdns3_controller_suspend(dev, PMSG_SUSPEND); +} + +static int cdns3_resume(struct device *dev) +{ + int ret; + + ret = cdns3_controller_resume(dev, PMSG_RESUME); + if (ret) + return ret; + pm_runtime_disable(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); - return 0; + return ret; } -#endif +#endif /* CONFIG_PM_SLEEP */ +#endif /* CONFIG_PM */ static const struct dev_pm_ops cdns3_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume) + SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL) }; #ifdef CONFIG_OF diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h index 1ad1f1fe61e9..8a40d53d5ede 100644 --- a/drivers/usb/cdns3/core.h +++ b/drivers/usb/cdns3/core.h @@ -38,6 +38,12 @@ struct cdns3_role_driver { }; #define CDNS3_XHCI_RESOURCES_NUM 2 + +struct cdns3_platform_data { + int (*platform_suspend)(struct device *dev, + bool suspend, bool wakeup); +}; + /** * struct cdns3 - Representation of Cadence USB3 DRD controller. * @dev: pointer to Cadence device struct @@ -50,6 +56,7 @@ struct cdns3_role_driver { * @otg_regs: pointer to base of otg registers * @otg_irq: irq number for otg controller * @dev_irq: irq number for device controller + * @wakeup_irq: irq number for wakeup event, it is optional * @roles: array of supported roles for this controller * @role: current role * @host_dev: the child host device pointer for cdns3 core @@ -62,6 +69,10 @@ struct cdns3_role_driver { * This field based on firmware setting, kernel configuration * and hardware configuration. * @role_sw: pointer to role switch object. + * @in_lpm: indicate the controller is in low power mode + * @wakeup_pending: wakeup interrupt pending + * @pdata: platform data from glue layer + * @lock: spinlock structure */ struct cdns3 { struct device *dev; @@ -76,9 +87,11 @@ struct cdns3 { #define CDNS3_CONTROLLER_V0 0 #define CDNS3_CONTROLLER_V1 1 u32 version; + bool phyrst_a_enable; int otg_irq; int dev_irq; + int wakeup_irq; struct cdns3_role_driver *roles[USB_ROLE_DEVICE + 1]; enum usb_role role; struct platform_device *host_dev; @@ -89,6 +102,10 @@ struct cdns3 { struct mutex mutex; enum usb_dr_mode dr_mode; struct usb_role_switch *role_sw; + bool in_lpm; + bool wakeup_pending; + struct cdns3_platform_data *pdata; + spinlock_t lock; }; int cdns3_hw_role_switch(struct cdns3 *cdns); diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c index 6234bcd6158a..38ccd29e4cde 100644 --- a/drivers/usb/cdns3/drd.c +++ b/drivers/usb/cdns3/drd.c @@ -15,6 +15,7 @@ #include <linux/delay.h> #include <linux/iopoll.h> #include <linux/usb/otg.h> +#include <linux/phy/phy.h> #include "gadget.h" #include "drd.h" @@ -42,6 +43,18 @@ int cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode) reg = readl(&cdns->otg_v1_regs->override); reg |= OVERRIDE_IDPULLUP; writel(reg, &cdns->otg_v1_regs->override); + + /* + * Enable work around feature built into the + * controller to address issue with RX Sensitivity + * est (EL_17) for USB2 PHY. The issue only occures + * for 0x0002450D controller version. + */ + if (cdns->phyrst_a_enable) { + reg = readl(&cdns->otg_v1_regs->phyrst_cfg); + reg |= PHYRST_CFG_PHYRST_A_ENABLE; + writel(reg, &cdns->otg_v1_regs->phyrst_cfg); + } } else { reg = readl(&cdns->otg_v0_regs->ctrl1); reg |= OVERRIDE_IDPULLUP_V0; @@ -145,6 +158,7 @@ int cdns3_drd_host_on(struct cdns3 *cdns) if (ret) dev_err(cdns->dev, "timeout waiting for xhci_ready\n"); + phy_set_mode(cdns->usb3_phy, PHY_MODE_USB_HOST); return ret; } @@ -164,6 +178,7 @@ void cdns3_drd_host_off(struct cdns3 *cdns) readl_poll_timeout_atomic(&cdns->otg_regs->state, val, !(val & OTGSTATE_HOST_STATE_MASK), 1, 2000000); + phy_set_mode(cdns->usb3_phy, PHY_MODE_INVALID); } /** @@ -190,6 +205,7 @@ int cdns3_drd_gadget_on(struct cdns3 *cdns) return ret; } + phy_set_mode(cdns->usb3_phy, PHY_MODE_USB_DEVICE); return 0; } @@ -213,6 +229,7 @@ void cdns3_drd_gadget_off(struct cdns3 *cdns) readl_poll_timeout_atomic(&cdns->otg_regs->state, val, !(val & OTGSTATE_DEV_STATE_MASK), 1, 2000000); + phy_set_mode(cdns->usb3_phy, PHY_MODE_INVALID); } /** @@ -293,6 +310,9 @@ static irqreturn_t cdns3_drd_irq(int irq, void *data) if (cdns->dr_mode != USB_DR_MODE_OTG) return IRQ_NONE; + if (cdns->in_lpm) + return ret; + reg = readl(&cdns->otg_regs->ivect); if (!reg) diff --git a/drivers/usb/cdns3/drd.h b/drivers/usb/cdns3/drd.h index 7e7cf7fa2dd3..f1ccae285a16 100644 --- a/drivers/usb/cdns3/drd.h +++ b/drivers/usb/cdns3/drd.h @@ -31,7 +31,7 @@ struct cdns3_otg_regs { __le32 simulate; __le32 override; __le32 susp_ctrl; - __le32 reserved4; + __le32 phyrst_cfg; __le32 anasts; __le32 adp_ramp_time; __le32 ctrl1; @@ -153,6 +153,9 @@ struct cdns3_otg_common_regs { /* Only for CDNS3_CONTROLLER_V0 version */ #define OVERRIDE_IDPULLUP_V0 BIT(24) +/* PHYRST_CFG - bitmasks */ +#define PHYRST_CFG_PHYRST_A_ENABLE BIT(0) + #define CDNS3_ID_PERIPHERAL 1 #define CDNS3_ID_HOST 0 diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c index d9779abc65b2..4761c852d9c4 100644 --- a/drivers/usb/cdns3/ep0.c +++ b/drivers/usb/cdns3/ep0.c @@ -717,9 +717,17 @@ static int cdns3_gadget_ep0_queue(struct usb_ep *ep, /* send STATUS stage. Should be called only for SET_CONFIGURATION */ if (priv_dev->ep0_stage == CDNS3_STATUS_STAGE) { + u32 val; + cdns3_select_ep(priv_dev, 0x00); cdns3_set_hw_configuration(priv_dev); cdns3_ep0_complete_setup(priv_dev, 0, 1); + /* wait until configuration set */ + ret = readl_poll_timeout_atomic(&priv_dev->regs->usb_sts, val, + val & USB_STS_CFGSTS_MASK, 1, 100); + if (ret == -ETIMEDOUT) + dev_warn(priv_dev->dev, "timeout for waiting configuration set\n"); + request->actual = 0; priv_dev->status_completion_no_call = true; priv_dev->pending_status_request = request; @@ -731,7 +739,7 @@ static int cdns3_gadget_ep0_queue(struct usb_ep *ep, * ep0_queue is back. */ queue_work(system_freezable_wq, &priv_dev->pending_status_wq); - return 0; + return ret; } if (!list_empty(&priv_ep->pending_req_list)) { diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c index dea649ee173b..6e7b70a2e352 100644 --- a/drivers/usb/cdns3/gadget.c +++ b/drivers/usb/cdns3/gadget.c @@ -261,8 +261,8 @@ int cdns3_allocate_trb_pool(struct cdns3_endpoint *priv_ep) */ link_trb->control = 0; } else { - link_trb->buffer = TRB_BUFFER(priv_ep->trb_pool_dma); - link_trb->control = TRB_CYCLE | TRB_TYPE(TRB_LINK) | TRB_TOGGLE; + link_trb->buffer = cpu_to_le32(TRB_BUFFER(priv_ep->trb_pool_dma)); + link_trb->control = cpu_to_le32(TRB_CYCLE | TRB_TYPE(TRB_LINK) | TRB_TOGGLE); } return 0; } @@ -462,6 +462,36 @@ static int cdns3_start_all_request(struct cdns3_device *priv_dev, (reg) |= EP_STS_EN_DESCMISEN; \ } } while (0) +static void __cdns3_descmiss_copy_data(struct usb_request *request, + struct usb_request *descmiss_req) +{ + int length = request->actual + descmiss_req->actual; + struct scatterlist *s = request->sg; + + if (!s) { + if (length <= request->length) { + memcpy(&((u8 *)request->buf)[request->actual], + descmiss_req->buf, + descmiss_req->actual); + request->actual = length; + } else { + /* It should never occures */ + request->status = -ENOMEM; + } + } else { + if (length <= sg_dma_len(s)) { + void *p = phys_to_virt(sg_dma_address(s)); + + memcpy(&((u8 *)p)[request->actual], + descmiss_req->buf, + descmiss_req->actual); + request->actual = length; + } else { + request->status = -ENOMEM; + } + } +} + /** * cdns3_wa2_descmiss_copy_data copy data from internal requests to * request queued by class driver. @@ -488,21 +518,9 @@ static void cdns3_wa2_descmiss_copy_data(struct cdns3_endpoint *priv_ep, chunk_end = descmiss_priv_req->flags & REQUEST_INTERNAL_CH; length = request->actual + descmiss_req->actual; - request->status = descmiss_req->status; - - if (length <= request->length) { - memcpy(&((u8 *)request->buf)[request->actual], - descmiss_req->buf, - descmiss_req->actual); - request->actual = length; - } else { - /* It should never occures */ - request->status = -ENOMEM; - } - + __cdns3_descmiss_copy_data(request, descmiss_req); list_del_init(&descmiss_priv_req->list); - kfree(descmiss_req->buf); cdns3_gadget_ep_free_request(&priv_ep->endpoint, descmiss_req); --priv_ep->wa2_counter; @@ -817,6 +835,8 @@ void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep, request->length); priv_req->flags &= ~(REQUEST_PENDING | REQUEST_UNALIGNED); + /* All TRBs have finished, clear the counter */ + priv_req->finished_trb = 0; trace_cdns3_gadget_giveback(priv_req); if (priv_dev->dev_ver < DEV_VER_V2) { @@ -847,10 +867,10 @@ static void cdns3_wa1_restore_cycle_bit(struct cdns3_endpoint *priv_ep) priv_ep->wa1_trb_index = 0xFFFF; if (priv_ep->wa1_cycle_bit) { priv_ep->wa1_trb->control = - priv_ep->wa1_trb->control | 0x1; + priv_ep->wa1_trb->control | cpu_to_le32(0x1); } else { priv_ep->wa1_trb->control = - priv_ep->wa1_trb->control & ~0x1; + priv_ep->wa1_trb->control & cpu_to_le32(~0x1); } } } @@ -1008,17 +1028,16 @@ static int cdns3_ep_run_stream_transfer(struct cdns3_endpoint *priv_ep, TRB_STREAM_ID(priv_req->request.stream_id) | TRB_ISP; if (!request->num_sgs) { - trb->buffer = TRB_BUFFER(trb_dma); + trb->buffer = cpu_to_le32(TRB_BUFFER(trb_dma)); length = request->length; } else { - trb->buffer = TRB_BUFFER(request->sg[sg_idx].dma_address); + trb->buffer = cpu_to_le32(TRB_BUFFER(request->sg[sg_idx].dma_address)); length = request->sg[sg_idx].length; } tdl = DIV_ROUND_UP(length, priv_ep->endpoint.maxpacket); - trb->length = TRB_BURST_LEN(16 /*priv_ep->trb_burst_size*/) | - TRB_LEN(length); + trb->length = cpu_to_le32(TRB_BURST_LEN(16) | TRB_LEN(length)); /* * For DEV_VER_V2 controller version we have enabled @@ -1027,11 +1046,11 @@ static int cdns3_ep_run_stream_transfer(struct cdns3_endpoint *priv_ep, */ if (priv_dev->dev_ver >= DEV_VER_V2) { if (priv_dev->gadget.speed == USB_SPEED_SUPER) - trb->length |= TRB_TDL_SS_SIZE(tdl); + trb->length |= cpu_to_le32(TRB_TDL_SS_SIZE(tdl)); } priv_req->flags |= REQUEST_PENDING; - trb->control = control; + trb->control = cpu_to_le32(control); trace_cdns3_prepare_trb(priv_ep, priv_req->trb); @@ -1091,6 +1110,7 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, struct cdns3_device *priv_dev = priv_ep->cdns3_dev; struct cdns3_request *priv_req; struct cdns3_trb *trb; + struct cdns3_trb *link_trb; dma_addr_t trb_dma; u32 togle_pcs = 1; int sg_iter = 0; @@ -1099,11 +1119,13 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, u32 control; int pcs; u16 total_tdl = 0; + struct scatterlist *s = NULL; + bool sg_supported = !!(request->num_mapped_sgs); if (priv_ep->type == USB_ENDPOINT_XFER_ISOC) num_trb = priv_ep->interval; else - num_trb = request->num_sgs ? request->num_sgs : 1; + num_trb = sg_supported ? request->num_mapped_sgs : 1; if (num_trb > priv_ep->free_trbs) { priv_ep->flags |= EP_RING_FULL; @@ -1129,7 +1151,6 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, /* prepare ring */ if ((priv_ep->enqueue + num_trb) >= (priv_ep->num_trbs - 1)) { - struct cdns3_trb *link_trb; int doorbell, dma_index; u32 ch_bit = 0; @@ -1156,13 +1177,16 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, TRBS_PER_SEGMENT > 2) ch_bit = TRB_CHAIN; - link_trb->control = ((priv_ep->pcs) ? TRB_CYCLE : 0) | - TRB_TYPE(TRB_LINK) | TRB_TOGGLE | ch_bit; + link_trb->control = cpu_to_le32(((priv_ep->pcs) ? TRB_CYCLE : 0) | + TRB_TYPE(TRB_LINK) | TRB_TOGGLE | ch_bit); } if (priv_dev->dev_ver <= DEV_VER_V2) togle_pcs = cdns3_wa1_update_guard(priv_ep, trb); + if (sg_supported) + s = request->sg; + /* set incorrect Cycle Bit for first trb*/ control = priv_ep->pcs ? 0 : TRB_CYCLE; @@ -1172,13 +1196,13 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, /* fill TRB */ control |= TRB_TYPE(TRB_NORMAL); - trb->buffer = TRB_BUFFER(request->num_sgs == 0 - ? trb_dma : request->sg[sg_iter].dma_address); - - if (likely(!request->num_sgs)) + if (sg_supported) { + trb->buffer = cpu_to_le32(TRB_BUFFER(sg_dma_address(s))); + length = sg_dma_len(s); + } else { + trb->buffer = cpu_to_le32(TRB_BUFFER(trb_dma)); length = request->length; - else - length = request->sg[sg_iter].length; + } if (likely(priv_dev->dev_ver >= DEV_VER_V2)) td_size = DIV_ROUND_UP(length, @@ -1187,10 +1211,10 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, total_tdl += DIV_ROUND_UP(length, priv_ep->endpoint.maxpacket); - trb->length = TRB_BURST_LEN(priv_ep->trb_burst_size) | - TRB_LEN(length); + trb->length = cpu_to_le32(TRB_BURST_LEN(priv_ep->trb_burst_size) | + TRB_LEN(length)); if (priv_dev->gadget.speed == USB_SPEED_SUPER) - trb->length |= TRB_TDL_SS_SIZE(td_size); + trb->length |= cpu_to_le32(TRB_TDL_SS_SIZE(td_size)); else control |= TRB_TDL_HS_SIZE(td_size); @@ -1212,9 +1236,18 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, } if (sg_iter) - trb->control = control; + trb->control = cpu_to_le32(control); else - priv_req->trb->control = control; + priv_req->trb->control = cpu_to_le32(control); + + if (sg_supported) { + trb->control |= TRB_ISP; + /* Don't set chain bit for last TRB */ + if (sg_iter < num_trb - 1) + trb->control |= TRB_CHAIN; + + s = sg_next(s); + } control = 0; ++sg_iter; @@ -1226,9 +1259,10 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, trb = priv_req->trb; priv_req->flags |= REQUEST_PENDING; + priv_req->num_of_trb = num_trb; if (sg_iter == 1) - trb->control |= TRB_IOC | TRB_ISP; + trb->control |= cpu_to_le32(TRB_IOC | TRB_ISP); if (priv_dev->dev_ver < DEV_VER_V2 && (priv_ep->flags & EP_TDLCHK_EN)) { @@ -1254,12 +1288,27 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, /* give the TD to the consumer*/ if (togle_pcs) - trb->control = trb->control ^ 1; + trb->control = trb->control ^ cpu_to_le32(1); if (priv_dev->dev_ver <= DEV_VER_V2) cdns3_wa1_tray_restore_cycle_bit(priv_dev, priv_ep); - trace_cdns3_prepare_trb(priv_ep, priv_req->trb); + if (num_trb > 1) { + int i = 0; + + while (i < num_trb) { + trace_cdns3_prepare_trb(priv_ep, trb + i); + if (trb + i == link_trb) { + trb = priv_ep->trb_pool; + num_trb = num_trb - i; + i = 0; + } else { + i++; + } + } + } else { + trace_cdns3_prepare_trb(priv_ep, priv_req->trb); + } /* * Memory barrier - Cycle Bit must be set before trb->length and @@ -1310,7 +1359,6 @@ void cdns3_set_hw_configuration(struct cdns3_device *priv_dev) { struct cdns3_endpoint *priv_ep; struct usb_ep *ep; - int val; if (priv_dev->hw_configured_flag) return; @@ -1320,10 +1368,6 @@ void cdns3_set_hw_configuration(struct cdns3_device *priv_dev) cdns3_set_register_bit(&priv_dev->regs->usb_conf, USB_CONF_U1EN | USB_CONF_U2EN); - /* wait until configuration set */ - readl_poll_timeout_atomic(&priv_dev->regs->usb_sts, val, - val & USB_STS_CFGSTS_MASK, 1, 100); - priv_dev->hw_configured_flag = 1; list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) { @@ -1337,7 +1381,7 @@ void cdns3_set_hw_configuration(struct cdns3_device *priv_dev) } /** - * cdns3_request_handled - check whether request has been handled by DMA + * cdns3_trb_handled - check whether trb has been handled by DMA * * @priv_ep: extended endpoint object. * @priv_req: request object for checking @@ -1354,32 +1398,28 @@ void cdns3_set_hw_configuration(struct cdns3_device *priv_dev) * ET = priv_req->end_trb - index of last TRB in transfer ring * CI = current_index - index of processed TRB by DMA. * - * As first step, function checks if cycle bit for priv_req->start_trb is - * correct. + * As first step, we check if the TRB between the ST and ET. + * Then, we check if cycle bit for index priv_ep->dequeue + * is correct. * * some rules: - * 1. priv_ep->dequeue never exceed current_index. + * 1. priv_ep->dequeue never equals to current_index. * 2 priv_ep->enqueue never exceed priv_ep->dequeue * 3. exception: priv_ep->enqueue == priv_ep->dequeue * and priv_ep->free_trbs is zero. * This case indicate that TR is full. * - * Then We can split recognition into two parts: + * At below two cases, the request have been handled. * Case 1 - priv_ep->dequeue < current_index * SR ... EQ ... DQ ... CI ... ER * SR ... DQ ... CI ... EQ ... ER * - * Request has been handled by DMA if ST and ET is between DQ and CI. - * * Case 2 - priv_ep->dequeue > current_index - * This situation take place when CI go through the LINK TRB at the end of + * This situation takes place when CI go through the LINK TRB at the end of * transfer ring. * SR ... CI ... EQ ... DQ ... ER - * - * Request has been handled by DMA if ET is less then CI or - * ET is greater or equal DQ. */ -static bool cdns3_request_handled(struct cdns3_endpoint *priv_ep, +static bool cdns3_trb_handled(struct cdns3_endpoint *priv_ep, struct cdns3_request *priv_req) { struct cdns3_device *priv_dev = priv_ep->cdns3_dev; @@ -1391,9 +1431,27 @@ static bool cdns3_request_handled(struct cdns3_endpoint *priv_ep, current_index = cdns3_get_dma_pos(priv_dev, priv_ep); doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); - trb = &priv_ep->trb_pool[priv_req->start_trb]; + /* current trb doesn't belong to this request */ + if (priv_req->start_trb < priv_req->end_trb) { + if (priv_ep->dequeue > priv_req->end_trb) + goto finish; + + if (priv_ep->dequeue < priv_req->start_trb) + goto finish; + } + + if ((priv_req->start_trb > priv_req->end_trb) && + (priv_ep->dequeue > priv_req->end_trb) && + (priv_ep->dequeue < priv_req->start_trb)) + goto finish; - if ((trb->control & TRB_CYCLE) != priv_ep->ccs) + if ((priv_req->start_trb == priv_req->end_trb) && + (priv_ep->dequeue != priv_req->end_trb)) + goto finish; + + trb = &priv_ep->trb_pool[priv_ep->dequeue]; + + if ((le32_to_cpu(trb->control) & TRB_CYCLE) != priv_ep->ccs) goto finish; if (doorbell == 1 && current_index == priv_ep->dequeue) @@ -1413,12 +1471,8 @@ static bool cdns3_request_handled(struct cdns3_endpoint *priv_ep, !priv_ep->dequeue) goto finish; - if (priv_req->end_trb >= priv_ep->dequeue && - priv_req->end_trb < current_index) - handled = 1; + handled = 1; } else if (priv_ep->dequeue > current_index) { - if (priv_req->end_trb < current_index || - priv_req->end_trb >= priv_ep->dequeue) handled = 1; } @@ -1434,6 +1488,8 @@ static void cdns3_transfer_completed(struct cdns3_device *priv_dev, struct cdns3_request *priv_req; struct usb_request *request; struct cdns3_trb *trb; + bool request_handled = false; + bool transfer_end = false; while (!list_empty(&priv_ep->pending_req_list)) { request = cdns3_next_request(&priv_ep->pending_req_list); @@ -1442,7 +1498,7 @@ static void cdns3_transfer_completed(struct cdns3_device *priv_dev, trb = priv_ep->trb_pool + priv_ep->dequeue; /* Request was dequeued and TRB was changed to TRB_LINK. */ - if (TRB_FIELD_TO_TYPE(trb->control) == TRB_LINK) { + if (TRB_FIELD_TO_TYPE(le32_to_cpu(trb->control)) == TRB_LINK) { trace_cdns3_complete_trb(priv_ep, trb); cdns3_move_deq_to_next_trb(priv_req); } @@ -1453,20 +1509,32 @@ static void cdns3_transfer_completed(struct cdns3_device *priv_dev, */ cdns3_select_ep(priv_dev, priv_ep->endpoint.address); - if (!cdns3_request_handled(priv_ep, priv_req)) - goto prepare_next_td; + while (cdns3_trb_handled(priv_ep, priv_req)) { + priv_req->finished_trb++; + if (priv_req->finished_trb >= priv_req->num_of_trb) + request_handled = true; - trb = priv_ep->trb_pool + priv_ep->dequeue; - trace_cdns3_complete_trb(priv_ep, trb); + trb = priv_ep->trb_pool + priv_ep->dequeue; + trace_cdns3_complete_trb(priv_ep, trb); - if (trb != priv_req->trb) - dev_warn(priv_dev->dev, - "request_trb=0x%p, queue_trb=0x%p\n", - priv_req->trb, trb); + if (!transfer_end) + request->actual += + TRB_LEN(le32_to_cpu(trb->length)); - request->actual = TRB_LEN(le32_to_cpu(trb->length)); - cdns3_move_deq_to_next_trb(priv_req); - cdns3_gadget_giveback(priv_ep, priv_req, 0); + if (priv_req->num_of_trb > 1 && + le32_to_cpu(trb->control) & TRB_SMM) + transfer_end = true; + + cdns3_ep_inc_deq(priv_ep); + } + + if (request_handled) { + cdns3_gadget_giveback(priv_ep, priv_req, 0); + request_handled = false; + transfer_end = false; + } else { + goto prepare_next_td; + } if (priv_ep->type != USB_ENDPOINT_XFER_ISOC && TRBS_PER_SEGMENT == 2) @@ -1574,7 +1642,7 @@ static int cdns3_check_ep_interrupt_proceed(struct cdns3_endpoint *priv_ep) * that host ignore the ERDY packet and driver has to send it * again. */ - if (tdl && (dbusy | !EP_STS_BUFFEMPTY(ep_sts_reg) | + if (tdl && (dbusy || !EP_STS_BUFFEMPTY(ep_sts_reg) || EP_STS_HOSTPP(ep_sts_reg))) { writel(EP_CMD_ERDY | EP_CMD_ERDY_SID(priv_ep->last_stream_id), @@ -1769,9 +1837,13 @@ static void cdns3_check_usb_interrupt_proceed(struct cdns3_device *priv_dev, static irqreturn_t cdns3_device_irq_handler(int irq, void *data) { struct cdns3_device *priv_dev = data; + struct cdns3 *cdns = dev_get_drvdata(priv_dev->dev); irqreturn_t ret = IRQ_NONE; u32 reg; + if (cdns->in_lpm) + return ret; + /* check USB device interrupt */ reg = readl(&priv_dev->regs->usb_ists); if (reg) { @@ -2552,10 +2624,10 @@ found: /* Update ring only if removed request is on pending_req_list list */ if (req_on_hw_ring && link_trb) { - link_trb->buffer = TRB_BUFFER(priv_ep->trb_pool_dma + - ((priv_req->end_trb + 1) * TRB_SIZE)); - link_trb->control = (link_trb->control & TRB_CYCLE) | - TRB_TYPE(TRB_LINK) | TRB_CHAIN; + link_trb->buffer = cpu_to_le32(TRB_BUFFER(priv_ep->trb_pool_dma + + ((priv_req->end_trb + 1) * TRB_SIZE))); + link_trb->control = cpu_to_le32((le32_to_cpu(link_trb->control) & TRB_CYCLE) | + TRB_TYPE(TRB_LINK) | TRB_CHAIN); if (priv_ep->wa1_trb == priv_req->trb) cdns3_wa1_restore_cycle_bit(priv_ep); @@ -2610,7 +2682,7 @@ int __cdns3_gadget_ep_clear_halt(struct cdns3_endpoint *priv_ep) priv_req = to_cdns3_request(request); trb = priv_req->trb; if (trb) - trb->control = trb->control ^ TRB_CYCLE; + trb->control = trb->control ^ cpu_to_le32(TRB_CYCLE); } writel(EP_CMD_CSTALL | EP_CMD_EPRST, &priv_dev->regs->ep_cmd); @@ -2625,7 +2697,8 @@ int __cdns3_gadget_ep_clear_halt(struct cdns3_endpoint *priv_ep) if (request) { if (trb) - trb->control = trb->control ^ TRB_CYCLE; + trb->control = trb->control ^ cpu_to_le32(TRB_CYCLE); + cdns3_rearm_transfer(priv_ep, 1); } @@ -2735,10 +2808,13 @@ static int cdns3_gadget_pullup(struct usb_gadget *gadget, int is_on) { struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); - if (is_on) + if (is_on) { writel(USB_CONF_DEVEN, &priv_dev->regs->usb_conf); - else + } else { + writel(~0, &priv_dev->regs->ep_ists); + writel(~0, &priv_dev->regs->usb_ists); writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf); + } return 0; } @@ -2779,6 +2855,8 @@ static void cdns3_gadget_config(struct cdns3_device *priv_dev) /* enable generic interrupt*/ writel(USB_IEN_INIT, ®s->usb_ien); writel(USB_CONF_CLK2OFFDS | USB_CONF_L1DS, ®s->usb_conf); + /* keep Fast Access bit */ + writel(PUSB_PWR_FST_REG_ACCESS, &priv_dev->regs->usb_pwr); cdns3_configure_dmult(priv_dev, NULL); } @@ -2862,6 +2940,7 @@ static int cdns3_gadget_udc_stop(struct usb_gadget *gadget) /* disable interrupt for device */ writel(0, &priv_dev->regs->usb_ien); + writel(0, &priv_dev->regs->usb_pwr); writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf); return 0; @@ -2984,18 +3063,26 @@ err: return -ENOMEM; } +static void cdns3_gadget_release(struct device *dev) +{ + struct cdns3_device *priv_dev = container_of(dev, + struct cdns3_device, gadget.dev); + + kfree(priv_dev); +} + void cdns3_gadget_exit(struct cdns3 *cdns) { struct cdns3_device *priv_dev; priv_dev = cdns->gadget_dev; - devm_free_irq(cdns->dev, cdns->dev_irq, priv_dev); pm_runtime_mark_last_busy(cdns->dev); pm_runtime_put_autosuspend(cdns->dev); - usb_del_gadget_udc(&priv_dev->gadget); + usb_del_gadget(&priv_dev->gadget); + devm_free_irq(cdns->dev, cdns->dev_irq, priv_dev); cdns3_free_all_eps(priv_dev); @@ -3015,7 +3102,7 @@ void cdns3_gadget_exit(struct cdns3 *cdns) priv_dev->setup_dma); kfree(priv_dev->zlp_buf); - kfree(priv_dev); + usb_put_gadget(&priv_dev->gadget); cdns->gadget_dev = NULL; cdns3_drd_gadget_off(cdns); } @@ -3030,6 +3117,8 @@ static int cdns3_gadget_start(struct cdns3 *cdns) if (!priv_dev) return -ENOMEM; + usb_initialize_gadget(cdns->dev, &priv_dev->gadget, + cdns3_gadget_release); cdns->gadget_dev = priv_dev; priv_dev->sysdev = cdns->dev; priv_dev->dev = cdns->dev; @@ -3070,7 +3159,6 @@ static int cdns3_gadget_start(struct cdns3 *cdns) priv_dev->gadget.speed = USB_SPEED_UNKNOWN; priv_dev->gadget.ops = &cdns3_gadget_ops; priv_dev->gadget.name = "usb-ss-gadget"; - priv_dev->gadget.sg_supported = 1; priv_dev->gadget.quirk_avoids_skb_reserve = 1; priv_dev->gadget.irq = cdns->dev_irq; @@ -3109,6 +3197,8 @@ static int cdns3_gadget_start(struct cdns3 *cdns) readl(&priv_dev->regs->usb_cap2)); priv_dev->dev_ver = GET_DEV_BASE_VERSION(priv_dev->dev_ver); + if (priv_dev->dev_ver >= DEV_VER_V2) + priv_dev->gadget.sg_supported = 1; priv_dev->zlp_buf = kzalloc(CDNS3_EP_ZLP_BUF_SIZE, GFP_KERNEL); if (!priv_dev->zlp_buf) { @@ -3117,10 +3207,9 @@ static int cdns3_gadget_start(struct cdns3 *cdns) } /* add USB gadget device */ - ret = usb_add_gadget_udc(priv_dev->dev, &priv_dev->gadget); + ret = usb_add_gadget(&priv_dev->gadget); if (ret < 0) { - dev_err(priv_dev->dev, - "Failed to register USB device controller\n"); + dev_err(priv_dev->dev, "Failed to add gadget\n"); goto err4; } @@ -3133,6 +3222,7 @@ err3: err2: cdns3_free_all_eps(priv_dev); err1: + usb_put_gadget(&priv_dev->gadget); cdns->gadget_dev = NULL; return ret; } diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h index 52765b098b9e..1ccecd237530 100644 --- a/drivers/usb/cdns3/gadget.h +++ b/drivers/usb/cdns3/gadget.h @@ -966,7 +966,7 @@ struct cdns3_usb_regs { /* * USBSS-DEV DMA interface. */ -#define TRBS_PER_SEGMENT 40 +#define TRBS_PER_SEGMENT 600 #define ISO_MAX_INTERVAL 10 @@ -1030,6 +1030,11 @@ struct cdns3_trb { * When set to '1', the device will toggle its interpretation of the Cycle bit */ #define TRB_TOGGLE BIT(1) +/* + * The controller will set it if OUTSMM (OUT size mismatch) is detected, + * this bit is for normal TRB + */ +#define TRB_SMM BIT(1) /* * Short Packet (SP). OUT EPs at DMULT=1 only. Indicates if the TRB was @@ -1215,6 +1220,8 @@ struct cdns3_aligned_buf { * this endpoint * @flags: flag specifying special usage of request * @list: used by internally allocated request to add to wa2_descmiss_req_list. + * @finished_trb: number of trb has already finished per request + * @num_of_trb: how many trbs in this request */ struct cdns3_request { struct usb_request request; @@ -1230,6 +1237,8 @@ struct cdns3_request { #define REQUEST_UNALIGNED BIT(4) u32 flags; struct list_head list; + int finished_trb; + int num_of_trb; }; #define to_cdns3_request(r) (container_of(r, struct cdns3_request, request)) diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c index 36c63d9ecd37..b3e2cb69762c 100644 --- a/drivers/usb/cdns3/host.c +++ b/drivers/usb/cdns3/host.c @@ -13,11 +13,13 @@ #include "core.h" #include "drd.h" #include "host-export.h" +#include <linux/usb/hcd.h> static int __cdns3_host_init(struct cdns3 *cdns) { struct platform_device *xhci; int ret; + struct usb_hcd *hcd; cdns3_drd_host_on(cdns); @@ -43,6 +45,11 @@ static int __cdns3_host_init(struct cdns3 *cdns) goto err1; } + /* Glue needs to access xHCI region register for Power management */ + hcd = platform_get_drvdata(xhci); + if (hcd) + cdns->xhci_regs = hcd->regs; + return 0; err1: platform_device_put(xhci); diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index c39e2b615ac6..25c65accf089 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -165,6 +165,11 @@ static struct imx_usbmisc_data *usbmisc_get_init_data(struct device *dev) if (of_usb_get_phy_mode(np) == USBPHY_INTERFACE_MODE_ULPI) data->ulpi = 1; + of_property_read_u32(np, "samsung,picophy-pre-emp-curr-control", + &data->emp_curr_control); + of_property_read_u32(np, "samsung,picophy-dc-vol-level-adjust", + &data->dc_vol_level_adjust); + return data; } @@ -609,7 +614,12 @@ static int __maybe_unused ci_hdrc_imx_suspend(struct device *dev) } } - return imx_controller_suspend(dev); + ret = imx_controller_suspend(dev); + if (ret) + return ret; + + pinctrl_pm_select_sleep_state(dev); + return ret; } static int __maybe_unused ci_hdrc_imx_resume(struct device *dev) @@ -617,6 +627,7 @@ static int __maybe_unused ci_hdrc_imx_resume(struct device *dev) struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); int ret; + pinctrl_pm_select_default_state(dev); ret = imx_controller_resume(dev); if (!ret && data->supports_runtime_pm) { pm_runtime_disable(dev); diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h index 99f846119c00..999c65390b7f 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.h +++ b/drivers/usb/chipidea/ci_hdrc_imx.h @@ -26,6 +26,8 @@ struct imx_usbmisc_data { unsigned int ext_vbus:1; /* Vbus from exteranl event */ struct usb_phy *usb_phy; enum usb_dr_mode available_role; /* runtime usb dr mode */ + int emp_curr_control; + int dc_vol_level_adjust; }; int imx_usbmisc_init(struct imx_usbmisc_data *data); diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c index 322e4de6b24a..6d8331e7da99 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -128,6 +128,12 @@ #define MX7D_USB_OTG_PHY_STATUS_VBUS_VLD BIT(3) #define MX7D_USB_OTG_PHY_STATUS_CHRGDET BIT(29) +#define MX7D_USB_OTG_PHY_CFG1 0x30 +#define TXPREEMPAMPTUNE0_BIT 28 +#define TXPREEMPAMPTUNE0_MASK (3 << 28) +#define TXVREFTUNE0_BIT 20 +#define TXVREFTUNE0_MASK (0xf << 20) + #define MX6_USB_OTG_WAKEUP_BITS (MX6_BM_WAKEUP_ENABLE | MX6_BM_VBUS_WAKEUP | \ MX6_BM_ID_WAKEUP) @@ -649,6 +655,21 @@ static int usbmisc_imx7d_init(struct imx_usbmisc_data *data) writel(reg | MX7D_USB_VBUS_WAKEUP_SOURCE_BVALID | MX7D_USBNC_AUTO_RESUME, usbmisc->base + MX7D_USBNC_USB_CTRL2); + /* PHY tuning for signal quality */ + reg = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG1); + if (data->emp_curr_control && data->emp_curr_control <= + (TXPREEMPAMPTUNE0_MASK >> TXPREEMPAMPTUNE0_BIT)) { + reg &= ~TXPREEMPAMPTUNE0_MASK; + reg |= (data->emp_curr_control << TXPREEMPAMPTUNE0_BIT); + } + + if (data->dc_vol_level_adjust && data->dc_vol_level_adjust <= + (TXVREFTUNE0_MASK >> TXVREFTUNE0_BIT)) { + reg &= ~TXVREFTUNE0_MASK; + reg |= (data->dc_vol_level_adjust << TXVREFTUNE0_BIT); + } + + writel(reg, usbmisc->base + MX7D_USB_OTG_PHY_CFG1); } spin_unlock_irqrestore(&usbmisc->lock, flags); diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 7f6f3ab5b8a6..30ef946a8e1a 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -173,7 +173,7 @@ static int acm_wb_alloc(struct acm *acm) for (;;) { wb = &acm->wb[wbn]; if (!wb->use) { - wb->use = 1; + wb->use = true; wb->len = 0; return wbn; } @@ -191,7 +191,8 @@ static int acm_wb_is_avail(struct acm *acm) n = ACM_NW; spin_lock_irqsave(&acm->write_lock, flags); for (i = 0; i < ACM_NW; i++) - n -= acm->wb[i].use; + if(acm->wb[i].use) + n--; spin_unlock_irqrestore(&acm->write_lock, flags); return n; } @@ -201,7 +202,7 @@ static int acm_wb_is_avail(struct acm *acm) */ static void acm_write_done(struct acm *acm, struct acm_wb *wb) { - wb->use = 0; + wb->use = false; acm->transmitting--; usb_autopm_put_interface_async(acm->control); } @@ -741,7 +742,7 @@ static void acm_port_shutdown(struct tty_port *port) if (!urb) break; wb = urb->context; - wb->use = 0; + wb->use = false; usb_autopm_put_interface_async(acm->control); } @@ -792,7 +793,7 @@ static int acm_tty_write(struct tty_struct *tty, wb = &acm->wb[wbn]; if (!acm->dev) { - wb->use = 0; + wb->use = false; spin_unlock_irqrestore(&acm->write_lock, flags); return -ENODEV; } @@ -804,7 +805,7 @@ static int acm_tty_write(struct tty_struct *tty, stat = usb_autopm_get_interface_async(acm->control); if (stat) { - wb->use = 0; + wb->use = false; spin_unlock_irqrestore(&acm->write_lock, flags); return stat; } @@ -1196,9 +1197,6 @@ static int acm_probe(struct usb_interface *intf, return -EINVAL; } - if (!intf->cur_altsetting) - return -EINVAL; - if (!buflen) { if (intf->cur_altsetting->endpoint && intf->cur_altsetting->endpoint->extralen && @@ -1221,39 +1219,42 @@ static int acm_probe(struct usb_interface *intf, call_intf_num = cmgmd->bDataInterface; if (!union_header) { - if (call_intf_num > 0) { + if (intf->cur_altsetting->desc.bNumEndpoints == 3) { + dev_dbg(&intf->dev, "No union descriptor, assuming single interface\n"); + combined_interfaces = 1; + control_interface = data_interface = intf; + goto look_for_collapsed_interface; + } else if (call_intf_num > 0) { dev_dbg(&intf->dev, "No union descriptor, using call management descriptor\n"); - /* quirks for Droids MuIn LCD */ - if (quirks & NO_DATA_INTERFACE) { - data_interface = usb_ifnum_to_if(usb_dev, 0); - } else { - data_intf_num = call_intf_num; - data_interface = usb_ifnum_to_if(usb_dev, data_intf_num); - } + data_intf_num = call_intf_num; + data_interface = usb_ifnum_to_if(usb_dev, data_intf_num); control_interface = intf; } else { - if (intf->cur_altsetting->desc.bNumEndpoints != 3) { - dev_dbg(&intf->dev,"No union descriptor, giving up\n"); - return -ENODEV; - } else { - dev_warn(&intf->dev,"No union descriptor, testing for castrated device\n"); - combined_interfaces = 1; - control_interface = data_interface = intf; - goto look_for_collapsed_interface; - } + dev_dbg(&intf->dev, "No union descriptor, giving up\n"); + return -ENODEV; } } else { + int class = -1; + data_intf_num = union_header->bSlaveInterface0; control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0); data_interface = usb_ifnum_to_if(usb_dev, data_intf_num); + + if (control_interface) + class = control_interface->cur_altsetting->desc.bInterfaceClass; + + if (class != USB_CLASS_COMM && class != USB_CLASS_CDC_DATA) { + dev_dbg(&intf->dev, "Broken union descriptor, assuming single interface\n"); + combined_interfaces = 1; + control_interface = data_interface = intf; + goto look_for_collapsed_interface; + } } if (!control_interface || !data_interface) { dev_dbg(&intf->dev, "no interfaces\n"); return -ENODEV; } - if (!data_interface->cur_altsetting || !control_interface->cur_altsetting) - return -ENODEV; if (data_intf_num != call_intf_num) dev_dbg(&intf->dev, "Separate call control interface. That is not fully supported.\n"); @@ -1280,10 +1281,8 @@ look_for_collapsed_interface: skip_normal_probe: /*workaround for switched interfaces */ - if (data_interface->cur_altsetting->desc.bInterfaceClass - != CDC_DATA_INTERFACE_TYPE) { - if (control_interface->cur_altsetting->desc.bInterfaceClass - == CDC_DATA_INTERFACE_TYPE) { + if (data_interface->cur_altsetting->desc.bInterfaceClass != USB_CLASS_CDC_DATA) { + if (control_interface->cur_altsetting->desc.bInterfaceClass == USB_CLASS_CDC_DATA) { dev_dbg(&intf->dev, "Your device has switched interfaces.\n"); swap(control_interface, data_interface); @@ -1876,11 +1875,6 @@ static const struct usb_device_id acm_ids[] = { /* NOTE: non-Nokia COMM/ACM/0xff is likely MSFT RNDIS... NOT a modem! */ - /* Support for Droids MuIn LCD */ - { USB_DEVICE(0x04d8, 0x000b), - .driver_info = NO_DATA_INTERFACE, - }, - #if IS_ENABLED(CONFIG_INPUT_IMS_PCU) { USB_DEVICE(0x04d8, 0x0082), /* Application mode */ .driver_info = IGNORE_DEVICE, @@ -1906,6 +1900,17 @@ static const struct usb_device_id acm_ids[] = { .driver_info = IGNORE_DEVICE, }, + /* Exclude ETAS ES58x */ + { USB_DEVICE(0x108c, 0x0159), /* ES581.4 */ + .driver_info = IGNORE_DEVICE, + }, + { USB_DEVICE(0x108c, 0x0168), /* ES582.1 */ + .driver_info = IGNORE_DEVICE, + }, + { USB_DEVICE(0x108c, 0x0169), /* ES584.1 */ + .driver_info = IGNORE_DEVICE, + }, + { USB_DEVICE(0x1bc7, 0x0021), /* Telit 3G ACM only composition */ .driver_info = SEND_ZERO_PACKET, }, diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index cd5e9d8ab237..9dce179d031b 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -64,12 +64,12 @@ #define ACM_NR 16 struct acm_wb { - unsigned char *buf; + u8 *buf; dma_addr_t dmah; - int len; - int use; + unsigned int len; struct urb *urb; struct acm *instance; + bool use; }; struct acm_rb { @@ -131,15 +131,12 @@ struct acm { unsigned long quirks; }; -#define CDC_DATA_INTERFACE_TYPE 0x0a - /* constants describing various quirks and errors */ #define NO_UNION_NORMAL BIT(0) #define SINGLE_RX_URB BIT(1) #define NO_CAP_LINE BIT(2) -#define NO_DATA_INTERFACE BIT(4) -#define IGNORE_DEVICE BIT(5) -#define QUIRK_CONTROL_LINE_STATE BIT(6) -#define CLEAR_HALT_CONDITIONS BIT(7) -#define SEND_ZERO_PACKET BIT(8) -#define DISABLE_ECHO BIT(9) +#define IGNORE_DEVICE BIT(3) +#define QUIRK_CONTROL_LINE_STATE BIT(4) +#define CLEAR_HALT_CONDITIONS BIT(5) +#define SEND_ZERO_PACKET BIT(6) +#define DISABLE_ECHO BIT(7) diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 7f5de956a2fc..02d0cfd23bb2 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -58,6 +58,9 @@ MODULE_DEVICE_TABLE (usb, wdm_ids); #define WDM_MAX 16 +/* we cannot wait forever at flush() */ +#define WDM_FLUSH_TIMEOUT (30 * HZ) + /* CDC-WMC r1.1 requires wMaxCommand to be "at least 256 decimal (0x100)" */ #define WDM_DEFAULT_BUFSIZE 256 @@ -151,7 +154,7 @@ static void wdm_out_callback(struct urb *urb) kfree(desc->outbuf); desc->outbuf = NULL; clear_bit(WDM_IN_USE, &desc->flags); - wake_up(&desc->wait); + wake_up_all(&desc->wait); } static void wdm_in_callback(struct urb *urb) @@ -393,6 +396,9 @@ static ssize_t wdm_write if (test_bit(WDM_RESETTING, &desc->flags)) r = -EIO; + if (test_bit(WDM_DISCONNECTING, &desc->flags)) + r = -ENODEV; + if (r < 0) { rv = r; goto out_free_mem_pm; @@ -424,6 +430,7 @@ static ssize_t wdm_write if (rv < 0) { desc->outbuf = NULL; clear_bit(WDM_IN_USE, &desc->flags); + wake_up_all(&desc->wait); /* for wdm_wait_for_response() */ dev_err(&desc->intf->dev, "Tx URB error: %d\n", rv); rv = usb_translate_errors(rv); goto out_free_mem_pm; @@ -583,28 +590,58 @@ err: return rv; } -static int wdm_flush(struct file *file, fl_owner_t id) +static int wdm_wait_for_response(struct file *file, long timeout) { struct wdm_device *desc = file->private_data; + long rv; /* Use long here because (int) MAX_SCHEDULE_TIMEOUT < 0. */ + + /* + * Needs both flags. We cannot do with one because resetting it would + * cause a race with write() yet we need to signal a disconnect. + */ + rv = wait_event_interruptible_timeout(desc->wait, + !test_bit(WDM_IN_USE, &desc->flags) || + test_bit(WDM_DISCONNECTING, &desc->flags), + timeout); - wait_event(desc->wait, - /* - * needs both flags. We cannot do with one - * because resetting it would cause a race - * with write() yet we need to signal - * a disconnect - */ - !test_bit(WDM_IN_USE, &desc->flags) || - test_bit(WDM_DISCONNECTING, &desc->flags)); - - /* cannot dereference desc->intf if WDM_DISCONNECTING */ + /* + * To report the correct error. This is best effort. + * We are inevitably racing with the hardware. + */ if (test_bit(WDM_DISCONNECTING, &desc->flags)) return -ENODEV; - if (desc->werr < 0) - dev_err(&desc->intf->dev, "Error in flush path: %d\n", - desc->werr); + if (!rv) + return -EIO; + if (rv < 0) + return -EINTR; + + spin_lock_irq(&desc->iuspin); + rv = desc->werr; + desc->werr = 0; + spin_unlock_irq(&desc->iuspin); + + return usb_translate_errors(rv); + +} + +/* + * You need to send a signal when you react to malicious or defective hardware. + * Also, don't abort when fsync() returned -EINVAL, for older kernels which do + * not implement wdm_flush() will return -EINVAL. + */ +static int wdm_fsync(struct file *file, loff_t start, loff_t end, int datasync) +{ + return wdm_wait_for_response(file, MAX_SCHEDULE_TIMEOUT); +} - return usb_translate_errors(desc->werr); +/* + * Same with wdm_fsync(), except it uses finite timeout in order to react to + * malicious or defective hardware which ceased communication after close() was + * implicitly called due to process termination. + */ +static int wdm_flush(struct file *file, fl_owner_t id) +{ + return wdm_wait_for_response(file, WDM_FLUSH_TIMEOUT); } static __poll_t wdm_poll(struct file *file, struct poll_table_struct *wait) @@ -729,6 +766,7 @@ static const struct file_operations wdm_fops = { .owner = THIS_MODULE, .read = wdm_read, .write = wdm_write, + .fsync = wdm_fsync, .open = wdm_open, .flush = wdm_flush, .release = wdm_release, diff --git a/drivers/usb/common/usb-conn-gpio.c b/drivers/usb/common/usb-conn-gpio.c index 7b3a21360d7c..6c4e3a19f42c 100644 --- a/drivers/usb/common/usb-conn-gpio.c +++ b/drivers/usb/common/usb-conn-gpio.c @@ -91,14 +91,14 @@ static void usb_conn_detect_cable(struct work_struct *work) return; } - if (info->last_role == USB_ROLE_HOST) + if (info->last_role == USB_ROLE_HOST && info->vbus) regulator_disable(info->vbus); ret = usb_role_switch_set_role(info->role_sw, role); if (ret) dev_err(info->dev, "failed to set role: %d\n", ret); - if (role == USB_ROLE_HOST) { + if (role == USB_ROLE_HOST && info->vbus) { ret = regulator_enable(info->vbus); if (ret) dev_err(info->dev, "enable vbus regulator failed\n"); @@ -106,8 +106,9 @@ static void usb_conn_detect_cable(struct work_struct *work) info->last_role = role; - dev_dbg(info->dev, "vbus regulator is %s\n", - regulator_is_enabled(info->vbus) ? "enabled" : "disabled"); + if (info->vbus) + dev_dbg(info->dev, "vbus regulator is %s\n", + regulator_is_enabled(info->vbus) ? "enabled" : "disabled"); power_supply_changed(info->charger); } @@ -156,6 +157,7 @@ static int usb_conn_probe(struct platform_device *pdev) struct power_supply_config cfg = { .of_node = dev->of_node, }; + bool need_vbus = true; int ret = 0; info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); @@ -185,10 +187,26 @@ static int usb_conn_probe(struct platform_device *pdev) INIT_DELAYED_WORK(&info->dw_det, usb_conn_detect_cable); - info->vbus = devm_regulator_get(dev, "vbus"); + /* + * If the USB connector is a child of a USB port and that port already provides the VBUS + * supply, there's no need for the USB connector to provide it again. + */ + if (dev->parent && dev->parent->of_node) { + if (of_find_property(dev->parent->of_node, "vbus-supply", NULL)) + need_vbus = false; + } + + if (!need_vbus) { + info->vbus = devm_regulator_get_optional(dev, "vbus"); + if (PTR_ERR(info->vbus) == -ENODEV) + info->vbus = NULL; + } else { + info->vbus = devm_regulator_get(dev, "vbus"); + } + if (IS_ERR(info->vbus)) { if (PTR_ERR(info->vbus) != -EPROBE_DEFER) - dev_err(dev, "failed to get vbus\n"); + dev_err(dev, "failed to get vbus: %ld\n", PTR_ERR(info->vbus)); return PTR_ERR(info->vbus); } @@ -266,7 +284,7 @@ static int usb_conn_remove(struct platform_device *pdev) cancel_delayed_work_sync(&info->dw_det); - if (info->last_role == USB_ROLE_HOST) + if (info->last_role == USB_ROLE_HOST && info->vbus) regulator_disable(info->vbus); usb_role_switch_put(info->role_sw); diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig index dfacc478a8fc..351ede4b5de2 100644 --- a/drivers/usb/core/Kconfig +++ b/drivers/usb/core/Kconfig @@ -32,6 +32,20 @@ config USB_DEFAULT_PERSIST If you have any questions about this, say Y here, only say N if you know exactly what you are doing. +config USB_FEW_INIT_RETRIES + bool "Limit USB device initialization to only a few retries" + help + When a new USB device is detected, the kernel tries very hard + to initialize and enumerate it, with lots of nested retry loops. + This almost always works, but when it fails it can take a long time. + This option tells the kernel to make only a few retry attempts, + so that the total time required for a failed initialization is + no more than 30 seconds (as required by the USB OTG spec). + + Say N here unless you require new-device enumeration failure to + occur within 30 seconds (as might be needed in an embedded + application). + config USB_DYNAMIC_MINORS bool "Dynamic USB minor allocation" help diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c index 696b2b692b83..1ef2de6e375a 100644 --- a/drivers/usb/core/devices.c +++ b/drivers/usb/core/devices.c @@ -39,7 +39,6 @@ #include <linux/fs.h> #include <linux/mm.h> #include <linux/gfp.h> -#include <linux/poll.h> #include <linux/usb.h> #include <linux/usbdevice_fs.h> #include <linux/usb/hcd.h> @@ -97,22 +96,6 @@ static const char format_endpt[] = /* E: Ad=xx(s) Atr=xx(ssss) MxPS=dddd Ivl=D?s */ "E: Ad=%02x(%c) Atr=%02x(%-4s) MxPS=%4d Ivl=%d%cs\n"; -/* - * Wait for an connect/disconnect event to happen. We initialize - * the event counter with an odd number, and each event will increment - * the event counter by two, so it will always _stay_ odd. That means - * that it will never be zero, so "event 0" will never match a current - * event, and thus 'poll' will always trigger as readable for the first - * time it gets called. - */ -static struct device_connect_event { - atomic_t count; - wait_queue_head_t wait; -} device_event = { - .count = ATOMIC_INIT(1), - .wait = __WAIT_QUEUE_HEAD_INITIALIZER(device_event.wait) -}; - struct class_info { int class; char *class_name; @@ -146,12 +129,6 @@ static const struct class_info clas_info[] = { /*****************************************************************/ -void usbfs_conn_disc_event(void) -{ - atomic_add(2, &device_event.count); - wake_up(&device_event.wait); -} - static const char *class_decode(const int class) { int ix; @@ -623,25 +600,7 @@ static ssize_t usb_device_read(struct file *file, char __user *buf, return total_written; } -/* Kernel lock for "lastev" protection */ -static __poll_t usb_device_poll(struct file *file, - struct poll_table_struct *wait) -{ - unsigned int event_count; - - poll_wait(file, &device_event.wait, wait); - - event_count = atomic_read(&device_event.count); - if (file->f_version != event_count) { - file->f_version = event_count; - return EPOLLIN | EPOLLRDNORM; - } - - return 0; -} - const struct file_operations usbfs_devices_fops = { .llseek = no_seek_end_llseek, .read = usb_device_read, - .poll = usb_device_poll, }; diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index b351962279e4..98b7449c11f3 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -991,8 +991,7 @@ int usb_register_device_driver(struct usb_device_driver *new_udriver, bus_for_each_dev(&usb_bus_type, NULL, new_udriver, __usb_bus_reprobe_drivers); } else { - printk(KERN_ERR "%s: error %d registering device " - " driver %s\n", + pr_err("%s: error %d registering device driver %s\n", usbcore_name, retval, new_udriver->name); } @@ -1068,9 +1067,8 @@ out: out_newid: driver_unregister(&new_driver->drvwrap.driver); - printk(KERN_ERR "%s: error %d registering interface " - " driver %s\n", - usbcore_name, retval, new_driver->name); + pr_err("%s: error %d registering interface driver %s\n", + usbcore_name, retval, new_driver->name); goto out; } EXPORT_SYMBOL_GPL(usb_register_driver); diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c index 2b2f1ab6e36a..22c887f5c497 100644 --- a/drivers/usb/core/generic.c +++ b/drivers/usb/core/generic.c @@ -195,7 +195,7 @@ int usb_choose_configuration(struct usb_device *udev) } EXPORT_SYMBOL_GPL(usb_choose_configuration); -static int __check_usb_generic(struct device_driver *drv, void *data) +static int __check_for_non_generic_match(struct device_driver *drv, void *data) { struct usb_device *udev = data; struct usb_device_driver *udrv; @@ -219,7 +219,7 @@ static bool usb_generic_driver_match(struct usb_device *udev) * If any other driver wants the device, leave the device to this other * driver. */ - if (bus_for_each_drv(&usb_bus_type, NULL, udev, __check_usb_generic)) + if (bus_for_each_drv(&usb_bus_type, NULL, udev, __check_for_non_generic_match)) return false; return true; diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index a33b849e8beb..2c6b9578a7d3 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1657,9 +1657,9 @@ static void __usb_hcd_giveback_urb(struct urb *urb) usb_put_urb(urb); } -static void usb_giveback_urb_bh(unsigned long param) +static void usb_giveback_urb_bh(struct tasklet_struct *t) { - struct giveback_urb_bh *bh = (struct giveback_urb_bh *)param; + struct giveback_urb_bh *bh = from_tasklet(bh, t, bh); struct list_head local_list; spin_lock_irq(&bh->lock); @@ -2403,7 +2403,7 @@ static void init_giveback_urb_bh(struct giveback_urb_bh *bh) spin_lock_init(&bh->lock); INIT_LIST_HEAD(&bh->head); - tasklet_init(&bh->bh, usb_giveback_urb_bh, (unsigned long)bh); + tasklet_setup(&bh->bh, usb_giveback_urb_bh); } struct usb_hcd *__usb_create_hcd(const struct hc_driver *driver, diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 5b768b80d1ee..17202b2ee063 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2705,11 +2705,20 @@ static unsigned hub_is_wusb(struct usb_hub *hub) } +#ifdef CONFIG_USB_FEW_INIT_RETRIES +#define PORT_RESET_TRIES 2 +#define SET_ADDRESS_TRIES 1 +#define GET_DESCRIPTOR_TRIES 1 +#define GET_MAXPACKET0_TRIES 1 +#define PORT_INIT_TRIES 4 + +#else #define PORT_RESET_TRIES 5 #define SET_ADDRESS_TRIES 2 #define GET_DESCRIPTOR_TRIES 2 -#define SET_CONFIG_TRIES (2 * (use_both_schemes + 1)) -#define USE_NEW_SCHEME(i, scheme) ((i) / 2 == (int)(scheme)) +#define GET_MAXPACKET0_TRIES 3 +#define PORT_INIT_TRIES 4 +#endif /* CONFIG_USB_FEW_INIT_RETRIES */ #define HUB_ROOT_RESET_TIME 60 /* times are in msec */ #define HUB_SHORT_RESET_TIME 10 @@ -2717,23 +2726,31 @@ static unsigned hub_is_wusb(struct usb_hub *hub) #define HUB_LONG_RESET_TIME 200 #define HUB_RESET_TIMEOUT 800 -/* - * "New scheme" enumeration causes an extra state transition to be - * exposed to an xhci host and causes USB3 devices to receive control - * commands in the default state. This has been seen to cause - * enumeration failures, so disable this enumeration scheme for USB3 - * devices. - */ static bool use_new_scheme(struct usb_device *udev, int retry, struct usb_port *port_dev) { int old_scheme_first_port = - port_dev->quirks & USB_PORT_QUIRK_OLD_SCHEME; + (port_dev->quirks & USB_PORT_QUIRK_OLD_SCHEME) || + old_scheme_first; + /* + * "New scheme" enumeration causes an extra state transition to be + * exposed to an xhci host and causes USB3 devices to receive control + * commands in the default state. This has been seen to cause + * enumeration failures, so disable this enumeration scheme for USB3 + * devices. + */ if (udev->speed >= USB_SPEED_SUPER) return false; - return USE_NEW_SCHEME(retry, old_scheme_first_port || old_scheme_first); + /* + * If use_both_schemes is set, use the first scheme (whichever + * it is) for the larger half of the retries, then use the other + * scheme. Otherwise, use the first scheme for all the retries. + */ + if (use_both_schemes && retry >= (PORT_INIT_TRIES + 1) / 2) + return old_scheme_first_port; /* Second half */ + return !old_scheme_first_port; /* First half or all */ } /* Is a USB 3.0 port in the Inactive or Compliance Mode state? @@ -4545,6 +4562,7 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, const char *speed; int devnum = udev->devnum; const char *driver_name; + bool do_new_scheme; /* root hub ports have a slightly longer reset period * (from USB 2.0 spec, section 7.1.7.5) @@ -4657,14 +4675,13 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, * first 8 bytes of the device descriptor to get the ep0 maxpacket * value. */ - for (retries = 0; retries < GET_DESCRIPTOR_TRIES; (++retries, msleep(100))) { - bool did_new_scheme = false; + do_new_scheme = use_new_scheme(udev, retry_counter, port_dev); - if (use_new_scheme(udev, retry_counter, port_dev)) { + for (retries = 0; retries < GET_DESCRIPTOR_TRIES; (++retries, msleep(100))) { + if (do_new_scheme) { struct usb_device_descriptor *buf; int r = 0; - did_new_scheme = true; retval = hub_enable_device(udev); if (retval < 0) { dev_err(&udev->dev, @@ -4684,7 +4701,8 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, * 255 is for WUSB devices, we actually need to use * 512 (WUSB1.0[4.8.1]). */ - for (operations = 0; operations < 3; ++operations) { + for (operations = 0; operations < GET_MAXPACKET0_TRIES; + ++operations) { buf->bMaxPacketSize0 = 0; r = usb_control_msg(udev, usb_rcvaddr0pipe(), USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, @@ -4773,11 +4791,7 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, * - read ep0 maxpacket even for high and low speed, */ msleep(10); - /* use_new_scheme() checks the speed which may have - * changed since the initial look so we cache the result - * in did_new_scheme - */ - if (did_new_scheme) + if (do_new_scheme) break; } @@ -5106,7 +5120,7 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus, unit_load = 100; status = 0; - for (i = 0; i < SET_CONFIG_TRIES; i++) { + for (i = 0; i < PORT_INIT_TRIES; i++) { /* reallocate for each attempt, since references * to the previous one can escape in various ways @@ -5239,7 +5253,7 @@ loop: break; /* When halfway through our retry count, power-cycle the port */ - if (i == (SET_CONFIG_TRIES / 2) - 1) { + if (i == (PORT_INIT_TRIES - 1) / 2) { dev_info(&port_dev->dev, "attempt power cycle\n"); usb_hub_set_port_power(hdev, hub, port1, false); msleep(2 * hub_power_on_good_delay(hub)); @@ -5770,7 +5784,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev) bos = udev->bos; udev->bos = NULL; - for (i = 0; i < SET_CONFIG_TRIES; ++i) { + for (i = 0; i < PORT_INIT_TRIES; ++i) { /* ep0 maxpacket size may change; let the HCD know about it. * Other endpoints will be handled by re-enumeration. */ diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index ae1de9cc4b09..f4107b9e8c38 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -163,6 +163,143 @@ int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, EXPORT_SYMBOL_GPL(usb_control_msg); /** + * usb_control_msg_send - Builds a control "send" message, sends it off and waits for completion + * @dev: pointer to the usb device to send the message to + * @endpoint: endpoint to send the message to + * @request: USB message request value + * @requesttype: USB message request type value + * @value: USB message value + * @index: USB message index value + * @driver_data: pointer to the data to send + * @size: length in bytes of the data to send + * @timeout: time in msecs to wait for the message to complete before timing + * out (if 0 the wait is forever) + * @memflags: the flags for memory allocation for buffers + * + * Context: !in_interrupt () + * + * This function sends a control message to a specified endpoint that is not + * expected to fill in a response (i.e. a "send message") and waits for the + * message to complete, or timeout. + * + * Do not use this function from within an interrupt context. If you need + * an asynchronous message, or need to send a message from within interrupt + * context, use usb_submit_urb(). If a thread in your driver uses this call, + * make sure your disconnect() method can wait for it to complete. Since you + * don't have a handle on the URB used, you can't cancel the request. + * + * The data pointer can be made to a reference on the stack, or anywhere else, + * as it will not be modified at all. This does not have the restriction that + * usb_control_msg() has where the data pointer must be to dynamically allocated + * memory (i.e. memory that can be successfully DMAed to a device). + * + * Return: If successful, 0 is returned, Otherwise, a negative error number. + */ +int usb_control_msg_send(struct usb_device *dev, __u8 endpoint, __u8 request, + __u8 requesttype, __u16 value, __u16 index, + const void *driver_data, __u16 size, int timeout, + gfp_t memflags) +{ + unsigned int pipe = usb_sndctrlpipe(dev, endpoint); + int ret; + u8 *data = NULL; + + if (usb_pipe_type_check(dev, pipe)) + return -EINVAL; + + if (size) { + data = kmemdup(driver_data, size, memflags); + if (!data) + return -ENOMEM; + } + + ret = usb_control_msg(dev, pipe, request, requesttype, value, index, + data, size, timeout); + kfree(data); + + if (ret < 0) + return ret; + if (ret == size) + return 0; + return -EINVAL; +} +EXPORT_SYMBOL_GPL(usb_control_msg_send); + +/** + * usb_control_msg_recv - Builds a control "receive" message, sends it off and waits for completion + * @dev: pointer to the usb device to send the message to + * @endpoint: endpoint to send the message to + * @request: USB message request value + * @requesttype: USB message request type value + * @value: USB message value + * @index: USB message index value + * @driver_data: pointer to the data to be filled in by the message + * @size: length in bytes of the data to be received + * @timeout: time in msecs to wait for the message to complete before timing + * out (if 0 the wait is forever) + * @memflags: the flags for memory allocation for buffers + * + * Context: !in_interrupt () + * + * This function sends a control message to a specified endpoint that is + * expected to fill in a response (i.e. a "receive message") and waits for the + * message to complete, or timeout. + * + * Do not use this function from within an interrupt context. If you need + * an asynchronous message, or need to send a message from within interrupt + * context, use usb_submit_urb(). If a thread in your driver uses this call, + * make sure your disconnect() method can wait for it to complete. Since you + * don't have a handle on the URB used, you can't cancel the request. + * + * The data pointer can be made to a reference on the stack, or anywhere else + * that can be successfully written to. This function does not have the + * restriction that usb_control_msg() has where the data pointer must be to + * dynamically allocated memory (i.e. memory that can be successfully DMAed to a + * device). + * + * The "whole" message must be properly received from the device in order for + * this function to be successful. If a device returns less than the expected + * amount of data, then the function will fail. Do not use this for messages + * where a variable amount of data might be returned. + * + * Return: If successful, 0 is returned, Otherwise, a negative error number. + */ +int usb_control_msg_recv(struct usb_device *dev, __u8 endpoint, __u8 request, + __u8 requesttype, __u16 value, __u16 index, + void *driver_data, __u16 size, int timeout, + gfp_t memflags) +{ + unsigned int pipe = usb_rcvctrlpipe(dev, endpoint); + int ret; + u8 *data; + + if (!size || !driver_data || usb_pipe_type_check(dev, pipe)) + return -EINVAL; + + data = kmalloc(size, memflags); + if (!data) + return -ENOMEM; + + ret = usb_control_msg(dev, pipe, request, requesttype, value, index, + data, size, timeout); + + if (ret < 0) + goto exit; + + if (ret == size) { + memcpy(driver_data, data, size); + ret = 0; + } else { + ret = -EINVAL; + } + +exit: + kfree(data); + return ret; +} +EXPORT_SYMBOL_GPL(usb_control_msg_recv); + +/** * usb_interrupt_msg - Builds an interrupt urb, sends it off and waits for completion * @usb_dev: pointer to the usb device to send the message to * @pipe: endpoint "pipe" to send the message to @@ -948,11 +1085,12 @@ int usb_set_isoch_delay(struct usb_device *dev) if (dev->speed < USB_SPEED_SUPER) return 0; - return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + return usb_control_msg_send(dev, 0, USB_REQ_SET_ISOCH_DELAY, USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, dev->hub_delay, 0, NULL, 0, - USB_CTRL_SET_TIMEOUT); + USB_CTRL_SET_TIMEOUT, + GFP_NOIO); } /** @@ -1070,13 +1208,13 @@ int usb_clear_halt(struct usb_device *dev, int pipe) * (like some ibmcam model 1 units) seem to expect hosts to make * this request for iso endpoints, which can't halt! */ - result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), - USB_REQ_CLEAR_FEATURE, USB_RECIP_ENDPOINT, - USB_ENDPOINT_HALT, endp, NULL, 0, - USB_CTRL_SET_TIMEOUT); + result = usb_control_msg_send(dev, 0, + USB_REQ_CLEAR_FEATURE, USB_RECIP_ENDPOINT, + USB_ENDPOINT_HALT, endp, NULL, 0, + USB_CTRL_SET_TIMEOUT, GFP_NOIO); /* don't un-halt or force to DATA0 except on success */ - if (result < 0) + if (result) return result; /* NOTE: seems like Microsoft and Apple don't bother verifying @@ -1438,9 +1576,11 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate) if (dev->quirks & USB_QUIRK_NO_SET_INTF) ret = -EPIPE; else - ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), - USB_REQ_SET_INTERFACE, USB_RECIP_INTERFACE, - alternate, interface, NULL, 0, 5000); + ret = usb_control_msg_send(dev, 0, + USB_REQ_SET_INTERFACE, + USB_RECIP_INTERFACE, alternate, + interface, NULL, 0, 5000, + GFP_NOIO); /* 9.4.10 says devices don't need this and are free to STALL the * request if the interface only has one alternate setting. @@ -1450,7 +1590,7 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate) "manual set_interface for iface %d, alt %d\n", interface, alternate); manual = 1; - } else if (ret < 0) { + } else if (ret) { /* Re-instate the old alt setting */ usb_hcd_alloc_bandwidth(dev, NULL, alt, iface->cur_altsetting); usb_enable_lpm(dev); @@ -1574,11 +1714,11 @@ int usb_reset_configuration(struct usb_device *dev) mutex_unlock(hcd->bandwidth_mutex); return retval; } - retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), - USB_REQ_SET_CONFIGURATION, 0, - config->desc.bConfigurationValue, 0, - NULL, 0, USB_CTRL_SET_TIMEOUT); - if (retval < 0) { + retval = usb_control_msg_send(dev, 0, USB_REQ_SET_CONFIGURATION, 0, + config->desc.bConfigurationValue, 0, + NULL, 0, USB_CTRL_SET_TIMEOUT, + GFP_NOIO); + if (retval) { usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL); usb_enable_lpm(dev); mutex_unlock(hcd->bandwidth_mutex); @@ -1963,10 +2103,10 @@ free_interfaces: } kfree(new_interfaces); - ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), - USB_REQ_SET_CONFIGURATION, 0, configuration, 0, - NULL, 0, USB_CTRL_SET_TIMEOUT); - if (ret < 0 && cp) { + ret = usb_control_msg_send(dev, 0, USB_REQ_SET_CONFIGURATION, 0, + configuration, 0, NULL, 0, + USB_CTRL_SET_TIMEOUT, GFP_NOIO); + if (ret && cp) { /* * All the old state is gone, so what else can we do? * The device is probably useless now anyway. diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c index 7bc23469f4e4..357b149b20d3 100644 --- a/drivers/usb/core/urb.c +++ b/drivers/usb/core/urb.c @@ -192,24 +192,39 @@ static const int pipetypes[4] = { }; /** - * usb_urb_ep_type_check - sanity check of endpoint in the given urb - * @urb: urb to be checked + * usb_pipe_type_check - sanity check of a specific pipe for a usb device + * @dev: struct usb_device to be checked + * @pipe: pipe to check * * This performs a light-weight sanity check for the endpoint in the - * given urb. It returns 0 if the urb contains a valid endpoint, otherwise - * a negative error code. + * given usb device. It returns 0 if the pipe is valid for the specific usb + * device, otherwise a negative error code. */ -int usb_urb_ep_type_check(const struct urb *urb) +int usb_pipe_type_check(struct usb_device *dev, unsigned int pipe) { const struct usb_host_endpoint *ep; - ep = usb_pipe_endpoint(urb->dev, urb->pipe); + ep = usb_pipe_endpoint(dev, pipe); if (!ep) return -EINVAL; - if (usb_pipetype(urb->pipe) != pipetypes[usb_endpoint_type(&ep->desc)]) + if (usb_pipetype(pipe) != pipetypes[usb_endpoint_type(&ep->desc)]) return -EINVAL; return 0; } +EXPORT_SYMBOL_GPL(usb_pipe_type_check); + +/** + * usb_urb_ep_type_check - sanity check of endpoint in the given urb + * @urb: urb to be checked + * + * This performs a light-weight sanity check for the endpoint in the + * given urb. It returns 0 if the urb contains a valid endpoint, otherwise + * a negative error code. + */ +int usb_urb_ep_type_check(const struct urb *urb) +{ + return usb_pipe_type_check(urb->dev, urb->pipe); +} EXPORT_SYMBOL_GPL(usb_urb_ep_type_check); /** @@ -474,7 +489,7 @@ int usb_submit_urb(struct urb *urb, gfp_t mem_flags) */ /* Check that the pipe's type matches the endpoint's type */ - if (usb_urb_ep_type_check(urb)) + if (usb_pipe_type_check(urb->dev, urb->pipe)) dev_WARN(&dev->dev, "BOGUS urb xfer, pipe %x != type %x\n", usb_pipetype(urb->pipe), pipetypes[xfertype]); @@ -772,11 +787,12 @@ void usb_block_urb(struct urb *urb) EXPORT_SYMBOL_GPL(usb_block_urb); /** - * usb_kill_anchored_urbs - cancel transfer requests en masse + * usb_kill_anchored_urbs - kill all URBs associated with an anchor * @anchor: anchor the requests are bound to * - * this allows all outstanding URBs to be killed starting - * from the back of the queue + * This kills all outstanding URBs starting from the back of the queue, + * with guarantee that no completer callbacks will take place from the + * anchor after this function returns. * * This routine should not be called by a driver after its disconnect * method has returned. @@ -784,20 +800,26 @@ EXPORT_SYMBOL_GPL(usb_block_urb); void usb_kill_anchored_urbs(struct usb_anchor *anchor) { struct urb *victim; + int surely_empty; - spin_lock_irq(&anchor->lock); - while (!list_empty(&anchor->urb_list)) { - victim = list_entry(anchor->urb_list.prev, struct urb, - anchor_list); - /* we must make sure the URB isn't freed before we kill it*/ - usb_get_urb(victim); - spin_unlock_irq(&anchor->lock); - /* this will unanchor the URB */ - usb_kill_urb(victim); - usb_put_urb(victim); + do { spin_lock_irq(&anchor->lock); - } - spin_unlock_irq(&anchor->lock); + while (!list_empty(&anchor->urb_list)) { + victim = list_entry(anchor->urb_list.prev, + struct urb, anchor_list); + /* make sure the URB isn't freed before we kill it */ + usb_get_urb(victim); + spin_unlock_irq(&anchor->lock); + /* this will unanchor the URB */ + usb_kill_urb(victim); + usb_put_urb(victim); + spin_lock_irq(&anchor->lock); + } + surely_empty = usb_anchor_check_wakeup(anchor); + + spin_unlock_irq(&anchor->lock); + cpu_relax(); + } while (!surely_empty); } EXPORT_SYMBOL_GPL(usb_kill_anchored_urbs); @@ -816,21 +838,27 @@ EXPORT_SYMBOL_GPL(usb_kill_anchored_urbs); void usb_poison_anchored_urbs(struct usb_anchor *anchor) { struct urb *victim; + int surely_empty; - spin_lock_irq(&anchor->lock); - anchor->poisoned = 1; - while (!list_empty(&anchor->urb_list)) { - victim = list_entry(anchor->urb_list.prev, struct urb, - anchor_list); - /* we must make sure the URB isn't freed before we kill it*/ - usb_get_urb(victim); - spin_unlock_irq(&anchor->lock); - /* this will unanchor the URB */ - usb_poison_urb(victim); - usb_put_urb(victim); + do { spin_lock_irq(&anchor->lock); - } - spin_unlock_irq(&anchor->lock); + anchor->poisoned = 1; + while (!list_empty(&anchor->urb_list)) { + victim = list_entry(anchor->urb_list.prev, + struct urb, anchor_list); + /* make sure the URB isn't freed before we kill it */ + usb_get_urb(victim); + spin_unlock_irq(&anchor->lock); + /* this will unanchor the URB */ + usb_poison_urb(victim); + usb_put_urb(victim); + spin_lock_irq(&anchor->lock); + } + surely_empty = usb_anchor_check_wakeup(anchor); + + spin_unlock_irq(&anchor->lock); + cpu_relax(); + } while (!surely_empty); } EXPORT_SYMBOL_GPL(usb_poison_anchored_urbs); @@ -970,14 +998,20 @@ void usb_scuttle_anchored_urbs(struct usb_anchor *anchor) { struct urb *victim; unsigned long flags; + int surely_empty; + + do { + spin_lock_irqsave(&anchor->lock, flags); + while (!list_empty(&anchor->urb_list)) { + victim = list_entry(anchor->urb_list.prev, + struct urb, anchor_list); + __usb_unanchor_urb(victim, anchor); + } + surely_empty = usb_anchor_check_wakeup(anchor); - spin_lock_irqsave(&anchor->lock, flags); - while (!list_empty(&anchor->urb_list)) { - victim = list_entry(anchor->urb_list.prev, struct urb, - anchor_list); - __usb_unanchor_urb(victim, anchor); - } - spin_unlock_irqrestore(&anchor->lock, flags); + spin_unlock_irqrestore(&anchor->lock, flags); + cpu_relax(); + } while (!surely_empty); } EXPORT_SYMBOL_GPL(usb_scuttle_anchored_urbs); diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 98e7d1ee63dc..c893f54a3420 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -191,7 +191,6 @@ extern const struct attribute_group *usb_interface_groups[]; extern struct usb_driver usbfs_driver; extern const struct file_operations usbfs_devices_fops; extern const struct file_operations usbdev_file_operations; -extern void usbfs_conn_disc_event(void); extern int usb_devio_init(void); extern void usb_devio_cleanup(void); diff --git a/drivers/usb/dwc2/Kconfig b/drivers/usb/dwc2/Kconfig index 16e1aa304edc..c131719367ec 100644 --- a/drivers/usb/dwc2/Kconfig +++ b/drivers/usb/dwc2/Kconfig @@ -5,6 +5,7 @@ config USB_DWC2 depends on HAS_DMA depends on USB || USB_GADGET depends on HAS_IOMEM + select USB_ROLE_SWITCH help Say Y here if your system has a Dual Role Hi-Speed USB controller based on the DesignWare HSOTG IP Core. diff --git a/drivers/usb/dwc2/Makefile b/drivers/usb/dwc2/Makefile index 440320cc20a4..2bcd6945df46 100644 --- a/drivers/usb/dwc2/Makefile +++ b/drivers/usb/dwc2/Makefile @@ -3,7 +3,7 @@ ccflags-$(CONFIG_USB_DWC2_DEBUG) += -DDEBUG ccflags-$(CONFIG_USB_DWC2_VERBOSE) += -DVERBOSE_DEBUG obj-$(CONFIG_USB_DWC2) += dwc2.o -dwc2-y := core.o core_intr.o platform.o +dwc2-y := core.o core_intr.o platform.o drd.o dwc2-y += params.o ifneq ($(filter y,$(CONFIG_USB_DWC2_HOST) $(CONFIG_USB_DWC2_DUAL_ROLE)),) diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 9deff0400a92..7161344c6522 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -860,6 +860,7 @@ struct dwc2_hregs_backup { * - USB_DR_MODE_PERIPHERAL * - USB_DR_MODE_HOST * - USB_DR_MODE_OTG + * @role_sw: usb_role_switch handle * @hcd_enabled: Host mode sub-driver initialization indicator. * @gadget_enabled: Peripheral mode sub-driver initialization indicator. * @ll_hw_enabled: Status of low-level hardware resources. @@ -1054,6 +1055,7 @@ struct dwc2_hsotg { struct dwc2_core_params params; enum usb_otg_state op_state; enum usb_dr_mode dr_mode; + struct usb_role_switch *role_sw; unsigned int hcd_enabled:1; unsigned int gadget_enabled:1; unsigned int ll_hw_enabled:1; @@ -1376,6 +1378,11 @@ static inline int dwc2_is_device_mode(struct dwc2_hsotg *hsotg) return (dwc2_readl(hsotg, GINTSTS) & GINTSTS_CURMODE_HOST) == 0; } +int dwc2_drd_init(struct dwc2_hsotg *hsotg); +void dwc2_drd_suspend(struct dwc2_hsotg *hsotg); +void dwc2_drd_resume(struct dwc2_hsotg *hsotg); +void dwc2_drd_exit(struct dwc2_hsotg *hsotg); + /* * Dump core registers and SPRAM */ @@ -1392,6 +1399,7 @@ int dwc2_hsotg_resume(struct dwc2_hsotg *dwc2); int dwc2_gadget_init(struct dwc2_hsotg *hsotg); void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, bool reset); +void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg); void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg); void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2); int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, int testmode); @@ -1417,6 +1425,7 @@ static inline int dwc2_gadget_init(struct dwc2_hsotg *hsotg) { return 0; } static inline void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, bool reset) {} +static inline void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) {} static inline void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg) {} static inline void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2) {} static inline int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, diff --git a/drivers/usb/dwc2/drd.c b/drivers/usb/dwc2/drd.c new file mode 100644 index 000000000000..2d4176f5788e --- /dev/null +++ b/drivers/usb/dwc2/drd.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drd.c - DesignWare USB2 DRD Controller Dual-role support + * + * Copyright (C) 2020 STMicroelectronics + * + * Author(s): Amelie Delaunay <amelie.delaunay@st.com> + */ + +#include <linux/iopoll.h> +#include <linux/platform_device.h> +#include <linux/usb/role.h> +#include "core.h" + +static void dwc2_ovr_init(struct dwc2_hsotg *hsotg) +{ + unsigned long flags; + u32 gotgctl; + + spin_lock_irqsave(&hsotg->lock, flags); + + gotgctl = dwc2_readl(hsotg, GOTGCTL); + gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | GOTGCTL_VBVALOEN; + gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS; + gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL); + dwc2_writel(hsotg, gotgctl, GOTGCTL); + + dwc2_force_mode(hsotg, false); + + spin_unlock_irqrestore(&hsotg->lock, flags); +} + +static int dwc2_ovr_avalid(struct dwc2_hsotg *hsotg, bool valid) +{ + u32 gotgctl = dwc2_readl(hsotg, GOTGCTL); + + /* Check if A-Session is already in the right state */ + if ((valid && (gotgctl & GOTGCTL_ASESVLD)) || + (!valid && !(gotgctl & GOTGCTL_ASESVLD))) + return -EALREADY; + + if (valid) + gotgctl |= GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL; + else + gotgctl &= ~(GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL); + dwc2_writel(hsotg, gotgctl, GOTGCTL); + + return 0; +} + +static int dwc2_ovr_bvalid(struct dwc2_hsotg *hsotg, bool valid) +{ + u32 gotgctl = dwc2_readl(hsotg, GOTGCTL); + + /* Check if B-Session is already in the right state */ + if ((valid && (gotgctl & GOTGCTL_BSESVLD)) || + (!valid && !(gotgctl & GOTGCTL_BSESVLD))) + return -EALREADY; + + if (valid) + gotgctl |= GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL; + else + gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL); + dwc2_writel(hsotg, gotgctl, GOTGCTL); + + return 0; +} + +static int dwc2_drd_role_sw_set(struct usb_role_switch *sw, enum usb_role role) +{ + struct dwc2_hsotg *hsotg = usb_role_switch_get_drvdata(sw); + unsigned long flags; + int already = 0; + + /* Skip session not in line with dr_mode */ + if ((role == USB_ROLE_DEVICE && hsotg->dr_mode == USB_DR_MODE_HOST) || + (role == USB_ROLE_HOST && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)) + return -EINVAL; + +#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ + IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) + /* Skip session if core is in test mode */ + if (role == USB_ROLE_NONE && hsotg->test_mode) { + dev_dbg(hsotg->dev, "Core is in test mode\n"); + return -EBUSY; + } +#endif + + spin_lock_irqsave(&hsotg->lock, flags); + + if (role == USB_ROLE_HOST) { + already = dwc2_ovr_avalid(hsotg, true); + } else if (role == USB_ROLE_DEVICE) { + already = dwc2_ovr_bvalid(hsotg, true); + /* This clear DCTL.SFTDISCON bit */ + dwc2_hsotg_core_connect(hsotg); + } else { + if (dwc2_is_device_mode(hsotg)) { + if (!dwc2_ovr_bvalid(hsotg, false)) + /* This set DCTL.SFTDISCON bit */ + dwc2_hsotg_core_disconnect(hsotg); + } else { + dwc2_ovr_avalid(hsotg, false); + } + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + + if (!already && hsotg->dr_mode == USB_DR_MODE_OTG) + /* This will raise a Connector ID Status Change Interrupt */ + dwc2_force_mode(hsotg, role == USB_ROLE_HOST); + + dev_dbg(hsotg->dev, "%s-session valid\n", + role == USB_ROLE_NONE ? "No" : + role == USB_ROLE_HOST ? "A" : "B"); + + return 0; +} + +int dwc2_drd_init(struct dwc2_hsotg *hsotg) +{ + struct usb_role_switch_desc role_sw_desc = {0}; + struct usb_role_switch *role_sw; + int ret; + + if (!device_property_read_bool(hsotg->dev, "usb-role-switch")) + return 0; + + role_sw_desc.driver_data = hsotg; + role_sw_desc.fwnode = dev_fwnode(hsotg->dev); + role_sw_desc.set = dwc2_drd_role_sw_set; + role_sw_desc.allow_userspace_control = true; + + role_sw = usb_role_switch_register(hsotg->dev, &role_sw_desc); + if (IS_ERR(role_sw)) { + ret = PTR_ERR(role_sw); + dev_err(hsotg->dev, + "failed to register role switch: %d\n", ret); + return ret; + } + + hsotg->role_sw = role_sw; + + /* Enable override and initialize values */ + dwc2_ovr_init(hsotg); + + return 0; +} + +void dwc2_drd_suspend(struct dwc2_hsotg *hsotg) +{ + u32 gintsts, gintmsk; + + if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) { + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk &= ~GINTSTS_CONIDSTSCHNG; + dwc2_writel(hsotg, gintmsk, GINTMSK); + gintsts = dwc2_readl(hsotg, GINTSTS); + dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS); + } +} + +void dwc2_drd_resume(struct dwc2_hsotg *hsotg) +{ + u32 gintsts, gintmsk; + + if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) { + gintsts = dwc2_readl(hsotg, GINTSTS); + dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS); + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk |= GINTSTS_CONIDSTSCHNG; + dwc2_writel(hsotg, gintmsk, GINTMSK); + } +} + +void dwc2_drd_exit(struct dwc2_hsotg *hsotg) +{ + if (hsotg->role_sw) + usb_role_switch_unregister(hsotg->role_sw); +} diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 5b9d23991c99..0a0d11151cfb 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -713,8 +713,11 @@ static u32 dwc2_hsotg_read_frameno(struct dwc2_hsotg *hsotg) */ static unsigned int dwc2_gadget_get_chain_limit(struct dwc2_hsotg_ep *hs_ep) { + const struct usb_endpoint_descriptor *ep_desc = hs_ep->ep.desc; int is_isoc = hs_ep->isochronous; unsigned int maxsize; + u32 mps = hs_ep->ep.maxpacket; + int dir_in = hs_ep->dir_in; if (is_isoc) maxsize = (hs_ep->dir_in ? DEV_DMA_ISOC_TX_NBYTES_LIMIT : @@ -723,6 +726,11 @@ static unsigned int dwc2_gadget_get_chain_limit(struct dwc2_hsotg_ep *hs_ep) else maxsize = DEV_DMA_NBYTES_LIMIT * MAX_DMA_DESC_NUM_GENERIC; + /* Interrupt OUT EP with mps not multiple of 4 */ + if (hs_ep->index) + if (usb_endpoint_xfer_int(ep_desc) && !dir_in && (mps % 4)) + maxsize = mps * MAX_DMA_DESC_NUM_GENERIC; + return maxsize; } @@ -738,11 +746,14 @@ static unsigned int dwc2_gadget_get_chain_limit(struct dwc2_hsotg_ep *hs_ep) * Isochronous - descriptor rx/tx bytes bitfield limit, * Control In/Bulk/Interrupt - multiple of mps. This will allow to not * have concatenations from various descriptors within one packet. + * Interrupt OUT - if mps not multiple of 4 then a single packet corresponds + * to a single descriptor. * * Selects corresponding mask for RX/TX bytes as well. */ static u32 dwc2_gadget_get_desc_params(struct dwc2_hsotg_ep *hs_ep, u32 *mask) { + const struct usb_endpoint_descriptor *ep_desc = hs_ep->ep.desc; u32 mps = hs_ep->ep.maxpacket; int dir_in = hs_ep->dir_in; u32 desc_size = 0; @@ -766,6 +777,13 @@ static u32 dwc2_gadget_get_desc_params(struct dwc2_hsotg_ep *hs_ep, u32 *mask) desc_size -= desc_size % mps; } + /* Interrupt OUT EP with mps not multiple of 4 */ + if (hs_ep->index) + if (usb_endpoint_xfer_int(ep_desc) && !dir_in && (mps % 4)) { + desc_size = mps; + *mask = DEV_DMA_NBYTES_MASK; + } + return desc_size; } @@ -1123,13 +1141,7 @@ static void dwc2_hsotg_start_req(struct dwc2_hsotg *hsotg, length += (mps - (length % mps)); } - /* - * If more data to send, adjust DMA for EP0 out data stage. - * ureq->dma stays unchanged, hence increment it by already - * passed passed data count before starting new transaction. - */ - if (!index && hsotg->ep0_state == DWC2_EP0_DATA_OUT && - continuing) + if (continuing) offset = ureq->actual; /* Fill DDMA chain entries */ @@ -2320,22 +2332,36 @@ static void dwc2_hsotg_change_ep_iso_parity(struct dwc2_hsotg *hsotg, */ static unsigned int dwc2_gadget_get_xfersize_ddma(struct dwc2_hsotg_ep *hs_ep) { + const struct usb_endpoint_descriptor *ep_desc = hs_ep->ep.desc; struct dwc2_hsotg *hsotg = hs_ep->parent; unsigned int bytes_rem = 0; + unsigned int bytes_rem_correction = 0; struct dwc2_dma_desc *desc = hs_ep->desc_list; int i; u32 status; + u32 mps = hs_ep->ep.maxpacket; + int dir_in = hs_ep->dir_in; if (!desc) return -EINVAL; + /* Interrupt OUT EP with mps not multiple of 4 */ + if (hs_ep->index) + if (usb_endpoint_xfer_int(ep_desc) && !dir_in && (mps % 4)) + bytes_rem_correction = 4 - (mps % 4); + for (i = 0; i < hs_ep->desc_count; ++i) { status = desc->status; bytes_rem += status & DEV_DMA_NBYTES_MASK; + bytes_rem -= bytes_rem_correction; if (status & DEV_DMA_STS_MASK) dev_err(hsotg->dev, "descriptor %d closed with %x\n", i, status & DEV_DMA_STS_MASK); + + if (status & DEV_DMA_L) + break; + desc++; } @@ -3530,7 +3556,7 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, dwc2_readl(hsotg, DOEPCTL0)); } -static void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) +void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) { /* set the soft-disconnect bit */ dwc2_set_bit(hsotg, DCTL, DCTL_SFTDISCON); diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index 8f9d061c4d5f..267543c3dc38 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -185,7 +185,7 @@ static void dwc2_set_stm32mp15_hsotg_params(struct dwc2_hsotg *hsotg) struct dwc2_core_params *p = &hsotg->params; p->otg_cap = DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE; - p->activate_stm_id_vb_detection = true; + p->activate_stm_id_vb_detection = !device_property_read_bool(hsotg->dev, "usb-role-switch"); p->host_rx_fifo_size = 440; p->host_nperio_tx_fifo_size = 256; p->host_perio_tx_fifo_size = 256; @@ -210,6 +210,7 @@ const struct of_device_id dwc2_of_match_table[] = { { .compatible = "amlogic,meson-g12a-usb", .data = dwc2_set_amlogic_g12a_params }, { .compatible = "amcc,dwc-otg", .data = dwc2_set_amcc_params }, + { .compatible = "apm,apm82181-dwc-otg", .data = dwc2_set_amcc_params }, { .compatible = "st,stm32f4x9-fsotg", .data = dwc2_set_stm32f4x9_fsotg_params }, { .compatible = "st,stm32f4x9-hsotg" }, @@ -860,7 +861,7 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) int dwc2_init_params(struct dwc2_hsotg *hsotg) { const struct of_device_id *match; - void (*set_params)(void *data); + void (*set_params)(struct dwc2_hsotg *data); dwc2_set_default_params(hsotg); dwc2_get_device_properties(hsotg); diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index db9fd4bd1a38..e2820676beb1 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -121,6 +121,13 @@ static int dwc2_get_dr_mode(struct dwc2_hsotg *hsotg) return 0; } +static void __dwc2_disable_regulators(void *data) +{ + struct dwc2_hsotg *hsotg = data; + + regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies); +} + static int __dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg) { struct platform_device *pdev = to_platform_device(hsotg->dev); @@ -131,6 +138,11 @@ static int __dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg) if (ret) return ret; + ret = devm_add_action_or_reset(&pdev->dev, + __dwc2_disable_regulators, hsotg); + if (ret) + return ret; + if (hsotg->clk) { ret = clk_prepare_enable(hsotg->clk); if (ret) @@ -186,10 +198,7 @@ static int __dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg) if (hsotg->clk) clk_disable_unprepare(hsotg->clk); - ret = regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), - hsotg->supplies); - - return ret; + return 0; } /** @@ -314,6 +323,8 @@ static int dwc2_driver_remove(struct platform_device *dev) if (hsotg->gadget_enabled) dwc2_hsotg_remove(hsotg); + dwc2_drd_exit(hsotg); + if (hsotg->params.activate_stm_id_vb_detection) regulator_disable(hsotg->usb33d); @@ -533,10 +544,17 @@ static int dwc2_driver_probe(struct platform_device *dev) dwc2_writel(hsotg, ggpio, GGPIO); } + retval = dwc2_drd_init(hsotg); + if (retval) { + if (retval != -EPROBE_DEFER) + dev_err(hsotg->dev, "failed to initialize dual-role\n"); + goto error_init; + } + if (hsotg->dr_mode != USB_DR_MODE_HOST) { retval = dwc2_gadget_init(hsotg); if (retval) - goto error_init; + goto error_drd; hsotg->gadget_enabled = 1; } @@ -562,7 +580,7 @@ static int dwc2_driver_probe(struct platform_device *dev) if (retval) { if (hsotg->gadget_enabled) dwc2_hsotg_remove(hsotg); - goto error_init; + goto error_drd; } hsotg->hcd_enabled = 1; } @@ -584,12 +602,19 @@ static int dwc2_driver_probe(struct platform_device *dev) if (retval) { hsotg->gadget.udc = NULL; dwc2_hsotg_remove(hsotg); - goto error_init; + goto error_debugfs; } } #endif /* CONFIG_USB_DWC2_PERIPHERAL || CONFIG_USB_DWC2_DUAL_ROLE */ return 0; +error_debugfs: + dwc2_debugfs_exit(hsotg); + if (hsotg->hcd_enabled) + dwc2_hcd_remove(hsotg); +error_drd: + dwc2_drd_exit(hsotg); + error_init: if (hsotg->params.activate_stm_id_vb_detection) regulator_disable(hsotg->usb33d); @@ -608,6 +633,8 @@ static int __maybe_unused dwc2_suspend(struct device *dev) if (is_device_mode) dwc2_hsotg_suspend(dwc2); + dwc2_drd_suspend(dwc2); + if (dwc2->params.activate_stm_id_vb_detection) { unsigned long flags; u32 ggpio, gotgctl; @@ -688,6 +715,8 @@ static int __maybe_unused dwc2_resume(struct device *dev) /* Need to restore FORCEDEVMODE/FORCEHOSTMODE */ dwc2_force_dr_mode(dwc2); + dwc2_drd_resume(dwc2); + if (dwc2_is_device_mode(dwc2)) ret = dwc2_hsotg_resume(dwc2); diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 2eb34c8b4065..bdf0925da6b6 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -119,9 +119,7 @@ static void __dwc3_set_mode(struct work_struct *work) struct dwc3 *dwc = work_to_dwc(work); unsigned long flags; int ret; - - if (dwc->dr_mode != USB_DR_MODE_OTG) - return; + u32 reg; pm_runtime_get_sync(dwc->dev); @@ -172,6 +170,11 @@ static void __dwc3_set_mode(struct work_struct *work) otg_set_vbus(dwc->usb2_phy->otg, true); phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_HOST); phy_set_mode(dwc->usb3_generic_phy, PHY_MODE_USB_HOST); + if (dwc->dis_split_quirk) { + reg = dwc3_readl(dwc->regs, DWC3_GUCTL3); + reg |= DWC3_GUCTL3_SPLITDISABLE; + dwc3_writel(dwc->regs, DWC3_GUCTL3, reg); + } } break; case DWC3_GCTL_PRTCAP_DEVICE: @@ -203,6 +206,9 @@ void dwc3_set_mode(struct dwc3 *dwc, u32 mode) { unsigned long flags; + if (dwc->dr_mode != USB_DR_MODE_OTG) + return; + spin_lock_irqsave(&dwc->lock, flags); dwc->desired_dr_role = mode; spin_unlock_irqrestore(&dwc->lock, flags); @@ -929,13 +935,6 @@ static int dwc3_core_init(struct dwc3 *dwc) */ dwc3_writel(dwc->regs, DWC3_GUID, LINUX_VERSION_CODE); - /* Handle USB2.0-only core configuration */ - if (DWC3_GHWPARAMS3_SSPHY_IFC(dwc->hwparams.hwparams3) == - DWC3_GHWPARAMS3_SSPHY_IFC_DIS) { - if (dwc->maximum_speed == USB_SPEED_SUPER) - dwc->maximum_speed = USB_SPEED_HIGH; - } - ret = dwc3_phy_setup(dwc); if (ret) goto err0; @@ -1356,6 +1355,9 @@ static void dwc3_get_properties(struct dwc3 *dwc) dwc->dis_metastability_quirk = device_property_read_bool(dev, "snps,dis_metastability_quirk"); + dwc->dis_split_quirk = device_property_read_bool(dev, + "snps,dis-split-quirk"); + dwc->lpm_nyet_threshold = lpm_nyet_threshold; dwc->tx_de_emphasis = tx_de_emphasis; @@ -1381,6 +1383,8 @@ bool dwc3_has_imod(struct dwc3 *dwc) static void dwc3_check_params(struct dwc3 *dwc) { struct device *dev = dwc->dev; + unsigned int hwparam_gen = + DWC3_GHWPARAMS3_SSPHY_IFC(dwc->hwparams.hwparams3); /* Check for proper value of imod_interval */ if (dwc->imod_interval && !dwc3_has_imod(dwc)) { @@ -1404,25 +1408,40 @@ static void dwc3_check_params(struct dwc3 *dwc) case USB_SPEED_LOW: case USB_SPEED_FULL: case USB_SPEED_HIGH: + break; case USB_SPEED_SUPER: + if (hwparam_gen == DWC3_GHWPARAMS3_SSPHY_IFC_DIS) + dev_warn(dev, "UDC doesn't support Gen 1\n"); + break; case USB_SPEED_SUPER_PLUS: + if ((DWC3_IP_IS(DWC32) && + hwparam_gen == DWC3_GHWPARAMS3_SSPHY_IFC_DIS) || + (!DWC3_IP_IS(DWC32) && + hwparam_gen != DWC3_GHWPARAMS3_SSPHY_IFC_GEN2)) + dev_warn(dev, "UDC doesn't support SSP\n"); break; default: dev_err(dev, "invalid maximum_speed parameter %d\n", dwc->maximum_speed); fallthrough; case USB_SPEED_UNKNOWN: - /* default to superspeed */ - dwc->maximum_speed = USB_SPEED_SUPER; - - /* - * default to superspeed plus if we are capable. - */ - if ((DWC3_IP_IS(DWC31) || DWC3_IP_IS(DWC32)) && - (DWC3_GHWPARAMS3_SSPHY_IFC(dwc->hwparams.hwparams3) == - DWC3_GHWPARAMS3_SSPHY_IFC_GEN2)) + switch (hwparam_gen) { + case DWC3_GHWPARAMS3_SSPHY_IFC_GEN2: dwc->maximum_speed = USB_SPEED_SUPER_PLUS; - + break; + case DWC3_GHWPARAMS3_SSPHY_IFC_GEN1: + if (DWC3_IP_IS(DWC32)) + dwc->maximum_speed = USB_SPEED_SUPER_PLUS; + else + dwc->maximum_speed = USB_SPEED_SUPER; + break; + case DWC3_GHWPARAMS3_SSPHY_IFC_DIS: + dwc->maximum_speed = USB_SPEED_HIGH; + break; + default: + dwc->maximum_speed = USB_SPEED_SUPER; + break; + } break; } } @@ -1554,6 +1573,17 @@ static int dwc3_probe(struct platform_device *pdev) err5: dwc3_event_buffers_cleanup(dwc); + + usb_phy_shutdown(dwc->usb2_phy); + usb_phy_shutdown(dwc->usb3_phy); + phy_exit(dwc->usb2_generic_phy); + phy_exit(dwc->usb3_generic_phy); + + usb_phy_set_suspend(dwc->usb2_phy, 1); + usb_phy_set_suspend(dwc->usb3_phy, 1); + phy_power_off(dwc->usb2_generic_phy); + phy_power_off(dwc->usb3_generic_phy); + dwc3_ulpi_exit(dwc); err4: @@ -1589,9 +1619,9 @@ static int dwc3_remove(struct platform_device *pdev) dwc3_core_exit(dwc); dwc3_ulpi_exit(dwc); - pm_runtime_put_sync(&pdev->dev); - pm_runtime_allow(&pdev->dev); pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); dwc3_free_event_buffers(dwc); dwc3_free_scratch_buffers(dwc); @@ -1865,10 +1895,26 @@ static int dwc3_resume(struct device *dev) return 0; } + +static void dwc3_complete(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + u32 reg; + + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST && + dwc->dis_split_quirk) { + reg = dwc3_readl(dwc->regs, DWC3_GUCTL3); + reg |= DWC3_GUCTL3_SPLITDISABLE; + dwc3_writel(dwc->regs, DWC3_GUCTL3, reg); + } +} +#else +#define dwc3_complete NULL #endif /* CONFIG_PM_SLEEP */ static const struct dev_pm_ops dwc3_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(dwc3_suspend, dwc3_resume) + .complete = dwc3_complete, SET_RUNTIME_PM_OPS(dwc3_runtime_suspend, dwc3_runtime_resume, dwc3_runtime_idle) }; diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 2f04b3e42bf1..74323b10a64a 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -138,6 +138,7 @@ #define DWC3_GEVNTCOUNT(n) (0xc40c + ((n) * 0x10)) #define DWC3_GHWPARAMS8 0xc600 +#define DWC3_GUCTL3 0xc60c #define DWC3_GFLADJ 0xc630 /* Device Registers */ @@ -380,6 +381,9 @@ /* Global User Control Register 2 */ #define DWC3_GUCTL2_RST_ACTBITLATER BIT(14) +/* Global User Control Register 3 */ +#define DWC3_GUCTL3_SPLITDISABLE BIT(14) + /* Device Configuration Register */ #define DWC3_DCFG_DEVADDR(addr) ((addr) << 3) #define DWC3_DCFG_DEVADDR_MASK DWC3_DCFG_DEVADDR(0x7f) @@ -634,7 +638,7 @@ struct dwc3_trb; struct dwc3_event_buffer { void *buf; void *cache; - unsigned length; + unsigned int length; unsigned int lpos; unsigned int count; unsigned int flags; @@ -694,7 +698,7 @@ struct dwc3_ep { struct dwc3 *dwc; u32 saved_state; - unsigned flags; + unsigned int flags; #define DWC3_EP_ENABLED BIT(0) #define DWC3_EP_STALL BIT(1) #define DWC3_EP_WEDGE BIT(2) @@ -706,6 +710,7 @@ struct dwc3_ep { #define DWC3_EP_IGNORE_NEXT_NOSTREAM BIT(8) #define DWC3_EP_FORCE_RESTART_STREAM BIT(9) #define DWC3_EP_FIRST_STREAM_PRIMED BIT(10) +#define DWC3_EP_PENDING_CLEAR_STALL BIT(11) /* This last one is specific to EP0 */ #define DWC3_EP0_DIR_IN BIT(31) @@ -893,9 +898,9 @@ struct dwc3_request { struct scatterlist *sg; struct scatterlist *start_sg; - unsigned num_pending_sgs; + unsigned int num_pending_sgs; unsigned int num_queued_sgs; - unsigned remaining; + unsigned int remaining; unsigned int status; #define DWC3_REQUEST_STATUS_QUEUED 0 @@ -908,11 +913,11 @@ struct dwc3_request { struct dwc3_trb *trb; dma_addr_t trb_dma; - unsigned num_trbs; + unsigned int num_trbs; - unsigned needs_extra_trb:1; - unsigned direction:1; - unsigned mapped:1; + unsigned int needs_extra_trb:1; + unsigned int direction:1; + unsigned int mapped:1; }; /* @@ -1010,8 +1015,8 @@ struct dwc3_scratchpad_array { * @has_lpm_erratum: true when core was configured with LPM Erratum. Note that * there's now way for software to detect this in runtime. * @is_utmi_l1_suspend: the core asserts output signal - * 0 - utmi_sleep_n - * 1 - utmi_l1_suspend_n + * 0 - utmi_sleep_n + * 1 - utmi_l1_suspend_n * @is_fpga: true when we are using the FPGA board * @pending_events: true when we have pending IRQs to be handled * @pullups_connected: true when Run/Stop bit is set @@ -1047,13 +1052,14 @@ struct dwc3_scratchpad_array { * instances in park mode. * @tx_de_emphasis_quirk: set if we enable Tx de-emphasis quirk * @tx_de_emphasis: Tx de-emphasis value - * 0 - -6dB de-emphasis - * 1 - -3.5dB de-emphasis - * 2 - No de-emphasis - * 3 - Reserved + * 0 - -6dB de-emphasis + * 1 - -3.5dB de-emphasis + * 2 - No de-emphasis + * 3 - Reserved * @dis_metastability_quirk: set to disable metastability quirk. + * @dis_split_quirk: set to disable split boundary. * @imod_interval: set the interrupt moderation interval in 250ns - * increments or 0 to disable. + * increments or 0 to disable. */ struct dwc3 { struct work_struct drd_work; @@ -1079,7 +1085,7 @@ struct dwc3 { struct dwc3_event_buffer *ev_buf; struct dwc3_ep *eps[DWC3_ENDPOINTS_NUM]; - struct usb_gadget gadget; + struct usb_gadget *gadget; struct usb_gadget_driver *gadget_driver; struct clk_bulk_data *clks; @@ -1245,6 +1251,8 @@ struct dwc3 { unsigned dis_metastability_quirk:1; + unsigned dis_split_quirk:1; + u16 imod_interval; }; @@ -1456,9 +1464,10 @@ void dwc3_gadget_exit(struct dwc3 *dwc); int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode); int dwc3_gadget_get_link_state(struct dwc3 *dwc); int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state); -int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, +int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd, struct dwc3_gadget_ep_cmd_params *params); -int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param); +int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned int cmd, + u32 param); #else static inline int dwc3_gadget_init(struct dwc3 *dwc) { return 0; } @@ -1472,7 +1481,7 @@ static inline int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state) { return 0; } -static inline int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, +static inline int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd, struct dwc3_gadget_ep_cmd_params *params) { return 0; } static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc, diff --git a/drivers/usb/dwc3/debug.h b/drivers/usb/dwc3/debug.h index 3d16dac4e5cc..8ab394942360 100644 --- a/drivers/usb/dwc3/debug.h +++ b/drivers/usb/dwc3/debug.h @@ -371,7 +371,9 @@ static inline const char *dwc3_gadget_event_type_string(u8 event) static inline const char *dwc3_decode_event(char *str, size_t size, u32 event, u32 ep0state) { - const union dwc3_event evt = (union dwc3_event) event; + union dwc3_event evt; + + memcpy(&evt, &event, sizeof(event)); if (evt.type.is_devspec) return dwc3_gadget_event_string(str, size, &evt.devt); @@ -411,8 +413,8 @@ static inline const char *dwc3_gadget_generic_cmd_status_string(int status) #ifdef CONFIG_DEBUG_FS -extern void dwc3_debugfs_init(struct dwc3 *); -extern void dwc3_debugfs_exit(struct dwc3 *); +extern void dwc3_debugfs_init(struct dwc3 *d); +extern void dwc3_debugfs_exit(struct dwc3 *d); #else static inline void dwc3_debugfs_init(struct dwc3 *d) { } diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c index 2c7b6dd79cdf..5da4f6082d93 100644 --- a/drivers/usb/dwc3/debugfs.c +++ b/drivers/usb/dwc3/debugfs.c @@ -397,13 +397,13 @@ static int dwc3_mode_show(struct seq_file *s, void *unused) switch (DWC3_GCTL_PRTCAP(reg)) { case DWC3_GCTL_PRTCAP_HOST: - seq_printf(s, "host\n"); + seq_puts(s, "host\n"); break; case DWC3_GCTL_PRTCAP_DEVICE: - seq_printf(s, "device\n"); + seq_puts(s, "device\n"); break; case DWC3_GCTL_PRTCAP_OTG: - seq_printf(s, "otg\n"); + seq_puts(s, "otg\n"); break; default: seq_printf(s, "UNKNOWN %08x\n", DWC3_GCTL_PRTCAP(reg)); @@ -428,6 +428,9 @@ static ssize_t dwc3_mode_write(struct file *file, if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) return -EFAULT; + if (dwc->dr_mode != USB_DR_MODE_OTG) + return count; + if (!strncmp(buf, "host", 4)) mode = DWC3_GCTL_PRTCAP_HOST; @@ -464,22 +467,22 @@ static int dwc3_testmode_show(struct seq_file *s, void *unused) switch (reg) { case 0: - seq_printf(s, "no test\n"); + seq_puts(s, "no test\n"); break; case USB_TEST_J: - seq_printf(s, "test_j\n"); + seq_puts(s, "test_j\n"); break; case USB_TEST_K: - seq_printf(s, "test_k\n"); + seq_puts(s, "test_k\n"); break; case USB_TEST_SE0_NAK: - seq_printf(s, "test_se0_nak\n"); + seq_puts(s, "test_se0_nak\n"); break; case USB_TEST_PACKET: - seq_printf(s, "test_packet\n"); + seq_puts(s, "test_packet\n"); break; case USB_TEST_FORCE_ENABLE: - seq_printf(s, "test_force_enable\n"); + seq_puts(s, "test_force_enable\n"); break; default: seq_printf(s, "UNKNOWN %d\n", reg); @@ -760,27 +763,26 @@ static int dwc3_transfer_type_show(struct seq_file *s, void *unused) unsigned long flags; spin_lock_irqsave(&dwc->lock, flags); - if (!(dep->flags & DWC3_EP_ENABLED) || - !dep->endpoint.desc) { - seq_printf(s, "--\n"); + if (!(dep->flags & DWC3_EP_ENABLED) || !dep->endpoint.desc) { + seq_puts(s, "--\n"); goto out; } switch (usb_endpoint_type(dep->endpoint.desc)) { case USB_ENDPOINT_XFER_CONTROL: - seq_printf(s, "control\n"); + seq_puts(s, "control\n"); break; case USB_ENDPOINT_XFER_ISOC: - seq_printf(s, "isochronous\n"); + seq_puts(s, "isochronous\n"); break; case USB_ENDPOINT_XFER_BULK: - seq_printf(s, "bulk\n"); + seq_puts(s, "bulk\n"); break; case USB_ENDPOINT_XFER_INT: - seq_printf(s, "interrupt\n"); + seq_puts(s, "interrupt\n"); break; default: - seq_printf(s, "--\n"); + seq_puts(s, "--\n"); } out: @@ -798,11 +800,11 @@ static int dwc3_trb_ring_show(struct seq_file *s, void *unused) spin_lock_irqsave(&dwc->lock, flags); if (dep->number <= 1) { - seq_printf(s, "--\n"); + seq_puts(s, "--\n"); goto out; } - seq_printf(s, "buffer_addr,size,type,ioc,isp_imi,csp,chn,lst,hwo\n"); + seq_puts(s, "buffer_addr,size,type,ioc,isp_imi,csp,chn,lst,hwo\n"); for (i = 0; i < DWC3_TRB_NUM; i++) { struct dwc3_trb *trb = &dep->trb_pool[i]; @@ -884,7 +886,7 @@ static void dwc3_debugfs_create_endpoint_files(struct dwc3_ep *dep, const struct file_operations *fops = dwc3_ep_file_map[i].fops; const char *name = dwc3_ep_file_map[i].name; - debugfs_create_file(name, S_IRUGO, parent, dep, fops); + debugfs_create_file(name, 0444, parent, dep, fops); } } @@ -929,21 +931,18 @@ void dwc3_debugfs_init(struct dwc3 *dwc) root = debugfs_create_dir(dev_name(dwc->dev), usb_debug_root); dwc->root = root; - debugfs_create_regset32("regdump", S_IRUGO, root, dwc->regset); - - debugfs_create_file("lsp_dump", S_IRUGO | S_IWUSR, root, dwc, - &dwc3_lsp_fops); + debugfs_create_regset32("regdump", 0444, root, dwc->regset); + debugfs_create_file("lsp_dump", 0644, root, dwc, &dwc3_lsp_fops); - if (IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)) { - debugfs_create_file("mode", S_IRUGO | S_IWUSR, root, dwc, + if (IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)) + debugfs_create_file("mode", 0644, root, dwc, &dwc3_mode_fops); - } if (IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) || IS_ENABLED(CONFIG_USB_DWC3_GADGET)) { - debugfs_create_file("testmode", S_IRUGO | S_IWUSR, root, dwc, - &dwc3_testmode_fops); - debugfs_create_file("link_state", S_IRUGO | S_IWUSR, root, dwc, + debugfs_create_file("testmode", 0644, root, dwc, + &dwc3_testmode_fops); + debugfs_create_file("link_state", 0644, root, dwc, &dwc3_link_state_fops); dwc3_debugfs_create_endpoint_dirs(dwc, root); } diff --git a/drivers/usb/dwc3/dwc3-meson-g12a.c b/drivers/usb/dwc3/dwc3-meson-g12a.c index 1f7f4d88ed9d..417e05381b5d 100644 --- a/drivers/usb/dwc3/dwc3-meson-g12a.c +++ b/drivers/usb/dwc3/dwc3-meson-g12a.c @@ -116,23 +116,24 @@ static struct clk_bulk_data meson_a1_clocks[] = { { .id = "xtal_usb_ctrl" }, }; -static const char *meson_gxm_phy_names[] = { +static const char * const meson_gxm_phy_names[] = { "usb2-phy0", "usb2-phy1", "usb2-phy2", }; -static const char *meson_g12a_phy_names[] = { +static const char * const meson_g12a_phy_names[] = { "usb2-phy0", "usb2-phy1", "usb3-phy0", }; /* * Amlogic A1 has a single physical PHY, in slot 1, but still has the * two U2 PHY controls register blocks like G12A. + * AXG has the similar scheme, thus needs the same tweak. * Handling the first PHY on slot 1 would need a large amount of code * changes, and the current management is generic enough to handle it * correctly when only the "usb2-phy1" phy is specified on-par with the * DT bindings. */ -static const char *meson_a1_phy_names[] = { +static const char * const meson_a1_phy_names[] = { "usb2-phy0", "usb2-phy1" }; @@ -143,7 +144,7 @@ struct dwc3_meson_g12a_drvdata { bool otg_phy_host_port_disable; struct clk_bulk_data *clks; int num_clks; - const char **phy_names; + const char * const *phy_names; int num_phys; int (*setup_regmaps)(struct dwc3_meson_g12a *priv, void __iomem *base); int (*usb2_init_phy)(struct dwc3_meson_g12a *priv, int i, @@ -215,6 +216,19 @@ static struct dwc3_meson_g12a_drvdata gxm_drvdata = { .usb_post_init = dwc3_meson_gxl_usb_post_init, }; +static struct dwc3_meson_g12a_drvdata axg_drvdata = { + .otg_switch_supported = true, + .clks = meson_gxl_clocks, + .num_clks = ARRAY_SIZE(meson_gxl_clocks), + .phy_names = meson_a1_phy_names, + .num_phys = ARRAY_SIZE(meson_a1_phy_names), + .setup_regmaps = dwc3_meson_gxl_setup_regmaps, + .usb2_init_phy = dwc3_meson_gxl_usb2_init_phy, + .set_phy_mode = dwc3_meson_gxl_set_phy_mode, + .usb_init = dwc3_meson_g12a_usb_init, + .usb_post_init = dwc3_meson_gxl_usb_post_init, +}; + static struct dwc3_meson_g12a_drvdata g12a_drvdata = { .otg_switch_supported = true, .clks = meson_g12a_clocks, @@ -520,11 +534,7 @@ static int dwc3_meson_g12a_role_set(struct usb_role_switch *sw, return 0; if (priv->drvdata->otg_phy_host_port_disable) - dev_warn_once(priv->dev, "Manual OTG switch is broken on this "\ - "SoC, when manual switching from "\ - "Host to device, DWC3 controller "\ - "will need to be resetted in order "\ - "to recover usage of the Host port"); + dev_warn_once(priv->dev, "Broken manual OTG switch\n"); return dwc3_meson_g12a_otg_mode_set(priv, mode); } @@ -626,10 +636,7 @@ static int dwc3_meson_gxl_setup_regmaps(struct dwc3_meson_g12a *priv, /* GXL controls the PHY mode in the PHY registers unlike G12A */ priv->usb_glue_regmap = devm_regmap_init_mmio(priv->dev, base, &phy_meson_g12a_usb_glue_regmap_conf); - if (IS_ERR(priv->usb_glue_regmap)) - return PTR_ERR(priv->usb_glue_regmap); - - return 0; + return PTR_ERR_OR_ZERO(priv->usb_glue_regmap); } static int dwc3_meson_g12a_setup_regmaps(struct dwc3_meson_g12a *priv, @@ -906,8 +913,8 @@ static int __maybe_unused dwc3_meson_g12a_resume(struct device *dev) return ret; } - if (priv->vbus && priv->otg_phy_mode == PHY_MODE_USB_HOST) { - ret = regulator_enable(priv->vbus); + if (priv->vbus && priv->otg_phy_mode == PHY_MODE_USB_HOST) { + ret = regulator_enable(priv->vbus); if (ret) return ret; } @@ -931,6 +938,10 @@ static const struct of_device_id dwc3_meson_g12a_match[] = { .data = &gxm_drvdata, }, { + .compatible = "amlogic,meson-axg-usb-ctrl", + .data = &axg_drvdata, + }, + { .compatible = "amlogic,meson-g12a-usb-ctrl", .data = &g12a_drvdata, }, diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c index 7df115012935..e62ecd22b3ed 100644 --- a/drivers/usb/dwc3/dwc3-of-simple.c +++ b/drivers/usb/dwc3/dwc3-of-simple.c @@ -176,6 +176,8 @@ static const struct of_device_id of_dwc3_simple_match[] = { { .compatible = "cavium,octeon-7130-usb-uctl" }, { .compatible = "sprd,sc9860-dwc3" }, { .compatible = "allwinner,sun50i-h6-dwc3" }, + { .compatible = "hisilicon,hi3670-dwc3" }, + { .compatible = "intel,keembay-dwc3" }, { /* Sentinel */ } }; MODULE_DEVICE_TABLE(of, of_dwc3_simple_match); diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index f5a61f57c74f..242b6210380a 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -147,7 +147,8 @@ static int dwc3_pci_quirks(struct dwc3_pci *dwc) if (pdev->vendor == PCI_VENDOR_ID_INTEL) { if (pdev->device == PCI_DEVICE_ID_INTEL_BXT || - pdev->device == PCI_DEVICE_ID_INTEL_BXT_M) { + pdev->device == PCI_DEVICE_ID_INTEL_BXT_M || + pdev->device == PCI_DEVICE_ID_INTEL_EHLLP) { guid_parse(PCI_INTEL_BXT_DSM_GUID, &dwc->guid); dwc->has_dsm_for_pm = true; } diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index e1e78e9824b1..c703d552bbcf 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -13,6 +13,7 @@ #include <linux/module.h> #include <linux/kernel.h> #include <linux/extcon.h> +#include <linux/interconnect.h> #include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/phy/phy.h> @@ -43,6 +44,14 @@ #define SDM845_QSCRATCH_SIZE 0x400 #define SDM845_DWC3_CORE_SIZE 0xcd00 +/* Interconnect path bandwidths in MBps */ +#define USB_MEMORY_AVG_HS_BW MBps_to_icc(240) +#define USB_MEMORY_PEAK_HS_BW MBps_to_icc(700) +#define USB_MEMORY_AVG_SS_BW MBps_to_icc(1000) +#define USB_MEMORY_PEAK_SS_BW MBps_to_icc(2500) +#define APPS_USB_AVG_BW 0 +#define APPS_USB_PEAK_BW MBps_to_icc(40) + struct dwc3_acpi_pdata { u32 qscratch_base_offset; u32 qscratch_base_size; @@ -76,6 +85,8 @@ struct dwc3_qcom { enum usb_dr_mode mode; bool is_suspended; bool pm_suspended; + struct icc_path *icc_path_ddr; + struct icc_path *icc_path_apps; }; static inline void dwc3_qcom_setbits(void __iomem *base, u32 offset, u32 val) @@ -190,6 +201,96 @@ static int dwc3_qcom_register_extcon(struct dwc3_qcom *qcom) return 0; } +static int dwc3_qcom_interconnect_enable(struct dwc3_qcom *qcom) +{ + int ret; + + ret = icc_enable(qcom->icc_path_ddr); + if (ret) + return ret; + + ret = icc_enable(qcom->icc_path_apps); + if (ret) + icc_disable(qcom->icc_path_ddr); + + return ret; +} + +static int dwc3_qcom_interconnect_disable(struct dwc3_qcom *qcom) +{ + int ret; + + ret = icc_disable(qcom->icc_path_ddr); + if (ret) + return ret; + + ret = icc_disable(qcom->icc_path_apps); + if (ret) + icc_enable(qcom->icc_path_ddr); + + return ret; +} + +/** + * dwc3_qcom_interconnect_init() - Get interconnect path handles + * and set bandwidhth. + * @qcom: Pointer to the concerned usb core. + * + */ +static int dwc3_qcom_interconnect_init(struct dwc3_qcom *qcom) +{ + struct device *dev = qcom->dev; + int ret; + + qcom->icc_path_ddr = of_icc_get(dev, "usb-ddr"); + if (IS_ERR(qcom->icc_path_ddr)) { + dev_err(dev, "failed to get usb-ddr path: %ld\n", + PTR_ERR(qcom->icc_path_ddr)); + return PTR_ERR(qcom->icc_path_ddr); + } + + qcom->icc_path_apps = of_icc_get(dev, "apps-usb"); + if (IS_ERR(qcom->icc_path_apps)) { + dev_err(dev, "failed to get apps-usb path: %ld\n", + PTR_ERR(qcom->icc_path_apps)); + return PTR_ERR(qcom->icc_path_apps); + } + + if (usb_get_maximum_speed(&qcom->dwc3->dev) >= USB_SPEED_SUPER || + usb_get_maximum_speed(&qcom->dwc3->dev) == USB_SPEED_UNKNOWN) + ret = icc_set_bw(qcom->icc_path_ddr, + USB_MEMORY_AVG_SS_BW, USB_MEMORY_PEAK_SS_BW); + else + ret = icc_set_bw(qcom->icc_path_ddr, + USB_MEMORY_AVG_HS_BW, USB_MEMORY_PEAK_HS_BW); + + if (ret) { + dev_err(dev, "failed to set bandwidth for usb-ddr path: %d\n", ret); + return ret; + } + + ret = icc_set_bw(qcom->icc_path_apps, + APPS_USB_AVG_BW, APPS_USB_PEAK_BW); + if (ret) { + dev_err(dev, "failed to set bandwidth for apps-usb path: %d\n", ret); + return ret; + } + + return 0; +} + +/** + * dwc3_qcom_interconnect_exit() - Release interconnect path handles + * @qcom: Pointer to the concerned usb core. + * + * This function is used to release interconnect path handle. + */ +static void dwc3_qcom_interconnect_exit(struct dwc3_qcom *qcom) +{ + icc_put(qcom->icc_path_ddr); + icc_put(qcom->icc_path_apps); +} + static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom) { if (qcom->hs_phy_irq) { @@ -239,7 +340,7 @@ static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom) static int dwc3_qcom_suspend(struct dwc3_qcom *qcom) { u32 val; - int i; + int i, ret; if (qcom->is_suspended) return 0; @@ -251,6 +352,10 @@ static int dwc3_qcom_suspend(struct dwc3_qcom *qcom) for (i = qcom->num_clocks - 1; i >= 0; i--) clk_disable_unprepare(qcom->clks[i]); + ret = dwc3_qcom_interconnect_disable(qcom); + if (ret) + dev_warn(qcom->dev, "failed to disable interconnect: %d\n", ret); + qcom->is_suspended = true; dwc3_qcom_enable_interrupts(qcom); @@ -276,6 +381,10 @@ static int dwc3_qcom_resume(struct dwc3_qcom *qcom) } } + ret = dwc3_qcom_interconnect_enable(qcom); + if (ret) + dev_warn(qcom->dev, "failed to enable interconnect: %d\n", ret); + /* Clear existing events from PHY related to L2 in/out */ dwc3_qcom_setbits(qcom->qscratch_base, PWR_EVNT_IRQ_STAT_REG, PWR_EVNT_LPM_IN_L2_MASK | PWR_EVNT_LPM_OUT_L2_MASK); @@ -335,7 +444,9 @@ static int dwc3_qcom_setup_irq(struct platform_device *pdev) { struct dwc3_qcom *qcom = platform_get_drvdata(pdev); const struct dwc3_acpi_pdata *pdata = qcom->acpi_pdata; - int irq, ret; + int irq; + int ret; + irq = dwc3_qcom_get_irq(pdev, "hs_phy_irq", pdata ? pdata->hs_phy_irq_index : -1); if (irq > 0) { @@ -454,7 +565,7 @@ static const struct property_entry dwc3_qcom_acpi_properties[] = { static int dwc3_qcom_acpi_register_core(struct platform_device *pdev) { - struct dwc3_qcom *qcom = platform_get_drvdata(pdev); + struct dwc3_qcom *qcom = platform_get_drvdata(pdev); struct device *dev = &pdev->dev; struct resource *res, *child_res = NULL; int irq; @@ -514,7 +625,7 @@ out: static int dwc3_qcom_of_register_core(struct platform_device *pdev) { - struct dwc3_qcom *qcom = platform_get_drvdata(pdev); + struct dwc3_qcom *qcom = platform_get_drvdata(pdev); struct device_node *np = pdev->dev.of_node, *dwc3_np; struct device *dev = &pdev->dev; int ret; @@ -638,6 +749,10 @@ static int dwc3_qcom_probe(struct platform_device *pdev) goto depopulate; } + ret = dwc3_qcom_interconnect_init(qcom); + if (ret) + goto depopulate; + qcom->mode = usb_get_dr_mode(&qcom->dwc3->dev); /* enable vbus override for device mode */ @@ -647,7 +762,7 @@ static int dwc3_qcom_probe(struct platform_device *pdev) /* register extcon to override sw_vbus on Vbus change later */ ret = dwc3_qcom_register_extcon(qcom); if (ret) - goto depopulate; + goto interconnect_exit; device_init_wakeup(&pdev->dev, 1); qcom->is_suspended = false; @@ -657,6 +772,8 @@ static int dwc3_qcom_probe(struct platform_device *pdev) return 0; +interconnect_exit: + dwc3_qcom_interconnect_exit(qcom); depopulate: if (np) of_platform_depopulate(&pdev->dev); @@ -687,6 +804,7 @@ static int dwc3_qcom_remove(struct platform_device *pdev) } qcom->num_clocks = 0; + dwc3_qcom_interconnect_exit(qcom); reset_control_assert(qcom->resets); pm_runtime_allow(dev); diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 59f2e8c31bd1..7be3903cb842 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -105,7 +105,7 @@ static int __dwc3_gadget_ep0_queue(struct dwc3_ep *dep, * IRQ we were waiting for is long gone. */ if (dep->flags & DWC3_EP_PENDING_REQUEST) { - unsigned direction; + unsigned int direction; direction = !!(dep->flags & DWC3_EP0_DIR_IN); @@ -127,11 +127,11 @@ static int __dwc3_gadget_ep0_queue(struct dwc3_ep *dep, * handle it here. */ if (dwc->delayed_status) { - unsigned direction; + unsigned int direction; direction = !dwc->ep0_expect_in; dwc->delayed_status = false; - usb_gadget_set_state(&dwc->gadget, USB_STATE_CONFIGURED); + usb_gadget_set_state(dwc->gadget, USB_STATE_CONFIGURED); if (dwc->ep0state == EP0_STATUS_PHASE) __dwc3_ep0_do_control_status(dwc, dwc->eps[direction]); @@ -172,7 +172,7 @@ static int __dwc3_gadget_ep0_queue(struct dwc3_ep *dep, * XferNotReady(STATUS). */ if (dwc->three_stage_setup) { - unsigned direction; + unsigned int direction; direction = dwc->ep0_expect_in; dwc->ep0state = EP0_DATA_PHASE; @@ -197,7 +197,7 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); - if (!dep->endpoint.desc) { + if (!dep->endpoint.desc || !dwc->pullups_connected) { dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n", dep->name); ret = -ESHUTDOWN; @@ -325,7 +325,7 @@ static int dwc3_ep0_handle_status(struct dwc3 *dwc, /* * LTM will be set once we know how to set this in HW. */ - usb_status |= dwc->gadget.is_selfpowered; + usb_status |= dwc->gadget->is_selfpowered; if ((dwc->speed == DWC3_DSTS_SUPERSPEED) || (dwc->speed == DWC3_DSTS_SUPERSPEED_PLUS)) { @@ -450,7 +450,7 @@ static int dwc3_ep0_handle_device(struct dwc3 *dwc, wValue = le16_to_cpu(ctrl->wValue); wIndex = le16_to_cpu(ctrl->wIndex); - state = dwc->gadget.state; + state = dwc->gadget->state; switch (wValue) { case USB_DEVICE_REMOTE_WAKEUP: @@ -524,6 +524,11 @@ static int dwc3_ep0_handle_endpoint(struct dwc3 *dwc, ret = __dwc3_gadget_ep_set_halt(dep, set, true); if (ret) return -EINVAL; + + /* ClearFeature(Halt) may need delayed status */ + if (!set && (dep->flags & DWC3_EP_END_TRANSFER_PENDING)) + return USB_GADGET_DELAYED_STATUS; + break; default: return -EINVAL; @@ -559,7 +564,7 @@ static int dwc3_ep0_handle_feature(struct dwc3 *dwc, static int dwc3_ep0_set_address(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) { - enum usb_device_state state = dwc->gadget.state; + enum usb_device_state state = dwc->gadget->state; u32 addr; u32 reg; @@ -580,9 +585,9 @@ static int dwc3_ep0_set_address(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) dwc3_writel(dwc->regs, DWC3_DCFG, reg); if (addr) - usb_gadget_set_state(&dwc->gadget, USB_STATE_ADDRESS); + usb_gadget_set_state(dwc->gadget, USB_STATE_ADDRESS); else - usb_gadget_set_state(&dwc->gadget, USB_STATE_DEFAULT); + usb_gadget_set_state(dwc->gadget, USB_STATE_DEFAULT); return 0; } @@ -592,14 +597,14 @@ static int dwc3_ep0_delegate_req(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) int ret; spin_unlock(&dwc->lock); - ret = dwc->gadget_driver->setup(&dwc->gadget, ctrl); + ret = dwc->gadget_driver->setup(dwc->gadget, ctrl); spin_lock(&dwc->lock); return ret; } static int dwc3_ep0_set_config(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) { - enum usb_device_state state = dwc->gadget.state; + enum usb_device_state state = dwc->gadget->state; u32 cfg; int ret; u32 reg; @@ -622,7 +627,7 @@ static int dwc3_ep0_set_config(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) * to change the state on the next usb_ep_queue() */ if (ret == 0) - usb_gadget_set_state(&dwc->gadget, + usb_gadget_set_state(dwc->gadget, USB_STATE_CONFIGURED); /* @@ -641,7 +646,7 @@ static int dwc3_ep0_set_config(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) case USB_STATE_CONFIGURED: ret = dwc3_ep0_delegate_req(dwc, ctrl); if (!cfg && !ret) - usb_gadget_set_state(&dwc->gadget, + usb_gadget_set_state(dwc->gadget, USB_STATE_ADDRESS); break; default: @@ -697,7 +702,7 @@ static void dwc3_ep0_set_sel_cmpl(struct usb_ep *ep, struct usb_request *req) static int dwc3_ep0_set_sel(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) { struct dwc3_ep *dep; - enum usb_device_state state = dwc->gadget.state; + enum usb_device_state state = dwc->gadget->state; u16 wLength; if (state == USB_STATE_DEFAULT) @@ -741,7 +746,7 @@ static int dwc3_ep0_set_isoch_delay(struct dwc3 *dwc, struct usb_ctrlrequest *ct if (wIndex || wLength) return -EINVAL; - dwc->gadget.isoch_delay = wValue; + dwc->gadget->isoch_delay = wValue; return 0; } @@ -942,12 +947,16 @@ static void dwc3_ep0_xfer_complete(struct dwc3 *dwc, static void __dwc3_ep0_do_control_data(struct dwc3 *dwc, struct dwc3_ep *dep, struct dwc3_request *req) { + unsigned int trb_length = 0; int ret; req->direction = !!dep->number; if (req->request.length == 0) { - dwc3_ep0_prepare_one_trb(dep, dwc->ep0_trb_addr, 0, + if (!req->direction) + trb_length = dep->endpoint.maxpacket; + + dwc3_ep0_prepare_one_trb(dep, dwc->bounce_addr, trb_length, DWC3_TRBCTL_CONTROL_DATA, false); ret = dwc3_ep0_start_trans(dep); } else if (!IS_ALIGNED(req->request.length, dep->endpoint.maxpacket) @@ -994,9 +1003,12 @@ static void __dwc3_ep0_do_control_data(struct dwc3 *dwc, req->trb = &dwc->ep0_trb[dep->trb_enqueue - 1]; + if (!req->direction) + trb_length = dep->endpoint.maxpacket; + /* Now prepare one extra TRB to align transfer size */ dwc3_ep0_prepare_one_trb(dep, dwc->bounce_addr, - 0, DWC3_TRBCTL_CONTROL_DATA, + trb_length, DWC3_TRBCTL_CONTROL_DATA, false); ret = dwc3_ep0_start_trans(dep); } else { @@ -1042,6 +1054,17 @@ static void dwc3_ep0_do_control_status(struct dwc3 *dwc, __dwc3_ep0_do_control_status(dwc, dep); } +void dwc3_ep0_send_delayed_status(struct dwc3 *dwc) +{ + unsigned int direction = !dwc->ep0_expect_in; + + if (dwc->ep0state != EP0_STATUS_PHASE) + return; + + dwc->delayed_status = false; + __dwc3_ep0_do_control_status(dwc, dwc->eps[direction]); +} + static void dwc3_ep0_end_control_data(struct dwc3 *dwc, struct dwc3_ep *dep) { struct dwc3_gadget_ep_cmd_params params; @@ -1102,7 +1125,7 @@ static void dwc3_ep0_xfernotready(struct dwc3 *dwc, */ if (!list_empty(&dep->pending_list)) { dwc->delayed_status = false; - usb_gadget_set_state(&dwc->gadget, + usb_gadget_set_state(dwc->gadget, USB_STATE_CONFIGURED); dwc3_ep0_do_control_status(dwc, event); } diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index c2a0f64f8d1e..78cb4db8a6e4 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -227,7 +227,8 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, * Caller should take care of locking. Issue @cmd with a given @param to @dwc * and wait for its completion. */ -int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param) +int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned int cmd, + u32 param) { u32 timeout = 500; int status = 0; @@ -268,7 +269,7 @@ static int __dwc3_gadget_wakeup(struct dwc3 *dwc); * Caller should handle locking. This function will issue @cmd with given * @params to @dep and wait for its completion. */ -int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, +int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd, struct dwc3_gadget_ep_cmd_params *params) { const struct usb_endpoint_descriptor *desc = dep->endpoint.desc; @@ -290,7 +291,7 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, * * DWC_usb3 3.30a and DWC_usb31 1.90a programming guide section 3.2.2 */ - if (dwc->gadget.speed <= USB_SPEED_HIGH) { + if (dwc->gadget->speed <= USB_SPEED_HIGH) { reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); if (unlikely(reg & DWC3_GUSB2PHYCFG_SUSPHY)) { saved_config |= DWC3_GUSB2PHYCFG_SUSPHY; @@ -422,7 +423,7 @@ static int dwc3_send_clear_stall_ep_cmd(struct dwc3_ep *dep) */ if (dep->direction && !DWC3_VER_IS_PRIOR(DWC3, 260A) && - (dwc->gadget.speed >= USB_SPEED_SUPER)) + (dwc->gadget->speed >= USB_SPEED_SUPER)) cmd |= DWC3_DEPCMD_CLEARPENDIN; memset(¶ms, 0, sizeof(params)); @@ -562,8 +563,9 @@ static int dwc3_gadget_set_ep_config(struct dwc3_ep *dep, unsigned int action) | DWC3_DEPCFG_MAX_PACKET_SIZE(usb_endpoint_maxp(desc)); /* Burst size is only needed in SuperSpeed mode */ - if (dwc->gadget.speed >= USB_SPEED_SUPER) { + if (dwc->gadget->speed >= USB_SPEED_SUPER) { u32 burst = dep->endpoint.maxburst; + params.param0 |= DWC3_DEPCFG_BURST_SIZE(burst - 1); } @@ -942,12 +944,13 @@ static u32 dwc3_calc_trbs_left(struct dwc3_ep *dep) } static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb, - dma_addr_t dma, unsigned length, unsigned chain, unsigned node, - unsigned stream_id, unsigned short_not_ok, - unsigned no_interrupt, unsigned is_last) + dma_addr_t dma, unsigned int length, unsigned int chain, + unsigned int node, unsigned int stream_id, + unsigned int short_not_ok, unsigned int no_interrupt, + unsigned int is_last, bool must_interrupt) { struct dwc3 *dwc = dep->dwc; - struct usb_gadget *gadget = &dwc->gadget; + struct usb_gadget *gadget = dwc->gadget; enum usb_device_speed speed = gadget->speed; trb->size = DWC3_TRB_SIZE_LENGTH(length); @@ -1031,8 +1034,7 @@ static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb, trb->ctrl |= DWC3_TRB_CTRL_ISP_IMI; } - if ((!no_interrupt && !chain) || - (dwc3_calc_trbs_left(dep) == 1)) + if ((!no_interrupt && !chain) || must_interrupt) trb->ctrl |= DWC3_TRB_CTRL_IOC; if (chain) @@ -1057,19 +1059,24 @@ static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb, * @trb_length: buffer size of the TRB * @chain: should this TRB be chained to the next? * @node: only for isochronous endpoints. First TRB needs different type. + * @use_bounce_buffer: set to use bounce buffer + * @must_interrupt: set to interrupt on TRB completion */ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_request *req, unsigned int trb_length, - unsigned chain, unsigned node) + unsigned int chain, unsigned int node, bool use_bounce_buffer, + bool must_interrupt) { struct dwc3_trb *trb; dma_addr_t dma; - unsigned stream_id = req->request.stream_id; - unsigned short_not_ok = req->request.short_not_ok; - unsigned no_interrupt = req->request.no_interrupt; - unsigned is_last = req->request.is_last; - - if (req->request.num_sgs > 0) + unsigned int stream_id = req->request.stream_id; + unsigned int short_not_ok = req->request.short_not_ok; + unsigned int no_interrupt = req->request.no_interrupt; + unsigned int is_last = req->request.is_last; + + if (use_bounce_buffer) + dma = dep->dwc->bounce_addr; + else if (req->request.num_sgs > 0) dma = sg_dma_address(req->start_sg); else dma = req->request.dma; @@ -1085,10 +1092,63 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, req->num_trbs++; __dwc3_prepare_one_trb(dep, trb, dma, trb_length, chain, node, - stream_id, short_not_ok, no_interrupt, is_last); + stream_id, short_not_ok, no_interrupt, is_last, + must_interrupt); } -static void dwc3_prepare_one_trb_sg(struct dwc3_ep *dep, +static bool dwc3_needs_extra_trb(struct dwc3_ep *dep, struct dwc3_request *req) +{ + unsigned int maxp = usb_endpoint_maxp(dep->endpoint.desc); + unsigned int rem = req->request.length % maxp; + + if ((req->request.length && req->request.zero && !rem && + !usb_endpoint_xfer_isoc(dep->endpoint.desc)) || + (!req->direction && rem)) + return true; + + return false; +} + +/** + * dwc3_prepare_last_sg - prepare TRBs for the last SG entry + * @dep: The endpoint that the request belongs to + * @req: The request to prepare + * @entry_length: The last SG entry size + * @node: Indicates whether this is not the first entry (for isoc only) + * + * Return the number of TRBs prepared. + */ +static int dwc3_prepare_last_sg(struct dwc3_ep *dep, + struct dwc3_request *req, unsigned int entry_length, + unsigned int node) +{ + unsigned int maxp = usb_endpoint_maxp(dep->endpoint.desc); + unsigned int rem = req->request.length % maxp; + unsigned int num_trbs = 1; + + if (dwc3_needs_extra_trb(dep, req)) + num_trbs++; + + if (dwc3_calc_trbs_left(dep) < num_trbs) + return 0; + + req->needs_extra_trb = num_trbs > 1; + + /* Prepare a normal TRB */ + if (req->direction || req->request.length) + dwc3_prepare_one_trb(dep, req, entry_length, + req->needs_extra_trb, node, false, false); + + /* Prepare extra TRBs for ZLP and MPS OUT transfer alignment */ + if ((!req->direction && !req->request.length) || req->needs_extra_trb) + dwc3_prepare_one_trb(dep, req, + req->direction ? 0 : maxp - rem, + false, 1, true, false); + + return num_trbs; +} + +static int dwc3_prepare_trbs_sg(struct dwc3_ep *dep, struct dwc3_request *req) { struct scatterlist *sg = req->start_sg; @@ -1097,6 +1157,8 @@ static void dwc3_prepare_one_trb_sg(struct dwc3_ep *dep, unsigned int length = req->request.length; unsigned int remaining = req->request.num_mapped_sgs - req->num_queued_sgs; + unsigned int num_trbs = req->num_trbs; + bool needs_extra_trb = dwc3_needs_extra_trb(dep, req); /* * If we resume preparing the request, then get the remaining length of @@ -1106,10 +1168,10 @@ static void dwc3_prepare_one_trb_sg(struct dwc3_ep *dep, length -= sg_dma_len(s); for_each_sg(sg, s, remaining, i) { - unsigned int maxp = usb_endpoint_maxp(dep->endpoint.desc); - unsigned int rem = length % maxp; + unsigned int num_trbs_left = dwc3_calc_trbs_left(dep); unsigned int trb_length; - unsigned chain = true; + bool must_interrupt = false; + bool last_sg = false; trb_length = min_t(unsigned int, length, sg_dma_len(s)); @@ -1123,59 +1185,28 @@ static void dwc3_prepare_one_trb_sg(struct dwc3_ep *dep, * mapped sg. */ if ((i == remaining - 1) || !length) - chain = false; - - if (rem && usb_endpoint_dir_out(dep->endpoint.desc) && !chain) { - struct dwc3 *dwc = dep->dwc; - struct dwc3_trb *trb; - - req->needs_extra_trb = true; - - /* prepare normal TRB */ - dwc3_prepare_one_trb(dep, req, trb_length, true, i); - - /* Now prepare one extra TRB to align transfer size */ - trb = &dep->trb_pool[dep->trb_enqueue]; - req->num_trbs++; - __dwc3_prepare_one_trb(dep, trb, dwc->bounce_addr, - maxp - rem, false, 1, - req->request.stream_id, - req->request.short_not_ok, - req->request.no_interrupt, - req->request.is_last); - } else if (req->request.zero && req->request.length && - !usb_endpoint_xfer_isoc(dep->endpoint.desc) && - !rem && !chain) { - struct dwc3 *dwc = dep->dwc; - struct dwc3_trb *trb; - - req->needs_extra_trb = true; - - /* Prepare normal TRB */ - dwc3_prepare_one_trb(dep, req, trb_length, true, i); - - /* Prepare one extra TRB to handle ZLP */ - trb = &dep->trb_pool[dep->trb_enqueue]; - req->num_trbs++; - __dwc3_prepare_one_trb(dep, trb, dwc->bounce_addr, 0, - !req->direction, 1, - req->request.stream_id, - req->request.short_not_ok, - req->request.no_interrupt, - req->request.is_last); - - /* Prepare one more TRB to handle MPS alignment */ - if (!req->direction) { - trb = &dep->trb_pool[dep->trb_enqueue]; - req->num_trbs++; - __dwc3_prepare_one_trb(dep, trb, dwc->bounce_addr, maxp, - false, 1, req->request.stream_id, - req->request.short_not_ok, - req->request.no_interrupt, - req->request.is_last); - } + last_sg = true; + + if (!num_trbs_left) + break; + + if (last_sg) { + if (!dwc3_prepare_last_sg(dep, req, trb_length, i)) + break; } else { - dwc3_prepare_one_trb(dep, req, trb_length, chain, i); + /* + * Look ahead to check if we have enough TRBs for the + * next SG entry. If not, set interrupt on this TRB to + * resume preparing the next SG entry when more TRBs are + * free. + */ + if (num_trbs_left == 1 || (needs_extra_trb && + num_trbs_left <= 2 && + sg_dma_len(sg_next(s)) >= length)) + must_interrupt = true; + + dwc3_prepare_one_trb(dep, req, trb_length, 1, i, false, + must_interrupt); } /* @@ -1185,7 +1216,7 @@ static void dwc3_prepare_one_trb_sg(struct dwc3_ep *dep, * we have free trbs we can continue queuing from where we * previously stopped */ - if (chain) + if (!last_sg) req->start_sg = sg_next(s); req->num_queued_sgs++; @@ -1200,68 +1231,17 @@ static void dwc3_prepare_one_trb_sg(struct dwc3_ep *dep, break; } - if (!dwc3_calc_trbs_left(dep)) + if (must_interrupt) break; } + + return req->num_trbs - num_trbs; } -static void dwc3_prepare_one_trb_linear(struct dwc3_ep *dep, +static int dwc3_prepare_trbs_linear(struct dwc3_ep *dep, struct dwc3_request *req) { - unsigned int length = req->request.length; - unsigned int maxp = usb_endpoint_maxp(dep->endpoint.desc); - unsigned int rem = length % maxp; - - if ((!length || rem) && usb_endpoint_dir_out(dep->endpoint.desc)) { - struct dwc3 *dwc = dep->dwc; - struct dwc3_trb *trb; - - req->needs_extra_trb = true; - - /* prepare normal TRB */ - dwc3_prepare_one_trb(dep, req, length, true, 0); - - /* Now prepare one extra TRB to align transfer size */ - trb = &dep->trb_pool[dep->trb_enqueue]; - req->num_trbs++; - __dwc3_prepare_one_trb(dep, trb, dwc->bounce_addr, maxp - rem, - false, 1, req->request.stream_id, - req->request.short_not_ok, - req->request.no_interrupt, - req->request.is_last); - } else if (req->request.zero && req->request.length && - !usb_endpoint_xfer_isoc(dep->endpoint.desc) && - (IS_ALIGNED(req->request.length, maxp))) { - struct dwc3 *dwc = dep->dwc; - struct dwc3_trb *trb; - - req->needs_extra_trb = true; - - /* prepare normal TRB */ - dwc3_prepare_one_trb(dep, req, length, true, 0); - - /* Prepare one extra TRB to handle ZLP */ - trb = &dep->trb_pool[dep->trb_enqueue]; - req->num_trbs++; - __dwc3_prepare_one_trb(dep, trb, dwc->bounce_addr, 0, - !req->direction, 1, req->request.stream_id, - req->request.short_not_ok, - req->request.no_interrupt, - req->request.is_last); - - /* Prepare one more TRB to handle MPS alignment for OUT */ - if (!req->direction) { - trb = &dep->trb_pool[dep->trb_enqueue]; - req->num_trbs++; - __dwc3_prepare_one_trb(dep, trb, dwc->bounce_addr, maxp, - false, 1, req->request.stream_id, - req->request.short_not_ok, - req->request.no_interrupt, - req->request.is_last); - } - } else { - dwc3_prepare_one_trb(dep, req, length, false, 0); - } + return dwc3_prepare_last_sg(dep, req, req->request.length, 0); } /* @@ -1271,10 +1251,13 @@ static void dwc3_prepare_one_trb_linear(struct dwc3_ep *dep, * The function goes through the requests list and sets up TRBs for the * transfers. The function returns once there are no more TRBs available or * it runs out of requests. + * + * Returns the number of TRBs prepared or negative errno. */ -static void dwc3_prepare_trbs(struct dwc3_ep *dep) +static int dwc3_prepare_trbs(struct dwc3_ep *dep) { struct dwc3_request *req, *n; + int ret = 0; BUILD_BUG_ON_NOT_POWER_OF_2(DWC3_TRB_NUM); @@ -1289,11 +1272,14 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep) * break things. */ list_for_each_entry(req, &dep->started_list, list) { - if (req->num_pending_sgs > 0) - dwc3_prepare_one_trb_sg(dep, req); + if (req->num_pending_sgs > 0) { + ret = dwc3_prepare_trbs_sg(dep, req); + if (!ret || req->num_pending_sgs) + return ret; + } if (!dwc3_calc_trbs_left(dep)) - return; + return ret; /* * Don't prepare beyond a transfer. In DWC_usb32, its transfer @@ -1301,30 +1287,32 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep) * active transfer instead of stopping. */ if (dep->stream_capable && req->request.is_last) - return; + return ret; } list_for_each_entry_safe(req, n, &dep->pending_list, list) { struct dwc3 *dwc = dep->dwc; - int ret; ret = usb_gadget_map_request_by_dev(dwc->sysdev, &req->request, dep->direction); if (ret) - return; + return ret; req->sg = req->request.sg; req->start_sg = req->sg; req->num_queued_sgs = 0; req->num_pending_sgs = req->request.num_mapped_sgs; - if (req->num_pending_sgs > 0) - dwc3_prepare_one_trb_sg(dep, req); - else - dwc3_prepare_one_trb_linear(dep, req); + if (req->num_pending_sgs > 0) { + ret = dwc3_prepare_trbs_sg(dep, req); + if (req->num_pending_sgs) + return ret; + } else { + ret = dwc3_prepare_trbs_linear(dep, req); + } - if (!dwc3_calc_trbs_left(dep)) - return; + if (!ret || !dwc3_calc_trbs_left(dep)) + return ret; /* * Don't prepare beyond a transfer. In DWC_usb32, its transfer @@ -1332,8 +1320,10 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep) * active transfer instead of stopping. */ if (dep->stream_capable && req->request.is_last) - return; + return ret; } + + return ret; } static void dwc3_gadget_ep_cleanup_cancelled_requests(struct dwc3_ep *dep); @@ -1346,12 +1336,24 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep) int ret; u32 cmd; - if (!dwc3_calc_trbs_left(dep)) - return 0; + /* + * Note that it's normal to have no new TRBs prepared (i.e. ret == 0). + * This happens when we need to stop and restart a transfer such as in + * the case of reinitiating a stream or retrying an isoc transfer. + */ + ret = dwc3_prepare_trbs(dep); + if (ret < 0) + return ret; starting = !(dep->flags & DWC3_EP_TRANSFER_STARTED); - dwc3_prepare_trbs(dep); + /* + * If there's no new TRB prepared and we don't need to restart a + * transfer, there's no need to update the transfer. + */ + if (!ret && !starting) + return ret; + req = next_request(&dep->started_list); if (!req) { dep->flags |= DWC3_EP_PENDING_REQUEST; @@ -1539,12 +1541,12 @@ static int __dwc3_gadget_start_isoc(struct dwc3_ep *dep) if (!dwc->dis_start_transfer_quirk && (DWC3_VER_IS_PRIOR(DWC31, 170A) || DWC3_VER_TYPE_IS_WITHIN(DWC31, 170A, EA01, EA06))) { - if (dwc->gadget.speed <= USB_SPEED_HIGH && dep->direction) + if (dwc->gadget->speed <= USB_SPEED_HIGH && dep->direction) return dwc3_gadget_start_isoc_quirk(dep); } if (desc->bInterval <= 14 && - dwc->gadget.speed >= USB_SPEED_HIGH) { + dwc->gadget->speed >= USB_SPEED_HIGH) { u32 frame = __dwc3_gadget_get_frame(dwc); bool rollover = frame < (dep->frame_number & DWC3_FRNUMBER_MASK); @@ -1600,7 +1602,7 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) { struct dwc3 *dwc = dep->dwc; - if (!dep->endpoint.desc) { + if (!dep->endpoint.desc || !dwc->pullups_connected) { dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n", dep->name); return -ESHUTDOWN; @@ -1628,8 +1630,13 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) if (dep->flags & DWC3_EP_WAIT_TRANSFER_COMPLETE) return 0; - /* Start the transfer only after the END_TRANSFER is completed */ - if (dep->flags & DWC3_EP_END_TRANSFER_PENDING) { + /* + * Start the transfer only after the END_TRANSFER is completed + * and endpoint STALL is cleared. + */ + if ((dep->flags & DWC3_EP_END_TRANSFER_PENDING) || + (dep->flags & DWC3_EP_WEDGE) || + (dep->flags & DWC3_EP_STALL)) { dep->flags |= DWC3_EP_DELAY_START; return 0; } @@ -1648,9 +1655,8 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) return 0; if ((dep->flags & DWC3_EP_PENDING_REQUEST)) { - if (!(dep->flags & DWC3_EP_TRANSFER_STARTED)) { + if (!(dep->flags & DWC3_EP_TRANSFER_STARTED)) return __dwc3_gadget_start_isoc(dep); - } } } @@ -1788,8 +1794,8 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol) if (value) { struct dwc3_trb *trb; - unsigned transfer_in_flight; - unsigned started; + unsigned int transfer_in_flight; + unsigned int started; if (dep->number > 1) trb = dwc3_ep_prev_trb(dep, dep->trb_enqueue); @@ -1822,6 +1828,18 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol) return 0; } + dwc3_stop_active_transfer(dep, true, true); + + list_for_each_entry_safe(req, tmp, &dep->started_list, list) + dwc3_gadget_move_cancelled_request(req); + + if (dep->flags & DWC3_EP_END_TRANSFER_PENDING) { + dep->flags |= DWC3_EP_PENDING_CLEAR_STALL; + return 0; + } + + dwc3_gadget_ep_cleanup_cancelled_requests(dep); + ret = dwc3_send_clear_stall_ep_cmd(dep); if (ret) { dev_err(dwc->dev, "failed to clear STALL on %s\n", @@ -1831,18 +1849,11 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol) dep->flags &= ~(DWC3_EP_STALL | DWC3_EP_WEDGE); - dwc3_stop_active_transfer(dep, true, true); - - list_for_each_entry_safe(req, tmp, &dep->started_list, list) - dwc3_gadget_move_cancelled_request(req); - - list_for_each_entry_safe(req, tmp, &dep->pending_list, list) - dwc3_gadget_move_cancelled_request(req); + if ((dep->flags & DWC3_EP_DELAY_START) && + !usb_endpoint_xfer_isoc(dep->endpoint.desc)) + __dwc3_gadget_kick_transfer(dep); - if (!(dep->flags & DWC3_EP_END_TRANSFER_PENDING)) { - dep->flags &= ~DWC3_EP_DELAY_START; - dwc3_gadget_ep_cleanup_cancelled_requests(dep); - } + dep->flags &= ~DWC3_EP_DELAY_START; } return ret; @@ -2010,6 +2021,21 @@ static int dwc3_gadget_set_selfpowered(struct usb_gadget *g, return 0; } +static void dwc3_stop_active_transfers(struct dwc3 *dwc) +{ + u32 epnum; + + for (epnum = 2; epnum < dwc->num_eps; epnum++) { + struct dwc3_ep *dep; + + dep = dwc->eps[epnum]; + if (!dep) + continue; + + dwc3_remove_requests(dwc, dep); + } +} + static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend) { u32 reg; @@ -2055,6 +2081,9 @@ static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend) return 0; } +static void dwc3_gadget_disable_irq(struct dwc3 *dwc); +static void __dwc3_gadget_stop(struct dwc3 *dwc); + static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) { struct dwc3 *dwc = gadget_to_dwc(g); @@ -2078,7 +2107,46 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) } } + /* + * Synchronize any pending event handling before executing the controller + * halt routine. + */ + if (!is_on) { + dwc3_gadget_disable_irq(dwc); + synchronize_irq(dwc->irq_gadget); + } + spin_lock_irqsave(&dwc->lock, flags); + + if (!is_on) { + u32 count; + + /* + * In the Synopsis DesignWare Cores USB3 Databook Rev. 3.30a + * Section 4.1.8 Table 4-7, it states that for a device-initiated + * disconnect, the SW needs to ensure that it sends "a DEPENDXFER + * command for any active transfers" before clearing the RunStop + * bit. + */ + dwc3_stop_active_transfers(dwc); + __dwc3_gadget_stop(dwc); + + /* + * In the Synopsis DesignWare Cores USB3 Databook Rev. 3.30a + * Section 1.3.4, it mentions that for the DEVCTRLHLT bit, the + * "software needs to acknowledge the events that are generated + * (by writing to GEVNTCOUNTn) while it is waiting for this bit + * to be set to '1'." + */ + count = dwc3_readl(dwc->regs, DWC3_GEVNTCOUNT(0)); + count &= DWC3_GEVNTCOUNT_MASK; + if (count > 0) { + dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), count); + dwc->ev_buf->lpos = (dwc->ev_buf->lpos + count) % + dwc->ev_buf->length; + } + } + ret = dwc3_gadget_run_stop(dwc, is_on, false); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2244,7 +2312,7 @@ static int dwc3_gadget_start(struct usb_gadget *g, spin_lock_irqsave(&dwc->lock, flags); if (dwc->gadget_driver) { dev_err(dwc->dev, "%s is already bound to %s\n", - dwc->gadget.name, + dwc->gadget->name, dwc->gadget_driver->driver.name); ret = -EBUSY; goto err1; @@ -2416,7 +2484,7 @@ static int dwc3_gadget_init_control_endpoint(struct dwc3_ep *dep) dep->endpoint.maxburst = 1; dep->endpoint.ops = &dwc3_gadget_ep0_ops; if (!dep->direction) - dwc->gadget.ep0 = &dep->endpoint; + dwc->gadget->ep0 = &dep->endpoint; dep->endpoint.caps.type_control = true; @@ -2459,10 +2527,10 @@ static int dwc3_gadget_init_in_endpoint(struct dwc3_ep *dep) usb_ep_set_maxpacket_limit(&dep->endpoint, size); - dep->endpoint.max_streams = 15; + dep->endpoint.max_streams = 16; dep->endpoint.ops = &dwc3_gadget_ep_ops; list_add_tail(&dep->endpoint.ep_list, - &dwc->gadget.ep_list); + &dwc->gadget->ep_list); dep->endpoint.caps.type_iso = true; dep->endpoint.caps.type_bulk = true; dep->endpoint.caps.type_int = true; @@ -2508,10 +2576,10 @@ static int dwc3_gadget_init_out_endpoint(struct dwc3_ep *dep) size /= 3; usb_ep_set_maxpacket_limit(&dep->endpoint, size); - dep->endpoint.max_streams = 15; + dep->endpoint.max_streams = 16; dep->endpoint.ops = &dwc3_gadget_ep_ops; list_add_tail(&dep->endpoint.ep_list, - &dwc->gadget.ep_list); + &dwc->gadget->ep_list); dep->endpoint.caps.type_iso = true; dep->endpoint.caps.type_bulk = true; dep->endpoint.caps.type_int = true; @@ -2572,7 +2640,7 @@ static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 total) { u8 epnum; - INIT_LIST_HEAD(&dwc->gadget.ep_list); + INIT_LIST_HEAD(&dwc->gadget->ep_list); for (epnum = 0; epnum < total; epnum++) { int ret; @@ -2652,12 +2720,12 @@ static int dwc3_gadget_ep_reclaim_completed_trb(struct dwc3_ep *dep, } /* - * If we're dealing with unaligned size OUT transfer, we will be left - * with one TRB pending in the ring. We need to manually clear HWO bit - * from that TRB. + * We use bounce buffer for requests that needs extra TRB or OUT ZLP. If + * this TRB points to the bounce buffer address, it's a MPS alignment + * TRB. Don't add it to req->remaining calculation. */ - - if (req->needs_extra_trb && !(trb->ctrl & DWC3_TRB_CTRL_CHN)) { + if (trb->bpl == lower_32_bits(dep->dwc->bounce_addr) && + trb->bph == upper_32_bits(dep->dwc->bounce_addr)) { trb->ctrl &= ~DWC3_TRB_CTRL_HWO; return 1; } @@ -2732,26 +2800,17 @@ static int dwc3_gadget_ep_cleanup_completed_request(struct dwc3_ep *dep, ret = dwc3_gadget_ep_reclaim_trb_linear(dep, req, event, status); - if (req->needs_extra_trb) { - unsigned int maxp = usb_endpoint_maxp(dep->endpoint.desc); + req->request.actual = req->request.length - req->remaining; + + if (!dwc3_gadget_ep_request_completed(req)) + goto out; + if (req->needs_extra_trb) { ret = dwc3_gadget_ep_reclaim_trb_linear(dep, req, event, status); - - /* Reclaim MPS padding TRB for ZLP */ - if (!req->direction && req->request.zero && req->request.length && - !usb_endpoint_xfer_isoc(dep->endpoint.desc) && - (IS_ALIGNED(req->request.length, maxp))) - ret = dwc3_gadget_ep_reclaim_trb_linear(dep, req, event, status); - req->needs_extra_trb = false; } - req->request.actual = req->request.length - req->remaining; - - if (!dwc3_gadget_ep_request_completed(req)) - goto out; - dwc3_gadget_giveback(dep, req, status); out: @@ -2896,6 +2955,43 @@ static void dwc3_gadget_endpoint_transfer_not_ready(struct dwc3_ep *dep, (void) __dwc3_gadget_start_isoc(dep); } +static void dwc3_gadget_endpoint_command_complete(struct dwc3_ep *dep, + const struct dwc3_event_depevt *event) +{ + u8 cmd = DEPEVT_PARAMETER_CMD(event->parameters); + + if (cmd != DWC3_DEPCMD_ENDTRANSFER) + return; + + dep->flags &= ~DWC3_EP_END_TRANSFER_PENDING; + dep->flags &= ~DWC3_EP_TRANSFER_STARTED; + dwc3_gadget_ep_cleanup_cancelled_requests(dep); + + if (dep->flags & DWC3_EP_PENDING_CLEAR_STALL) { + struct dwc3 *dwc = dep->dwc; + + dep->flags &= ~DWC3_EP_PENDING_CLEAR_STALL; + if (dwc3_send_clear_stall_ep_cmd(dep)) { + struct usb_ep *ep0 = &dwc->eps[0]->endpoint; + + dev_err(dwc->dev, "failed to clear STALL on %s\n", dep->name); + if (dwc->delayed_status) + __dwc3_gadget_ep0_set_halt(ep0, 1); + return; + } + + dep->flags &= ~(DWC3_EP_STALL | DWC3_EP_WEDGE); + if (dwc->delayed_status) + dwc3_ep0_send_delayed_status(dwc); + } + + if ((dep->flags & DWC3_EP_DELAY_START) && + !usb_endpoint_xfer_isoc(dep->endpoint.desc)) + __dwc3_gadget_kick_transfer(dep); + + dep->flags &= ~DWC3_EP_DELAY_START; +} + static void dwc3_gadget_endpoint_stream_event(struct dwc3_ep *dep, const struct dwc3_event_depevt *event) { @@ -2965,7 +3061,6 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, { struct dwc3_ep *dep; u8 epnum = event->endpoint_number; - u8 cmd; dep = dwc->eps[epnum]; @@ -2991,18 +3086,7 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, dwc3_gadget_endpoint_transfer_not_ready(dep, event); break; case DWC3_DEPEVT_EPCMDCMPLT: - cmd = DEPEVT_PARAMETER_CMD(event->parameters); - - if (cmd == DWC3_DEPCMD_ENDTRANSFER) { - dep->flags &= ~DWC3_EP_END_TRANSFER_PENDING; - dep->flags &= ~DWC3_EP_TRANSFER_STARTED; - dwc3_gadget_ep_cleanup_cancelled_requests(dep); - if ((dep->flags & DWC3_EP_DELAY_START) && - !usb_endpoint_xfer_isoc(dep->endpoint.desc)) - __dwc3_gadget_kick_transfer(dep); - - dep->flags &= ~DWC3_EP_DELAY_START; - } + dwc3_gadget_endpoint_command_complete(dep, event); break; case DWC3_DEPEVT_XFERCOMPLETE: dwc3_gadget_endpoint_transfer_complete(dep, event); @@ -3019,7 +3103,7 @@ static void dwc3_disconnect_gadget(struct dwc3 *dwc) { if (dwc->gadget_driver && dwc->gadget_driver->disconnect) { spin_unlock(&dwc->lock); - dwc->gadget_driver->disconnect(&dwc->gadget); + dwc->gadget_driver->disconnect(dwc->gadget); spin_lock(&dwc->lock); } } @@ -3028,7 +3112,7 @@ static void dwc3_suspend_gadget(struct dwc3 *dwc) { if (dwc->gadget_driver && dwc->gadget_driver->suspend) { spin_unlock(&dwc->lock); - dwc->gadget_driver->suspend(&dwc->gadget); + dwc->gadget_driver->suspend(dwc->gadget); spin_lock(&dwc->lock); } } @@ -3037,7 +3121,7 @@ static void dwc3_resume_gadget(struct dwc3 *dwc) { if (dwc->gadget_driver && dwc->gadget_driver->resume) { spin_unlock(&dwc->lock); - dwc->gadget_driver->resume(&dwc->gadget); + dwc->gadget_driver->resume(dwc->gadget); spin_lock(&dwc->lock); } } @@ -3047,9 +3131,9 @@ static void dwc3_reset_gadget(struct dwc3 *dwc) if (!dwc->gadget_driver) return; - if (dwc->gadget.speed != USB_SPEED_UNKNOWN) { + if (dwc->gadget->speed != USB_SPEED_UNKNOWN) { spin_unlock(&dwc->lock); - usb_gadget_udc_reset(&dwc->gadget, dwc->gadget_driver); + usb_gadget_udc_reset(dwc->gadget, dwc->gadget_driver); spin_lock(&dwc->lock); } } @@ -3150,9 +3234,9 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc) dwc3_disconnect_gadget(dwc); - dwc->gadget.speed = USB_SPEED_UNKNOWN; + dwc->gadget->speed = USB_SPEED_UNKNOWN; dwc->setup_packet_pending = false; - usb_gadget_set_state(&dwc->gadget, USB_STATE_NOTATTACHED); + usb_gadget_set_state(dwc->gadget, USB_STATE_NOTATTACHED); dwc->connected = false; } @@ -3195,6 +3279,13 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) } dwc3_reset_gadget(dwc); + /* + * In the Synopsis DesignWare Cores USB3 Databook Rev. 3.30a + * Section 4.1.2 Table 4-2, it states that during a USB reset, the SW + * needs to ensure that it sends "a DEPENDXFER command for any active + * transfers." + */ + dwc3_stop_active_transfers(dwc); reg = dwc3_readl(dwc->regs, DWC3_DCTL); reg &= ~DWC3_DCTL_TSTCTRL_MASK; @@ -3231,8 +3322,8 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) switch (speed) { case DWC3_DSTS_SUPERSPEED_PLUS: dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); - dwc->gadget.ep0->maxpacket = 512; - dwc->gadget.speed = USB_SPEED_SUPER_PLUS; + dwc->gadget->ep0->maxpacket = 512; + dwc->gadget->speed = USB_SPEED_SUPER_PLUS; break; case DWC3_DSTS_SUPERSPEED: /* @@ -3252,27 +3343,27 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) dwc3_gadget_reset_interrupt(dwc); dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); - dwc->gadget.ep0->maxpacket = 512; - dwc->gadget.speed = USB_SPEED_SUPER; + dwc->gadget->ep0->maxpacket = 512; + dwc->gadget->speed = USB_SPEED_SUPER; break; case DWC3_DSTS_HIGHSPEED: dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); - dwc->gadget.ep0->maxpacket = 64; - dwc->gadget.speed = USB_SPEED_HIGH; + dwc->gadget->ep0->maxpacket = 64; + dwc->gadget->speed = USB_SPEED_HIGH; break; case DWC3_DSTS_FULLSPEED: dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); - dwc->gadget.ep0->maxpacket = 64; - dwc->gadget.speed = USB_SPEED_FULL; + dwc->gadget->ep0->maxpacket = 64; + dwc->gadget->speed = USB_SPEED_FULL; break; case DWC3_DSTS_LOWSPEED: dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(8); - dwc->gadget.ep0->maxpacket = 8; - dwc->gadget.speed = USB_SPEED_LOW; + dwc->gadget->ep0->maxpacket = 8; + dwc->gadget->speed = USB_SPEED_LOW; break; } - dwc->eps[1]->endpoint.maxpacket = dwc->gadget.ep0->maxpacket; + dwc->eps[1]->endpoint.maxpacket = dwc->gadget->ep0->maxpacket; /* Enable USB2 LPM Capability */ @@ -3340,7 +3431,7 @@ static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc) if (dwc->gadget_driver && dwc->gadget_driver->resume) { spin_unlock(&dwc->lock); - dwc->gadget_driver->resume(&dwc->gadget); + dwc->gadget_driver->resume(dwc->gadget); spin_lock(&dwc->lock); } } @@ -3511,7 +3602,7 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc, * Ignore suspend event until the gadget enters into * USB_STATE_CONFIGURED state. */ - if (dwc->gadget.state >= USB_STATE_CONFIGURED) + if (dwc->gadget->state >= USB_STATE_CONFIGURED) dwc3_gadget_suspend_interrupt(dwc, event->event_info); } @@ -3686,6 +3777,13 @@ out: return irq; } +static void dwc_gadget_release(struct device *dev) +{ + struct usb_gadget *gadget = container_of(dev, struct usb_gadget, dev); + + kfree(gadget); +} + /** * dwc3_gadget_init - initializes gadget related registers * @dwc: pointer to our controller context structure @@ -3696,6 +3794,7 @@ int dwc3_gadget_init(struct dwc3 *dwc) { int ret; int irq; + struct device *dev; irq = dwc3_gadget_get_irq(dwc); if (irq < 0) { @@ -3728,12 +3827,21 @@ int dwc3_gadget_init(struct dwc3 *dwc) } init_completion(&dwc->ep0_in_setup); + dwc->gadget = kzalloc(sizeof(struct usb_gadget), GFP_KERNEL); + if (!dwc->gadget) { + ret = -ENOMEM; + goto err3; + } - dwc->gadget.ops = &dwc3_gadget_ops; - dwc->gadget.speed = USB_SPEED_UNKNOWN; - dwc->gadget.sg_supported = true; - dwc->gadget.name = "dwc3-gadget"; - dwc->gadget.lpm_capable = true; + + usb_initialize_gadget(dwc->dev, dwc->gadget, dwc_gadget_release); + dev = &dwc->gadget->dev; + dev->platform_data = dwc; + dwc->gadget->ops = &dwc3_gadget_ops; + dwc->gadget->speed = USB_SPEED_UNKNOWN; + dwc->gadget->sg_supported = true; + dwc->gadget->name = "dwc3-gadget"; + dwc->gadget->lpm_capable = true; /* * FIXME We might be setting max_speed to <SUPER, however versions @@ -3756,7 +3864,7 @@ int dwc3_gadget_init(struct dwc3 *dwc) dev_info(dwc->dev, "changing max_speed on rev %08x\n", dwc->revision); - dwc->gadget.max_speed = dwc->maximum_speed; + dwc->gadget->max_speed = dwc->maximum_speed; /* * REVISIT: Here we should clear all pending IRQs to be @@ -3765,21 +3873,22 @@ int dwc3_gadget_init(struct dwc3 *dwc) ret = dwc3_gadget_init_endpoints(dwc, dwc->num_eps); if (ret) - goto err3; + goto err4; - ret = usb_add_gadget_udc(dwc->dev, &dwc->gadget); + ret = usb_add_gadget(dwc->gadget); if (ret) { - dev_err(dwc->dev, "failed to register udc\n"); - goto err4; + dev_err(dwc->dev, "failed to add gadget\n"); + goto err5; } - dwc3_gadget_set_speed(&dwc->gadget, dwc->maximum_speed); + dwc3_gadget_set_speed(dwc->gadget, dwc->maximum_speed); return 0; -err4: +err5: dwc3_gadget_free_endpoints(dwc); - +err4: + usb_put_gadget(dwc->gadget); err3: dma_free_coherent(dwc->sysdev, DWC3_BOUNCE_SIZE, dwc->bounce, dwc->bounce_addr); @@ -3799,7 +3908,7 @@ err0: void dwc3_gadget_exit(struct dwc3 *dwc) { - usb_del_gadget_udc(&dwc->gadget); + usb_del_gadget_udc(dwc->gadget); dwc3_gadget_free_endpoints(dwc); dma_free_coherent(dwc->sysdev, DWC3_BOUNCE_SIZE, dwc->bounce, dwc->bounce_addr); diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h index bd85eb7fa9ef..0cd281949970 100644 --- a/drivers/usb/dwc3/gadget.h +++ b/drivers/usb/dwc3/gadget.h @@ -17,7 +17,7 @@ struct dwc3; #define to_dwc3_ep(ep) (container_of(ep, struct dwc3_ep, endpoint)) -#define gadget_to_dwc(g) (container_of(g, struct dwc3, gadget)) +#define gadget_to_dwc(g) (dev_get_platdata(&g->dev)) /* DEPCFG parameter 1 */ #define DWC3_DEPCFG_INT_NUM(n) (((n) & 0x1f) << 0) @@ -113,6 +113,7 @@ int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value); int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, gfp_t gfp_flags); int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol); +void dwc3_ep0_send_delayed_status(struct dwc3 *dwc); /** * dwc3_gadget_ep_get_transfer_index - Gets transfer index from HW diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h index da1be01637c8..97f4f1125a41 100644 --- a/drivers/usb/dwc3/trace.h +++ b/drivers/usb/dwc3/trace.h @@ -104,8 +104,8 @@ DECLARE_EVENT_CLASS(dwc3_log_request, TP_STRUCT__entry( __string(name, req->dep->name) __field(struct dwc3_request *, req) - __field(unsigned, actual) - __field(unsigned, length) + __field(unsigned int, actual) + __field(unsigned int, length) __field(int, status) __field(int, zero) __field(int, short_not_ok) @@ -246,6 +246,7 @@ DECLARE_EVENT_CLASS(dwc3_log_trb, __entry->dequeue, __entry->bph, __entry->bpl, ({char *s; int pcm = ((__entry->size >> 24) & 3) + 1; + switch (__entry->type) { case USB_ENDPOINT_XFER_INT: case USB_ENDPOINT_XFER_ISOC: @@ -291,12 +292,12 @@ DECLARE_EVENT_CLASS(dwc3_log_ep, TP_ARGS(dep), TP_STRUCT__entry( __string(name, dep->name) - __field(unsigned, maxpacket) - __field(unsigned, maxpacket_limit) - __field(unsigned, max_streams) - __field(unsigned, maxburst) - __field(unsigned, flags) - __field(unsigned, direction) + __field(unsigned int, maxpacket) + __field(unsigned int, maxpacket_limit) + __field(unsigned int, max_streams) + __field(unsigned int, maxburst) + __field(unsigned int, flags) + __field(unsigned int, direction) __field(u8, trb_enqueue) __field(u8, trb_dequeue) ), diff --git a/drivers/usb/dwc3/ulpi.c b/drivers/usb/dwc3/ulpi.c index e6e6176386a4..aa213c9815f6 100644 --- a/drivers/usb/dwc3/ulpi.c +++ b/drivers/usb/dwc3/ulpi.c @@ -19,7 +19,7 @@ static int dwc3_ulpi_busyloop(struct dwc3 *dwc) { - unsigned count = 1000; + unsigned int count = 1000; u32 reg; while (count--) { diff --git a/drivers/usb/early/ehci-dbgp.c b/drivers/usb/early/ehci-dbgp.c index b075dbfad730..45b42d8f6453 100644 --- a/drivers/usb/early/ehci-dbgp.c +++ b/drivers/usb/early/ehci-dbgp.c @@ -15,6 +15,7 @@ #include <linux/console.h> #include <linux/errno.h> #include <linux/init.h> +#include <linux/iopoll.h> #include <linux/pci_regs.h> #include <linux/pci_ids.h> #include <linux/usb/ch9.h> @@ -161,17 +162,11 @@ static inline u32 dbgp_pid_read_update(u32 x, u32 tok) static int dbgp_wait_until_complete(void) { u32 ctrl; - int loop = DBGP_TIMEOUT; - - do { - ctrl = readl(&ehci_debug->control); - /* Stop when the transaction is finished */ - if (ctrl & DBGP_DONE) - break; - udelay(1); - } while (--loop > 0); + int ret; - if (!loop) + ret = readl_poll_timeout_atomic(&ehci_debug->control, ctrl, + (ctrl & DBGP_DONE), 1, DBGP_TIMEOUT); + if (ret) return -DBGP_TIMEOUT; /* diff --git a/drivers/usb/early/xhci-dbc.c b/drivers/usb/early/xhci-dbc.c index c0507767a8e3..be4ecbabdd58 100644 --- a/drivers/usb/early/xhci-dbc.c +++ b/drivers/usb/early/xhci-dbc.c @@ -14,6 +14,7 @@ #include <linux/pci_ids.h> #include <linux/memblock.h> #include <linux/io.h> +#include <linux/iopoll.h> #include <asm/pci-direct.h> #include <asm/fixmap.h> #include <linux/bcd.h> @@ -135,16 +136,9 @@ static int handshake(void __iomem *ptr, u32 mask, u32 done, int wait, int delay) { u32 result; - do { - result = readl(ptr); - result &= mask; - if (result == done) - return 0; - udelay(delay); - wait -= delay; - } while (wait > 0); - - return -ETIMEDOUT; + return readl_poll_timeout_atomic(ptr, result, + ((result & mask) == done), + delay, wait); } static void __init xdbc_bios_handoff(void) diff --git a/drivers/usb/gadget/function/f_acm.c b/drivers/usb/gadget/function/f_acm.c index 200596ea9557..46647bfac2ef 100644 --- a/drivers/usb/gadget/function/f_acm.c +++ b/drivers/usb/gadget/function/f_acm.c @@ -425,9 +425,11 @@ static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) /* we know alt == 0, so this is an activation or a reset */ if (intf == acm->ctrl_id) { - dev_vdbg(&cdev->gadget->dev, - "reset acm control interface %d\n", intf); - usb_ep_disable(acm->notify); + if (acm->notify->enabled) { + dev_vdbg(&cdev->gadget->dev, + "reset acm control interface %d\n", intf); + usb_ep_disable(acm->notify); + } if (!acm->notify->desc) if (config_ep_by_speed(cdev->gadget, f, acm->notify)) diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c index 46af0aa07e2e..85cb15734aa8 100644 --- a/drivers/usb/gadget/function/f_midi.c +++ b/drivers/usb/gadget/function/f_midi.c @@ -698,9 +698,9 @@ drop_out: f_midi_drop_out_substreams(midi); } -static void f_midi_in_tasklet(unsigned long data) +static void f_midi_in_tasklet(struct tasklet_struct *t) { - struct f_midi *midi = (struct f_midi *) data; + struct f_midi *midi = from_tasklet(midi, t, tasklet); f_midi_transmit(midi); } @@ -875,7 +875,7 @@ static int f_midi_bind(struct usb_configuration *c, struct usb_function *f) int status, n, jack = 1, i = 0, endpoint_descriptor_index = 0; midi->gadget = cdev->gadget; - tasklet_init(&midi->tasklet, f_midi_in_tasklet, (unsigned long) midi); + tasklet_setup(&midi->tasklet, f_midi_in_tasklet); status = f_midi_register_card(midi); if (status < 0) goto fail_register; diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index 1f638759a953..019bea8e09cc 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -85,8 +85,10 @@ static inline struct f_ncm *func_to_ncm(struct usb_function *f) /* peak (theoretical) bulk transfer rate in bits-per-second */ static inline unsigned ncm_bitrate(struct usb_gadget *g) { - if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER) - return 13 * 1024 * 8 * 1000 * 8; + if (gadget_is_superspeed(g) && g->speed >= USB_SPEED_SUPER_PLUS) + return 4250000000U; + else if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER) + return 3750000000U; else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) return 13 * 512 * 8 * 1000 * 8; else @@ -376,7 +378,7 @@ static struct usb_ss_ep_comp_descriptor ss_ncm_bulk_comp_desc = { .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, /* the following 2 values can be tweaked if necessary */ - /* .bMaxBurst = 0, */ + .bMaxBurst = 15, /* .bmAttributes = 0, */ }; @@ -1534,7 +1536,7 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) fs_ncm_notify_desc.bEndpointAddress; status = usb_assign_descriptors(f, ncm_fs_function, ncm_hs_function, - ncm_ss_function, NULL); + ncm_ss_function, ncm_ss_function); if (status) goto fail; diff --git a/drivers/usb/gadget/function/f_printer.c b/drivers/usb/gadget/function/f_printer.c index 68697f596066..64a4112068fc 100644 --- a/drivers/usb/gadget/function/f_printer.c +++ b/drivers/usb/gadget/function/f_printer.c @@ -31,6 +31,7 @@ #include <linux/types.h> #include <linux/ctype.h> #include <linux/cdev.h> +#include <linux/kref.h> #include <asm/byteorder.h> #include <linux/io.h> @@ -64,7 +65,7 @@ struct printer_dev { struct usb_gadget *gadget; s8 interface; struct usb_ep *in_ep, *out_ep; - + struct kref kref; struct list_head rx_reqs; /* List of free RX structs */ struct list_head rx_reqs_active; /* List of Active RX xfers */ struct list_head rx_buffers; /* List of completed xfers */ @@ -218,6 +219,13 @@ static inline struct usb_endpoint_descriptor *ep_desc(struct usb_gadget *gadget, /*-------------------------------------------------------------------------*/ +static void printer_dev_free(struct kref *kref) +{ + struct printer_dev *dev = container_of(kref, struct printer_dev, kref); + + kfree(dev); +} + static struct usb_request * printer_req_alloc(struct usb_ep *ep, unsigned len, gfp_t gfp_flags) { @@ -353,6 +361,7 @@ printer_open(struct inode *inode, struct file *fd) spin_unlock_irqrestore(&dev->lock, flags); + kref_get(&dev->kref); DBG(dev, "printer_open returned %x\n", ret); return ret; } @@ -370,6 +379,7 @@ printer_close(struct inode *inode, struct file *fd) dev->printer_status &= ~PRINTER_SELECTED; spin_unlock_irqrestore(&dev->lock, flags); + kref_put(&dev->kref, printer_dev_free); DBG(dev, "printer_close\n"); return 0; @@ -1386,7 +1396,8 @@ static void gprinter_free(struct usb_function *f) struct f_printer_opts *opts; opts = container_of(f->fi, struct f_printer_opts, func_inst); - kfree(dev); + + kref_put(&dev->kref, printer_dev_free); mutex_lock(&opts->lock); --opts->refcnt; mutex_unlock(&opts->lock); @@ -1455,6 +1466,7 @@ static struct usb_function *gprinter_alloc(struct usb_function_instance *fi) return ERR_PTR(-ENOMEM); } + kref_init(&dev->kref); ++opts->refcnt; dev->minor = opts->minor; dev->pnp_string = opts->pnp_string; diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c index 184165e27908..410fa89eae8f 100644 --- a/drivers/usb/gadget/function/f_tcm.c +++ b/drivers/usb/gadget/function/f_tcm.c @@ -392,12 +392,12 @@ static void bot_set_alt(struct f_uas *fu) fu->flags = USBG_IS_BOT; - config_ep_by_speed(gadget, f, fu->ep_in); + config_ep_by_speed_and_alt(gadget, f, fu->ep_in, USB_G_ALT_INT_BBB); ret = usb_ep_enable(fu->ep_in); if (ret) goto err_b_in; - config_ep_by_speed(gadget, f, fu->ep_out); + config_ep_by_speed_and_alt(gadget, f, fu->ep_out, USB_G_ALT_INT_BBB); ret = usb_ep_enable(fu->ep_out); if (ret) goto err_b_out; @@ -852,21 +852,21 @@ static void uasp_set_alt(struct f_uas *fu) if (gadget->speed >= USB_SPEED_SUPER) fu->flags |= USBG_USE_STREAMS; - config_ep_by_speed(gadget, f, fu->ep_in); + config_ep_by_speed_and_alt(gadget, f, fu->ep_in, USB_G_ALT_INT_UAS); ret = usb_ep_enable(fu->ep_in); if (ret) goto err_b_in; - config_ep_by_speed(gadget, f, fu->ep_out); + config_ep_by_speed_and_alt(gadget, f, fu->ep_out, USB_G_ALT_INT_UAS); ret = usb_ep_enable(fu->ep_out); if (ret) goto err_b_out; - config_ep_by_speed(gadget, f, fu->ep_cmd); + config_ep_by_speed_and_alt(gadget, f, fu->ep_cmd, USB_G_ALT_INT_UAS); ret = usb_ep_enable(fu->ep_cmd); if (ret) goto err_cmd; - config_ep_by_speed(gadget, f, fu->ep_status); + config_ep_by_speed_and_alt(gadget, f, fu->ep_status, USB_G_ALT_INT_UAS); ret = usb_ep_enable(fu->ep_status); if (ret) goto err_status; diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 0b9712616455..44b4352a2676 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -740,20 +740,20 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) /* Initialise video. */ ret = uvcg_video_init(&uvc->video, uvc); if (ret < 0) - goto error; + goto v4l2_error; /* Register a V4L2 device. */ ret = uvc_register_video(uvc); if (ret < 0) { uvcg_err(f, "failed to register video device\n"); - goto error; + goto v4l2_error; } return 0; -error: +v4l2_error: v4l2_device_unregister(&uvc->v4l2_dev); - +error: if (uvc->control_req) usb_ep_free_request(cdev->gadget->ep0, uvc->control_req); kfree(uvc->control_buf); diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c index c3cc6bd14e61..31ea76adcc0d 100644 --- a/drivers/usb/gadget/function/u_ether.c +++ b/drivers/usb/gadget/function/u_ether.c @@ -93,7 +93,7 @@ struct eth_dev { static inline int qlen(struct usb_gadget *gadget, unsigned qmult) { if (gadget_is_dualspeed(gadget) && (gadget->speed == USB_SPEED_HIGH || - gadget->speed == USB_SPEED_SUPER)) + gadget->speed >= USB_SPEED_SUPER)) return qmult * DEFAULT_QLEN; else return DEFAULT_QLEN; diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c index 127ecc2b4317..2caccbb6e014 100644 --- a/drivers/usb/gadget/function/u_serial.c +++ b/drivers/usb/gadget/function/u_serial.c @@ -1391,6 +1391,7 @@ void gserial_disconnect(struct gserial *gser) if (port->port.tty) tty_hangup(port->port.tty); } + port->suspended = false; spin_unlock_irqrestore(&port->port_lock, flags); /* disable endpoints, aborting down any active I/O */ diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c index cdf96911e4b1..be7bb64e3594 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/core.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c @@ -135,13 +135,9 @@ static irqreturn_t ast_vhub_irq(int irq, void *data) /* Handle device interrupts */ if (istat & vhub->port_irq_mask) { - unsigned long bitmap = istat; - int offset = VHUB_IRQ_DEV1_BIT; - int size = VHUB_IRQ_DEV1_BIT + vhub->max_ports; - - for_each_set_bit_from(offset, &bitmap, size) { - i = offset - VHUB_IRQ_DEV1_BIT; - ast_vhub_dev_irq(&vhub->ports[i].dev); + for (i = 0; i < vhub->max_ports; i++) { + if (istat & VHUB_DEV_IRQ(i)) + ast_vhub_dev_irq(&vhub->ports[i].dev); } } diff --git a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h index 2e5a1ef14a75..87a5dea12d3c 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +++ b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h @@ -67,6 +67,9 @@ #define VHUB_IRQ_HUB_EP0_SETUP (1 << 0) #define VHUB_IRQ_ACK_ALL 0x1ff +/* Downstream device IRQ mask. */ +#define VHUB_DEV_IRQ(n) (VHUB_IRQ_DEVICE1 << (n)) + /* SW reset reg */ #define VHUB_SW_RESET_EP_POOL (1 << 9) #define VHUB_SW_RESET_DMA_CONTROLLER (1 << 8) diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index a6426dd1cfef..2b893bceea45 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -1056,16 +1056,19 @@ found_ep: switch (usb_endpoint_type(desc)) { case USB_ENDPOINT_XFER_CONTROL: + ep->nr_banks = 1; break; case USB_ENDPOINT_XFER_ISOC: ep->fifo_size = 1024; - ep->nr_banks = 2; + if (ep->udc->ep_prealloc) + ep->nr_banks = 2; break; case USB_ENDPOINT_XFER_BULK: ep->fifo_size = 512; - ep->nr_banks = 1; + if (ep->udc->ep_prealloc) + ep->nr_banks = 1; break; case USB_ENDPOINT_XFER_INT: @@ -1075,7 +1078,8 @@ found_ep: else ep->fifo_size = roundup_pow_of_two(le16_to_cpu(desc->wMaxPacketSize)); - ep->nr_banks = 1; + if (ep->udc->ep_prealloc) + ep->nr_banks = 1; break; } @@ -1091,8 +1095,6 @@ found_ep: USBA_BF(EPT_SIZE, fls(ep->fifo_size - 1) - 3); ep->ept_cfg |= USBA_BF(BK_NUMBER, ep->nr_banks); - - ep->udc->configured_ep++; } return _ep; @@ -1786,7 +1788,7 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) if (status & USBA_END_OF_RESET) { struct usba_ep *ep0, *ep; - int i, n; + int i; usba_writel(udc, INT_CLR, USBA_END_OF_RESET|USBA_END_OF_RESUME @@ -1834,13 +1836,14 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) "ODD: EP0 configuration is invalid!\n"); /* Preallocate other endpoints */ - n = fifo_mode ? udc->num_ep : udc->configured_ep; - for (i = 1; i < n; i++) { + for (i = 1; i < udc->num_ep; i++) { ep = &udc->usba_ep[i]; - usba_ep_writel(ep, CFG, ep->ept_cfg); - if (!(usba_ep_readl(ep, CFG) & USBA_EPT_MAPPED)) - dev_err(&udc->pdev->dev, - "ODD: EP%d configuration is invalid!\n", i); + if (ep->ep.claimed) { + usba_ep_writel(ep, CFG, ep->ept_cfg); + if (!(usba_ep_readl(ep, CFG) & USBA_EPT_MAPPED)) + dev_err(&udc->pdev->dev, + "ODD: EP%d configuration is invalid!\n", i); + } } } @@ -2025,9 +2028,6 @@ static int atmel_usba_stop(struct usb_gadget *gadget) if (udc->vbus_pin) disable_irq(gpiod_to_irq(udc->vbus_pin)); - if (fifo_mode == 0) - udc->configured_ep = 1; - udc->suspended = false; usba_stop(udc); @@ -2090,33 +2090,51 @@ static const struct usba_udc_config udc_at91sam9rl_cfg = { .errata = &at91sam9rl_errata, .config = ep_config_sam9, .num_ep = ARRAY_SIZE(ep_config_sam9), + .ep_prealloc = true, }; static const struct usba_udc_config udc_at91sam9g45_cfg = { .errata = &at91sam9g45_errata, .config = ep_config_sam9, .num_ep = ARRAY_SIZE(ep_config_sam9), + .ep_prealloc = true, }; static const struct usba_udc_config udc_sama5d3_cfg = { .config = ep_config_sama5, .num_ep = ARRAY_SIZE(ep_config_sama5), + .ep_prealloc = true, +}; + +static const struct usba_udc_config udc_sam9x60_cfg = { + .num_ep = ARRAY_SIZE(ep_config_sam9), + .config = ep_config_sam9, + .ep_prealloc = false, }; static const struct of_device_id atmel_udc_dt_ids[] = { { .compatible = "atmel,at91sam9rl-udc", .data = &udc_at91sam9rl_cfg }, { .compatible = "atmel,at91sam9g45-udc", .data = &udc_at91sam9g45_cfg }, { .compatible = "atmel,sama5d3-udc", .data = &udc_sama5d3_cfg }, + { .compatible = "microchip,sam9x60-udc", .data = &udc_sam9x60_cfg }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, atmel_udc_dt_ids); +static const struct of_device_id atmel_pmc_dt_ids[] = { + { .compatible = "atmel,at91sam9g45-pmc" }, + { .compatible = "atmel,at91sam9rl-pmc" }, + { .compatible = "atmel,at91sam9x5-pmc" }, + { /* sentinel */ } +}; + static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, struct usba_udc *udc) { struct device_node *np = pdev->dev.of_node; const struct of_device_id *match; + struct device_node *pp; int i, ret; struct usba_ep *eps, *ep; const struct usba_udc_config *udc_config; @@ -2126,14 +2144,19 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, return ERR_PTR(-EINVAL); udc_config = match->data; + udc->ep_prealloc = udc_config->ep_prealloc; udc->errata = udc_config->errata; - udc->pmc = syscon_regmap_lookup_by_compatible("atmel,at91sam9g45-pmc"); - if (IS_ERR(udc->pmc)) - udc->pmc = syscon_regmap_lookup_by_compatible("atmel,at91sam9rl-pmc"); - if (IS_ERR(udc->pmc)) - udc->pmc = syscon_regmap_lookup_by_compatible("atmel,at91sam9x5-pmc"); - if (udc->errata && IS_ERR(udc->pmc)) - return ERR_CAST(udc->pmc); + if (udc->errata) { + pp = of_find_matching_node_and_match(NULL, atmel_pmc_dt_ids, + NULL); + if (!pp) + return ERR_PTR(-ENODEV); + + udc->pmc = syscon_node_to_regmap(pp); + of_node_put(pp); + if (IS_ERR(udc->pmc)) + return ERR_CAST(udc->pmc); + } udc->num_ep = 0; @@ -2142,7 +2165,6 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, if (fifo_mode == 0) { udc->num_ep = udc_config->num_ep; - udc->configured_ep = 1; } else { udc->num_ep = usba_config_fifo_table(udc); } diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.h b/drivers/usb/gadget/udc/atmel_usba_udc.h index 48e332439ed5..620472f218bc 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.h +++ b/drivers/usb/gadget/udc/atmel_usba_udc.h @@ -317,6 +317,7 @@ struct usba_udc_config { const struct usba_udc_errata *errata; const struct usba_ep_config *config; const int num_ep; + const bool ep_prealloc; }; struct usba_udc { @@ -336,7 +337,6 @@ struct usba_udc { int irq; struct gpio_desc *vbus_pin; int num_ep; - int configured_ep; struct usba_fifo_cfg *fifo_cfg; struct clk *pclk; struct clk *hclk; @@ -344,6 +344,7 @@ struct usba_udc { bool bias_pulse_needed; bool clocked; bool suspended; + bool ep_prealloc; u16 devstatus; diff --git a/drivers/usb/gadget/udc/bcm63xx_udc.c b/drivers/usb/gadget/udc/bcm63xx_udc.c index feaec00a3c16..9cd4a70ccdd6 100644 --- a/drivers/usb/gadget/udc/bcm63xx_udc.c +++ b/drivers/usb/gadget/udc/bcm63xx_udc.c @@ -26,6 +26,7 @@ #include <linux/seq_file.h> #include <linux/slab.h> #include <linux/timer.h> +#include <linux/usb.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> #include <linux/workqueue.h> diff --git a/drivers/usb/gadget/udc/bdc/bdc_core.c b/drivers/usb/gadget/udc/bdc/bdc_core.c index 5ff36525044e..0bef6b3f049b 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_core.c +++ b/drivers/usb/gadget/udc/bdc/bdc_core.c @@ -484,7 +484,7 @@ static void bdc_phy_exit(struct bdc *bdc) static int bdc_probe(struct platform_device *pdev) { struct bdc *bdc; - int ret = -ENOMEM; + int ret; int irq; u32 temp; struct device *dev = &pdev->dev; @@ -510,10 +510,9 @@ static int bdc_probe(struct platform_device *pdev) bdc->clk = clk; bdc->regs = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(bdc->regs)) { - dev_err(dev, "ioremap error\n"); - return -ENOMEM; - } + if (IS_ERR(bdc->regs)) + return PTR_ERR(bdc->regs); + irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index 4f82bcd31fd3..debf54205d22 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -715,6 +715,9 @@ int usb_gadget_disconnect(struct usb_gadget *gadget) goto out; } + if (!gadget->connected) + goto out; + if (gadget->deactivated) { /* * If gadget is deactivated we only save new state. @@ -1164,21 +1167,18 @@ static int check_pending_gadget_drivers(struct usb_udc *udc) } /** - * usb_add_gadget_udc_release - adds a new gadget to the udc class driver list + * usb_initialize_gadget - initialize a gadget and its embedded struct device * @parent: the parent device to this udc. Usually the controller driver's * device. - * @gadget: the gadget to be added to the list. + * @gadget: the gadget to be initialized. * @release: a gadget release function. * * Returns zero on success, negative errno otherwise. * Calls the gadget release function in the latter case. */ -int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, +void usb_initialize_gadget(struct device *parent, struct usb_gadget *gadget, void (*release)(struct device *dev)) { - struct usb_udc *udc; - int ret = -ENOMEM; - dev_set_name(&gadget->dev, "gadget"); INIT_WORK(&gadget->work, usb_gadget_state_work); gadget->dev.parent = parent; @@ -1189,17 +1189,32 @@ int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, gadget->dev.release = usb_udc_nop_release; device_initialize(&gadget->dev); +} +EXPORT_SYMBOL_GPL(usb_initialize_gadget); + +/** + * usb_add_gadget - adds a new gadget to the udc class driver list + * @gadget: the gadget to be added to the list. + * + * Returns zero on success, negative errno otherwise. + * Does not do a final usb_put_gadget() if an error occurs. + */ +int usb_add_gadget(struct usb_gadget *gadget) +{ + struct usb_udc *udc; + int ret = -ENOMEM; udc = kzalloc(sizeof(*udc), GFP_KERNEL); if (!udc) - goto err_put_gadget; + goto error; device_initialize(&udc->dev); udc->dev.release = usb_udc_release; udc->dev.class = udc_class; udc->dev.groups = usb_udc_attr_groups; - udc->dev.parent = parent; - ret = dev_set_name(&udc->dev, "%s", kobject_name(&parent->kobj)); + udc->dev.parent = gadget->dev.parent; + ret = dev_set_name(&udc->dev, "%s", + kobject_name(&gadget->dev.parent->kobj)); if (ret) goto err_put_udc; @@ -1242,8 +1257,30 @@ int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, err_put_udc: put_device(&udc->dev); - err_put_gadget: - put_device(&gadget->dev); + error: + return ret; +} +EXPORT_SYMBOL_GPL(usb_add_gadget); + +/** + * usb_add_gadget_udc_release - adds a new gadget to the udc class driver list + * @parent: the parent device to this udc. Usually the controller driver's + * device. + * @gadget: the gadget to be added to the list. + * @release: a gadget release function. + * + * Returns zero on success, negative errno otherwise. + * Calls the gadget release function in the latter case. + */ +int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, + void (*release)(struct device *dev)) +{ + int ret; + + usb_initialize_gadget(parent, gadget, release); + ret = usb_add_gadget(gadget); + if (ret) + usb_put_gadget(gadget); return ret; } EXPORT_SYMBOL_GPL(usb_add_gadget_udc_release); @@ -1311,13 +1348,14 @@ static void usb_gadget_remove_driver(struct usb_udc *udc) } /** - * usb_del_gadget_udc - deletes @udc from udc_list + * usb_del_gadget - deletes @udc from udc_list * @gadget: the gadget to be removed. * - * This, will call usb_gadget_unregister_driver() if + * This will call usb_gadget_unregister_driver() if * the @udc is still busy. + * It will not do a final usb_put_gadget(). */ -void usb_del_gadget_udc(struct usb_gadget *gadget) +void usb_del_gadget(struct usb_gadget *gadget) { struct usb_udc *udc = gadget->udc; @@ -1340,8 +1378,20 @@ void usb_del_gadget_udc(struct usb_gadget *gadget) kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE); flush_work(&gadget->work); device_unregister(&udc->dev); - device_unregister(&gadget->dev); - memset(&gadget->dev, 0x00, sizeof(gadget->dev)); + device_del(&gadget->dev); +} +EXPORT_SYMBOL_GPL(usb_del_gadget); + +/** + * usb_del_gadget_udc - deletes @udc from udc_list + * @gadget: the gadget to be removed. + * + * Calls usb_del_gadget() and does a final usb_put_gadget(). + */ +void usb_del_gadget_udc(struct usb_gadget *gadget) +{ + usb_del_gadget(gadget); + usb_put_gadget(gadget); } EXPORT_SYMBOL_GPL(usb_del_gadget_udc); diff --git a/drivers/usb/gadget/udc/fsl_qe_udc.c b/drivers/usb/gadget/udc/fsl_qe_udc.c index 2707be628298..fa66449b3907 100644 --- a/drivers/usb/gadget/udc/fsl_qe_udc.c +++ b/drivers/usb/gadget/udc/fsl_qe_udc.c @@ -923,9 +923,9 @@ static int qe_ep_rxframe_handle(struct qe_ep *ep) return 0; } -static void ep_rx_tasklet(unsigned long data) +static void ep_rx_tasklet(struct tasklet_struct *t) { - struct qe_udc *udc = (struct qe_udc *)data; + struct qe_udc *udc = from_tasklet(udc, t, rx_tasklet); struct qe_ep *ep; struct qe_frame *pframe; struct qe_bd __iomem *bd; @@ -2553,8 +2553,7 @@ static int qe_udc_probe(struct platform_device *ofdev) DMA_TO_DEVICE); } - tasklet_init(&udc->rx_tasklet, ep_rx_tasklet, - (unsigned long)udc); + tasklet_setup(&udc->rx_tasklet, ep_rx_tasklet); /* request irq and disable DR */ udc->usb_irq = irq_of_parse_and_map(np, 0); if (!udc->usb_irq) { diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index a6f7b2594c09..de528e3b0662 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -2061,7 +2061,7 @@ static int fsl_proc_read(struct seq_file *m, void *v) "Sleep Enable: %d SOF Received Enable: %d " "Reset Enable: %d\n" "System Error Enable: %d " - "Port Change Dectected Enable: %d\n" + "Port Change Detected Enable: %d\n" "USB Error Intr Enable: %d USB Intr Enable: %d\n\n", (tmp_reg & USB_INTR_DEVICE_SUSPEND) ? 1 : 0, (tmp_reg & USB_INTR_SOF_EN) ? 1 : 0, @@ -2439,11 +2439,12 @@ static int fsl_udc_probe(struct platform_device *pdev) /* DEN is bidirectional ep number, max_ep doubles the number */ udc_controller->max_ep = (dccparams & DCCPARAMS_DEN_MASK) * 2; - udc_controller->irq = platform_get_irq(pdev, 0); - if (udc_controller->irq <= 0) { - ret = udc_controller->irq ? : -ENODEV; + ret = platform_get_irq(pdev, 0); + if (ret <= 0) { + ret = ret ? : -ENODEV; goto err_iounmap; } + udc_controller->irq = ret; ret = request_irq(udc_controller->irq, fsl_udc_irq, IRQF_SHARED, driver_name, udc_controller); diff --git a/drivers/usb/gadget/udc/lpc32xx_udc.c b/drivers/usb/gadget/udc/lpc32xx_udc.c index e8a4637a9a17..3f1c62adce4b 100644 --- a/drivers/usb/gadget/udc/lpc32xx_udc.c +++ b/drivers/usb/gadget/udc/lpc32xx_udc.c @@ -495,7 +495,7 @@ static void proc_ep_show(struct seq_file *s, struct lpc32xx_ep *ep) } } -static int proc_udc_show(struct seq_file *s, void *unused) +static int udc_show(struct seq_file *s, void *unused) { struct lpc32xx_udc *udc = s->private; struct lpc32xx_ep *ep; @@ -524,22 +524,11 @@ static int proc_udc_show(struct seq_file *s, void *unused) return 0; } -static int proc_udc_open(struct inode *inode, struct file *file) -{ - return single_open(file, proc_udc_show, PDE_DATA(inode)); -} - -static const struct file_operations proc_ops = { - .owner = THIS_MODULE, - .open = proc_udc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(udc); static void create_debug_file(struct lpc32xx_udc *udc) { - udc->pde = debugfs_create_file(debug_filename, 0, NULL, udc, &proc_ops); + udc->pde = debugfs_create_file(debug_filename, 0, NULL, udc, &udc_fops); } static void remove_debug_file(struct lpc32xx_udc *udc) diff --git a/drivers/usb/gadget/udc/net2272.c b/drivers/usb/gadget/udc/net2272.c index 44d1ea2307bb..23a735641c3d 100644 --- a/drivers/usb/gadget/udc/net2272.c +++ b/drivers/usb/gadget/udc/net2272.c @@ -9,7 +9,6 @@ #include <linux/delay.h> #include <linux/device.h> #include <linux/errno.h> -#include <linux/gpio.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/io.h> @@ -2196,7 +2195,8 @@ static int net2272_present(struct net2272 *dev) static void net2272_gadget_release(struct device *_dev) { - struct net2272 *dev = dev_get_drvdata(_dev); + struct net2272 *dev = container_of(_dev, struct net2272, gadget.dev); + kfree(dev); } @@ -2205,7 +2205,8 @@ net2272_gadget_release(struct device *_dev) static void net2272_remove(struct net2272 *dev) { - usb_del_gadget_udc(&dev->gadget); + if (dev->added) + usb_del_gadget(&dev->gadget); free_irq(dev->irq, dev); iounmap(dev->base_addr); device_remove_file(dev->dev, &dev_attr_registers); @@ -2235,6 +2236,7 @@ static struct net2272 *net2272_probe_init(struct device *dev, unsigned int irq) /* the "gadget" abstracts/virtualizes the controller */ ret->gadget.name = driver_name; + usb_initialize_gadget(dev, &ret->gadget, net2272_gadget_release); return ret; } @@ -2273,10 +2275,10 @@ net2272_probe_fin(struct net2272 *dev, unsigned int irqflags) if (ret) goto err_irq; - ret = usb_add_gadget_udc_release(dev->dev, &dev->gadget, - net2272_gadget_release); + ret = usb_add_gadget(&dev->gadget); if (ret) goto err_add_udc; + dev->added = 1; return 0; @@ -2451,7 +2453,7 @@ net2272_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (pci_enable_device(pdev) < 0) { ret = -ENODEV; - goto err_free; + goto err_put; } pci_set_master(pdev); @@ -2474,8 +2476,8 @@ net2272_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) err_pci: pci_disable_device(pdev); - err_free: - kfree(dev); + err_put: + usb_put_gadget(&dev->gadget); return ret; } @@ -2536,7 +2538,7 @@ net2272_pci_remove(struct pci_dev *pdev) pci_disable_device(pdev); - kfree(dev); + usb_put_gadget(&dev->gadget); } /* Table of matching PCI IDs */ @@ -2649,7 +2651,7 @@ net2272_plat_probe(struct platform_device *pdev) err_req: release_mem_region(base, len); err: - kfree(dev); + usb_put_gadget(&dev->gadget); return ret; } @@ -2664,7 +2666,7 @@ net2272_plat_remove(struct platform_device *pdev) release_mem_region(pdev->resource[0].start, resource_size(&pdev->resource[0])); - kfree(dev); + usb_put_gadget(&dev->gadget); return 0; } diff --git a/drivers/usb/gadget/udc/net2272.h b/drivers/usb/gadget/udc/net2272.h index 87d0ab9ffeeb..c669308111c2 100644 --- a/drivers/usb/gadget/udc/net2272.h +++ b/drivers/usb/gadget/udc/net2272.h @@ -441,6 +441,7 @@ struct net2272 { unsigned protocol_stall:1, softconnect:1, wakeup:1, + added:1, dma_eot_polarity:1, dma_dack_polarity:1, dma_dreq_polarity:1, diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 7530bd9a08c4..fc9f99fe7f37 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -52,6 +52,7 @@ #include <linux/usb/gadget.h> #include <linux/prefetch.h> #include <linux/io.h> +#include <linux/iopoll.h> #include <asm/byteorder.h> #include <asm/irq.h> @@ -360,18 +361,16 @@ print_err: static int handshake(u32 __iomem *ptr, u32 mask, u32 done, int usec) { u32 result; + int ret; - do { - result = readl(ptr); - if (result == ~(u32)0) /* "device unplugged" */ - return -ENODEV; - result &= mask; - if (result == done) - return 0; - udelay(1); - usec--; - } while (usec > 0); - return -ETIMEDOUT; + ret = readl_poll_timeout_atomic(ptr, result, + ((result & mask) == done || + result == U32_MAX), + 1, usec); + if (result == U32_MAX) /* device unplugged */ + return -ENODEV; + + return ret; } static const struct usb_ep_ops net2280_ep_ops; @@ -3561,7 +3560,7 @@ static irqreturn_t net2280_irq(int irq, void *_dev) static void gadget_release(struct device *_dev) { - struct net2280 *dev = dev_get_drvdata(_dev); + struct net2280 *dev = container_of(_dev, struct net2280, gadget.dev); kfree(dev); } @@ -3572,7 +3571,8 @@ static void net2280_remove(struct pci_dev *pdev) { struct net2280 *dev = pci_get_drvdata(pdev); - usb_del_gadget_udc(&dev->gadget); + if (dev->added) + usb_del_gadget(&dev->gadget); BUG_ON(dev->driver); @@ -3603,6 +3603,7 @@ static void net2280_remove(struct pci_dev *pdev) device_remove_file(&pdev->dev, &dev_attr_registers); ep_info(dev, "unbind\n"); + usb_put_gadget(&dev->gadget); } /* wrap this driver around the specified device, but @@ -3624,6 +3625,7 @@ static int net2280_probe(struct pci_dev *pdev, const struct pci_device_id *id) } pci_set_drvdata(pdev, dev); + usb_initialize_gadget(&pdev->dev, &dev->gadget, gadget_release); spin_lock_init(&dev->lock); dev->quirks = id->driver_data; dev->pdev = pdev; @@ -3774,10 +3776,10 @@ static int net2280_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (retval) goto done; - retval = usb_add_gadget_udc_release(&pdev->dev, &dev->gadget, - gadget_release); + retval = usb_add_gadget(&dev->gadget); if (retval) goto done; + dev->added = 1; return 0; done: diff --git a/drivers/usb/gadget/udc/net2280.h b/drivers/usb/gadget/udc/net2280.h index 85d3ca1698ba..7da3dc1e9729 100644 --- a/drivers/usb/gadget/udc/net2280.h +++ b/drivers/usb/gadget/udc/net2280.h @@ -156,6 +156,7 @@ struct net2280 { softconnect : 1, got_irq : 1, region:1, + added:1, u1_enable:1, u2_enable:1, ltm_enable:1, diff --git a/drivers/usb/gadget/udc/pch_udc.c b/drivers/usb/gadget/udc/pch_udc.c index 8afc31d94b0e..a3c1fc924268 100644 --- a/drivers/usb/gadget/udc/pch_udc.c +++ b/drivers/usb/gadget/udc/pch_udc.c @@ -12,12 +12,9 @@ #include <linux/interrupt.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/irq.h> -/* GPIO port for VBUS detecting */ -static int vbus_gpio_port = -1; /* GPIO port number (-1:Not used) */ - #define PCH_VBUS_PERIOD 3000 /* VBUS polling period (msec) */ #define PCH_VBUS_INTERVAL 10 /* VBUS polling interval (msec) */ @@ -301,13 +298,13 @@ struct pch_udc_ep { /** * struct pch_vbus_gpio_data - Structure holding GPIO informaton * for detecting VBUS - * @port: gpio port number + * @port: gpio descriptor for the VBUS GPIO * @intr: gpio interrupt number * @irq_work_fall: Structure for WorkQueue * @irq_work_rise: Structure for WorkQueue */ struct pch_vbus_gpio_data { - int port; + struct gpio_desc *port; int intr; struct work_struct irq_work_fall; struct work_struct irq_work_rise; @@ -1254,7 +1251,7 @@ static int pch_vbus_gpio_get_value(struct pch_udc_dev *dev) int vbus = 0; if (dev->vbus_gpio.port) - vbus = gpio_get_value(dev->vbus_gpio.port) ? 1 : 0; + vbus = gpiod_get_value(dev->vbus_gpio.port) ? 1 : 0; else vbus = -1; @@ -1356,42 +1353,30 @@ static irqreturn_t pch_vbus_gpio_irq(int irq, void *data) /** * pch_vbus_gpio_init() - This API initializes GPIO port detecting VBUS. * @dev: Reference to the driver structure - * @vbus_gpio_port: Number of GPIO port to detect gpio * * Return codes: * 0: Success * -EINVAL: GPIO port is invalid or can't be initialized. */ -static int pch_vbus_gpio_init(struct pch_udc_dev *dev, int vbus_gpio_port) +static int pch_vbus_gpio_init(struct pch_udc_dev *dev) { int err; int irq_num = 0; + struct gpio_desc *gpiod; - dev->vbus_gpio.port = 0; + dev->vbus_gpio.port = NULL; dev->vbus_gpio.intr = 0; - if (vbus_gpio_port <= -1) - return -EINVAL; - - err = gpio_is_valid(vbus_gpio_port); - if (!err) { - pr_err("%s: gpio port %d is invalid\n", - __func__, vbus_gpio_port); - return -EINVAL; - } - - err = gpio_request(vbus_gpio_port, "pch_vbus"); - if (err) { - pr_err("%s: can't request gpio port %d, err: %d\n", - __func__, vbus_gpio_port, err); - return -EINVAL; - } + /* Retrieve the GPIO line from the USB gadget device */ + gpiod = devm_gpiod_get(dev->gadget.dev.parent, NULL, GPIOD_IN); + if (IS_ERR(gpiod)) + return PTR_ERR(gpiod); + gpiod_set_consumer_name(gpiod, "pch_vbus"); - dev->vbus_gpio.port = vbus_gpio_port; - gpio_direction_input(vbus_gpio_port); + dev->vbus_gpio.port = gpiod; INIT_WORK(&dev->vbus_gpio.irq_work_fall, pch_vbus_gpio_work_fall); - irq_num = gpio_to_irq(vbus_gpio_port); + irq_num = gpiod_to_irq(gpiod); if (irq_num > 0) { irq_set_irq_type(irq_num, IRQ_TYPE_EDGE_BOTH); err = request_irq(irq_num, pch_vbus_gpio_irq, 0, @@ -1417,9 +1402,6 @@ static void pch_vbus_gpio_free(struct pch_udc_dev *dev) { if (dev->vbus_gpio.intr) free_irq(dev->vbus_gpio.intr, dev); - - if (dev->vbus_gpio.port) - gpio_free(dev->vbus_gpio.port); } /** @@ -2894,7 +2876,7 @@ static int pch_udc_pcd_init(struct pch_udc_dev *dev) { pch_udc_init(dev); pch_udc_pcd_reinit(dev); - pch_vbus_gpio_init(dev, vbus_gpio_port); + pch_vbus_gpio_init(dev); return 0; } @@ -3096,6 +3078,13 @@ static int pch_udc_probe(struct pci_dev *pdev, dev->base_addr = pcim_iomap_table(pdev)[bar]; + /* + * FIXME: add a GPIO descriptor table to pdev.dev using + * gpiod_add_descriptor_table() from <linux/gpio/machine.h> based on + * the PCI subsystem ID. The system-dependent GPIO is necessary for + * VBUS operation. + */ + /* initialize the hardware */ if (pch_udc_pcd_init(dev)) return -ENODEV; diff --git a/drivers/usb/gadget/udc/s3c2410_udc.c b/drivers/usb/gadget/udc/s3c2410_udc.c index bc2e8eb737c3..e875a0b967c0 100644 --- a/drivers/usb/gadget/udc/s3c2410_udc.c +++ b/drivers/usb/gadget/udc/s3c2410_udc.c @@ -1270,7 +1270,6 @@ static int s3c2410_udc_queue(struct usb_ep *_ep, struct usb_request *_req, static int s3c2410_udc_dequeue(struct usb_ep *_ep, struct usb_request *_req) { struct s3c2410_ep *ep = to_s3c2410_ep(_ep); - struct s3c2410_udc *udc; int retval = -EINVAL; unsigned long flags; struct s3c2410_request *req = NULL; @@ -1283,8 +1282,6 @@ static int s3c2410_udc_dequeue(struct usb_ep *_ep, struct usb_request *_req) if (!_ep || !_req) return retval; - udc = to_s3c2410_udc(ep->gadget); - local_irq_save(flags); list_for_each_entry(req, &ep->queue, queue) { diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index d6ff68c06911..580bef8eb4cb 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -705,11 +705,11 @@ static void tegra_xudc_device_mode_on(struct tegra_xudc *xudc) err = phy_power_on(xudc->curr_utmi_phy); if (err < 0) - dev_err(xudc->dev, "utmi power on failed %d\n", err); + dev_err(xudc->dev, "UTMI power on failed: %d\n", err); err = phy_power_on(xudc->curr_usb3_phy); if (err < 0) - dev_err(xudc->dev, "usb3 phy power on failed %d\n", err); + dev_err(xudc->dev, "USB3 PHY power on failed: %d\n", err); dev_dbg(xudc->dev, "device mode on\n"); @@ -759,11 +759,11 @@ static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc) err = phy_power_off(xudc->curr_utmi_phy); if (err < 0) - dev_err(xudc->dev, "utmi_phy power off failed %d\n", err); + dev_err(xudc->dev, "UTMI PHY power off failed: %d\n", err); err = phy_power_off(xudc->curr_usb3_phy); if (err < 0) - dev_err(xudc->dev, "usb3_phy power off failed %d\n", err); + dev_err(xudc->dev, "USB3 PHY power off failed: %d\n", err); pm_runtime_put(xudc->dev); } @@ -1539,7 +1539,7 @@ static int __tegra_xudc_ep_set_halt(struct tegra_xudc_ep *ep, bool halt) return -EINVAL; if (usb_endpoint_xfer_isoc(ep->desc)) { - dev_err(xudc->dev, "can't halt isoc EP\n"); + dev_err(xudc->dev, "can't halt isochronous EP\n"); return -ENOTSUPP; } @@ -1788,7 +1788,7 @@ static int __tegra_xudc_ep_enable(struct tegra_xudc_ep *ep, if (usb_endpoint_xfer_isoc(desc)) { if (xudc->nr_isoch_eps > XUDC_MAX_ISOCH_EPS) { - dev_err(xudc->dev, "too many isoch endpoints\n"); + dev_err(xudc->dev, "too many isochronous endpoints\n"); return -EBUSY; } xudc->nr_isoch_eps++; @@ -3509,7 +3509,7 @@ static int tegra_xudc_phy_get(struct tegra_xudc *xudc) if (IS_ERR(xudc->utmi_phy[i])) { err = PTR_ERR(xudc->utmi_phy[i]); if (err != -EPROBE_DEFER) - dev_err(xudc->dev, "failed to get usb2-%d phy: %d\n", + dev_err(xudc->dev, "failed to get usb2-%d PHY: %d\n", i, err); goto clean_up; @@ -3539,12 +3539,12 @@ static int tegra_xudc_phy_get(struct tegra_xudc *xudc) if (IS_ERR(xudc->usb3_phy[i])) { err = PTR_ERR(xudc->usb3_phy[i]); if (err != -EPROBE_DEFER) - dev_err(xudc->dev, "failed to get usb3-%d phy: %d\n", + dev_err(xudc->dev, "failed to get usb3-%d PHY: %d\n", usb3, err); goto clean_up; } else if (xudc->usb3_phy[i]) - dev_dbg(xudc->dev, "usb3_phy-%d registered", usb3); + dev_dbg(xudc->dev, "usb3-%d PHY registered", usb3); } return err; @@ -3577,13 +3577,13 @@ static int tegra_xudc_phy_init(struct tegra_xudc *xudc) for (i = 0; i < xudc->soc->num_phys; i++) { err = phy_init(xudc->utmi_phy[i]); if (err < 0) { - dev_err(xudc->dev, "utmi phy init failed: %d\n", err); + dev_err(xudc->dev, "UTMI PHY #%u initialization failed: %d\n", i, err); goto exit_phy; } err = phy_init(xudc->usb3_phy[i]); if (err < 0) { - dev_err(xudc->dev, "usb3 phy init failed: %d\n", err); + dev_err(xudc->dev, "USB3 PHY #%u initialization failed: %d\n", i, err); goto exit_phy; } } @@ -3692,34 +3692,33 @@ static int tegra_xudc_powerdomain_init(struct tegra_xudc *xudc) struct device *dev = xudc->dev; int err; - xudc->genpd_dev_device = dev_pm_domain_attach_by_name(dev, - "dev"); + 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); - dev_err(dev, "failed to get dev pm-domain: %d\n", err); + 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); - dev_err(dev, "failed to get superspeed pm-domain: %d\n", err); + dev_err(dev, "failed to get SuperSpeed power domain: %d\n", err); return err; } xudc->genpd_dl_device = device_link_add(dev, xudc->genpd_dev_device, - DL_FLAG_PM_RUNTIME | - DL_FLAG_STATELESS); + DL_FLAG_PM_RUNTIME | + DL_FLAG_STATELESS); if (!xudc->genpd_dl_device) { - dev_err(dev, "adding usb device device link failed!\n"); + dev_err(dev, "failed to add USB device link\n"); return -ENODEV; } xudc->genpd_dl_ss = device_link_add(dev, xudc->genpd_dev_ss, - DL_FLAG_PM_RUNTIME | - DL_FLAG_STATELESS); + DL_FLAG_PM_RUNTIME | + DL_FLAG_STATELESS); if (!xudc->genpd_dl_ss) { - dev_err(dev, "adding superspeed device link failed!\n"); + dev_err(dev, "failed to add SuperSpeed device link\n"); return -ENODEV; } @@ -3733,7 +3732,7 @@ static int tegra_xudc_probe(struct platform_device *pdev) unsigned int i; int err; - xudc = devm_kzalloc(&pdev->dev, sizeof(*xudc), GFP_ATOMIC); + xudc = devm_kzalloc(&pdev->dev, sizeof(*xudc), GFP_KERNEL); if (!xudc) return -ENOMEM; @@ -3772,18 +3771,19 @@ static int tegra_xudc_probe(struct platform_device *pdev) return err; } - xudc->clks = devm_kcalloc(&pdev->dev, xudc->soc->num_clks, - sizeof(*xudc->clks), GFP_KERNEL); + xudc->clks = devm_kcalloc(&pdev->dev, xudc->soc->num_clks, sizeof(*xudc->clks), + GFP_KERNEL); if (!xudc->clks) return -ENOMEM; for (i = 0; i < xudc->soc->num_clks; i++) xudc->clks[i].id = xudc->soc->clock_names[i]; - err = devm_clk_bulk_get(&pdev->dev, xudc->soc->num_clks, - xudc->clks); + err = devm_clk_bulk_get(&pdev->dev, xudc->soc->num_clks, xudc->clks); if (err) { - dev_err(xudc->dev, "failed to request clks %d\n", err); + if (err != -EPROBE_DEFER) + dev_err(xudc->dev, "failed to request clocks: %d\n", err); + return err; } @@ -3798,7 +3798,9 @@ static int tegra_xudc_probe(struct platform_device *pdev) err = devm_regulator_bulk_get(&pdev->dev, xudc->soc->num_supplies, xudc->supplies); if (err) { - dev_err(xudc->dev, "failed to request regulators %d\n", err); + if (err != -EPROBE_DEFER) + dev_err(xudc->dev, "failed to request regulators: %d\n", err); + return err; } @@ -3808,7 +3810,7 @@ static int tegra_xudc_probe(struct platform_device *pdev) err = regulator_bulk_enable(xudc->soc->num_supplies, xudc->supplies); if (err) { - dev_err(xudc->dev, "failed to enable regulators %d\n", err); + dev_err(xudc->dev, "failed to enable regulators: %d\n", err); goto put_padctl; } diff --git a/drivers/usb/host/bcma-hcd.c b/drivers/usb/host/bcma-hcd.c index b1b777f33521..337b425dd4b0 100644 --- a/drivers/usb/host/bcma-hcd.c +++ b/drivers/usb/host/bcma-hcd.c @@ -498,15 +498,4 @@ static struct bcma_driver bcma_hcd_driver = { .suspend = bcma_hcd_suspend, .resume = bcma_hcd_resume, }; - -static int __init bcma_hcd_init(void) -{ - return bcma_driver_register(&bcma_hcd_driver); -} -module_init(bcma_hcd_init); - -static void __exit bcma_hcd_exit(void) -{ - bcma_driver_unregister(&bcma_hcd_driver); -} -module_exit(bcma_hcd_exit); +module_bcma_driver(bcma_hcd_driver); diff --git a/drivers/usb/host/ehci-npcm7xx.c b/drivers/usb/host/ehci-npcm7xx.c index adaf8fb4b459..6b5a7a873e01 100644 --- a/drivers/usb/host/ehci-npcm7xx.c +++ b/drivers/usb/host/ehci-npcm7xx.c @@ -37,8 +37,7 @@ static const char hcd_name[] = "npcm7xx-ehci"; static struct hc_driver __read_mostly ehci_npcm7xx_hc_driver; -#ifdef CONFIG_PM_SLEEP -static int ehci_npcm7xx_drv_suspend(struct device *dev) +static int __maybe_unused ehci_npcm7xx_drv_suspend(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); bool do_wakeup = device_may_wakeup(dev); @@ -46,14 +45,13 @@ static int ehci_npcm7xx_drv_suspend(struct device *dev) return ehci_suspend(hcd, do_wakeup); } -static int ehci_npcm7xx_drv_resume(struct device *dev) +static int __maybe_unused ehci_npcm7xx_drv_resume(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); ehci_resume(hcd, false); return 0; } -#endif /* CONFIG_PM_SLEEP */ static SIMPLE_DEV_PM_OPS(ehci_npcm7xx_pm_ops, ehci_npcm7xx_drv_suspend, ehci_npcm7xx_drv_resume); @@ -183,7 +181,7 @@ static struct platform_driver npcm7xx_ehci_hcd_driver = { .driver = { .name = "npcm7xx-ehci", .bus = &platform_bus_type, - .pm = &ehci_npcm7xx_pm_ops, + .pm = pm_ptr(&ehci_npcm7xx_pm_ops), .of_match_table = npcm7xx_ehci_id_table, } }; diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index 006c4f6188a5..a48dd3fac153 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -42,6 +42,9 @@ #define EHCI_MAX_CLKS 4 #define hcd_to_ehci_priv(h) ((struct ehci_platform_priv *)hcd_to_ehci(h)->priv) +#define BCM_USB_FIFO_THRESHOLD 0x00800040 +#define bcm_iproc_insnreg01 hostpc[0] + struct ehci_platform_priv { struct clk *clks[EHCI_MAX_CLKS]; struct reset_control *rsts; @@ -75,6 +78,11 @@ static int ehci_platform_reset(struct usb_hcd *hcd) if (pdata->no_io_watchdog) ehci->need_io_watchdog = 0; + + if (of_device_is_compatible(pdev->dev.of_node, "brcm,xgs-iproc-ehci")) + ehci_writel(ehci, BCM_USB_FIFO_THRESHOLD, + &ehci->regs->bcm_iproc_insnreg01); + return 0; } @@ -410,8 +418,7 @@ static int ehci_platform_remove(struct platform_device *dev) return 0; } -#ifdef CONFIG_PM_SLEEP -static int ehci_platform_suspend(struct device *dev) +static int __maybe_unused ehci_platform_suspend(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); struct usb_ehci_pdata *pdata = dev_get_platdata(dev); @@ -433,7 +440,7 @@ static int ehci_platform_suspend(struct device *dev) return ret; } -static int ehci_platform_resume(struct device *dev) +static int __maybe_unused ehci_platform_resume(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); struct usb_ehci_pdata *pdata = dev_get_platdata(dev); @@ -464,7 +471,6 @@ static int ehci_platform_resume(struct device *dev) return 0; } -#endif /* CONFIG_PM_SLEEP */ static const struct of_device_id vt8500_ehci_ids[] = { { .compatible = "via,vt8500-ehci", }, @@ -499,7 +505,7 @@ static struct platform_driver ehci_platform_driver = { .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "ehci-platform", - .pm = &ehci_platform_pm_ops, + .pm = pm_ptr(&ehci_platform_pm_ops), .of_match_table = vt8500_ehci_ids, .acpi_match_table = ACPI_PTR(ehci_acpi_match), } diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index 847979f265b1..6dfb242f9a4b 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -307,26 +307,6 @@ static int __maybe_unused same_tt(struct usb_device *dev1, #ifdef CONFIG_USB_EHCI_TT_NEWSCHED -/* Which uframe does the low/fullspeed transfer start in? - * - * The parameter is the mask of ssplits in "H-frame" terms - * and this returns the transfer start uframe in "B-frame" terms, - * which allows both to match, e.g. a ssplit in "H-frame" uframe 0 - * will cause a transfer in "B-frame" uframe 0. "B-frames" lag - * "H-frames" by 1 uframe. See the EHCI spec sec 4.5 and figure 4.7. - */ -static inline unsigned char tt_start_uframe(struct ehci_hcd *ehci, __hc32 mask) -{ - unsigned char smask = hc32_to_cpu(ehci, mask) & QH_SMASK; - - if (!smask) { - ehci_err(ehci, "invalid empty smask!\n"); - /* uframe 7 can't have bw so this will indicate failure */ - return 7; - } - return ffs(smask) - 1; -} - static const unsigned char max_tt_usecs[] = { 125, 125, 125, 125, 125, 125, 30, 0 }; diff --git a/drivers/usb/host/ehci-spear.c b/drivers/usb/host/ehci-spear.c index add796c78561..3694e450a11a 100644 --- a/drivers/usb/host/ehci-spear.c +++ b/drivers/usb/host/ehci-spear.c @@ -34,8 +34,7 @@ struct spear_ehci { static struct hc_driver __read_mostly ehci_spear_hc_driver; -#ifdef CONFIG_PM_SLEEP -static int ehci_spear_drv_suspend(struct device *dev) +static int __maybe_unused ehci_spear_drv_suspend(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); bool do_wakeup = device_may_wakeup(dev); @@ -43,14 +42,13 @@ static int ehci_spear_drv_suspend(struct device *dev) return ehci_suspend(hcd, do_wakeup); } -static int ehci_spear_drv_resume(struct device *dev) +static int __maybe_unused ehci_spear_drv_resume(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); ehci_resume(hcd, false); return 0; } -#endif /* CONFIG_PM_SLEEP */ static SIMPLE_DEV_PM_OPS(ehci_spear_pm_ops, ehci_spear_drv_suspend, ehci_spear_drv_resume); @@ -155,7 +153,7 @@ static struct platform_driver spear_ehci_hcd_driver = { .driver = { .name = "spear-ehci", .bus = &platform_bus_type, - .pm = &ehci_spear_pm_ops, + .pm = pm_ptr(&ehci_spear_pm_ops), .of_match_table = spear_ehci_id_table, } }; diff --git a/drivers/usb/host/fotg210-hcd.c b/drivers/usb/host/fotg210-hcd.c index 194df8282471..1d94fcfac2c2 100644 --- a/drivers/usb/host/fotg210-hcd.c +++ b/drivers/usb/host/fotg210-hcd.c @@ -32,6 +32,7 @@ #include <linux/uaccess.h> #include <linux/platform_device.h> #include <linux/io.h> +#include <linux/iopoll.h> #include <linux/clk.h> #include <asm/byteorder.h> @@ -883,18 +884,15 @@ static int handshake(struct fotg210_hcd *fotg210, void __iomem *ptr, u32 mask, u32 done, int usec) { u32 result; + int ret; - do { - result = fotg210_readl(fotg210, ptr); - if (result == ~(u32)0) /* card removed */ - return -ENODEV; - result &= mask; - if (result == done) - return 0; - udelay(1); - usec--; - } while (usec > 0); - return -ETIMEDOUT; + ret = readl_poll_timeout_atomic(ptr, result, + ((result & mask) == done || + result == U32_MAX), 1, usec); + if (result == U32_MAX) /* card removed */ + return -ENODEV; + + return ret; } /* Force HC to halt state from unknown (EHCI spec section 2.3). diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index dd37e77dae00..73e13e7c2b46 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -102,7 +102,7 @@ static void io_watchdog_func(struct timer_list *t); /* Some boards misreport power switching/overcurrent */ -static bool distrust_firmware = true; +static bool distrust_firmware; module_param (distrust_firmware, bool, 0); MODULE_PARM_DESC (distrust_firmware, "true to distrust firmware power/overcurrent setup"); @@ -673,20 +673,24 @@ retry: /* handle root hub init quirks ... */ val = roothub_a (ohci); - val &= ~(RH_A_PSM | RH_A_OCPM); + /* Configure for per-port over-current protection by default */ + val &= ~RH_A_NOCP; + val |= RH_A_OCPM; if (ohci->flags & OHCI_QUIRK_SUPERIO) { - /* NSC 87560 and maybe others */ + /* NSC 87560 and maybe others. + * Ganged power switching, no over-current protection. + */ val |= RH_A_NOCP; - val &= ~(RH_A_POTPGT | RH_A_NPS); - ohci_writel (ohci, val, &ohci->regs->roothub.a); + val &= ~(RH_A_POTPGT | RH_A_NPS | RH_A_PSM | RH_A_OCPM); } else if ((ohci->flags & OHCI_QUIRK_AMD756) || (ohci->flags & OHCI_QUIRK_HUB_POWER)) { /* hub power always on; required for AMD-756 and some - * Mac platforms. ganged overcurrent reporting, if any. + * Mac platforms. */ val |= RH_A_NPS; - ohci_writel (ohci, val, &ohci->regs->roothub.a); } + ohci_writel(ohci, val, &ohci->regs->roothub.a); + ohci_writel (ohci, RH_HS_LPSC, &ohci->regs->roothub.status); ohci_writel (ohci, (val & RH_A_NPS) ? 0 : RH_B_PPCM, &ohci->regs->roothub.b); diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index cfa7dd2cc7d3..27dbbe1b28b1 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -24,6 +24,7 @@ #include <linux/moduleparam.h> #include <linux/dma-mapping.h> #include <linux/io.h> +#include <linux/iopoll.h> #include <asm/irq.h> #include <asm/unaligned.h> @@ -748,18 +749,16 @@ static int handshake(struct oxu_hcd *oxu, void __iomem *ptr, u32 mask, u32 done, int usec) { u32 result; + int ret; - do { - result = readl(ptr); - if (result == ~(u32)0) /* card removed */ - return -ENODEV; - result &= mask; - if (result == done) - return 0; - udelay(1); - usec--; - } while (usec > 0); - return -ETIMEDOUT; + ret = readl_poll_timeout_atomic(ptr, result, + ((result & mask) == done || + result == U32_MAX), + 1, usec); + if (result == U32_MAX) /* card removed */ + return -ENODEV; + + return ret; } /* Force HC to halt state from unknown (EHCI spec section 2.3) */ diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index 8c1bbac6d136..ef08d68b9714 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -16,8 +16,8 @@ #include <linux/export.h> #include <linux/acpi.h> #include <linux/dmi.h> - -#include <soc/bcm2835/raspberrypi-firmware.h> +#include <linux/of.h> +#include <linux/iopoll.h> #include "pci-quirks.h" #include "xhci-ext-caps.h" @@ -1013,15 +1013,9 @@ static int handshake(void __iomem *ptr, u32 mask, u32 done, { u32 result; - do { - result = readl(ptr); - result &= mask; - if (result == done) - return 0; - udelay(delay_usec); - wait_usec -= delay_usec; - } while (wait_usec > 0); - return -ETIMEDOUT; + return readl_poll_timeout_atomic(ptr, result, + ((result & mask) == done), + delay_usec, wait_usec); } /* @@ -1247,7 +1241,8 @@ iounmap: static void quirk_usb_early_handoff(struct pci_dev *pdev) { - int ret; + struct device_node *parent; + bool is_rpi; /* Skip Netlogic mips SoC's internal PCI USB controller. * This device does not need/support EHCI/OHCI handoff @@ -1255,14 +1250,16 @@ static void quirk_usb_early_handoff(struct pci_dev *pdev) if (pdev->vendor == 0x184e) /* vendor Netlogic */ return; + /* + * Bypass the Raspberry Pi 4 controller xHCI controller, things are + * taken care of by the board's co-processor. + */ if (pdev->vendor == PCI_VENDOR_ID_VIA && pdev->device == 0x3483) { - ret = rpi_firmware_init_vl805(pdev); - if (ret) { - /* Firmware might be outdated, or something failed */ - dev_warn(&pdev->dev, - "Failed to load VL805's firmware: %d. Will continue to attempt to work, but bad things might happen. You should fix this...\n", - ret); - } + parent = of_get_parent(pdev->bus->dev.of_node); + is_rpi = of_device_is_compatible(parent, "brcm,bcm2711-pcie"); + of_node_put(parent); + if (is_rpi) + return; } if (pdev->class != PCI_CLASS_SERIAL_USB_UHCI && diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c index b8918f73a432..ae4e4ab638b5 100644 --- a/drivers/usb/host/xhci-dbgtty.c +++ b/drivers/usb/host/xhci-dbgtty.c @@ -288,14 +288,14 @@ static const struct tty_operations dbc_tty_ops = { .unthrottle = dbc_tty_unthrottle, }; -static void dbc_rx_push(unsigned long _port) +static void dbc_rx_push(struct tasklet_struct *t) { struct dbc_request *req; struct tty_struct *tty; unsigned long flags; bool do_push = false; bool disconnect = false; - struct dbc_port *port = (void *)_port; + struct dbc_port *port = from_tasklet(port, t, push); struct list_head *queue = &port->read_queue; spin_lock_irqsave(&port->port_lock, flags); @@ -382,7 +382,7 @@ xhci_dbc_tty_init_port(struct xhci_dbc *dbc, struct dbc_port *port) { tty_port_init(&port->port); spin_lock_init(&port->port_lock); - tasklet_init(&port->push, dbc_rx_push, (unsigned long)port); + tasklet_setup(&port->push, dbc_rx_push); INIT_LIST_HEAD(&port->read_pool); INIT_LIST_HEAD(&port->read_queue); INIT_LIST_HEAD(&port->write_pool); diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c index c88bffd68742..2c0fda57869e 100644 --- a/drivers/usb/host/xhci-debugfs.c +++ b/drivers/usb/host/xhci-debugfs.c @@ -451,9 +451,11 @@ void xhci_debugfs_create_endpoint(struct xhci_hcd *xhci, if (!epriv) return; + epriv->show_ring = dev->eps[ep_index].ring; + snprintf(epriv->name, sizeof(epriv->name), "ep%02d", ep_index); epriv->root = xhci_debugfs_create_ring_dir(xhci, - &dev->eps[ep_index].ring, + &epriv->show_ring, epriv->name, spriv->root); spriv->eps[ep_index] = epriv; @@ -475,6 +477,111 @@ void xhci_debugfs_remove_endpoint(struct xhci_hcd *xhci, kfree(epriv); } +static int xhci_stream_id_show(struct seq_file *s, void *unused) +{ + struct xhci_ep_priv *epriv = s->private; + + if (!epriv->stream_info) + return -EPERM; + + seq_printf(s, "Show stream ID %d trb ring, supported [1 - %d]\n", + epriv->stream_id, epriv->stream_info->num_streams - 1); + + return 0; +} + +static int xhci_stream_id_open(struct inode *inode, struct file *file) +{ + return single_open(file, xhci_stream_id_show, inode->i_private); +} + +static ssize_t xhci_stream_id_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct xhci_ep_priv *epriv = s->private; + int ret; + u16 stream_id; /* MaxPStreams + 1 <= 16 */ + + if (!epriv->stream_info) + return -EPERM; + + /* Decimal number */ + ret = kstrtou16_from_user(ubuf, count, 10, &stream_id); + if (ret) + return ret; + + if (stream_id == 0 || stream_id >= epriv->stream_info->num_streams) + return -EINVAL; + + epriv->stream_id = stream_id; + epriv->show_ring = epriv->stream_info->stream_rings[stream_id]; + + return count; +} + +static const struct file_operations stream_id_fops = { + .open = xhci_stream_id_open, + .write = xhci_stream_id_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int xhci_stream_context_array_show(struct seq_file *s, void *unused) +{ + struct xhci_ep_priv *epriv = s->private; + struct xhci_stream_ctx *stream_ctx; + dma_addr_t dma; + int id; + + if (!epriv->stream_info) + return -EPERM; + + seq_printf(s, "Allocated %d streams and %d stream context array entries\n", + epriv->stream_info->num_streams, + epriv->stream_info->num_stream_ctxs); + + for (id = 0; id < epriv->stream_info->num_stream_ctxs; id++) { + stream_ctx = epriv->stream_info->stream_ctx_array + id; + dma = epriv->stream_info->ctx_array_dma + id * 16; + if (id < epriv->stream_info->num_streams) + seq_printf(s, "%pad stream id %d deq %016llx\n", &dma, + id, le64_to_cpu(stream_ctx->stream_ring)); + else + seq_printf(s, "%pad stream context entry not used deq %016llx\n", + &dma, le64_to_cpu(stream_ctx->stream_ring)); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(xhci_stream_context_array); + +void xhci_debugfs_create_stream_files(struct xhci_hcd *xhci, + struct xhci_virt_device *dev, + int ep_index) +{ + struct xhci_slot_priv *spriv = dev->debugfs_private; + struct xhci_ep_priv *epriv; + + if (!spriv || !spriv->eps[ep_index] || + !dev->eps[ep_index].stream_info) + return; + + epriv = spriv->eps[ep_index]; + epriv->stream_info = dev->eps[ep_index].stream_info; + + /* Show trb ring of stream ID 1 by default */ + epriv->stream_id = 1; + epriv->show_ring = epriv->stream_info->stream_rings[1]; + debugfs_create_file("stream_id", 0644, + epriv->root, epriv, + &stream_id_fops); + debugfs_create_file("stream_context_array", 0444, + epriv->root, epriv, + &xhci_stream_context_array_fops); +} + void xhci_debugfs_create_slot(struct xhci_hcd *xhci, int slot_id) { struct xhci_slot_priv *priv; diff --git a/drivers/usb/host/xhci-debugfs.h b/drivers/usb/host/xhci-debugfs.h index 56db635fcd6e..7c074b4be819 100644 --- a/drivers/usb/host/xhci-debugfs.h +++ b/drivers/usb/host/xhci-debugfs.h @@ -91,6 +91,9 @@ struct xhci_file_map { struct xhci_ep_priv { char name[DEBUGFS_NAMELEN]; struct dentry *root; + struct xhci_stream_info *stream_info; + struct xhci_ring *show_ring; + unsigned int stream_id; }; struct xhci_slot_priv { @@ -113,6 +116,9 @@ void xhci_debugfs_create_endpoint(struct xhci_hcd *xhci, void xhci_debugfs_remove_endpoint(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, int ep_index); +void xhci_debugfs_create_stream_files(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + int ep_index); #else static inline void xhci_debugfs_init(struct xhci_hcd *xhci) { } static inline void xhci_debugfs_exit(struct xhci_hcd *xhci) { } @@ -128,6 +134,10 @@ static inline void xhci_debugfs_remove_endpoint(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, int ep_index) { } +static inline void +xhci_debugfs_create_stream_files(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + int ep_index) { } #endif /* CONFIG_DEBUG_FS */ #endif /* __LINUX_XHCI_DEBUGFS_H */ diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c index 4311d4c9b68d..8f321f39ab96 100644 --- a/drivers/usb/host/xhci-mtk.c +++ b/drivers/usb/host/xhci-mtk.c @@ -77,7 +77,7 @@ static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk) { struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs; u32 value, check_val; - int u3_ports_disabed = 0; + int u3_ports_disabled = 0; int ret; int i; @@ -92,7 +92,7 @@ static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk) /* power on and enable u3 ports except skipped ones */ for (i = 0; i < mtk->num_u3_ports; i++) { if ((0x1 << i) & mtk->u3p_dis_msk) { - u3_ports_disabed++; + u3_ports_disabled++; continue; } @@ -117,7 +117,7 @@ static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk) check_val = STS1_SYSPLL_STABLE | STS1_REF_RST | STS1_SYS125_RST | STS1_XHCI_RST; - if (mtk->num_u3_ports > u3_ports_disabed) + if (mtk->num_u3_ports > u3_ports_disabled) check_val |= STS1_U3_MAC_RST; ret = readl_poll_timeout(&ippc->ip_pw_sts1, value, diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 3feaafebfe58..c26c06e5c88c 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -12,6 +12,7 @@ #include <linux/slab.h> #include <linux/module.h> #include <linux/acpi.h> +#include <linux/reset.h> #include "xhci.h" #include "xhci-trace.h" @@ -346,6 +347,7 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) struct xhci_hcd *xhci; struct usb_hcd *hcd; struct xhci_driver_data *driver_data; + struct reset_control *reset; driver_data = (struct xhci_driver_data *)id->driver_data; if (driver_data && driver_data->quirks & XHCI_RENESAS_FW_QUIRK) { @@ -354,6 +356,11 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) return retval; } + reset = devm_reset_control_get_optional_exclusive(&dev->dev, NULL); + if (IS_ERR(reset)) + return PTR_ERR(reset); + reset_control_reset(reset); + /* Prevent runtime suspending between USB-2 and USB-3 initialization */ pm_runtime_get_noresume(&dev->dev); @@ -371,6 +378,7 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) /* USB 2.0 roothub is stored in the PCI device now. */ hcd = dev_get_drvdata(&dev->dev); xhci = hcd_to_xhci(hcd); + xhci->reset = reset; xhci->shared_hcd = usb_create_shared_hcd(&xhci_pci_hc_driver, &dev->dev, pci_name(dev), hcd); if (!xhci->shared_hcd) { @@ -522,6 +530,8 @@ static int xhci_pci_resume(struct usb_hcd *hcd, bool hibernated) struct pci_dev *pdev = to_pci_dev(hcd->self.controller); int retval = 0; + reset_control_reset(xhci->reset); + /* The BIOS on systems with the Intel Panther Point chipset may or may * not support xHCI natively. That means that during system resume, it * may switch the ports back to EHCI so that users can use their diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 3057cfc76d6a..aa2d35f98200 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -54,6 +54,16 @@ static int xhci_priv_init_quirk(struct usb_hcd *hcd) return priv->init_quirk(hcd); } +static int xhci_priv_suspend_quirk(struct usb_hcd *hcd) +{ + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (!priv->suspend_quirk) + return 0; + + return priv->suspend_quirk(hcd); +} + static int xhci_priv_resume_quirk(struct usb_hcd *hcd) { struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); @@ -173,6 +183,8 @@ static int xhci_plat_probe(struct platform_device *pdev) struct usb_hcd *hcd; int ret; int irq; + struct xhci_plat_priv *priv = NULL; + if (usb_disabled()) return -ENODEV; @@ -264,16 +276,18 @@ static int xhci_plat_probe(struct platform_device *pdev) if (ret) goto disable_reg_clk; - priv_match = of_device_get_match_data(&pdev->dev); - if (priv_match) { - struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + if (pdev->dev.of_node) + priv_match = of_device_get_match_data(&pdev->dev); + else + priv_match = dev_get_platdata(&pdev->dev); + if (priv_match) { + priv = hcd_to_xhci_priv(hcd); /* Just copy data for now */ - if (priv_match) - *priv = *priv_match; + *priv = *priv_match; } - device_wakeup_enable(hcd->self.controller); + device_set_wakeup_capable(&pdev->dev, true); xhci->main_hcd = hcd; xhci->shared_hcd = __usb_create_hcd(driver, sysdev, &pdev->dev, @@ -316,6 +330,9 @@ static int xhci_plat_probe(struct platform_device *pdev) hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node); xhci->shared_hcd->tpl_support = hcd->tpl_support; + if (priv && (priv->quirks & XHCI_SKIP_PHY_INIT)) + hcd->skip_phy_initialization = 1; + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); if (ret) goto disable_usb_phy; @@ -397,14 +414,14 @@ static int __maybe_unused xhci_plat_suspend(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int ret; + ret = xhci_priv_suspend_quirk(hcd); + if (ret) + return ret; /* * xhci_suspend() needs `do_wakeup` to know whether host is allowed - * to do wakeup during suspend. Since xhci_plat_suspend is currently - * only designed for system suspend, device_may_wakeup() is enough - * to dertermine whether host is allowed to do wakeup. Need to - * reconsider this when xhci_plat_suspend enlarges its scope, e.g., - * also applies to runtime suspend. + * to do wakeup during suspend. */ return xhci_suspend(xhci, device_may_wakeup(dev)); } @@ -434,6 +451,11 @@ static int __maybe_unused xhci_plat_runtime_suspend(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int ret; + + ret = xhci_priv_suspend_quirk(hcd); + if (ret) + return ret; return xhci_suspend(xhci, true); } diff --git a/drivers/usb/host/xhci-plat.h b/drivers/usb/host/xhci-plat.h index b49f6447bd3a..1fb149d1fbce 100644 --- a/drivers/usb/host/xhci-plat.h +++ b/drivers/usb/host/xhci-plat.h @@ -15,6 +15,7 @@ struct xhci_plat_priv { unsigned long long quirks; void (*plat_start)(struct usb_hcd *); int (*init_quirk)(struct usb_hcd *); + int (*suspend_quirk)(struct usb_hcd *); int (*resume_quirk)(struct usb_hcd *); }; diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c index c1025d321a41..1bc4fe7b8c75 100644 --- a/drivers/usb/host/xhci-rcar.c +++ b/drivers/usb/host/xhci-rcar.c @@ -6,6 +6,7 @@ */ #include <linux/firmware.h> +#include <linux/iopoll.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> @@ -127,8 +128,7 @@ static int xhci_rcar_download_firmware(struct usb_hcd *hcd) void __iomem *regs = hcd->regs; struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); const struct firmware *fw; - int retval, index, j, time; - int timeout = 10000; + int retval, index, j; u32 data, val, temp; u32 quirks = 0; const struct soc_device_attribute *attr; @@ -166,32 +166,19 @@ static int xhci_rcar_download_firmware(struct usb_hcd *hcd) temp |= RCAR_USB3_DL_CTRL_FW_SET_DATA0; writel(temp, regs + RCAR_USB3_DL_CTRL); - for (time = 0; time < timeout; time++) { - val = readl(regs + RCAR_USB3_DL_CTRL); - if ((val & RCAR_USB3_DL_CTRL_FW_SET_DATA0) == 0) - break; - udelay(1); - } - if (time == timeout) { - retval = -ETIMEDOUT; + retval = readl_poll_timeout_atomic(regs + RCAR_USB3_DL_CTRL, + val, !(val & RCAR_USB3_DL_CTRL_FW_SET_DATA0), + 1, 10000); + if (retval < 0) break; - } } temp = readl(regs + RCAR_USB3_DL_CTRL); temp &= ~RCAR_USB3_DL_CTRL_ENABLE; writel(temp, regs + RCAR_USB3_DL_CTRL); - for (time = 0; time < timeout; time++) { - val = readl(regs + RCAR_USB3_DL_CTRL); - if (val & RCAR_USB3_DL_CTRL_FW_SUCCESS) { - retval = 0; - break; - } - udelay(1); - } - if (time == timeout) - retval = -ETIMEDOUT; + retval = readl_poll_timeout_atomic((regs + RCAR_USB3_DL_CTRL), + val, val & RCAR_USB3_DL_CTRL_FW_SUCCESS, 1, 10000); release_firmware(fw); @@ -200,18 +187,12 @@ static int xhci_rcar_download_firmware(struct usb_hcd *hcd) static bool xhci_rcar_wait_for_pll_active(struct usb_hcd *hcd) { - int timeout = 1000; + int retval; u32 val, mask = RCAR_USB3_AXH_STA_PLL_ACTIVE_MASK; - while (timeout > 0) { - val = readl(hcd->regs + RCAR_USB3_AXH_STA); - if ((val & mask) == mask) - return true; - udelay(1); - timeout--; - } - - return false; + retval = readl_poll_timeout_atomic(hcd->regs + RCAR_USB3_AXH_STA, + val, (val & mask) == mask, 1, 1000); + return !retval; } /* This function needs to initialize a "phy" of usb before */ diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index a741a38a4c69..167dae117f73 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -3736,6 +3736,24 @@ static int xhci_get_isoc_frame_id(struct xhci_hcd *xhci, return start_frame; } +/* Check if we should generate event interrupt for a TD in an isoc URB */ +static bool trb_block_event_intr(struct xhci_hcd *xhci, int num_tds, int i) +{ + if (xhci->hci_version < 0x100) + return false; + /* always generate an event interrupt for the last TD */ + if (i == num_tds - 1) + return false; + /* + * If AVOID_BEI is set the host handles full event rings poorly, + * generate an event at least every 8th TD to clear the event ring + */ + if (i && xhci->quirks & XHCI_AVOID_BEI) + return !!(i % 8); + + return true; +} + /* This is for isoc transfer */ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index) @@ -3843,10 +3861,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, more_trbs_coming = false; td->last_trb = ep_ring->enqueue; field |= TRB_IOC; - /* set BEI, except for the last TD */ - if (xhci->hci_version >= 0x100 && - !(xhci->quirks & XHCI_AVOID_BEI) && - i < num_tds - 1) + if (trb_block_event_intr(xhci, num_tds, i)) field |= TRB_BEI; } /* Calculate TRB length */ diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 190923d8b246..934be1686352 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -1866,7 +1866,6 @@ static const struct tegra_xusb_phy_type tegra124_phy_types[] = { static const unsigned int tegra124_xusb_context_ipfs[] = { IPFS_XUSB_HOST_MSI_BAR_SZ_0, - IPFS_XUSB_HOST_MSI_BAR_SZ_0, IPFS_XUSB_HOST_MSI_AXI_BAR_ST_0, IPFS_XUSB_HOST_MSI_FPCI_BAR_ST_0, IPFS_XUSB_HOST_MSI_VEC0_0, diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index f4cedcaee14b..482fe8c5e3b4 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -982,12 +982,15 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup) xhci->shared_hcd->state != HC_STATE_SUSPENDED) return -EINVAL; - xhci_dbc_suspend(xhci); - /* Clear root port wake on bits if wakeup not allowed. */ if (!do_wakeup) xhci_disable_port_wake_on_bits(xhci); + if (!HCD_HW_ACCESSIBLE(hcd)) + return 0; + + xhci_dbc_suspend(xhci); + /* Don't poll the roothubs on bus suspend. */ xhci_dbg(xhci, "%s: stopping port polling.\n", __func__); clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); @@ -1915,8 +1918,6 @@ static int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); trace_xhci_add_endpoint(ep_ctx); - xhci_debugfs_create_endpoint(xhci, virt_dev, ep_index); - xhci_dbg(xhci, "add ep 0x%x, slot id %d, new drop flags = %#x, new add flags = %#x\n", (unsigned int) ep->desc.bEndpointAddress, udev->slot_id, @@ -2949,6 +2950,7 @@ static int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) xhci_check_bw_drop_ep_streams(xhci, virt_dev, i); virt_dev->eps[i].ring = virt_dev->eps[i].new_ring; virt_dev->eps[i].new_ring = NULL; + xhci_debugfs_create_endpoint(xhci, virt_dev, i); } command_cleanup: kfree(command->completion); @@ -3531,6 +3533,7 @@ static int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, xhci_dbg(xhci, "Slot %u ep ctx %u now has streams.\n", udev->slot_id, ep_index); vdev->eps[ep_index].ep_state |= EP_HAS_STREAMS; + xhci_debugfs_create_stream_files(xhci, vdev, ep_index); } xhci_free_command(xhci, config_cmd); spin_unlock_irqrestore(&xhci->lock, flags); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index ea1754f185a2..8be88379c0fb 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1770,6 +1770,8 @@ struct xhci_hcd { /* optional clocks */ struct clk *clk; struct clk *reg_clk; + /* optional reset controller */ + struct reset_control *reset; /* data structures */ struct xhci_device_context_array *dcbaa; struct xhci_ring *cmd_ring; @@ -1874,6 +1876,7 @@ struct xhci_hcd { #define XHCI_RESET_PLL_ON_DISCONNECT BIT_ULL(34) #define XHCI_SNPS_BROKEN_SUSPEND BIT_ULL(35) #define XHCI_RENESAS_FW_QUIRK BIT_ULL(36) +#define XHCI_SKIP_PHY_INIT BIT_ULL(37) unsigned int num_active_eps; unsigned int limit_active_eps; diff --git a/drivers/usb/image/microtek.c b/drivers/usb/image/microtek.c index 360416680e82..59b02a539963 100644 --- a/drivers/usb/image/microtek.c +++ b/drivers/usb/image/microtek.c @@ -389,7 +389,7 @@ void mts_int_submit_urb (struct urb* transfer, res = usb_submit_urb( transfer, GFP_ATOMIC ); if ( unlikely(res) ) { MTS_INT_ERROR( "could not submit URB! Error was %d\n",(int)res ); - context->srb->result = DID_ERROR << 16; + set_host_byte(context->srb, DID_ERROR); mts_transfer_cleanup(transfer); } } @@ -438,7 +438,7 @@ static void mts_data_done( struct urb* transfer ) scsi_set_resid(context->srb, context->data_length - transfer->actual_length); } else if ( unlikely(status) ) { - context->srb->result = (status == -ENOENT ? DID_ABORT : DID_ERROR)<<16; + set_host_byte(context->srb, (status == -ENOENT ? DID_ABORT : DID_ERROR)); } mts_get_status(transfer); @@ -455,12 +455,12 @@ static void mts_command_done( struct urb *transfer ) if (status == -ENOENT) { /* We are being killed */ MTS_DEBUG_GOT_HERE(); - context->srb->result = DID_ABORT<<16; + set_host_byte(context->srb, DID_ABORT); } else { /* A genuine error has occurred */ MTS_DEBUG_GOT_HERE(); - context->srb->result = DID_ERROR<<16; + set_host_byte(context->srb, DID_ERROR); } mts_transfer_cleanup(transfer); @@ -495,7 +495,7 @@ static void mts_do_sg (struct urb* transfer) scsi_sg_count(context->srb)); if (unlikely(status)) { - context->srb->result = (status == -ENOENT ? DID_ABORT : DID_ERROR)<<16; + set_host_byte(context->srb, (status == -ENOENT ? DID_ABORT : DID_ERROR)); mts_transfer_cleanup(transfer); } @@ -578,7 +578,7 @@ mts_scsi_queuecommand_lck(struct scsi_cmnd *srb, mts_scsi_cmnd_callback callback MTS_DEBUG("this device doesn't exist\n"); - srb->result = DID_BAD_TARGET << 16; + set_host_byte(srb, DID_BAD_TARGET); if(likely(callback != NULL)) callback(srb); @@ -605,7 +605,7 @@ mts_scsi_queuecommand_lck(struct scsi_cmnd *srb, mts_scsi_cmnd_callback callback if(unlikely(res)){ MTS_ERROR("error %d submitting URB\n",(int)res); - srb->result = DID_ERROR << 16; + set_host_byte(srb, DID_ERROR); if(likely(callback != NULL)) callback(srb); diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index dd74ab7a2f9c..33ae656c4b68 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -22,6 +22,7 @@ #include <linux/debugfs.h> #include <linux/uaccess.h> #include <linux/io.h> +#include <linux/iopoll.h> #include <linux/mm.h> #include <linux/timer.h> #include <asm/unaligned.h> @@ -380,18 +381,15 @@ static int handshake(struct usb_hcd *hcd, u32 reg, u32 mask, u32 done, int usec) { u32 result; + int ret; + + ret = readl_poll_timeout_atomic(hcd->regs + reg, result, + ((result & mask) == done || + result == U32_MAX), 1, usec); + if (result == U32_MAX) + return -ENODEV; - do { - result = reg_read32(hcd->regs, reg); - if (result == ~0) - return -ENODEV; - result &= mask; - if (result == done) - return 0; - udelay(1); - usec--; - } while (usec > 0); - return -ETIMEDOUT; + return ret; } /* reset a non-running (STS_HALT == 1) controller */ diff --git a/drivers/usb/misc/adutux.c b/drivers/usb/misc/adutux.c index a7eefe11f31a..45a387979935 100644 --- a/drivers/usb/misc/adutux.c +++ b/drivers/usb/misc/adutux.c @@ -209,6 +209,7 @@ static void adu_interrupt_out_callback(struct urb *urb) if (status != 0) { if ((status != -ENOENT) && + (status != -ESHUTDOWN) && (status != -ECONNRESET)) { dev_dbg(&dev->udev->dev, "%s :nonzero status received: %d\n", __func__, diff --git a/drivers/usb/misc/appledisplay.c b/drivers/usb/misc/appledisplay.c index 36fed1a09666..c8098e9b432e 100644 --- a/drivers/usb/misc/appledisplay.c +++ b/drivers/usb/misc/appledisplay.c @@ -342,20 +342,8 @@ static struct usb_driver appledisplay_driver = { .disconnect = appledisplay_disconnect, .id_table = appledisplay_table, }; - -static int __init appledisplay_init(void) -{ - return usb_register(&appledisplay_driver); -} - -static void __exit appledisplay_exit(void) -{ - usb_deregister(&appledisplay_driver); -} +module_usb_driver(appledisplay_driver); MODULE_AUTHOR("Michael Hanselmann"); MODULE_DESCRIPTION("Apple Cinema Display driver"); MODULE_LICENSE("GPL"); - -module_init(appledisplay_init); -module_exit(appledisplay_exit); diff --git a/drivers/usb/misc/legousbtower.c b/drivers/usb/misc/legousbtower.c index f922544056de..ba655b4af4fc 100644 --- a/drivers/usb/misc/legousbtower.c +++ b/drivers/usb/misc/legousbtower.c @@ -308,15 +308,9 @@ static int tower_open(struct inode *inode, struct file *file) int subminor; int retval = 0; struct usb_interface *interface; - struct tower_reset_reply *reset_reply; + struct tower_reset_reply reset_reply; int result; - reset_reply = kmalloc(sizeof(*reset_reply), GFP_KERNEL); - if (!reset_reply) { - retval = -ENOMEM; - goto exit; - } - nonseekable_open(inode, file); subminor = iminor(inode); @@ -347,15 +341,12 @@ static int tower_open(struct inode *inode, struct file *file) } /* reset the tower */ - result = usb_control_msg(dev->udev, - usb_rcvctrlpipe(dev->udev, 0), - LEGO_USB_TOWER_REQUEST_RESET, - USB_TYPE_VENDOR | USB_DIR_IN | USB_RECIP_DEVICE, - 0, - 0, - reset_reply, - sizeof(*reset_reply), - 1000); + result = usb_control_msg_recv(dev->udev, 0, + LEGO_USB_TOWER_REQUEST_RESET, + USB_TYPE_VENDOR | USB_DIR_IN | USB_RECIP_DEVICE, + 0, 0, + &reset_reply, sizeof(reset_reply), 1000, + GFP_KERNEL); if (result < 0) { dev_err(&dev->udev->dev, "LEGO USB Tower reset control request failed\n"); @@ -394,7 +385,6 @@ unlock_exit: mutex_unlock(&dev->lock); exit: - kfree(reset_reply); return retval; } @@ -753,7 +743,7 @@ static int tower_probe(struct usb_interface *interface, const struct usb_device_ struct device *idev = &interface->dev; struct usb_device *udev = interface_to_usbdev(interface); struct lego_usb_tower *dev; - struct tower_get_version_reply *get_version_reply = NULL; + struct tower_get_version_reply get_version_reply; int retval = -ENOMEM; int result; @@ -798,34 +788,25 @@ static int tower_probe(struct usb_interface *interface, const struct usb_device_ dev->interrupt_in_interval = interrupt_in_interval ? interrupt_in_interval : dev->interrupt_in_endpoint->bInterval; dev->interrupt_out_interval = interrupt_out_interval ? interrupt_out_interval : dev->interrupt_out_endpoint->bInterval; - get_version_reply = kmalloc(sizeof(*get_version_reply), GFP_KERNEL); - if (!get_version_reply) { - retval = -ENOMEM; - goto error; - } - /* get the firmware version and log it */ - result = usb_control_msg(udev, - usb_rcvctrlpipe(udev, 0), - LEGO_USB_TOWER_REQUEST_GET_VERSION, - USB_TYPE_VENDOR | USB_DIR_IN | USB_RECIP_DEVICE, - 0, - 0, - get_version_reply, - sizeof(*get_version_reply), - 1000); - if (result != sizeof(*get_version_reply)) { - if (result >= 0) - result = -EIO; + result = usb_control_msg_recv(udev, 0, + LEGO_USB_TOWER_REQUEST_GET_VERSION, + USB_TYPE_VENDOR | USB_DIR_IN | USB_RECIP_DEVICE, + 0, + 0, + &get_version_reply, + sizeof(get_version_reply), + 1000, GFP_KERNEL); + if (!result) { dev_err(idev, "get version request failed: %d\n", result); retval = result; goto error; } dev_info(&interface->dev, "LEGO USB Tower firmware version is %d.%d build %d\n", - get_version_reply->major, - get_version_reply->minor, - le16_to_cpu(get_version_reply->build_no)); + get_version_reply.major, + get_version_reply.minor, + le16_to_cpu(get_version_reply.build_no)); /* we can register the device now, as it is ready */ usb_set_intfdata(interface, dev); @@ -844,11 +825,9 @@ static int tower_probe(struct usb_interface *interface, const struct usb_device_ USB_MAJOR, dev->minor); exit: - kfree(get_version_reply); return retval; error: - kfree(get_version_reply); tower_delete(dev); return retval; } diff --git a/drivers/usb/misc/usb3503.c b/drivers/usb/misc/usb3503.c index 116bd789e568..48099c6bf04c 100644 --- a/drivers/usb/misc/usb3503.c +++ b/drivers/usb/misc/usb3503.c @@ -322,8 +322,7 @@ static int usb3503_platform_remove(struct platform_device *pdev) return 0; } -#ifdef CONFIG_PM_SLEEP -static int usb3503_suspend(struct usb3503 *hub) +static int __maybe_unused usb3503_suspend(struct usb3503 *hub) { usb3503_switch_mode(hub, USB3503_MODE_STANDBY); clk_disable_unprepare(hub->clk); @@ -331,7 +330,7 @@ static int usb3503_suspend(struct usb3503 *hub) return 0; } -static int usb3503_resume(struct usb3503 *hub) +static int __maybe_unused usb3503_resume(struct usb3503 *hub) { clk_prepare_enable(hub->clk); usb3503_switch_mode(hub, hub->mode); @@ -339,30 +338,29 @@ static int usb3503_resume(struct usb3503 *hub) return 0; } -static int usb3503_i2c_suspend(struct device *dev) +static int __maybe_unused usb3503_i2c_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); return usb3503_suspend(i2c_get_clientdata(client)); } -static int usb3503_i2c_resume(struct device *dev) +static int __maybe_unused usb3503_i2c_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); return usb3503_resume(i2c_get_clientdata(client)); } -static int usb3503_platform_suspend(struct device *dev) +static int __maybe_unused usb3503_platform_suspend(struct device *dev) { return usb3503_suspend(dev_get_drvdata(dev)); } -static int usb3503_platform_resume(struct device *dev) +static int __maybe_unused usb3503_platform_resume(struct device *dev) { return usb3503_resume(dev_get_drvdata(dev)); } -#endif static SIMPLE_DEV_PM_OPS(usb3503_i2c_pm_ops, usb3503_i2c_suspend, usb3503_i2c_resume); @@ -388,7 +386,7 @@ MODULE_DEVICE_TABLE(of, usb3503_of_match); static struct i2c_driver usb3503_i2c_driver = { .driver = { .name = USB3503_I2C_NAME, - .pm = &usb3503_i2c_pm_ops, + .pm = pm_ptr(&usb3503_i2c_pm_ops), .of_match_table = of_match_ptr(usb3503_of_match), }, .probe = usb3503_i2c_probe, @@ -400,7 +398,7 @@ static struct platform_driver usb3503_platform_driver = { .driver = { .name = USB3503_I2C_NAME, .of_match_table = of_match_ptr(usb3503_of_match), - .pm = &usb3503_platform_pm_ops, + .pm = pm_ptr(&usb3503_platform_pm_ops), }, .probe = usb3503_platform_probe, .remove = usb3503_platform_remove, diff --git a/drivers/usb/misc/usb4604.c b/drivers/usb/misc/usb4604.c index 1b4de651e697..2142af9bbdec 100644 --- a/drivers/usb/misc/usb4604.c +++ b/drivers/usb/misc/usb4604.c @@ -112,8 +112,7 @@ static int usb4604_i2c_probe(struct i2c_client *i2c, return usb4604_probe(hub); } -#ifdef CONFIG_PM_SLEEP -static int usb4604_i2c_suspend(struct device *dev) +static int __maybe_unused usb4604_i2c_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct usb4604 *hub = i2c_get_clientdata(client); @@ -123,7 +122,7 @@ static int usb4604_i2c_suspend(struct device *dev) return 0; } -static int usb4604_i2c_resume(struct device *dev) +static int __maybe_unused usb4604_i2c_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct usb4604 *hub = i2c_get_clientdata(client); @@ -132,7 +131,6 @@ static int usb4604_i2c_resume(struct device *dev) return 0; } -#endif static SIMPLE_DEV_PM_OPS(usb4604_i2c_pm_ops, usb4604_i2c_suspend, usb4604_i2c_resume); @@ -154,7 +152,7 @@ MODULE_DEVICE_TABLE(of, usb4604_of_match); static struct i2c_driver usb4604_i2c_driver = { .driver = { .name = "usb4604", - .pm = &usb4604_i2c_pm_ops, + .pm = pm_ptr(&usb4604_i2c_pm_ops), .of_match_table = of_match_ptr(usb4604_of_match), }, .probe = usb4604_i2c_probe, diff --git a/drivers/usb/misc/usblcd.c b/drivers/usb/misc/usblcd.c index 61e9e987fe4a..bb546f624a45 100644 --- a/drivers/usb/misc/usblcd.c +++ b/drivers/usb/misc/usblcd.c @@ -187,7 +187,6 @@ static long lcd_ioctl(struct file *file, unsigned int cmd, unsigned long arg) break; default: return -ENOTTY; - break; } return 0; diff --git a/drivers/usb/misc/yurex.c b/drivers/usb/misc/yurex.c index b2e09883c7e2..e3165d79b5f6 100644 --- a/drivers/usb/misc/yurex.c +++ b/drivers/usb/misc/yurex.c @@ -96,15 +96,13 @@ static void yurex_delete(struct kref *kref) if (dev->cntl_urb) { usb_kill_urb(dev->cntl_urb); kfree(dev->cntl_req); - if (dev->cntl_buffer) - usb_free_coherent(dev->udev, YUREX_BUF_SIZE, + usb_free_coherent(dev->udev, YUREX_BUF_SIZE, dev->cntl_buffer, dev->cntl_urb->transfer_dma); usb_free_urb(dev->cntl_urb); } if (dev->urb) { usb_kill_urb(dev->urb); - if (dev->int_buffer) - usb_free_coherent(dev->udev, YUREX_BUF_SIZE, + usb_free_coherent(dev->udev, YUREX_BUF_SIZE, dev->int_buffer, dev->urb->transfer_dma); usb_free_urb(dev->urb); } diff --git a/drivers/usb/mtu3/mtu3.h b/drivers/usb/mtu3/mtu3.h index 71f4f02c05c6..aef0a0bba25a 100644 --- a/drivers/usb/mtu3/mtu3.h +++ b/drivers/usb/mtu3/mtu3.h @@ -370,12 +370,6 @@ static inline struct mtu3 *gadget_to_mtu3(struct usb_gadget *g) return container_of(g, struct mtu3, g); } -static inline int is_first_entry(const struct list_head *list, - const struct list_head *head) -{ - return list_is_last(head, list); -} - static inline struct mtu3_request *to_mtu3_request(struct usb_request *req) { return req ? container_of(req, struct mtu3_request, request) : NULL; diff --git a/drivers/usb/musb/musb_gadget_ep0.c b/drivers/usb/musb/musb_gadget_ep0.c index 44d3cb02fa76..6d7336727388 100644 --- a/drivers/usb/musb/musb_gadget_ep0.c +++ b/drivers/usb/musb/musb_gadget_ep0.c @@ -1024,7 +1024,7 @@ static int musb_g_ep0_halt(struct usb_ep *e, int value) case MUSB_EP0_STAGE_ACKWAIT: /* STALL for zero-length data */ case MUSB_EP0_STAGE_RX: /* control-OUT data */ csr = musb_readw(regs, MUSB_CSR0); - /* FALLTHROUGH */ + fallthrough; /* It's also OK to issue stalls during callbacks when a non-empty * DATA stage buffer has been read (or even written). diff --git a/drivers/usb/phy/phy-ab8500-usb.c b/drivers/usb/phy/phy-ab8500-usb.c index aa4a3140394b..4c52ba96f17e 100644 --- a/drivers/usb/phy/phy-ab8500-usb.c +++ b/drivers/usb/phy/phy-ab8500-usb.c @@ -518,7 +518,7 @@ static int ab8500_usb_link_status_update(struct ab8500_usb *ab, * 3. Enable AB regulators * 4. Enable USB phy * 5. Reset the musb controller - * 6. Switch the ULPI GPIO pins to fucntion mode + * 6. Switch the ULPI GPIO pins to function mode * 7. Enable the musb Peripheral5 clock * 8. Restore MUSB context */ diff --git a/drivers/usb/phy/phy-mv-usb.c b/drivers/usb/phy/phy-mv-usb.c index ce767ecc0636..576d925af77c 100644 --- a/drivers/usb/phy/phy-mv-usb.c +++ b/drivers/usb/phy/phy-mv-usb.c @@ -8,6 +8,7 @@ #include <linux/module.h> #include <linux/kernel.h> #include <linux/io.h> +#include <linux/iopoll.h> #include <linux/uaccess.h> #include <linux/device.h> #include <linux/proc_fs.h> @@ -135,8 +136,8 @@ static int mv_otg_set_timer(struct mv_otg *mvotg, unsigned int id, static int mv_otg_reset(struct mv_otg *mvotg) { - unsigned int loops; u32 tmp; + int ret; /* Stop the controller */ tmp = readl(&mvotg->op_regs->usbcmd); @@ -146,15 +147,12 @@ static int mv_otg_reset(struct mv_otg *mvotg) /* Reset the controller to get default values */ writel(USBCMD_CTRL_RESET, &mvotg->op_regs->usbcmd); - loops = 500; - while (readl(&mvotg->op_regs->usbcmd) & USBCMD_CTRL_RESET) { - if (loops == 0) { - dev_err(&mvotg->pdev->dev, - "Wait for RESET completed TIMEOUT\n"); - return -ETIMEDOUT; - } - loops--; - udelay(20); + ret = readl_poll_timeout_atomic(&mvotg->op_regs->usbcmd, tmp, + (tmp & USBCMD_CTRL_RESET), 10, 10000); + if (ret < 0) { + dev_err(&mvotg->pdev->dev, + "Wait for RESET completed TIMEOUT\n"); + return ret; } writel(0x0, &mvotg->op_regs->usbintr); diff --git a/drivers/usb/phy/phy-ulpi-viewport.c b/drivers/usb/phy/phy-ulpi-viewport.c index 7a14e0e3b635..0f61e328eaef 100644 --- a/drivers/usb/phy/phy-ulpi-viewport.c +++ b/drivers/usb/phy/phy-ulpi-viewport.c @@ -7,6 +7,7 @@ #include <linux/kernel.h> #include <linux/usb.h> #include <linux/io.h> +#include <linux/iopoll.h> #include <linux/usb/otg.h> #include <linux/usb/ulpi.h> @@ -20,16 +21,9 @@ static int ulpi_viewport_wait(void __iomem *view, u32 mask) { - unsigned long usec = 2000; + u32 val; - while (usec--) { - if (!(readl(view) & mask)) - return 0; - - udelay(1); - } - - return -ETIMEDOUT; + return readl_poll_timeout_atomic(view, val, !(val & mask), 1, 2000); } static int ulpi_viewport_read(struct usb_phy *otg, u32 reg) diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 9823bb424abd..e0f4c3d9649c 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -1037,6 +1037,11 @@ static const struct usb_device_id id_table_combined[] = { /* U-Blox devices */ { USB_DEVICE(UBLOX_VID, UBLOX_C099F9P_ZED_PID) }, { USB_DEVICE(UBLOX_VID, UBLOX_C099F9P_ODIN_PID) }, + /* FreeCalypso USB adapters */ + { USB_DEVICE(FTDI_VID, FTDI_FALCONIA_JTAG_BUF_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_FALCONIA_JTAG_UNBUF_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, { } /* Terminating entry */ }; @@ -1566,7 +1571,8 @@ static void ftdi_determine_type(struct usb_serial_port *port) dev_dbg(&port->dev, "%s: bcdDevice = 0x%x, bNumInterfaces = %u\n", __func__, version, interfaces); if (interfaces > 1) { - int inter; + struct usb_interface *intf = serial->interface; + int ifnum = intf->cur_altsetting->desc.bInterfaceNumber; /* Multiple interfaces.*/ if (version == 0x0800) { @@ -1581,16 +1587,15 @@ static void ftdi_determine_type(struct usb_serial_port *port) priv->chip_type = FT2232C; /* Determine interface code. */ - inter = serial->interface->altsetting->desc.bInterfaceNumber; - if (inter == 0) { + if (ifnum == 0) priv->interface = INTERFACE_A; - } else if (inter == 1) { + else if (ifnum == 1) priv->interface = INTERFACE_B; - } else if (inter == 2) { + else if (ifnum == 2) priv->interface = INTERFACE_C; - } else if (inter == 3) { + else if (ifnum == 3) priv->interface = INTERFACE_D; - } + /* BM-type devices have a bug where bcdDevice gets set * to 0x200 when iSerialNumber is 0. */ if (version < 0x500) { @@ -2330,12 +2335,11 @@ static int ftdi_NDI_device_setup(struct usb_serial *serial) */ static int ftdi_jtag_probe(struct usb_serial *serial) { - struct usb_device *udev = serial->dev; - struct usb_interface *interface = serial->interface; + struct usb_interface *intf = serial->interface; + int ifnum = intf->cur_altsetting->desc.bInterfaceNumber; - if (interface == udev->actconfig->interface[0]) { - dev_info(&udev->dev, - "Ignoring serial port reserved for JTAG\n"); + if (ifnum == 0) { + dev_info(&intf->dev, "Ignoring interface reserved for JTAG\n"); return -ENODEV; } @@ -2367,12 +2371,11 @@ static int ftdi_8u2232c_probe(struct usb_serial *serial) */ static int ftdi_stmclite_probe(struct usb_serial *serial) { - struct usb_device *udev = serial->dev; - struct usb_interface *interface = serial->interface; + struct usb_interface *intf = serial->interface; + int ifnum = intf->cur_altsetting->desc.bInterfaceNumber; - if (interface == udev->actconfig->interface[0] || - interface == udev->actconfig->interface[1]) { - dev_info(&udev->dev, "Ignoring serial port reserved for JTAG\n"); + if (ifnum < 2) { + dev_info(&intf->dev, "Ignoring interface reserved for JTAG\n"); return -ENODEV; } diff --git a/drivers/usb/serial/ftdi_sio_ids.h b/drivers/usb/serial/ftdi_sio_ids.h index b5ca17a5967a..3d47c6d72256 100644 --- a/drivers/usb/serial/ftdi_sio_ids.h +++ b/drivers/usb/serial/ftdi_sio_ids.h @@ -39,6 +39,13 @@ #define FTDI_LUMEL_PD12_PID 0x6002 +/* + * Custom USB adapters made by Falconia Partners LLC + * for FreeCalypso project, ID codes allocated to Falconia by FTDI. + */ +#define FTDI_FALCONIA_JTAG_BUF_PID 0x7150 +#define FTDI_FALCONIA_JTAG_UNBUF_PID 0x7151 + /* Sienna Serial Interface by Secyourit GmbH */ #define FTDI_SIENNA_PID 0x8348 diff --git a/drivers/usb/serial/mos7720.c b/drivers/usb/serial/mos7720.c index 2ec4eeacebc7..5eed1078fac8 100644 --- a/drivers/usb/serial/mos7720.c +++ b/drivers/usb/serial/mos7720.c @@ -282,11 +282,12 @@ static void destroy_urbtracker(struct kref *kref) * port callback had to be deferred because the disconnect mutex could not be * obtained at the time. */ -static void send_deferred_urbs(unsigned long _mos_parport) +static void send_deferred_urbs(struct tasklet_struct *t) { int ret_val; unsigned long flags; - struct mos7715_parport *mos_parport = (void *)_mos_parport; + struct mos7715_parport *mos_parport = from_tasklet(mos_parport, t, + urb_tasklet); struct urbtracker *urbtrack, *tmp; struct list_head *cursor, *next; struct device *dev; @@ -716,8 +717,7 @@ static int mos7715_parport_init(struct usb_serial *serial) INIT_LIST_HEAD(&mos_parport->deferred_urbs); usb_set_serial_data(serial, mos_parport); /* hijack private pointer */ mos_parport->serial = serial; - tasklet_init(&mos_parport->urb_tasklet, send_deferred_urbs, - (unsigned long) mos_parport); + tasklet_setup(&mos_parport->urb_tasklet, send_deferred_urbs); init_completion(&mos_parport->syncmsg_compl); /* cycle parallel port reset bit */ diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 0c6f160a214a..2a3bfd6f867e 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -528,6 +528,7 @@ static void option_instat_callback(struct urb *urb); /* Cellient products */ #define CELLIENT_VENDOR_ID 0x2692 #define CELLIENT_PRODUCT_MEN200 0x9005 +#define CELLIENT_PRODUCT_MPL200 0x9025 /* Hyundai Petatel Inc. products */ #define PETATEL_VENDOR_ID 0x1ff4 @@ -1186,6 +1187,8 @@ static const struct usb_device_id option_ids[] = { .driver_info = NCTRL(2) | RSVD(3) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1053, 0xff), /* Telit FN980 (ECM) */ .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1054, 0xff), /* Telit FT980-KS */ + .driver_info = NCTRL(2) | RSVD(3) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_ME910), .driver_info = NCTRL(0) | RSVD(1) | RSVD(3) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_ME910_DUAL_MODEM), @@ -1982,6 +1985,8 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_4COM2, 0xff, 0x02, 0x01) }, { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_4COM2, 0xff, 0x00, 0x00) }, { USB_DEVICE(CELLIENT_VENDOR_ID, CELLIENT_PRODUCT_MEN200) }, + { USB_DEVICE(CELLIENT_VENDOR_ID, CELLIENT_PRODUCT_MPL200), + .driver_info = RSVD(1) | RSVD(4) }, { USB_DEVICE(PETATEL_VENDOR_ID, PETATEL_PRODUCT_NP10T_600A) }, { USB_DEVICE(PETATEL_VENDOR_ID, PETATEL_PRODUCT_NP10T_600E) }, { USB_DEVICE_AND_INTERFACE_INFO(TPLINK_VENDOR_ID, TPLINK_PRODUCT_LTE, 0xff, 0x00, 0x00) }, /* TP-Link LTE Module */ diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index 048452d8a4a4..be8067017eaa 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -100,6 +100,7 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(HP_VENDOR_ID, HP_LD220_PRODUCT_ID) }, { USB_DEVICE(HP_VENDOR_ID, HP_LD220TA_PRODUCT_ID) }, { USB_DEVICE(HP_VENDOR_ID, HP_LD381_PRODUCT_ID) }, + { USB_DEVICE(HP_VENDOR_ID, HP_LD381GC_PRODUCT_ID) }, { USB_DEVICE(HP_VENDOR_ID, HP_LD960_PRODUCT_ID) }, { USB_DEVICE(HP_VENDOR_ID, HP_LD960TA_PRODUCT_ID) }, { USB_DEVICE(HP_VENDOR_ID, HP_LCM220_PRODUCT_ID) }, diff --git a/drivers/usb/serial/pl2303.h b/drivers/usb/serial/pl2303.h index 7d3090ee7e0c..0f681ddbfd28 100644 --- a/drivers/usb/serial/pl2303.h +++ b/drivers/usb/serial/pl2303.h @@ -127,6 +127,7 @@ /* Hewlett-Packard POS Pole Displays */ #define HP_VENDOR_ID 0x03f0 +#define HP_LD381GC_PRODUCT_ID 0x0183 #define HP_LM920_PRODUCT_ID 0x026b #define HP_TD620_PRODUCT_ID 0x0956 #define HP_LD960_PRODUCT_ID 0x0b39 diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c index c8d1ea0e6e6f..83da8236e3c8 100644 --- a/drivers/usb/serial/qcserial.c +++ b/drivers/usb/serial/qcserial.c @@ -243,11 +243,11 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) /* QDL mode */ /* Gobi 2000 has a single altsetting, older ones have two */ if (serial->interface->num_altsetting == 2) - intf = &serial->interface->altsetting[1]; + intf = usb_altnum_to_altsetting(serial->interface, 1); else if (serial->interface->num_altsetting > 2) goto done; - if (intf->desc.bNumEndpoints == 2 && + if (intf && intf->desc.bNumEndpoints == 2 && usb_endpoint_is_bulk_in(&intf->endpoint[0].desc) && usb_endpoint_is_bulk_out(&intf->endpoint[1].desc)) { dev_dbg(dev, "QDL port found\n"); diff --git a/drivers/usb/storage/isd200.c b/drivers/usb/storage/isd200.c index 89f5e33a6e6d..3c76336e43bb 100644 --- a/drivers/usb/storage/isd200.c +++ b/drivers/usb/storage/isd200.c @@ -1383,7 +1383,7 @@ static int isd200_scsi_to_ata(struct scsi_cmnd *srb, struct us_data *us, ATA_CMD_MEDIA_LOCK : ATA_CMD_MEDIA_UNLOCK; isd200_srb_set_bufflen(srb, 0); } else { - usb_stor_dbg(us, " Not removeable media, just report okay\n"); + usb_stor_dbg(us, " Not removable media, just report okay\n"); srb->result = SAM_STAT_GOOD; sendToTransport = 0; } diff --git a/drivers/usb/storage/scsiglue.c b/drivers/usb/storage/scsiglue.c index e5a971b83e3f..560efd1479ba 100644 --- a/drivers/usb/storage/scsiglue.c +++ b/drivers/usb/storage/scsiglue.c @@ -92,7 +92,7 @@ static int slave_alloc (struct scsi_device *sdev) static int slave_configure(struct scsi_device *sdev) { struct us_data *us = host_to_us(sdev->host); - struct device *dev = us->pusb_dev->bus->sysdev; + struct device *dev = sdev->host->dma_dev; /* * Many devices have trouble transferring more than 32KB at a time, diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 8183504e3abb..c8a577309e8f 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -279,17 +279,17 @@ static bool uas_evaluate_response_iu(struct response_iu *riu, struct scsi_cmnd * switch (response_code) { case RC_INCORRECT_LUN: - cmnd->result = DID_BAD_TARGET << 16; + set_host_byte(cmnd, DID_BAD_TARGET); break; case RC_TMF_SUCCEEDED: - cmnd->result = DID_OK << 16; + set_host_byte(cmnd, DID_OK); break; case RC_TMF_NOT_SUPPORTED: - cmnd->result = DID_TARGET_FAILURE << 16; + set_host_byte(cmnd, DID_TARGET_FAILURE); break; default: uas_log_cmd_state(cmnd, "response iu", response_code); - cmnd->result = DID_ERROR << 16; + set_host_byte(cmnd, DID_ERROR); break; } @@ -660,7 +660,7 @@ static int uas_queuecommand_lck(struct scsi_cmnd *cmnd, spin_lock_irqsave(&devinfo->lock, flags); if (devinfo->resetting) { - cmnd->result = DID_ERROR << 16; + set_host_byte(cmnd, DID_ERROR); cmnd->scsi_done(cmnd); goto zombie; } @@ -704,7 +704,7 @@ static int uas_queuecommand_lck(struct scsi_cmnd *cmnd, * of queueing, no matter how fatal the error */ if (err == -ENODEV) { - cmnd->result = DID_ERROR << 16; + set_host_byte(cmnd, DID_ERROR); cmnd->scsi_done(cmnd); goto zombie; } @@ -837,17 +837,24 @@ static int uas_slave_alloc(struct scsi_device *sdev) */ blk_queue_update_dma_alignment(sdev->request_queue, (512 - 1)); - if (devinfo->flags & US_FL_MAX_SECTORS_64) - blk_queue_max_hw_sectors(sdev->request_queue, 64); - else if (devinfo->flags & US_FL_MAX_SECTORS_240) - blk_queue_max_hw_sectors(sdev->request_queue, 240); - return 0; } static int uas_slave_configure(struct scsi_device *sdev) { struct uas_dev_info *devinfo = sdev->hostdata; + struct device *dev = sdev->host->dma_dev; + + if (devinfo->flags & US_FL_MAX_SECTORS_64) + blk_queue_max_hw_sectors(sdev->request_queue, 64); + else if (devinfo->flags & US_FL_MAX_SECTORS_240) + blk_queue_max_hw_sectors(sdev->request_queue, 240); + else if (devinfo->udev->speed >= USB_SPEED_SUPER) + blk_queue_max_hw_sectors(sdev->request_queue, 2048); + + blk_queue_max_hw_sectors(sdev->request_queue, + min_t(size_t, queue_max_hw_sectors(sdev->request_queue), + dma_max_mapping_size(dev) >> SECTOR_SHIFT)); if (devinfo->flags & US_FL_NO_REPORT_OPCODES) sdev->no_report_opcodes = 1; @@ -1033,7 +1040,7 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) shost->can_queue = devinfo->qdepth - 2; usb_set_intfdata(intf, shost); - result = scsi_add_host(shost, &intf->dev); + result = scsi_add_host_with_dma(shost, &intf->dev, udev->bus->sysdev); if (result) goto free_streams; diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c index 94a64729dc27..c2ef367cf257 100644 --- a/drivers/usb/storage/usb.c +++ b/drivers/usb/storage/usb.c @@ -1049,8 +1049,9 @@ int usb_stor_probe2(struct us_data *us) goto BadDevice; usb_autopm_get_interface_no_resume(us->pusb_intf); snprintf(us->scsi_name, sizeof(us->scsi_name), "usb-storage %s", - dev_name(&us->pusb_intf->dev)); - result = scsi_add_host(us_to_host(us), dev); + dev_name(dev)); + result = scsi_add_host_with_dma(us_to_host(us), dev, + us->pusb_dev->bus->sysdev); if (result) { dev_warn(dev, "Unable to add the scsi host\n"); diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index 559dd06117e7..6c5908a37ee8 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -73,6 +73,30 @@ config TYPEC_TPS6598X If you choose to build this driver as a dynamically linked module, the module will be called tps6598x.ko. +config TYPEC_STUSB160X + tristate "STMicroelectronics STUSB160x Type-C controller driver" + depends on I2C + depends on REGMAP_I2C + depends on USB_ROLE_SWITCH || !USB_ROLE_SWITCH + help + Say Y or M here if your system has STMicroelectronics STUSB160x + Type-C port controller. + + If you choose to build this driver as a dynamically linked module, the + module will be called stusb160x.ko. + +config TYPEC_QCOM_PMIC + tristate "Qualcomm PMIC USB Type-C driver" + depends on ARCH_QCOM || COMPILE_TEST + help + Driver for supporting role switch over the Qualcomm PMIC. This will + handle the USB Type-C role and orientation detection reported by the + QCOM PMIC if the PMIC has the capability to handle USB Type-C + detection. + + It will also enable the VBUS output to connected devices when a + DFP connection is made. + source "drivers/usb/typec/mux/Kconfig" source "drivers/usb/typec/altmodes/Kconfig" diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index 7753a5c3cd46..d03b48c4b864 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -6,4 +6,6 @@ obj-$(CONFIG_TYPEC_TCPM) += tcpm/ obj-$(CONFIG_TYPEC_UCSI) += ucsi/ obj-$(CONFIG_TYPEC_HD3SS3220) += hd3ss3220.o obj-$(CONFIG_TYPEC_TPS6598X) += tps6598x.o +obj-$(CONFIG_TYPEC_QCOM_PMIC) += qcom-pmic-typec.o +obj-$(CONFIG_TYPEC_STUSB160X) += stusb160x.o obj-$(CONFIG_TYPEC) += mux/ diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index 7b20073d7fc0..e62e5e3da01e 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -190,7 +190,7 @@ static void dp_altmode_work(struct work_struct *work) switch (dp->state) { case DP_STATE_ENTER: ret = typec_altmode_enter(dp->alt, NULL); - if (ret) + if (ret && ret != -EBUSY) dev_err(&dp->alt->dev, "failed to enter mode\n"); break; case DP_STATE_UPDATE: diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 02655694f200..35eec707cb51 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -1449,6 +1449,21 @@ void typec_set_pwr_opmode(struct typec_port *port, EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); /** + * typec_find_pwr_opmode - Get the typec power operation mode capability + * @name: power operation mode string + * + * This routine is used to find the typec_pwr_opmode by its string @name. + * + * Returns typec_pwr_opmode if success, otherwise negative error code. + */ +int typec_find_pwr_opmode(const char *name) +{ + return match_string(typec_pwr_opmodes, + ARRAY_SIZE(typec_pwr_opmodes), name); +} +EXPORT_SYMBOL_GPL(typec_find_pwr_opmode); + +/** * typec_find_orientation - Convert orientation string to enum typec_orientation * @name: Orientation string * diff --git a/drivers/usb/typec/hd3ss3220.c b/drivers/usb/typec/hd3ss3220.c index 323dfa8160ab..f633ec15b1a1 100644 --- a/drivers/usb/typec/hd3ss3220.c +++ b/drivers/usb/typec/hd3ss3220.c @@ -155,7 +155,7 @@ static int hd3ss3220_probe(struct i2c_client *client, { struct typec_capability typec_cap = { }; struct hd3ss3220 *hd3ss3220; - struct fwnode_handle *connector; + struct fwnode_handle *connector, *ep; int ret; unsigned int data; @@ -173,11 +173,21 @@ static int hd3ss3220_probe(struct i2c_client *client, hd3ss3220_set_source_pref(hd3ss3220, HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_DEFAULT); + /* For backward compatibility check the connector child node first */ connector = device_get_named_child_node(hd3ss3220->dev, "connector"); - if (!connector) - return -ENODEV; + if (connector) { + hd3ss3220->role_sw = fwnode_usb_role_switch_get(connector); + } else { + ep = fwnode_graph_get_next_endpoint(dev_fwnode(hd3ss3220->dev), NULL); + if (!ep) + return -ENODEV; + connector = fwnode_graph_get_remote_port_parent(ep); + fwnode_handle_put(ep); + if (!connector) + return -ENODEV; + hd3ss3220->role_sw = usb_role_switch_get(hd3ss3220->dev); + } - hd3ss3220->role_sw = fwnode_usb_role_switch_get(connector); if (IS_ERR(hd3ss3220->role_sw)) { ret = PTR_ERR(hd3ss3220->role_sw); goto err_put_fwnode; diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig index a4dbd11f8ee2..edead555835e 100644 --- a/drivers/usb/typec/mux/Kconfig +++ b/drivers/usb/typec/mux/Kconfig @@ -11,6 +11,7 @@ config TYPEC_MUX_PI3USB30532 config TYPEC_MUX_INTEL_PMC tristate "Intel PMC mux control" + depends on ACPI depends on INTEL_SCU_IPC select USB_ROLE_SWITCH help diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c index 676b525c2a66..d7f63b74c6b1 100644 --- a/drivers/usb/typec/mux/intel_pmc_mux.c +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -80,10 +80,48 @@ enum { #define PMC_USB_DP_HPD_LVL BIT(4) #define PMC_USB_DP_HPD_IRQ BIT(5) +/* + * Input Output Manager (IOM) PORT STATUS + */ +#define IOM_PORT_STATUS_OFFSET 0x560 + +#define IOM_PORT_STATUS_ACTIVITY_TYPE_MASK GENMASK(9, 6) +#define IOM_PORT_STATUS_ACTIVITY_TYPE_SHIFT 6 +#define IOM_PORT_STATUS_ACTIVITY_TYPE_USB 0x03 +/* activity type: Safe Mode */ +#define IOM_PORT_STATUS_ACTIVITY_TYPE_SAFE_MODE 0x04 +/* activity type: Display Port */ +#define IOM_PORT_STATUS_ACTIVITY_TYPE_DP 0x05 +/* activity type: Display Port Multi Function Device */ +#define IOM_PORT_STATUS_ACTIVITY_TYPE_DP_MFD 0x06 +/* activity type: Thunderbolt */ +#define IOM_PORT_STATUS_ACTIVITY_TYPE_TBT 0x07 +#define IOM_PORT_STATUS_ACTIVITY_TYPE_ALT_MODE_USB 0x0c +#define IOM_PORT_STATUS_ACTIVITY_TYPE_ALT_MODE_TBT_USB 0x0d +/* Upstream Facing Port Information */ +#define IOM_PORT_STATUS_UFP BIT(10) +/* Display Port Hot Plug Detect status */ +#define IOM_PORT_STATUS_DHPD_HPD_STATUS_MASK GENMASK(13, 12) +#define IOM_PORT_STATUS_DHPD_HPD_STATUS_SHIFT 12 +#define IOM_PORT_STATUS_DHPD_HPD_STATUS_ASSERT 0x01 +#define IOM_PORT_STATUS_DHPD_HPD_SOURCE_TBT BIT(14) +#define IOM_PORT_STATUS_CONNECTED BIT(31) + +#define IOM_PORT_ACTIVITY_IS(_status_, _type_) \ + ((((_status_) & IOM_PORT_STATUS_ACTIVITY_TYPE_MASK) >> \ + IOM_PORT_STATUS_ACTIVITY_TYPE_SHIFT) == \ + (IOM_PORT_STATUS_ACTIVITY_TYPE_##_type_)) + +#define IOM_PORT_HPD_ASSERTED(_status_) \ + ((((_status_) & IOM_PORT_STATUS_DHPD_HPD_STATUS_MASK) >> \ + IOM_PORT_STATUS_DHPD_HPD_STATUS_SHIFT) & \ + IOM_PORT_STATUS_DHPD_HPD_STATUS_ASSERT) + struct pmc_usb; struct pmc_usb_port { int num; + u32 iom_status; struct pmc_usb *pmc; struct typec_mux *typec_mux; struct typec_switch *typec_sw; @@ -104,8 +142,21 @@ struct pmc_usb { struct device *dev; struct intel_scu_ipc_dev *ipc; struct pmc_usb_port *port; + struct acpi_device *iom_adev; + void __iomem *iom_base; }; +static void update_port_status(struct pmc_usb_port *port) +{ + u8 port_num; + + /* SoC expects the USB Type-C port numbers to start with 0 */ + port_num = port->usb3_port - 1; + + port->iom_status = readl(port->pmc->iom_base + IOM_PORT_STATUS_OFFSET + + port_num * sizeof(u32)); +} + static int sbu_orientation(struct pmc_usb_port *port) { if (port->sbu_orientation) @@ -148,18 +199,17 @@ static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len) } static int -pmc_usb_mux_dp_hpd(struct pmc_usb_port *port, struct typec_mux_state *state) +pmc_usb_mux_dp_hpd(struct pmc_usb_port *port, struct typec_displayport_data *dp) { - struct typec_displayport_data *data = state->data; u8 msg[2] = { }; msg[0] = PMC_USB_DP_HPD; msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; - if (data->status & DP_STATUS_IRQ_HPD) + if (dp->status & DP_STATUS_IRQ_HPD) msg[1] = PMC_USB_DP_HPD_IRQ; - if (data->status & DP_STATUS_HPD_STATE) + if (dp->status & DP_STATUS_HPD_STATE) msg[1] |= PMC_USB_DP_HPD_LVL; return pmc_usb_command(port, msg, sizeof(msg)); @@ -172,8 +222,15 @@ pmc_usb_mux_dp(struct pmc_usb_port *port, struct typec_mux_state *state) struct altmode_req req = { }; int ret; - if (data->status & DP_STATUS_IRQ_HPD) - return pmc_usb_mux_dp_hpd(port, state); + if (IOM_PORT_ACTIVITY_IS(port->iom_status, DP) || + IOM_PORT_ACTIVITY_IS(port->iom_status, DP_MFD)) { + if (IOM_PORT_HPD_ASSERTED(port->iom_status) && + (!(data->status & DP_STATUS_IRQ_HPD) && + data->status & DP_STATUS_HPD_STATE)) + return 0; + + return pmc_usb_mux_dp_hpd(port, state->data); + } req.usage = PMC_USB_ALT_MODE; req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; @@ -189,8 +246,8 @@ pmc_usb_mux_dp(struct pmc_usb_port *port, struct typec_mux_state *state) if (ret) return ret; - if (data->status & DP_STATUS_HPD_STATE) - return pmc_usb_mux_dp_hpd(port, state); + if (data->status & (DP_STATUS_IRQ_HPD | DP_STATUS_HPD_STATE)) + return pmc_usb_mux_dp_hpd(port, state->data); return 0; } @@ -202,6 +259,10 @@ pmc_usb_mux_tbt(struct pmc_usb_port *port, struct typec_mux_state *state) u8 cable_speed = TBT_CABLE_SPEED(data->cable_mode); struct altmode_req req = { }; + if (IOM_PORT_ACTIVITY_IS(port->iom_status, TBT) || + IOM_PORT_ACTIVITY_IS(port->iom_status, ALT_MODE_TBT_USB)) + return 0; + req.usage = PMC_USB_ALT_MODE; req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; req.mode_type = PMC_USB_MODE_TYPE_TBT << PMC_USB_MODE_TYPE_SHIFT; @@ -233,6 +294,10 @@ pmc_usb_mux_usb4(struct pmc_usb_port *port, struct typec_mux_state *state) struct altmode_req req = { }; u8 cable_speed; + if (IOM_PORT_ACTIVITY_IS(port->iom_status, TBT) || + IOM_PORT_ACTIVITY_IS(port->iom_status, ALT_MODE_TBT_USB)) + return 0; + req.usage = PMC_USB_ALT_MODE; req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; req.mode_type = PMC_USB_MODE_TYPE_TBT << PMC_USB_MODE_TYPE_SHIFT; @@ -267,34 +332,61 @@ static int pmc_usb_mux_safe_state(struct pmc_usb_port *port) { u8 msg; + if (IOM_PORT_ACTIVITY_IS(port->iom_status, SAFE_MODE)) + return 0; + msg = PMC_USB_SAFE_MODE; msg |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; return pmc_usb_command(port, &msg, sizeof(msg)); } -static int pmc_usb_connect(struct pmc_usb_port *port) +static int pmc_usb_disconnect(struct pmc_usb_port *port) { + struct typec_displayport_data data = { }; u8 msg[2]; - msg[0] = PMC_USB_CONNECT; + if (!(port->iom_status & IOM_PORT_STATUS_CONNECTED)) + return 0; + + /* Clear DisplayPort HPD if it's still asserted. */ + if (IOM_PORT_HPD_ASSERTED(port->iom_status)) + pmc_usb_mux_dp_hpd(port, &data); + + msg[0] = PMC_USB_DISCONNECT; msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; msg[1] = port->usb2_port << PMC_USB_MSG_USB2_PORT_SHIFT; - msg[1] |= hsl_orientation(port) << PMC_USB_MSG_ORI_HSL_SHIFT; - msg[1] |= sbu_orientation(port) << PMC_USB_MSG_ORI_AUX_SHIFT; return pmc_usb_command(port, msg, sizeof(msg)); } -static int pmc_usb_disconnect(struct pmc_usb_port *port) +static int pmc_usb_connect(struct pmc_usb_port *port, enum usb_role role) { + u8 ufp = role == USB_ROLE_DEVICE ? 1 : 0; u8 msg[2]; + int ret; - msg[0] = PMC_USB_DISCONNECT; + if (port->orientation == TYPEC_ORIENTATION_NONE) + return -EINVAL; + + if (port->iom_status & IOM_PORT_STATUS_CONNECTED) { + if (port->role == role || port->role == USB_ROLE_NONE) + return 0; + + /* Role swap */ + ret = pmc_usb_disconnect(port); + if (ret) + return ret; + } + + msg[0] = PMC_USB_CONNECT; msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; msg[1] = port->usb2_port << PMC_USB_MSG_USB2_PORT_SHIFT; + msg[1] |= ufp << PMC_USB_MSG_UFP_SHIFT; + msg[1] |= hsl_orientation(port) << PMC_USB_MSG_ORI_HSL_SHIFT; + msg[1] |= sbu_orientation(port) << PMC_USB_MSG_ORI_AUX_SHIFT; return pmc_usb_command(port, msg, sizeof(msg)); } @@ -304,13 +396,15 @@ pmc_usb_mux_set(struct typec_mux *mux, struct typec_mux_state *state) { struct pmc_usb_port *port = typec_mux_get_drvdata(mux); + update_port_status(port); + if (port->orientation == TYPEC_ORIENTATION_NONE || port->role == USB_ROLE_NONE) return 0; if (state->mode == TYPEC_STATE_SAFE) return pmc_usb_mux_safe_state(port); if (state->mode == TYPEC_STATE_USB) - return pmc_usb_connect(port); + return pmc_usb_connect(port, port->role); if (state->alt) { switch (state->alt->svid) { @@ -325,7 +419,7 @@ pmc_usb_mux_set(struct typec_mux *mux, struct typec_mux_state *state) /* REVISIT: Try with usb3_port set to 0? */ break; case TYPEC_MODE_USB3: - return pmc_usb_connect(port); + return pmc_usb_connect(port, port->role); case TYPEC_MODE_USB4: return pmc_usb_mux_usb4(port, state); } @@ -339,38 +433,28 @@ static int pmc_usb_set_orientation(struct typec_switch *sw, { struct pmc_usb_port *port = typec_switch_get_drvdata(sw); - if (port->orientation == orientation) - return 0; + update_port_status(port); port->orientation = orientation; - if (port->role) { - if (orientation == TYPEC_ORIENTATION_NONE) - return pmc_usb_disconnect(port); - else - return pmc_usb_connect(port); - } - return 0; } static int pmc_usb_set_role(struct usb_role_switch *sw, enum usb_role role) { struct pmc_usb_port *port = usb_role_switch_get_drvdata(sw); + int ret; - if (port->role == role) - return 0; + update_port_status(port); - port->role = role; + if (role == USB_ROLE_NONE) + ret = pmc_usb_disconnect(port); + else + ret = pmc_usb_connect(port, role); - if (port->orientation) { - if (role == USB_ROLE_NONE) - return pmc_usb_disconnect(port); - else - return pmc_usb_connect(port); - } + port->role = role; - return 0; + return ret; } static int pmc_usb_register_port(struct pmc_usb *pmc, int index, @@ -444,6 +528,45 @@ err_unregister_switch: return ret; } +static int is_memory(struct acpi_resource *res, void *data) +{ + struct resource r; + + return !acpi_dev_resource_memory(res, &r); +} + +static int pmc_usb_probe_iom(struct pmc_usb *pmc) +{ + struct list_head resource_list; + struct resource_entry *rentry; + struct acpi_device *adev; + int ret; + + adev = acpi_dev_get_first_match_dev("INTC1072", NULL, -1); + if (!adev) + return -ENODEV; + + INIT_LIST_HEAD(&resource_list); + ret = acpi_dev_get_resources(adev, &resource_list, is_memory, NULL); + if (ret < 0) + return ret; + + rentry = list_first_entry_or_null(&resource_list, struct resource_entry, node); + if (rentry) + pmc->iom_base = devm_ioremap_resource(pmc->dev, rentry->res); + + acpi_dev_free_resource_list(&resource_list); + + if (!pmc->iom_base) { + put_device(&adev->dev); + return -ENOMEM; + } + + pmc->iom_adev = adev; + + return 0; +} + static int pmc_usb_probe(struct platform_device *pdev) { struct fwnode_handle *fwnode = NULL; @@ -458,6 +581,12 @@ static int pmc_usb_probe(struct platform_device *pdev) device_for_each_child_node(&pdev->dev, fwnode) pmc->num_ports++; + /* The IOM microcontroller has a limitation of max 4 ports. */ + if (pmc->num_ports > 4) { + dev_err(&pdev->dev, "driver limited to 4 ports\n"); + return -ERANGE; + } + pmc->port = devm_kcalloc(&pdev->dev, pmc->num_ports, sizeof(struct pmc_usb_port), GFP_KERNEL); if (!pmc->port) @@ -469,6 +598,10 @@ static int pmc_usb_probe(struct platform_device *pdev) pmc->dev = &pdev->dev; + ret = pmc_usb_probe_iom(pmc); + if (ret) + return ret; + /* * For every physical USB connector (USB2 and USB3 combo) there is a * child ACPI device node under the PMC mux ACPI device object. @@ -494,6 +627,8 @@ err_remove_ports: usb_role_switch_unregister(pmc->port[i].usb_sw); } + put_device(&pmc->iom_adev->dev); + return ret; } @@ -508,6 +643,8 @@ static int pmc_usb_remove(struct platform_device *pdev) usb_role_switch_unregister(pmc->port[i].usb_sw); } + put_device(&pmc->iom_adev->dev); + return 0; } diff --git a/drivers/usb/typec/qcom-pmic-typec.c b/drivers/usb/typec/qcom-pmic-typec.c new file mode 100644 index 000000000000..a0454a80c4a2 --- /dev/null +++ b/drivers/usb/typec/qcom-pmic-typec.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/usb/role.h> +#include <linux/usb/typec_mux.h> + +#define TYPEC_MISC_STATUS 0xb +#define CC_ATTACHED BIT(0) +#define CC_ORIENTATION BIT(1) +#define SNK_SRC_MODE BIT(6) +#define TYPEC_MODE_CFG 0x44 +#define TYPEC_DISABLE_CMD BIT(0) +#define EN_SNK_ONLY BIT(1) +#define EN_SRC_ONLY BIT(2) +#define TYPEC_VCONN_CONTROL 0x46 +#define VCONN_EN_SRC BIT(0) +#define VCONN_EN_VAL BIT(1) +#define TYPEC_EXIT_STATE_CFG 0x50 +#define SEL_SRC_UPPER_REF BIT(2) +#define TYPEC_INTR_EN_CFG_1 0x5e +#define TYPEC_INTR_EN_CFG_1_MASK GENMASK(7, 0) + +struct qcom_pmic_typec { + struct device *dev; + struct regmap *regmap; + u32 base; + + struct typec_port *port; + struct usb_role_switch *role_sw; + + struct regulator *vbus_reg; + bool vbus_enabled; +}; + +static void qcom_pmic_typec_enable_vbus_regulator(struct qcom_pmic_typec + *qcom_usb, bool enable) +{ + int ret; + + if (enable == qcom_usb->vbus_enabled) + return; + + if (enable) { + ret = regulator_enable(qcom_usb->vbus_reg); + if (ret) + return; + } else { + ret = regulator_disable(qcom_usb->vbus_reg); + if (ret) + return; + } + qcom_usb->vbus_enabled = enable; +} + +static void qcom_pmic_typec_check_connection(struct qcom_pmic_typec *qcom_usb) +{ + enum typec_orientation orientation; + enum usb_role role; + unsigned int stat; + bool enable_vbus; + + regmap_read(qcom_usb->regmap, qcom_usb->base + TYPEC_MISC_STATUS, + &stat); + + if (stat & CC_ATTACHED) { + orientation = (stat & CC_ORIENTATION) ? + TYPEC_ORIENTATION_REVERSE : + TYPEC_ORIENTATION_NORMAL; + typec_set_orientation(qcom_usb->port, orientation); + + role = (stat & SNK_SRC_MODE) ? USB_ROLE_HOST : USB_ROLE_DEVICE; + if (role == USB_ROLE_HOST) + enable_vbus = true; + else + enable_vbus = false; + } else { + role = USB_ROLE_NONE; + enable_vbus = false; + } + + qcom_pmic_typec_enable_vbus_regulator(qcom_usb, enable_vbus); + usb_role_switch_set_role(qcom_usb->role_sw, role); +} + +static irqreturn_t qcom_pmic_typec_interrupt(int irq, void *_qcom_usb) +{ + struct qcom_pmic_typec *qcom_usb = _qcom_usb; + + qcom_pmic_typec_check_connection(qcom_usb); + return IRQ_HANDLED; +} + +static void qcom_pmic_typec_typec_hw_init(struct qcom_pmic_typec *qcom_usb, + enum typec_port_type type) +{ + u8 mode = 0; + + regmap_update_bits(qcom_usb->regmap, + qcom_usb->base + TYPEC_INTR_EN_CFG_1, + TYPEC_INTR_EN_CFG_1_MASK, 0); + + if (type == TYPEC_PORT_SRC) + mode = EN_SRC_ONLY; + else if (type == TYPEC_PORT_SNK) + mode = EN_SNK_ONLY; + + regmap_update_bits(qcom_usb->regmap, qcom_usb->base + TYPEC_MODE_CFG, + EN_SNK_ONLY | EN_SRC_ONLY, mode); + + regmap_update_bits(qcom_usb->regmap, + qcom_usb->base + TYPEC_VCONN_CONTROL, + VCONN_EN_SRC | VCONN_EN_VAL, VCONN_EN_SRC); + regmap_update_bits(qcom_usb->regmap, + qcom_usb->base + TYPEC_EXIT_STATE_CFG, + SEL_SRC_UPPER_REF, SEL_SRC_UPPER_REF); +} + +static int qcom_pmic_typec_probe(struct platform_device *pdev) +{ + struct qcom_pmic_typec *qcom_usb; + struct device *dev = &pdev->dev; + struct fwnode_handle *fwnode; + struct typec_capability cap; + const char *buf; + int ret, irq, role; + u32 reg; + + ret = device_property_read_u32(dev, "reg", ®); + if (ret < 0) { + dev_err(dev, "missing base address\n"); + return ret; + } + + qcom_usb = devm_kzalloc(dev, sizeof(*qcom_usb), GFP_KERNEL); + if (!qcom_usb) + return -ENOMEM; + + qcom_usb->dev = dev; + qcom_usb->base = reg; + + qcom_usb->regmap = dev_get_regmap(dev->parent, NULL); + if (!qcom_usb->regmap) { + dev_err(dev, "Failed to get regmap\n"); + return -EINVAL; + } + + qcom_usb->vbus_reg = devm_regulator_get(qcom_usb->dev, "usb_vbus"); + if (IS_ERR(qcom_usb->vbus_reg)) + return PTR_ERR(qcom_usb->vbus_reg); + + fwnode = device_get_named_child_node(dev, "connector"); + if (!fwnode) + return -EINVAL; + + ret = fwnode_property_read_string(fwnode, "power-role", &buf); + if (!ret) { + role = typec_find_port_power_role(buf); + if (role < 0) + role = TYPEC_PORT_SNK; + } else { + role = TYPEC_PORT_SNK; + } + cap.type = role; + + ret = fwnode_property_read_string(fwnode, "data-role", &buf); + if (!ret) { + role = typec_find_port_data_role(buf); + if (role < 0) + role = TYPEC_PORT_UFP; + } else { + role = TYPEC_PORT_UFP; + } + cap.data = role; + + cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; + cap.fwnode = fwnode; + qcom_usb->port = typec_register_port(dev, &cap); + if (IS_ERR(qcom_usb->port)) { + ret = PTR_ERR(qcom_usb->port); + dev_err(dev, "Failed to register type c port %d\n", ret); + goto err_put_node; + } + fwnode_handle_put(fwnode); + + qcom_usb->role_sw = fwnode_usb_role_switch_get(dev_fwnode(qcom_usb->dev)); + if (IS_ERR(qcom_usb->role_sw)) { + if (PTR_ERR(qcom_usb->role_sw) != -EPROBE_DEFER) + dev_err(dev, "failed to get role switch\n"); + ret = PTR_ERR(qcom_usb->role_sw); + goto err_typec_port; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + goto err_usb_role_sw; + + ret = devm_request_threaded_irq(qcom_usb->dev, irq, NULL, + qcom_pmic_typec_interrupt, IRQF_ONESHOT, + "qcom-pmic-typec", qcom_usb); + if (ret) { + dev_err(&pdev->dev, "Could not request IRQ\n"); + goto err_usb_role_sw; + } + + platform_set_drvdata(pdev, qcom_usb); + qcom_pmic_typec_typec_hw_init(qcom_usb, cap.type); + qcom_pmic_typec_check_connection(qcom_usb); + + return 0; + +err_usb_role_sw: + usb_role_switch_put(qcom_usb->role_sw); +err_typec_port: + typec_unregister_port(qcom_usb->port); +err_put_node: + fwnode_handle_put(fwnode); + + return ret; +} + +static int qcom_pmic_typec_remove(struct platform_device *pdev) +{ + struct qcom_pmic_typec *qcom_usb = platform_get_drvdata(pdev); + + usb_role_switch_set_role(qcom_usb->role_sw, USB_ROLE_NONE); + qcom_pmic_typec_enable_vbus_regulator(qcom_usb, 0); + + typec_unregister_port(qcom_usb->port); + usb_role_switch_put(qcom_usb->role_sw); + + return 0; +} + +static const struct of_device_id qcom_pmic_typec_table[] = { + { .compatible = "qcom,pm8150b-usb-typec" }, + { } +}; +MODULE_DEVICE_TABLE(of, qcom_pmic_typec_table); + +static struct platform_driver qcom_pmic_typec = { + .driver = { + .name = "qcom,pmic-typec", + .of_match_table = qcom_pmic_typec_table, + }, + .probe = qcom_pmic_typec_probe, + .remove = qcom_pmic_typec_remove, +}; +module_platform_driver(qcom_pmic_typec); + +MODULE_DESCRIPTION("QCOM PMIC USB type C driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/typec/stusb160x.c b/drivers/usb/typec/stusb160x.c new file mode 100644 index 000000000000..ce0bd7b3ad88 --- /dev/null +++ b/drivers/usb/typec/stusb160x.c @@ -0,0 +1,875 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * STMicroelectronics STUSB160x Type-C controller family driver + * + * Copyright (C) 2020, STMicroelectronics + * Author(s): Amelie Delaunay <amelie.delaunay@st.com> + */ + +#include <linux/bitfield.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/usb/role.h> +#include <linux/usb/typec.h> + +#define STUSB160X_ALERT_STATUS 0x0B /* RC */ +#define STUSB160X_ALERT_STATUS_MASK_CTRL 0x0C /* RW */ +#define STUSB160X_CC_CONNECTION_STATUS_TRANS 0x0D /* RC */ +#define STUSB160X_CC_CONNECTION_STATUS 0x0E /* RO */ +#define STUSB160X_MONITORING_STATUS_TRANS 0x0F /* RC */ +#define STUSB160X_MONITORING_STATUS 0x10 /* RO */ +#define STUSB160X_CC_OPERATION_STATUS 0x11 /* RO */ +#define STUSB160X_HW_FAULT_STATUS_TRANS 0x12 /* RC */ +#define STUSB160X_HW_FAULT_STATUS 0x13 /* RO */ +#define STUSB160X_CC_CAPABILITY_CTRL 0x18 /* RW */ +#define STUSB160X_CC_VCONN_SWITCH_CTRL 0x1E /* RW */ +#define STUSB160X_VCONN_MONITORING_CTRL 0x20 /* RW */ +#define STUSB160X_VBUS_MONITORING_RANGE_CTRL 0x22 /* RW */ +#define STUSB160X_RESET_CTRL 0x23 /* RW */ +#define STUSB160X_VBUS_DISCHARGE_TIME_CTRL 0x25 /* RW */ +#define STUSB160X_VBUS_DISCHARGE_STATUS 0x26 /* RO */ +#define STUSB160X_VBUS_ENABLE_STATUS 0x27 /* RO */ +#define STUSB160X_CC_POWER_MODE_CTRL 0x28 /* RW */ +#define STUSB160X_VBUS_MONITORING_CTRL 0x2E /* RW */ +#define STUSB1600_REG_MAX 0x2F /* RO - Reserved */ + +/* STUSB160X_ALERT_STATUS/STUSB160X_ALERT_STATUS_MASK_CTRL bitfields */ +#define STUSB160X_HW_FAULT BIT(4) +#define STUSB160X_MONITORING BIT(5) +#define STUSB160X_CC_CONNECTION BIT(6) +#define STUSB160X_ALL_ALERTS GENMASK(6, 4) + +/* STUSB160X_CC_CONNECTION_STATUS_TRANS bitfields */ +#define STUSB160X_CC_ATTACH_TRANS BIT(0) + +/* STUSB160X_CC_CONNECTION_STATUS bitfields */ +#define STUSB160X_CC_ATTACH BIT(0) +#define STUSB160X_CC_VCONN_SUPPLY BIT(1) +#define STUSB160X_CC_DATA_ROLE(s) (!!((s) & BIT(2))) +#define STUSB160X_CC_POWER_ROLE(s) (!!((s) & BIT(3))) +#define STUSB160X_CC_ATTACHED_MODE GENMASK(7, 5) + +/* STUSB160X_MONITORING_STATUS_TRANS bitfields */ +#define STUSB160X_VCONN_PRESENCE_TRANS BIT(0) +#define STUSB160X_VBUS_PRESENCE_TRANS BIT(1) +#define STUSB160X_VBUS_VSAFE0V_TRANS BIT(2) +#define STUSB160X_VBUS_VALID_TRANS BIT(3) + +/* STUSB160X_MONITORING_STATUS bitfields */ +#define STUSB160X_VCONN_PRESENCE BIT(0) +#define STUSB160X_VBUS_PRESENCE BIT(1) +#define STUSB160X_VBUS_VSAFE0V BIT(2) +#define STUSB160X_VBUS_VALID BIT(3) + +/* STUSB160X_CC_OPERATION_STATUS bitfields */ +#define STUSB160X_TYPEC_FSM_STATE GENMASK(4, 0) +#define STUSB160X_SINK_POWER_STATE GENMASK(6, 5) +#define STUSB160X_CC_ATTACHED BIT(7) + +/* STUSB160X_HW_FAULT_STATUS_TRANS bitfields */ +#define STUSB160X_VCONN_SW_OVP_FAULT_TRANS BIT(0) +#define STUSB160X_VCONN_SW_OCP_FAULT_TRANS BIT(1) +#define STUSB160X_VCONN_SW_RVP_FAULT_TRANS BIT(2) +#define STUSB160X_VPU_VALID_TRANS BIT(4) +#define STUSB160X_VPU_OVP_FAULT_TRANS BIT(5) +#define STUSB160X_THERMAL_FAULT BIT(7) + +/* STUSB160X_HW_FAULT_STATUS bitfields */ +#define STUSB160X_VCONN_SW_OVP_FAULT_CC2 BIT(0) +#define STUSB160X_VCONN_SW_OVP_FAULT_CC1 BIT(1) +#define STUSB160X_VCONN_SW_OCP_FAULT_CC2 BIT(2) +#define STUSB160X_VCONN_SW_OCP_FAULT_CC1 BIT(3) +#define STUSB160X_VCONN_SW_RVP_FAULT_CC2 BIT(4) +#define STUSB160X_VCONN_SW_RVP_FAULT_CC1 BIT(5) +#define STUSB160X_VPU_VALID BIT(6) +#define STUSB160X_VPU_OVP_FAULT BIT(7) + +/* STUSB160X_CC_CAPABILITY_CTRL bitfields */ +#define STUSB160X_CC_VCONN_SUPPLY_EN BIT(0) +#define STUSB160X_CC_VCONN_DISCHARGE_EN BIT(4) +#define STUSB160X_CC_CURRENT_ADVERTISED GENMASK(7, 6) + +/* STUSB160X_VCONN_SWITCH_CTRL bitfields */ +#define STUSB160X_CC_VCONN_SWITCH_ILIM GENMASK(3, 0) + +/* STUSB160X_VCONN_MONITORING_CTRL bitfields */ +#define STUSB160X_VCONN_UVLO_THRESHOLD BIT(6) +#define STUSB160X_VCONN_MONITORING_EN BIT(7) + +/* STUSB160X_VBUS_MONITORING_RANGE_CTRL bitfields */ +#define STUSB160X_SHIFT_LOW_VBUS_LIMIT GENMASK(3, 0) +#define STUSB160X_SHIFT_HIGH_VBUS_LIMIT GENMASK(7, 4) + +/* STUSB160X_RESET_CTRL bitfields */ +#define STUSB160X_SW_RESET_EN BIT(0) + +/* STUSB160X_VBUS_DISCHARGE_TIME_CTRL bitfields */ +#define STUSBXX02_VBUS_DISCHARGE_TIME_TO_PDO GENMASK(3, 0) +#define STUSB160X_VBUS_DISCHARGE_TIME_TO_0V GENMASK(7, 4) + +/* STUSB160X_VBUS_DISCHARGE_STATUS bitfields */ +#define STUSB160X_VBUS_DISCHARGE_EN BIT(7) + +/* STUSB160X_VBUS_ENABLE_STATUS bitfields */ +#define STUSB160X_VBUS_SOURCE_EN BIT(0) +#define STUSB160X_VBUS_SINK_EN BIT(1) + +/* STUSB160X_CC_POWER_MODE_CTRL bitfields */ +#define STUSB160X_CC_POWER_MODE GENMASK(2, 0) + +/* STUSB160X_VBUS_MONITORING_CTRL bitfields */ +#define STUSB160X_VDD_UVLO_DISABLE BIT(0) +#define STUSB160X_VBUS_VSAFE0V_THRESHOLD GENMASK(2, 1) +#define STUSB160X_VBUS_RANGE_DISABLE BIT(4) +#define STUSB160X_VDD_OVLO_DISABLE BIT(6) + +enum stusb160x_pwr_mode { + SOURCE_WITH_ACCESSORY, + SINK_WITH_ACCESSORY, + SINK_WITHOUT_ACCESSORY, + DUAL_WITH_ACCESSORY, + DUAL_WITH_ACCESSORY_AND_TRY_SRC, + DUAL_WITH_ACCESSORY_AND_TRY_SNK, +}; + +enum stusb160x_attached_mode { + NO_DEVICE_ATTACHED, + SINK_ATTACHED, + SOURCE_ATTACHED, + DEBUG_ACCESSORY_ATTACHED, + AUDIO_ACCESSORY_ATTACHED, +}; + +struct stusb160x { + struct device *dev; + struct regmap *regmap; + struct regulator *vdd_supply; + struct regulator *vsys_supply; + struct regulator *vconn_supply; + struct regulator *main_supply; + + struct typec_port *port; + struct typec_capability capability; + struct typec_partner *partner; + + enum typec_port_type port_type; + enum typec_pwr_opmode pwr_opmode; + bool vbus_on; + + struct usb_role_switch *role_sw; +}; + +static bool stusb160x_reg_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STUSB160X_ALERT_STATUS_MASK_CTRL: + case STUSB160X_CC_CAPABILITY_CTRL: + case STUSB160X_CC_VCONN_SWITCH_CTRL: + case STUSB160X_VCONN_MONITORING_CTRL: + case STUSB160X_VBUS_MONITORING_RANGE_CTRL: + case STUSB160X_RESET_CTRL: + case STUSB160X_VBUS_DISCHARGE_TIME_CTRL: + case STUSB160X_CC_POWER_MODE_CTRL: + case STUSB160X_VBUS_MONITORING_CTRL: + return true; + default: + return false; + } +} + +static bool stusb160x_reg_readable(struct device *dev, unsigned int reg) +{ + if (reg <= 0x0A || + (reg >= 0x14 && reg <= 0x17) || + (reg >= 0x19 && reg <= 0x1D) || + (reg >= 0x29 && reg <= 0x2D) || + (reg == 0x1F || reg == 0x21 || reg == 0x24 || reg == 0x2F)) + return false; + else + return true; +} + +static bool stusb160x_reg_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STUSB160X_ALERT_STATUS: + case STUSB160X_CC_CONNECTION_STATUS_TRANS: + case STUSB160X_CC_CONNECTION_STATUS: + case STUSB160X_MONITORING_STATUS_TRANS: + case STUSB160X_MONITORING_STATUS: + case STUSB160X_CC_OPERATION_STATUS: + case STUSB160X_HW_FAULT_STATUS_TRANS: + case STUSB160X_HW_FAULT_STATUS: + case STUSB160X_VBUS_DISCHARGE_STATUS: + case STUSB160X_VBUS_ENABLE_STATUS: + return true; + default: + return false; + } +} + +static bool stusb160x_reg_precious(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STUSB160X_ALERT_STATUS: + case STUSB160X_CC_CONNECTION_STATUS_TRANS: + case STUSB160X_MONITORING_STATUS_TRANS: + case STUSB160X_HW_FAULT_STATUS_TRANS: + return true; + default: + return false; + } +} + +static const struct regmap_config stusb1600_regmap_config = { + .reg_bits = 8, + .reg_stride = 1, + .val_bits = 8, + .max_register = STUSB1600_REG_MAX, + .writeable_reg = stusb160x_reg_writeable, + .readable_reg = stusb160x_reg_readable, + .volatile_reg = stusb160x_reg_volatile, + .precious_reg = stusb160x_reg_precious, + .cache_type = REGCACHE_RBTREE, +}; + +static bool stusb160x_get_vconn(struct stusb160x *chip) +{ + u32 val; + int ret; + + ret = regmap_read(chip->regmap, STUSB160X_CC_CAPABILITY_CTRL, &val); + if (ret) { + dev_err(chip->dev, "Unable to get Vconn status: %d\n", ret); + return false; + } + + return !!FIELD_GET(STUSB160X_CC_VCONN_SUPPLY_EN, val); +} + +static int stusb160x_set_vconn(struct stusb160x *chip, bool on) +{ + int ret; + + /* Manage VCONN input supply */ + if (chip->vconn_supply) { + if (on) { + ret = regulator_enable(chip->vconn_supply); + if (ret) { + dev_err(chip->dev, + "failed to enable vconn supply: %d\n", + ret); + return ret; + } + } else { + regulator_disable(chip->vconn_supply); + } + } + + /* Manage VCONN monitoring and power path */ + ret = regmap_update_bits(chip->regmap, STUSB160X_VCONN_MONITORING_CTRL, + STUSB160X_VCONN_MONITORING_EN, + on ? STUSB160X_VCONN_MONITORING_EN : 0); + if (ret) + goto vconn_reg_disable; + + return 0; + +vconn_reg_disable: + if (chip->vconn_supply && on) + regulator_disable(chip->vconn_supply); + + return ret; +} + +static enum typec_pwr_opmode stusb160x_get_pwr_opmode(struct stusb160x *chip) +{ + u32 val; + int ret; + + ret = regmap_read(chip->regmap, STUSB160X_CC_CAPABILITY_CTRL, &val); + if (ret) { + dev_err(chip->dev, "Unable to get pwr opmode: %d\n", ret); + return TYPEC_PWR_MODE_USB; + } + + return FIELD_GET(STUSB160X_CC_CURRENT_ADVERTISED, val); +} + +static enum typec_accessory stusb160x_get_accessory(u32 status) +{ + enum stusb160x_attached_mode mode; + + mode = FIELD_GET(STUSB160X_CC_ATTACHED_MODE, status); + + switch (mode) { + case DEBUG_ACCESSORY_ATTACHED: + return TYPEC_ACCESSORY_DEBUG; + case AUDIO_ACCESSORY_ATTACHED: + return TYPEC_ACCESSORY_AUDIO; + default: + return TYPEC_ACCESSORY_NONE; + } +} + +static enum typec_role stusb160x_get_vconn_role(u32 status) +{ + if (FIELD_GET(STUSB160X_CC_VCONN_SUPPLY, status)) + return TYPEC_SOURCE; + + return TYPEC_SINK; +} + +static void stusb160x_set_data_role(struct stusb160x *chip, + enum typec_data_role data_role, + bool attached) +{ + enum usb_role usb_role = USB_ROLE_NONE; + + if (attached) { + if (data_role == TYPEC_HOST) + usb_role = USB_ROLE_HOST; + else + usb_role = USB_ROLE_DEVICE; + } + + usb_role_switch_set_role(chip->role_sw, usb_role); + typec_set_data_role(chip->port, data_role); +} + +static int stusb160x_attach(struct stusb160x *chip, u32 status) +{ + struct typec_partner_desc desc; + int ret; + + if ((STUSB160X_CC_POWER_ROLE(status) == TYPEC_SOURCE) && + chip->vdd_supply) { + ret = regulator_enable(chip->vdd_supply); + if (ret) { + dev_err(chip->dev, + "Failed to enable Vbus supply: %d\n", ret); + return ret; + } + chip->vbus_on = true; + } + + desc.usb_pd = false; + desc.accessory = stusb160x_get_accessory(status); + desc.identity = NULL; + + chip->partner = typec_register_partner(chip->port, &desc); + if (IS_ERR(chip->partner)) { + ret = PTR_ERR(chip->partner); + goto vbus_disable; + } + + typec_set_pwr_role(chip->port, STUSB160X_CC_POWER_ROLE(status)); + typec_set_pwr_opmode(chip->port, stusb160x_get_pwr_opmode(chip)); + typec_set_vconn_role(chip->port, stusb160x_get_vconn_role(status)); + stusb160x_set_data_role(chip, STUSB160X_CC_DATA_ROLE(status), true); + + return 0; + +vbus_disable: + if (chip->vbus_on) { + regulator_disable(chip->vdd_supply); + chip->vbus_on = false; + } + + return ret; +} + +static void stusb160x_detach(struct stusb160x *chip, u32 status) +{ + typec_unregister_partner(chip->partner); + chip->partner = NULL; + + typec_set_pwr_role(chip->port, STUSB160X_CC_POWER_ROLE(status)); + typec_set_pwr_opmode(chip->port, TYPEC_PWR_MODE_USB); + typec_set_vconn_role(chip->port, stusb160x_get_vconn_role(status)); + stusb160x_set_data_role(chip, STUSB160X_CC_DATA_ROLE(status), false); + + if (chip->vbus_on) { + regulator_disable(chip->vdd_supply); + chip->vbus_on = false; + } +} + +static irqreturn_t stusb160x_irq_handler(int irq, void *data) +{ + struct stusb160x *chip = data; + u32 pending, trans, status; + int ret; + + ret = regmap_read(chip->regmap, STUSB160X_ALERT_STATUS, &pending); + if (ret) + goto err; + + if (pending & STUSB160X_CC_CONNECTION) { + ret = regmap_read(chip->regmap, + STUSB160X_CC_CONNECTION_STATUS_TRANS, &trans); + if (ret) + goto err; + ret = regmap_read(chip->regmap, + STUSB160X_CC_CONNECTION_STATUS, &status); + if (ret) + goto err; + + if (trans & STUSB160X_CC_ATTACH_TRANS) { + if (status & STUSB160X_CC_ATTACH) { + ret = stusb160x_attach(chip, status); + if (ret) + goto err; + } else { + stusb160x_detach(chip, status); + } + } + } +err: + return IRQ_HANDLED; +} + +static int stusb160x_irq_init(struct stusb160x *chip, int irq) +{ + u32 status; + int ret; + + ret = regmap_read(chip->regmap, + STUSB160X_CC_CONNECTION_STATUS, &status); + if (ret) + return ret; + + if (status & STUSB160X_CC_ATTACH) { + ret = stusb160x_attach(chip, status); + if (ret) + dev_err(chip->dev, "attach failed: %d\n", ret); + } + + ret = devm_request_threaded_irq(chip->dev, irq, NULL, + stusb160x_irq_handler, IRQF_ONESHOT, + dev_name(chip->dev), chip); + if (ret) + goto partner_unregister; + + /* Unmask CC_CONNECTION events */ + ret = regmap_write_bits(chip->regmap, STUSB160X_ALERT_STATUS_MASK_CTRL, + STUSB160X_CC_CONNECTION, 0); + if (ret) + goto partner_unregister; + + return 0; + +partner_unregister: + if (chip->partner) { + typec_unregister_partner(chip->partner); + chip->partner = NULL; + } + + return ret; +} + +static int stusb160x_chip_init(struct stusb160x *chip) +{ + u32 val; + int ret; + + /* Change the default Type-C power mode */ + if (chip->port_type == TYPEC_PORT_SRC) + ret = regmap_update_bits(chip->regmap, + STUSB160X_CC_POWER_MODE_CTRL, + STUSB160X_CC_POWER_MODE, + SOURCE_WITH_ACCESSORY); + else if (chip->port_type == TYPEC_PORT_SNK) + ret = regmap_update_bits(chip->regmap, + STUSB160X_CC_POWER_MODE_CTRL, + STUSB160X_CC_POWER_MODE, + SINK_WITH_ACCESSORY); + else /* (chip->port_type == TYPEC_PORT_DRP) */ + ret = regmap_update_bits(chip->regmap, + STUSB160X_CC_POWER_MODE_CTRL, + STUSB160X_CC_POWER_MODE, + DUAL_WITH_ACCESSORY); + if (ret) + return ret; + + if (chip->port_type == TYPEC_PORT_SNK) + goto skip_src; + + /* Change the default Type-C Source power operation mode capability */ + ret = regmap_update_bits(chip->regmap, STUSB160X_CC_CAPABILITY_CTRL, + STUSB160X_CC_CURRENT_ADVERTISED, + FIELD_PREP(STUSB160X_CC_CURRENT_ADVERTISED, + chip->pwr_opmode)); + if (ret) + return ret; + + /* Manage Type-C Source Vconn supply */ + if (stusb160x_get_vconn(chip)) { + ret = stusb160x_set_vconn(chip, true); + if (ret) + return ret; + } + +skip_src: + /* Mask all events interrupts - to be unmasked with interrupt support */ + ret = regmap_update_bits(chip->regmap, STUSB160X_ALERT_STATUS_MASK_CTRL, + STUSB160X_ALL_ALERTS, STUSB160X_ALL_ALERTS); + if (ret) + return ret; + + /* Read status at least once to clear any stale interrupts */ + regmap_read(chip->regmap, STUSB160X_ALERT_STATUS, &val); + regmap_read(chip->regmap, STUSB160X_CC_CONNECTION_STATUS_TRANS, &val); + regmap_read(chip->regmap, STUSB160X_MONITORING_STATUS_TRANS, &val); + regmap_read(chip->regmap, STUSB160X_HW_FAULT_STATUS_TRANS, &val); + + return 0; +} + +static int stusb160x_get_fw_caps(struct stusb160x *chip, + struct fwnode_handle *fwnode) +{ + const char *cap_str; + int ret; + + chip->capability.fwnode = fwnode; + + /* + * Supported port type can be configured through device tree + * else it is read from chip registers in stusb160x_get_caps. + */ + ret = fwnode_property_read_string(fwnode, "power-role", &cap_str); + if (!ret) { + chip->port_type = typec_find_port_power_role(cap_str); + if (chip->port_type < 0) { + ret = chip->port_type; + return ret; + } + } + chip->capability.type = chip->port_type; + + /* Skip DRP/Source capabilities in case of Sink only */ + if (chip->port_type == TYPEC_PORT_SNK) + return 0; + + if (chip->port_type == TYPEC_PORT_DRP) + chip->capability.prefer_role = TYPEC_SINK; + + /* + * Supported power operation mode can be configured through device tree + * else it is read from chip registers in stusb160x_get_caps. + */ + ret = fwnode_property_read_string(fwnode, "power-opmode", &cap_str); + if (!ret) { + chip->pwr_opmode = typec_find_pwr_opmode(cap_str); + /* Power delivery not yet supported */ + if (chip->pwr_opmode < 0 || + chip->pwr_opmode == TYPEC_PWR_MODE_PD) { + ret = chip->pwr_opmode < 0 ? chip->pwr_opmode : -EINVAL; + dev_err(chip->dev, "bad power operation mode: %d\n", + chip->pwr_opmode); + return ret; + } + } + + return 0; +} + +static int stusb160x_get_caps(struct stusb160x *chip) +{ + enum typec_port_type *type = &chip->capability.type; + enum typec_port_data *data = &chip->capability.data; + enum typec_accessory *accessory = chip->capability.accessory; + u32 val; + int ret; + + chip->capability.revision = USB_TYPEC_REV_1_2; + + ret = regmap_read(chip->regmap, STUSB160X_CC_POWER_MODE_CTRL, &val); + if (ret) + return ret; + + switch (FIELD_GET(STUSB160X_CC_POWER_MODE, val)) { + case SOURCE_WITH_ACCESSORY: + *type = TYPEC_PORT_SRC; + *data = TYPEC_PORT_DFP; + *accessory++ = TYPEC_ACCESSORY_AUDIO; + *accessory++ = TYPEC_ACCESSORY_DEBUG; + break; + case SINK_WITH_ACCESSORY: + *type = TYPEC_PORT_SNK; + *data = TYPEC_PORT_UFP; + *accessory++ = TYPEC_ACCESSORY_AUDIO; + *accessory++ = TYPEC_ACCESSORY_DEBUG; + break; + case SINK_WITHOUT_ACCESSORY: + *type = TYPEC_PORT_SNK; + *data = TYPEC_PORT_UFP; + break; + case DUAL_WITH_ACCESSORY: + case DUAL_WITH_ACCESSORY_AND_TRY_SRC: + case DUAL_WITH_ACCESSORY_AND_TRY_SNK: + *type = TYPEC_PORT_DRP; + *data = TYPEC_PORT_DRD; + *accessory++ = TYPEC_ACCESSORY_AUDIO; + *accessory++ = TYPEC_ACCESSORY_DEBUG; + break; + default: + return -EINVAL; + } + + chip->port_type = *type; + chip->pwr_opmode = stusb160x_get_pwr_opmode(chip); + + return 0; +} + +static const struct of_device_id stusb160x_of_match[] = { + { .compatible = "st,stusb1600", .data = &stusb1600_regmap_config}, + {}, +}; + +static int stusb160x_probe(struct i2c_client *client) +{ + struct stusb160x *chip; + const struct of_device_id *match; + struct regmap_config *regmap_config; + struct fwnode_handle *fwnode; + int ret; + + chip = devm_kzalloc(&client->dev, sizeof(struct stusb160x), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + i2c_set_clientdata(client, chip); + + match = i2c_of_match_device(stusb160x_of_match, client); + regmap_config = (struct regmap_config *)match->data; + chip->regmap = devm_regmap_init_i2c(client, regmap_config); + if (IS_ERR(chip->regmap)) { + ret = PTR_ERR(chip->regmap); + dev_err(&client->dev, + "Failed to allocate register map:%d\n", ret); + return ret; + } + + chip->dev = &client->dev; + + chip->vsys_supply = devm_regulator_get_optional(chip->dev, "vsys"); + if (IS_ERR(chip->vsys_supply)) { + ret = PTR_ERR(chip->vsys_supply); + if (ret != -ENODEV) + return ret; + chip->vsys_supply = NULL; + } + + chip->vdd_supply = devm_regulator_get_optional(chip->dev, "vdd"); + if (IS_ERR(chip->vdd_supply)) { + ret = PTR_ERR(chip->vdd_supply); + if (ret != -ENODEV) + return ret; + chip->vdd_supply = NULL; + } + + chip->vconn_supply = devm_regulator_get_optional(chip->dev, "vconn"); + if (IS_ERR(chip->vconn_supply)) { + ret = PTR_ERR(chip->vconn_supply); + if (ret != -ENODEV) + return ret; + chip->vconn_supply = NULL; + } + + fwnode = device_get_named_child_node(chip->dev, "connector"); + if (IS_ERR(fwnode)) + return PTR_ERR(fwnode); + + /* + * When both VDD and VSYS power supplies are present, the low power + * supply VSYS is selected when VSYS voltage is above 3.1 V. + * Otherwise VDD is selected. + */ + if (chip->vdd_supply && + (!chip->vsys_supply || + (regulator_get_voltage(chip->vsys_supply) <= 3100000))) + chip->main_supply = chip->vdd_supply; + else + chip->main_supply = chip->vsys_supply; + + if (chip->main_supply) { + ret = regulator_enable(chip->main_supply); + if (ret) { + dev_err(chip->dev, + "Failed to enable main supply: %d\n", ret); + goto fwnode_put; + } + } + + /* Get configuration from chip */ + ret = stusb160x_get_caps(chip); + if (ret) { + dev_err(chip->dev, "Failed to get port caps: %d\n", ret); + goto main_reg_disable; + } + + /* Get optional re-configuration from device tree */ + ret = stusb160x_get_fw_caps(chip, fwnode); + if (ret) { + dev_err(chip->dev, "Failed to get connector caps: %d\n", ret); + goto main_reg_disable; + } + + ret = stusb160x_chip_init(chip); + if (ret) { + dev_err(chip->dev, "Failed to init port: %d\n", ret); + goto main_reg_disable; + } + + chip->port = typec_register_port(chip->dev, &chip->capability); + if (!chip->port) { + ret = -ENODEV; + goto all_reg_disable; + } + + /* + * Default power operation mode initialization: will be updated upon + * attach/detach interrupt + */ + typec_set_pwr_opmode(chip->port, chip->pwr_opmode); + + if (client->irq) { + ret = stusb160x_irq_init(chip, client->irq); + if (ret) + goto port_unregister; + + chip->role_sw = fwnode_usb_role_switch_get(fwnode); + if (IS_ERR(chip->role_sw)) { + ret = PTR_ERR(chip->role_sw); + if (ret != -EPROBE_DEFER) + dev_err(chip->dev, + "Failed to get usb role switch: %d\n", + ret); + goto port_unregister; + } + } else { + /* + * If Source or Dual power role, need to enable VDD supply + * providing Vbus if present. In case of interrupt support, + * VDD supply will be dynamically managed upon attach/detach + * interrupt. + */ + if (chip->port_type != TYPEC_PORT_SNK && chip->vdd_supply) { + ret = regulator_enable(chip->vdd_supply); + if (ret) { + dev_err(chip->dev, + "Failed to enable VDD supply: %d\n", + ret); + goto port_unregister; + } + chip->vbus_on = true; + } + } + + fwnode_handle_put(fwnode); + + return 0; + +port_unregister: + typec_unregister_port(chip->port); +all_reg_disable: + if (stusb160x_get_vconn(chip)) + stusb160x_set_vconn(chip, false); +main_reg_disable: + if (chip->main_supply) + regulator_disable(chip->main_supply); +fwnode_put: + fwnode_handle_put(fwnode); + + return ret; +} + +static int stusb160x_remove(struct i2c_client *client) +{ + struct stusb160x *chip = i2c_get_clientdata(client); + + if (chip->partner) { + typec_unregister_partner(chip->partner); + chip->partner = NULL; + } + + if (chip->vbus_on) + regulator_disable(chip->vdd_supply); + + if (chip->role_sw) + usb_role_switch_put(chip->role_sw); + + typec_unregister_port(chip->port); + + if (stusb160x_get_vconn(chip)) + stusb160x_set_vconn(chip, false); + + if (chip->main_supply) + regulator_disable(chip->main_supply); + + return 0; +} + +static int __maybe_unused stusb160x_suspend(struct device *dev) +{ + struct stusb160x *chip = dev_get_drvdata(dev); + + /* Mask interrupts */ + return regmap_update_bits(chip->regmap, + STUSB160X_ALERT_STATUS_MASK_CTRL, + STUSB160X_ALL_ALERTS, STUSB160X_ALL_ALERTS); +} + +static int __maybe_unused stusb160x_resume(struct device *dev) +{ + struct stusb160x *chip = dev_get_drvdata(dev); + u32 status; + int ret; + + ret = regcache_sync(chip->regmap); + if (ret) + return ret; + + /* Check if attach/detach occurred during low power */ + ret = regmap_read(chip->regmap, + STUSB160X_CC_CONNECTION_STATUS, &status); + if (ret) + return ret; + + if (chip->partner && !(status & STUSB160X_CC_ATTACH)) + stusb160x_detach(chip, status); + + if (!chip->partner && (status & STUSB160X_CC_ATTACH)) { + ret = stusb160x_attach(chip, status); + if (ret) + dev_err(chip->dev, "attach failed: %d\n", ret); + } + + /* Unmask interrupts */ + return regmap_write_bits(chip->regmap, STUSB160X_ALERT_STATUS_MASK_CTRL, + STUSB160X_CC_CONNECTION, 0); +} + +static SIMPLE_DEV_PM_OPS(stusb160x_pm_ops, stusb160x_suspend, stusb160x_resume); + +static struct i2c_driver stusb160x_driver = { + .driver = { + .name = "stusb160x", + .pm = &stusb160x_pm_ops, + .of_match_table = stusb160x_of_match, + }, + .probe_new = stusb160x_probe, + .remove = stusb160x_remove, +}; +module_i2c_driver(stusb160x_driver); + +MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics STUSB160x Type-C controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/typec/tcpm/Kconfig b/drivers/usb/typec/tcpm/Kconfig index fa3f39336246..557f392fe24d 100644 --- a/drivers/usb/typec/tcpm/Kconfig +++ b/drivers/usb/typec/tcpm/Kconfig @@ -27,6 +27,20 @@ config TYPEC_RT1711H Type-C Port Controller Manager to provide USB PD and USB Type-C functionalities. +config TYPEC_MT6360 + tristate "Mediatek MT6360 Type-C driver" + depends on MFD_MT6360 + help + Mediatek MT6360 is a multi-functional IC that includes + USB Type-C. It works with Type-C Port Controller Manager + to provide USB PD and USB Type-C functionalities. + +config TYPEC_TCPCI_MAXIM + tristate "Maxim TCPCI based Type-C chip driver" + help + MAXIM TCPCI based Type-C/PD chip driver. Works with + with Type-C Port Controller Manager. + endif # TYPEC_TCPCI config TYPEC_FUSB302 diff --git a/drivers/usb/typec/tcpm/Makefile b/drivers/usb/typec/tcpm/Makefile index a5ff6c8eb892..7d499f3569fd 100644 --- a/drivers/usb/typec/tcpm/Makefile +++ b/drivers/usb/typec/tcpm/Makefile @@ -1,7 +1,9 @@ # SPDX-License-Identifier: GPL-2.0 -obj-$(CONFIG_TYPEC_TCPM) += tcpm.o -obj-$(CONFIG_TYPEC_FUSB302) += fusb302.o -obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o -typec_wcove-y := wcove.o -obj-$(CONFIG_TYPEC_TCPCI) += tcpci.o -obj-$(CONFIG_TYPEC_RT1711H) += tcpci_rt1711h.o +obj-$(CONFIG_TYPEC_TCPM) += tcpm.o +obj-$(CONFIG_TYPEC_FUSB302) += fusb302.o +obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o +typec_wcove-y := wcove.o +obj-$(CONFIG_TYPEC_TCPCI) += tcpci.o +obj-$(CONFIG_TYPEC_RT1711H) += tcpci_rt1711h.o +obj-$(CONFIG_TYPEC_MT6360) += tcpci_mt6360.o +obj-$(CONFIG_TYPEC_TCPCI_MAXIM) += tcpci_maxim.o diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index bd80e03b2b6f..f9f0af64da5f 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -38,6 +38,12 @@ struct tcpci_chip { struct tcpci_data data; }; +struct tcpm_port *tcpci_get_tcpm_port(struct tcpci *tcpci) +{ + return tcpci->port; +} +EXPORT_SYMBOL_GPL(tcpci_get_tcpm_port); + static inline struct tcpci *tcpc_to_tcpci(struct tcpc_dev *tcpc) { return container_of(tcpc, struct tcpci, tcpc); @@ -191,12 +197,47 @@ static int tcpci_set_polarity(struct tcpc_dev *tcpc, struct tcpci *tcpci = tcpc_to_tcpci(tcpc); unsigned int reg; int ret; + enum typec_cc_status cc1, cc2; - /* Keep the disconnect cc line open */ + /* Obtain Rp setting from role control */ ret = regmap_read(tcpci->regmap, TCPC_ROLE_CTRL, ®); if (ret < 0) return ret; + ret = tcpci_get_cc(tcpc, &cc1, &cc2); + if (ret < 0) + return ret; + + /* + * When port has drp toggling enabled, ROLE_CONTROL would only have the initial + * terminations for the toggling and does not indicate the final cc + * terminations when ConnectionResult is 0 i.e. drp toggling stops and + * the connection is resolbed. Infer port role from TCPC_CC_STATUS based on the + * terminations seen. The port role is then used to set the cc terminations. + */ + if (reg & TCPC_ROLE_CTRL_DRP) { + /* Disable DRP for the OPEN setting to take effect */ + reg = reg & ~TCPC_ROLE_CTRL_DRP; + + if (polarity == TYPEC_POLARITY_CC2) { + reg &= ~(TCPC_ROLE_CTRL_CC2_MASK << TCPC_ROLE_CTRL_CC2_SHIFT); + /* Local port is source */ + if (cc2 == TYPEC_CC_RD) + /* Role control would have the Rp setting when DRP was enabled */ + reg |= TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT; + else + reg |= TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT; + } else { + reg &= ~(TCPC_ROLE_CTRL_CC1_MASK << TCPC_ROLE_CTRL_CC1_SHIFT); + /* Local port is source */ + if (cc1 == TYPEC_CC_RD) + /* Role control would have the Rp setting when DRP was enabled */ + reg |= TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT; + else + reg |= TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT; + } + } + if (polarity == TYPEC_POLARITY_CC2) reg |= TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT; else @@ -227,6 +268,22 @@ static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable) enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0); } +static int tcpci_enable_frs(struct tcpc_dev *dev, bool enable) +{ + struct tcpci *tcpci = tcpc_to_tcpci(dev); + int ret; + + /* To prevent disconnect during FRS, set disconnect threshold to 3.5V */ + ret = tcpci_write16(tcpci, TCPC_VBUS_SINK_DISCONNECT_THRESH, enable ? 0 : 0x8c); + if (ret < 0) + return ret; + + ret = regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, TCPC_FAST_ROLE_SWAP_EN, enable ? + TCPC_FAST_ROLE_SWAP_EN : 0); + + return ret; +} + static int tcpci_set_bist_data(struct tcpc_dev *tcpc, bool enable) { struct tcpci *tcpci = tcpc_to_tcpci(tcpc); @@ -287,6 +344,13 @@ static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink) struct tcpci *tcpci = tcpc_to_tcpci(tcpc); int ret; + if (tcpci->data->set_vbus) { + ret = tcpci->data->set_vbus(tcpci, tcpci->data, source, sink); + /* Bypass when ret > 0 */ + if (ret != 0) + return ret < 0 ? ret : 0; + } + /* Disable both source and sink first before enabling anything */ if (!source) { @@ -330,23 +394,47 @@ static int tcpci_pd_transmit(struct tcpc_dev *tcpc, int ret; cnt = msg ? pd_header_cnt(header) * 4 : 0; - ret = regmap_write(tcpci->regmap, TCPC_TX_BYTE_CNT, cnt + 2); - if (ret < 0) - return ret; + /** + * TCPCI spec forbids direct access of TCPC_TX_DATA. + * But, since some of the chipsets offer this capability, + * it's fair to support both. + */ + if (tcpci->data->TX_BUF_BYTE_x_hidden) { + u8 buf[TCPC_TRANSMIT_BUFFER_MAX_LEN] = {0,}; + u8 pos = 0; - ret = tcpci_write16(tcpci, TCPC_TX_HDR, header); - if (ret < 0) - return ret; + /* Payload + header + TCPC_TX_BYTE_CNT */ + buf[pos++] = cnt + 2; + + if (msg) + memcpy(&buf[pos], &msg->header, sizeof(msg->header)); - if (cnt > 0) { - ret = regmap_raw_write(tcpci->regmap, TCPC_TX_DATA, - &msg->payload, cnt); + pos += sizeof(header); + + if (cnt > 0) + memcpy(&buf[pos], msg->payload, cnt); + + pos += cnt; + ret = regmap_raw_write(tcpci->regmap, TCPC_TX_BYTE_CNT, buf, pos); + if (ret < 0) + return ret; + } else { + ret = regmap_write(tcpci->regmap, TCPC_TX_BYTE_CNT, cnt + 2); if (ret < 0) return ret; + + ret = tcpci_write16(tcpci, TCPC_TX_HDR, header); + if (ret < 0) + return ret; + + if (cnt > 0) { + ret = regmap_raw_write(tcpci->regmap, TCPC_TX_DATA, &msg->payload, cnt); + if (ret < 0) + return ret; + } } - reg = (PD_RETRY_COUNT << TCPC_TRANSMIT_RETRY_SHIFT) | - (type << TCPC_TRANSMIT_TYPE_SHIFT); + reg = (PD_RETRY_COUNT << TCPC_TRANSMIT_RETRY_SHIFT) | (type << TCPC_TRANSMIT_TYPE_SHIFT); ret = regmap_write(tcpci->regmap, TCPC_TRANSMIT, reg); if (ret < 0) return ret; @@ -539,6 +627,7 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) tcpci->tcpc.set_roles = tcpci_set_roles; tcpci->tcpc.pd_transmit = tcpci_pd_transmit; tcpci->tcpc.set_bist_data = tcpci_set_bist_data; + tcpci->tcpc.enable_frs = tcpci_enable_frs; err = tcpci_parse_config(tcpci); if (err < 0) diff --git a/drivers/usb/typec/tcpm/tcpci.h b/drivers/usb/typec/tcpm/tcpci.h index 11c36d086c86..5ef07a56d67a 100644 --- a/drivers/usb/typec/tcpm/tcpci.h +++ b/drivers/usb/typec/tcpm/tcpci.h @@ -16,6 +16,8 @@ #define TCPC_PD_INT_REV 0xa #define TCPC_ALERT 0x10 +#define TCPC_ALERT_EXTND BIT(14) +#define TCPC_ALERT_EXTENDED_STATUS BIT(13) #define TCPC_ALERT_VBUS_DISCNCT BIT(11) #define TCPC_ALERT_RX_BUF_OVF BIT(10) #define TCPC_ALERT_FAULT BIT(9) @@ -32,6 +34,13 @@ #define TCPC_ALERT_MASK 0x12 #define TCPC_POWER_STATUS_MASK 0x14 #define TCPC_FAULT_STATUS_MASK 0x15 + +#define TCPC_EXTENDED_STATUS_MASK 0x16 +#define TCPC_EXTENDED_STATUS_MASK_VSAFE0V BIT(0) + +#define TCPC_ALERT_EXTENDED_MASK 0x17 +#define TCPC_SINK_FAST_ROLE_SWAP BIT(0) + #define TCPC_CONFIG_STD_OUTPUT 0x18 #define TCPC_TCPC_CTRL 0x19 @@ -58,6 +67,7 @@ #define TCPC_POWER_CTRL 0x1c #define TCPC_POWER_CTRL_VCONN_ENABLE BIT(0) +#define TCPC_FAST_ROLE_SWAP_EN BIT(7) #define TCPC_CC_STATUS 0x1d #define TCPC_CC_STATUS_TOGGLING BIT(5) @@ -69,11 +79,14 @@ #define TCPC_POWER_STATUS 0x1e #define TCPC_POWER_STATUS_UNINIT BIT(6) +#define TCPC_POWER_STATUS_SOURCING_VBUS BIT(4) #define TCPC_POWER_STATUS_VBUS_DET BIT(3) #define TCPC_POWER_STATUS_VBUS_PRES BIT(2) #define TCPC_FAULT_STATUS 0x1f +#define TCPC_ALERT_EXTENDED 0x21 + #define TCPC_COMMAND 0x23 #define TCPC_CMD_WAKE_I2C 0x11 #define TCPC_CMD_DISABLE_VBUS_DETECT 0x22 @@ -104,6 +117,7 @@ #define TCPC_RX_BYTE_CNT 0x30 #define TCPC_RX_BUF_FRAME_TYPE 0x31 +#define TCPC_RX_BUF_FRAME_TYPE_SOP 0 #define TCPC_RX_HDR 0x32 #define TCPC_RX_DATA 0x34 /* through 0x4f */ @@ -123,18 +137,29 @@ #define TCPC_VBUS_VOLTAGE_ALARM_HI_CFG 0x76 #define TCPC_VBUS_VOLTAGE_ALARM_LO_CFG 0x78 +/* I2C_WRITE_BYTE_COUNT + 1 when TX_BUF_BYTE_x is only accessible I2C_WRITE_BYTE_COUNT */ +#define TCPC_TRANSMIT_BUFFER_MAX_LEN 31 + +/* + * @TX_BUF_BYTE_x_hidden + * optional; Set when TX_BUF_BYTE_x can only be accessed through I2C_WRITE_BYTE_COUNT. + */ struct tcpci; struct tcpci_data { struct regmap *regmap; + unsigned char TX_BUF_BYTE_x_hidden:1; int (*init)(struct tcpci *tcpci, struct tcpci_data *data); int (*set_vconn)(struct tcpci *tcpci, struct tcpci_data *data, bool enable); int (*start_drp_toggling)(struct tcpci *tcpci, struct tcpci_data *data, enum typec_cc_status cc); + int (*set_vbus)(struct tcpci *tcpci, struct tcpci_data *data, bool source, bool sink); }; struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data); void tcpci_unregister_port(struct tcpci *tcpci); irqreturn_t tcpci_irq(struct tcpci *tcpci); +struct tcpm_port; +struct tcpm_port *tcpci_get_tcpm_port(struct tcpci *tcpci); #endif /* __LINUX_USB_TCPCI_H */ diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.c b/drivers/usb/typec/tcpm/tcpci_maxim.c new file mode 100644 index 000000000000..723d7dd38f75 --- /dev/null +++ b/drivers/usb/typec/tcpm/tcpci_maxim.c @@ -0,0 +1,503 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020, Google LLC + * + * MAXIM TCPCI based TCPC driver + */ + +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/regmap.h> +#include <linux/usb/pd.h> +#include <linux/usb/tcpm.h> +#include <linux/usb/typec.h> + +#include "tcpci.h" + +#define PD_ACTIVITY_TIMEOUT_MS 10000 + +#define TCPC_VENDOR_ALERT 0x80 + +#define TCPC_RECEIVE_BUFFER_COUNT_OFFSET 0 +#define TCPC_RECEIVE_BUFFER_FRAME_TYPE_OFFSET 1 +#define TCPC_RECEIVE_BUFFER_RX_BYTE_BUF_OFFSET 2 + +/* + * LongMessage not supported, hence 32 bytes for buf to be read from RECEIVE_BUFFER. + * DEVICE_CAPABILITIES_2.LongMessage = 0, the value in READABLE_BYTE_COUNT reg shall be + * less than or equal to 31. Since, RECEIVE_BUFFER len = 31 + 1(READABLE_BYTE_COUNT). + */ +#define TCPC_RECEIVE_BUFFER_LEN 32 + +#define MAX_BUCK_BOOST_SID 0x69 +#define MAX_BUCK_BOOST_OP 0xb9 +#define MAX_BUCK_BOOST_OFF 0 +#define MAX_BUCK_BOOST_SOURCE 0xa +#define MAX_BUCK_BOOST_SINK 0x5 + +struct max_tcpci_chip { + struct tcpci_data data; + struct tcpci *tcpci; + struct device *dev; + struct i2c_client *client; + struct tcpm_port *port; +}; + +static const struct regmap_range max_tcpci_tcpci_range[] = { + regmap_reg_range(0x00, 0x95) +}; + +const struct regmap_access_table max_tcpci_tcpci_write_table = { + .yes_ranges = max_tcpci_tcpci_range, + .n_yes_ranges = ARRAY_SIZE(max_tcpci_tcpci_range), +}; + +static const struct regmap_config max_tcpci_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x95, + .wr_table = &max_tcpci_tcpci_write_table, +}; + +static struct max_tcpci_chip *tdata_to_max_tcpci(struct tcpci_data *tdata) +{ + return container_of(tdata, struct max_tcpci_chip, data); +} + +static int max_tcpci_read16(struct max_tcpci_chip *chip, unsigned int reg, u16 *val) +{ + return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u16)); +} + +static int max_tcpci_write16(struct max_tcpci_chip *chip, unsigned int reg, u16 val) +{ + return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u16)); +} + +static int max_tcpci_read8(struct max_tcpci_chip *chip, unsigned int reg, u8 *val) +{ + return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u8)); +} + +static int max_tcpci_write8(struct max_tcpci_chip *chip, unsigned int reg, u8 val) +{ + return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u8)); +} + +static void max_tcpci_init_regs(struct max_tcpci_chip *chip) +{ + u16 alert_mask = 0; + int ret; + + ret = max_tcpci_write16(chip, TCPC_ALERT, 0xffff); + if (ret < 0) { + dev_err(chip->dev, "Error writing to TCPC_ALERT ret:%d\n", ret); + return; + } + + ret = max_tcpci_write16(chip, TCPC_VENDOR_ALERT, 0xffff); + if (ret < 0) { + dev_err(chip->dev, "Error writing to TCPC_VENDOR_ALERT ret:%d\n", ret); + return; + } + + ret = max_tcpci_write8(chip, TCPC_ALERT_EXTENDED, 0xff); + if (ret < 0) { + dev_err(chip->dev, "Unable to clear TCPC_ALERT_EXTENDED ret:%d\n", ret); + return; + } + + alert_mask = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_TX_FAILED | + TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_RX_STATUS | TCPC_ALERT_CC_STATUS | + TCPC_ALERT_VBUS_DISCNCT | TCPC_ALERT_RX_BUF_OVF | TCPC_ALERT_POWER_STATUS | + /* Enable Extended alert for detecting Fast Role Swap Signal */ + TCPC_ALERT_EXTND; + + ret = max_tcpci_write16(chip, TCPC_ALERT_MASK, alert_mask); + if (ret < 0) { + dev_err(chip->dev, + "Error enabling TCPC_ALERT: TCPC_ALERT_MASK write failed ret:%d\n", ret); + return; + } + + /* Enable vbus voltage monitoring and voltage alerts */ + ret = max_tcpci_write8(chip, TCPC_POWER_CTRL, 0); + if (ret < 0) { + dev_err(chip->dev, "Error writing to TCPC_POWER_CTRL ret:%d\n", ret); + return; + } + + ret = max_tcpci_write8(chip, TCPC_ALERT_EXTENDED_MASK, TCPC_SINK_FAST_ROLE_SWAP); + if (ret < 0) + return; +} + +static void process_rx(struct max_tcpci_chip *chip, u16 status) +{ + struct pd_message msg; + u8 count, frame_type, rx_buf[TCPC_RECEIVE_BUFFER_LEN]; + int ret, payload_index; + u8 *rx_buf_ptr; + + /* + * READABLE_BYTE_COUNT: Indicates the number of bytes in the RX_BUF_BYTE_x registers + * plus one (for the RX_BUF_FRAME_TYPE) Table 4-36. + * Read the count and frame type. + */ + ret = regmap_raw_read(chip->data.regmap, TCPC_RX_BYTE_CNT, rx_buf, 2); + if (ret < 0) { + dev_err(chip->dev, "TCPC_RX_BYTE_CNT read failed ret:%d", ret); + return; + } + + count = rx_buf[TCPC_RECEIVE_BUFFER_COUNT_OFFSET]; + frame_type = rx_buf[TCPC_RECEIVE_BUFFER_FRAME_TYPE_OFFSET]; + + if (count == 0 || frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP) { + max_tcpci_write16(chip, TCPC_ALERT, TCPC_ALERT_RX_STATUS); + dev_err(chip->dev, "%s", count == 0 ? "error: count is 0" : + "error frame_type is not SOP"); + return; + } + + if (count > sizeof(struct pd_message) || count + 1 > TCPC_RECEIVE_BUFFER_LEN) { + dev_err(chip->dev, "Invalid TCPC_RX_BYTE_CNT %d", count); + return; + } + + /* + * Read count + 1 as RX_BUF_BYTE_x is hidden and can only be read through + * TCPC_RX_BYTE_CNT + */ + count += 1; + ret = regmap_raw_read(chip->data.regmap, TCPC_RX_BYTE_CNT, rx_buf, count); + if (ret < 0) { + dev_err(chip->dev, "Error: TCPC_RX_BYTE_CNT read failed: %d", ret); + return; + } + + rx_buf_ptr = rx_buf + TCPC_RECEIVE_BUFFER_RX_BYTE_BUF_OFFSET; + msg.header = cpu_to_le16(*(u16 *)rx_buf_ptr); + rx_buf_ptr = rx_buf_ptr + sizeof(msg.header); + for (payload_index = 0; payload_index < pd_header_cnt_le(msg.header); payload_index++, + rx_buf_ptr += sizeof(msg.payload[0])) + msg.payload[payload_index] = cpu_to_le32(*(u32 *)rx_buf_ptr); + + /* + * Read complete, clear RX status alert bit. + * Clear overflow as well if set. + */ + ret = max_tcpci_write16(chip, TCPC_ALERT, status & TCPC_ALERT_RX_BUF_OVF ? + TCPC_ALERT_RX_STATUS | TCPC_ALERT_RX_BUF_OVF : + TCPC_ALERT_RX_STATUS); + if (ret < 0) + return; + + tcpm_pd_receive(chip->port, &msg); +} + +static int max_tcpci_set_vbus(struct tcpci *tcpci, struct tcpci_data *tdata, bool source, bool sink) +{ + struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); + u8 buffer_source[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_SOURCE}; + u8 buffer_sink[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_SINK}; + u8 buffer_none[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_OFF}; + struct i2c_client *i2c = chip->client; + int ret; + + struct i2c_msg msgs[] = { + { + .addr = MAX_BUCK_BOOST_SID, + .flags = i2c->flags & I2C_M_TEN, + .len = 2, + .buf = source ? buffer_source : sink ? buffer_sink : buffer_none, + }, + }; + + if (source && sink) { + dev_err(chip->dev, "Both source and sink set\n"); + return -EINVAL; + } + + ret = i2c_transfer(i2c->adapter, msgs, 1); + + return ret < 0 ? ret : 1; +} + +static void process_power_status(struct max_tcpci_chip *chip) +{ + u8 pwr_status; + int ret; + + ret = max_tcpci_read8(chip, TCPC_POWER_STATUS, &pwr_status); + if (ret < 0) + return; + + if (pwr_status == 0xff) { + max_tcpci_init_regs(chip); + } else if (pwr_status & TCPC_POWER_STATUS_SOURCING_VBUS) { + tcpm_sourcing_vbus(chip->port); + /* + * Alawys re-enable boost here. + * In normal case, when say an headset is attached, TCPM would + * have instructed to TCPC to enable boost, so the call is a + * no-op. + * But for Fast Role Swap case, Boost turns on autonomously without + * AP intervention, but, needs AP to enable source mode explicitly + * for AP to regain control. + */ + max_tcpci_set_vbus(chip->tcpci, &chip->data, true, false); + } else { + tcpm_vbus_change(chip->port); + } +} + +static void process_tx(struct max_tcpci_chip *chip, u16 status) +{ + if (status & TCPC_ALERT_TX_SUCCESS) + tcpm_pd_transmit_complete(chip->port, TCPC_TX_SUCCESS); + else if (status & TCPC_ALERT_TX_DISCARDED) + tcpm_pd_transmit_complete(chip->port, TCPC_TX_DISCARDED); + else if (status & TCPC_ALERT_TX_FAILED) + tcpm_pd_transmit_complete(chip->port, TCPC_TX_FAILED); + + /* Reinit regs as Hard reset sets them to default value */ + if ((status & TCPC_ALERT_TX_SUCCESS) && (status & TCPC_ALERT_TX_FAILED)) + max_tcpci_init_regs(chip); +} + +static irqreturn_t _max_tcpci_irq(struct max_tcpci_chip *chip, u16 status) +{ + u16 mask; + int ret; + u8 reg_status; + + /* + * Clear alert status for everything except RX_STATUS, which shouldn't + * be cleared until we have successfully retrieved message. + */ + if (status & ~TCPC_ALERT_RX_STATUS) { + mask = status & TCPC_ALERT_RX_BUF_OVF ? + status & ~(TCPC_ALERT_RX_STATUS | TCPC_ALERT_RX_BUF_OVF) : + status & ~TCPC_ALERT_RX_STATUS; + ret = max_tcpci_write16(chip, TCPC_ALERT, mask); + if (ret < 0) { + dev_err(chip->dev, "ALERT clear failed\n"); + return ret; + } + } + + if (status & TCPC_ALERT_RX_BUF_OVF && !(status & TCPC_ALERT_RX_STATUS)) { + ret = max_tcpci_write16(chip, TCPC_ALERT, (TCPC_ALERT_RX_STATUS | + TCPC_ALERT_RX_BUF_OVF)); + if (ret < 0) { + dev_err(chip->dev, "ALERT clear failed\n"); + return ret; + } + } + + if (status & TCPC_ALERT_EXTND) { + ret = max_tcpci_read8(chip, TCPC_ALERT_EXTENDED, ®_status); + if (ret < 0) + return ret; + + ret = max_tcpci_write8(chip, TCPC_ALERT_EXTENDED, reg_status); + if (ret < 0) + return ret; + + if (reg_status & TCPC_SINK_FAST_ROLE_SWAP) { + dev_info(chip->dev, "FRS Signal"); + tcpm_sink_frs(chip->port); + } + } + + if (status & TCPC_ALERT_RX_STATUS) + process_rx(chip, status); + + if (status & TCPC_ALERT_VBUS_DISCNCT) + tcpm_vbus_change(chip->port); + + if (status & TCPC_ALERT_CC_STATUS) + tcpm_cc_change(chip->port); + + if (status & TCPC_ALERT_POWER_STATUS) + process_power_status(chip); + + if (status & TCPC_ALERT_RX_HARD_RST) { + tcpm_pd_hard_reset(chip->port); + max_tcpci_init_regs(chip); + } + + if (status & TCPC_ALERT_TX_SUCCESS || status & TCPC_ALERT_TX_DISCARDED || status & + TCPC_ALERT_TX_FAILED) + process_tx(chip, status); + + return IRQ_HANDLED; +} + +static irqreturn_t max_tcpci_irq(int irq, void *dev_id) +{ + struct max_tcpci_chip *chip = dev_id; + u16 status; + irqreturn_t irq_return; + int ret; + + if (!chip->port) + return IRQ_HANDLED; + + ret = max_tcpci_read16(chip, TCPC_ALERT, &status); + if (ret < 0) { + dev_err(chip->dev, "ALERT read failed\n"); + return ret; + } + while (status) { + irq_return = _max_tcpci_irq(chip, status); + /* Do not return if the ALERT is already set. */ + ret = max_tcpci_read16(chip, TCPC_ALERT, &status); + if (ret < 0) + break; + } + + return irq_return; +} + +static irqreturn_t max_tcpci_isr(int irq, void *dev_id) +{ + struct max_tcpci_chip *chip = dev_id; + + pm_wakeup_event(chip->dev, PD_ACTIVITY_TIMEOUT_MS); + + if (!chip->port) + return IRQ_HANDLED; + + return IRQ_WAKE_THREAD; +} + +static int max_tcpci_init_alert(struct max_tcpci_chip *chip, struct i2c_client *client) +{ + int ret; + + ret = devm_request_threaded_irq(chip->dev, client->irq, max_tcpci_isr, max_tcpci_irq, + (IRQF_TRIGGER_LOW | IRQF_ONESHOT), dev_name(chip->dev), + chip); + + if (ret < 0) + return ret; + + enable_irq_wake(client->irq); + return 0; +} + +static int max_tcpci_start_toggling(struct tcpci *tcpci, struct tcpci_data *tdata, + enum typec_cc_status cc) +{ + struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); + + max_tcpci_init_regs(chip); + + return 0; +} + +static int tcpci_init(struct tcpci *tcpci, struct tcpci_data *data) +{ + /* + * Generic TCPCI overwrites the regs once this driver initializes + * them. Prevent this by returning -1. + */ + return -1; +} + +static int max_tcpci_probe(struct i2c_client *client, const struct i2c_device_id *i2c_id) +{ + int ret; + struct max_tcpci_chip *chip; + u8 power_status; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->data.regmap = devm_regmap_init_i2c(client, &max_tcpci_regmap_config); + if (IS_ERR(chip->data.regmap)) { + dev_err(&client->dev, "Regmap init failed\n"); + return PTR_ERR(chip->data.regmap); + } + + chip->dev = &client->dev; + i2c_set_clientdata(client, chip); + + ret = max_tcpci_read8(chip, TCPC_POWER_STATUS, &power_status); + if (ret < 0) + return ret; + + /* Chip level tcpci callbacks */ + chip->data.set_vbus = max_tcpci_set_vbus; + chip->data.start_drp_toggling = max_tcpci_start_toggling; + chip->data.TX_BUF_BYTE_x_hidden = true; + chip->data.init = tcpci_init; + + max_tcpci_init_regs(chip); + chip->tcpci = tcpci_register_port(chip->dev, &chip->data); + if (IS_ERR_OR_NULL(chip->tcpci)) { + dev_err(&client->dev, "TCPCI port registration failed"); + ret = PTR_ERR(chip->tcpci); + return PTR_ERR(chip->tcpci); + } + chip->port = tcpci_get_tcpm_port(chip->tcpci); + ret = max_tcpci_init_alert(chip, client); + if (ret < 0) + goto unreg_port; + + device_init_wakeup(chip->dev, true); + return 0; + +unreg_port: + tcpci_unregister_port(chip->tcpci); + + return ret; +} + +static int max_tcpci_remove(struct i2c_client *client) +{ + struct max_tcpci_chip *chip = i2c_get_clientdata(client); + + if (!IS_ERR_OR_NULL(chip->tcpci)) + tcpci_unregister_port(chip->tcpci); + + return 0; +} + +static const struct i2c_device_id max_tcpci_id[] = { + { "maxtcpc", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max_tcpci_id); + +#ifdef CONFIG_OF +static const struct of_device_id max_tcpci_of_match[] = { + { .compatible = "maxim,tcpc", }, + {}, +}; +MODULE_DEVICE_TABLE(of, max_tcpci_of_match); +#endif + +static struct i2c_driver max_tcpci_i2c_driver = { + .driver = { + .name = "maxtcpc", + .of_match_table = of_match_ptr(max_tcpci_of_match), + }, + .probe = max_tcpci_probe, + .remove = max_tcpci_remove, + .id_table = max_tcpci_id, +}; +module_i2c_driver(max_tcpci_i2c_driver); + +MODULE_AUTHOR("Badhri Jagan Sridharan <badhri@google.com>"); +MODULE_DESCRIPTION("Maxim TCPCI based USB Type-C Port Controller Interface Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/typec/tcpm/tcpci_mt6360.c b/drivers/usb/typec/tcpm/tcpci_mt6360.c new file mode 100644 index 000000000000..f1bd9e09bc87 --- /dev/null +++ b/drivers/usb/typec/tcpm/tcpci_mt6360.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020 MediaTek Inc. + * + * Author: ChiYuan Huang <cy_huang@richtek.com> + */ + +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/usb/tcpm.h> + +#include "tcpci.h" + +#define MT6360_REG_VCONNCTRL1 0x8C +#define MT6360_REG_MODECTRL2 0x8F +#define MT6360_REG_SWRESET 0xA0 +#define MT6360_REG_DEBCTRL1 0xA1 +#define MT6360_REG_DRPCTRL1 0xA2 +#define MT6360_REG_DRPCTRL2 0xA3 +#define MT6360_REG_I2CTORST 0xBF +#define MT6360_REG_RXCTRL2 0xCF +#define MT6360_REG_CTDCTRL2 0xEC + +/* MT6360_REG_VCONNCTRL1 */ +#define MT6360_VCONNCL_ENABLE BIT(0) +/* MT6360_REG_RXCTRL2 */ +#define MT6360_OPEN40M_ENABLE BIT(7) +/* MT6360_REG_CTDCTRL2 */ +#define MT6360_RPONESHOT_ENABLE BIT(6) + +struct mt6360_tcpc_info { + struct tcpci_data tdata; + struct tcpci *tcpci; + struct device *dev; + int irq; +}; + +static inline int mt6360_tcpc_read16(struct regmap *regmap, + unsigned int reg, u16 *val) +{ + return regmap_raw_read(regmap, reg, val, sizeof(u16)); +} + +static inline int mt6360_tcpc_write16(struct regmap *regmap, + unsigned int reg, u16 val) +{ + return regmap_raw_write(regmap, reg, &val, sizeof(u16)); +} + +static int mt6360_tcpc_init(struct tcpci *tcpci, struct tcpci_data *tdata) +{ + struct regmap *regmap = tdata->regmap; + int ret; + + ret = regmap_write(regmap, MT6360_REG_SWRESET, 0x01); + if (ret) + return ret; + + /* after reset command, wait 1~2ms to wait IC action */ + usleep_range(1000, 2000); + + /* write all alert to masked */ + ret = mt6360_tcpc_write16(regmap, TCPC_ALERT_MASK, 0); + if (ret) + return ret; + + /* config I2C timeout reset enable , and timeout to 200ms */ + ret = regmap_write(regmap, MT6360_REG_I2CTORST, 0x8F); + if (ret) + return ret; + + /* config CC Detect Debounce : 26.7*val us */ + ret = regmap_write(regmap, MT6360_REG_DEBCTRL1, 0x10); + if (ret) + return ret; + + /* DRP Toggle Cycle : 51.2 + 6.4*val ms */ + ret = regmap_write(regmap, MT6360_REG_DRPCTRL1, 4); + if (ret) + return ret; + + /* DRP Duyt Ctrl : dcSRC: /1024 */ + ret = mt6360_tcpc_write16(regmap, MT6360_REG_DRPCTRL2, 330); + if (ret) + return ret; + + /* Enable VCONN Current Limit function */ + ret = regmap_update_bits(regmap, MT6360_REG_VCONNCTRL1, MT6360_VCONNCL_ENABLE, + MT6360_VCONNCL_ENABLE); + if (ret) + return ret; + + /* Enable cc open 40ms when pmic send vsysuv signal */ + ret = regmap_update_bits(regmap, MT6360_REG_RXCTRL2, MT6360_OPEN40M_ENABLE, + MT6360_OPEN40M_ENABLE); + if (ret) + return ret; + + /* Enable Rpdet oneshot detection */ + ret = regmap_update_bits(regmap, MT6360_REG_CTDCTRL2, MT6360_RPONESHOT_ENABLE, + MT6360_RPONESHOT_ENABLE); + if (ret) + return ret; + + /* Set shipping mode off, AUTOIDLE on */ + return regmap_write(regmap, MT6360_REG_MODECTRL2, 0x7A); +} + +static irqreturn_t mt6360_irq(int irq, void *dev_id) +{ + struct mt6360_tcpc_info *mti = dev_id; + + return tcpci_irq(mti->tcpci); +} + +static int mt6360_tcpc_probe(struct platform_device *pdev) +{ + struct mt6360_tcpc_info *mti; + int ret; + + mti = devm_kzalloc(&pdev->dev, sizeof(*mti), GFP_KERNEL); + if (!mti) + return -ENOMEM; + + mti->dev = &pdev->dev; + + mti->tdata.regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!mti->tdata.regmap) { + dev_err(&pdev->dev, "Failed to get parent regmap\n"); + return -ENODEV; + } + + mti->irq = platform_get_irq_byname(pdev, "PD_IRQB"); + if (mti->irq < 0) + return mti->irq; + + mti->tdata.init = mt6360_tcpc_init; + mti->tcpci = tcpci_register_port(&pdev->dev, &mti->tdata); + if (IS_ERR(mti->tcpci)) { + dev_err(&pdev->dev, "Failed to register tcpci port\n"); + return PTR_ERR(mti->tcpci); + } + + ret = devm_request_threaded_irq(mti->dev, mti->irq, NULL, mt6360_irq, IRQF_ONESHOT, + dev_name(&pdev->dev), mti); + if (ret) { + dev_err(mti->dev, "Failed to register irq\n"); + tcpci_unregister_port(mti->tcpci); + return ret; + } + + device_init_wakeup(&pdev->dev, true); + platform_set_drvdata(pdev, mti); + + return 0; +} + +static int mt6360_tcpc_remove(struct platform_device *pdev) +{ + struct mt6360_tcpc_info *mti = platform_get_drvdata(pdev); + + disable_irq(mti->irq); + tcpci_unregister_port(mti->tcpci); + return 0; +} + +static int __maybe_unused mt6360_tcpc_suspend(struct device *dev) +{ + struct mt6360_tcpc_info *mti = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + enable_irq_wake(mti->irq); + + return 0; +} + +static int __maybe_unused mt6360_tcpc_resume(struct device *dev) +{ + struct mt6360_tcpc_info *mti = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + disable_irq_wake(mti->irq); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(mt6360_tcpc_pm_ops, mt6360_tcpc_suspend, mt6360_tcpc_resume); + +static const struct of_device_id __maybe_unused mt6360_tcpc_of_id[] = { + { .compatible = "mediatek,mt6360-tcpc", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mt6360_tcpc_of_id); + +static struct platform_driver mt6360_tcpc_driver = { + .driver = { + .name = "mt6360-tcpc", + .pm = &mt6360_tcpc_pm_ops, + .of_match_table = mt6360_tcpc_of_id, + }, + .probe = mt6360_tcpc_probe, + .remove = mt6360_tcpc_remove, +}; +module_platform_driver(mt6360_tcpc_driver); + +MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>"); +MODULE_DESCRIPTION("MT6360 USB Type-C Port Controller Interface Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index a48e3f90d196..55535c4f66bf 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -8,8 +8,10 @@ #include <linux/completion.h> #include <linux/debugfs.h> #include <linux/device.h> +#include <linux/hrtimer.h> #include <linux/jiffies.h> #include <linux/kernel.h> +#include <linux/kthread.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/power_supply.h> @@ -28,7 +30,8 @@ #include <linux/usb/role.h> #include <linux/usb/tcpm.h> #include <linux/usb/typec_altmode.h> -#include <linux/workqueue.h> + +#include <uapi/linux/sched/types.h> #define FOREACH_STATE(S) \ S(INVALID_STATE), \ @@ -103,6 +106,13 @@ S(VCONN_SWAP_TURN_ON_VCONN), \ S(VCONN_SWAP_TURN_OFF_VCONN), \ \ + S(FR_SWAP_SEND), \ + S(FR_SWAP_SEND_TIMEOUT), \ + S(FR_SWAP_SNK_SRC_TRANSITION_TO_OFF), \ + S(FR_SWAP_SNK_SRC_NEW_SINK_READY), \ + S(FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED), \ + S(FR_SWAP_CANCEL), \ + \ S(SNK_TRY), \ S(SNK_TRY_WAIT), \ S(SNK_TRY_WAIT_DEBOUNCE), \ @@ -124,6 +134,9 @@ S(GET_PPS_STATUS_SEND), \ S(GET_PPS_STATUS_SEND_TIMEOUT), \ \ + S(GET_SINK_CAP), \ + S(GET_SINK_CAP_TIMEOUT), \ + \ S(ERROR_RECOVERY), \ S(PORT_RESET), \ S(PORT_RESET_WAIT_OFF) @@ -167,11 +180,25 @@ enum adev_actions { ADEV_ATTENTION, }; +/* + * Initial current capability of the new source when vSafe5V is applied during PD3.0 Fast Role Swap. + * Based on "Table 6-14 Fixed Supply PDO - Sink" of "USB Power Delivery Specification Revision 3.0, + * Version 1.2" + */ +enum frs_typec_current { + FRS_NOT_SUPPORTED, + FRS_DEFAULT_POWER, + FRS_5V_1P5A, + FRS_5V_3A, +}; + /* Events from low level driver */ #define TCPM_CC_EVENT BIT(0) #define TCPM_VBUS_EVENT BIT(1) #define TCPM_RESET_EVENT BIT(2) +#define TCPM_FRS_EVENT BIT(3) +#define TCPM_SOURCING_VBUS BIT(4) #define LOG_BUFFER_ENTRIES 1024 #define LOG_BUFFER_ENTRY_SIZE 128 @@ -181,6 +208,8 @@ enum adev_actions { #define SVID_DISCOVERY_MAX 16 #define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX) +#define GET_SINK_CAP_RETRY_MS 100 + struct pd_mode_data { int svid_index; /* current SVID index */ int nsvids; @@ -203,7 +232,7 @@ struct tcpm_port { struct device *dev; struct mutex lock; /* tcpm state machine lock */ - struct workqueue_struct *wq; + struct kthread_worker *wq; struct typec_capability typec_caps; struct typec_port *typec_port; @@ -247,15 +276,19 @@ struct tcpm_port { enum tcpm_state prev_state; enum tcpm_state state; enum tcpm_state delayed_state; - unsigned long delayed_runtime; + ktime_t delayed_runtime; unsigned long delay_ms; spinlock_t pd_event_lock; u32 pd_events; - struct work_struct event_work; - struct delayed_work state_machine; - struct delayed_work vdm_state_machine; + struct kthread_work event_work; + struct hrtimer state_machine_timer; + struct kthread_work state_machine; + struct hrtimer vdm_state_machine_timer; + struct kthread_work vdm_state_machine; + struct hrtimer enable_frs_timer; + struct kthread_work enable_frs; bool state_machine_running; struct completion tx_complete; @@ -330,6 +363,12 @@ struct tcpm_port { /* port belongs to a self powered device */ bool self_powered; + /* FRS */ + enum frs_typec_current frs_current; + + /* Sink caps have been queried */ + bool sink_cap_done; + #ifdef CONFIG_DEBUG_FS struct dentry *dentry; struct mutex logbuffer_lock; /* log buffer access lock */ @@ -340,7 +379,7 @@ struct tcpm_port { }; struct pd_rx_event { - struct work_struct work; + struct kthread_work work; struct tcpm_port *port; struct pd_message msg; }; @@ -914,6 +953,37 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port) return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); } +static void mod_tcpm_delayed_work(struct tcpm_port *port, unsigned int delay_ms) +{ + if (delay_ms) { + hrtimer_start(&port->state_machine_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); + } else { + hrtimer_cancel(&port->state_machine_timer); + kthread_queue_work(port->wq, &port->state_machine); + } +} + +static void mod_vdm_delayed_work(struct tcpm_port *port, unsigned int delay_ms) +{ + if (delay_ms) { + hrtimer_start(&port->vdm_state_machine_timer, ms_to_ktime(delay_ms), + HRTIMER_MODE_REL); + } else { + hrtimer_cancel(&port->vdm_state_machine_timer); + kthread_queue_work(port->wq, &port->vdm_state_machine); + } +} + +static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int delay_ms) +{ + if (delay_ms) { + hrtimer_start(&port->enable_frs_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); + } else { + hrtimer_cancel(&port->enable_frs_timer); + kthread_queue_work(port->wq, &port->enable_frs); + } +} + static void tcpm_set_state(struct tcpm_port *port, enum tcpm_state state, unsigned int delay_ms) { @@ -922,9 +992,8 @@ static void tcpm_set_state(struct tcpm_port *port, enum tcpm_state state, tcpm_states[port->state], tcpm_states[state], delay_ms); port->delayed_state = state; - mod_delayed_work(port->wq, &port->state_machine, - msecs_to_jiffies(delay_ms)); - port->delayed_runtime = jiffies + msecs_to_jiffies(delay_ms); + mod_tcpm_delayed_work(port, delay_ms); + port->delayed_runtime = ktime_add(ktime_get(), ms_to_ktime(delay_ms)); port->delay_ms = delay_ms; } else { tcpm_log(port, "state change %s -> %s", @@ -939,7 +1008,7 @@ static void tcpm_set_state(struct tcpm_port *port, enum tcpm_state state, * machine. */ if (!port->state_machine_running) - mod_delayed_work(port->wq, &port->state_machine, 0); + mod_tcpm_delayed_work(port, 0); } } @@ -960,7 +1029,7 @@ static void tcpm_queue_message(struct tcpm_port *port, enum pd_msg_request message) { port->queued_message = message; - mod_delayed_work(port->wq, &port->state_machine, 0); + mod_tcpm_delayed_work(port, 0); } /* @@ -981,7 +1050,7 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, port->vdm_retries = 0; port->vdm_state = VDM_STATE_READY; - mod_delayed_work(port->wq, &port->vdm_state_machine, 0); + mod_vdm_delayed_work(port, 0); } static void tcpm_queue_vdm_unlocked(struct tcpm_port *port, const u32 header, @@ -1244,8 +1313,7 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, port->vdm_state = VDM_STATE_WAIT_RSP_BUSY; port->vdo_retry = (p[0] & ~VDO_CMDT_MASK) | CMDT_INIT; - mod_delayed_work(port->wq, &port->vdm_state_machine, - msecs_to_jiffies(PD_T_VDM_BUSY)); + mod_vdm_delayed_work(port, PD_T_VDM_BUSY); return; } port->vdm_state = VDM_STATE_DONE; @@ -1390,8 +1458,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) port->vdm_retries = 0; port->vdm_state = VDM_STATE_BUSY; timeout = vdm_ready_timeout(port->vdo_data[0]); - mod_delayed_work(port->wq, &port->vdm_state_machine, - timeout); + mod_vdm_delayed_work(port, timeout); } break; case VDM_STATE_WAIT_RSP_BUSY: @@ -1420,10 +1487,9 @@ static void vdm_run_state_machine(struct tcpm_port *port) } } -static void vdm_state_machine_work(struct work_struct *work) +static void vdm_state_machine_work(struct kthread_work *work) { - struct tcpm_port *port = container_of(work, struct tcpm_port, - vdm_state_machine.work); + struct tcpm_port *port = container_of(work, struct tcpm_port, vdm_state_machine); enum vdm_states prev_state; mutex_lock(&port->lock); @@ -1591,6 +1657,7 @@ static int tcpm_altmode_vdm(struct typec_altmode *altmode, struct tcpm_port *port = typec_altmode_get_drvdata(altmode); tcpm_queue_vdm_unlocked(port, header, data, count - 1); + return 0; } @@ -1646,6 +1713,9 @@ static void tcpm_pd_data_request(struct tcpm_port *port, unsigned int cnt = pd_header_cnt_le(msg->header); unsigned int rev = pd_header_rev_le(msg->header); unsigned int i; + enum frs_typec_current frs_current; + bool frs_enable; + int ret; switch (type) { case PD_DATA_SOURCE_CAP: @@ -1715,7 +1785,21 @@ static void tcpm_pd_data_request(struct tcpm_port *port, /* We don't do anything with this at the moment... */ for (i = 0; i < cnt; i++) port->sink_caps[i] = le32_to_cpu(msg->payload[i]); + + frs_current = (port->sink_caps[0] & PDO_FIXED_FRS_CURR_MASK) >> + PDO_FIXED_FRS_CURR_SHIFT; + frs_enable = frs_current && (frs_current <= port->frs_current); + tcpm_log(port, + "Port partner FRS capable partner_frs_current:%u port_frs_current:%u enable:%c", + frs_current, port->frs_current, frs_enable ? 'y' : 'n'); + if (frs_enable) { + ret = port->tcpc->enable_frs(port->tcpc, true); + tcpm_log(port, "Enable FRS %s, ret:%d\n", ret ? "fail" : "success", ret); + } + port->nr_sink_caps = cnt; + port->sink_cap_done = true; + tcpm_set_state(port, SNK_READY, 0); break; case PD_DATA_VENDOR_DEF: tcpm_handle_vdm_request(port, msg->payload, cnt); @@ -1810,6 +1894,9 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, case VCONN_SWAP_WAIT_FOR_VCONN: tcpm_set_state(port, VCONN_SWAP_TURN_OFF_VCONN, 0); break; + case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF: + tcpm_set_state(port, FR_SWAP_SNK_SRC_NEW_SINK_READY, 0); + break; default: break; } @@ -1849,6 +1936,13 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, -EAGAIN : -EOPNOTSUPP); tcpm_set_state(port, VCONN_SWAP_CANCEL, 0); break; + case FR_SWAP_SEND: + tcpm_set_state(port, FR_SWAP_CANCEL, 0); + break; + case GET_SINK_CAP: + port->sink_cap_done = true; + tcpm_set_state(port, ready_state(port), 0); + break; default: break; } @@ -1883,6 +1977,9 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, case VCONN_SWAP_SEND: tcpm_set_state(port, VCONN_SWAP_START, 0); break; + case FR_SWAP_SEND: + tcpm_set_state(port, FR_SWAP_SNK_SRC_TRANSITION_TO_OFF, 0); + break; default: break; } @@ -2005,7 +2102,7 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, } } -static void tcpm_pd_rx_handler(struct work_struct *work) +static void tcpm_pd_rx_handler(struct kthread_work *work) { struct pd_rx_event *event = container_of(work, struct pd_rx_event, work); @@ -2067,10 +2164,10 @@ void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg) if (!event) return; - INIT_WORK(&event->work, tcpm_pd_rx_handler); + kthread_init_work(&event->work, tcpm_pd_rx_handler); event->port = port; memcpy(&event->msg, msg, sizeof(*msg)); - queue_work(port->wq, &event->work); + kthread_queue_work(port->wq, &event->work); } EXPORT_SYMBOL_GPL(tcpm_pd_receive); @@ -2123,9 +2220,9 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) } while (port->queued_message != PD_MSG_NONE); if (port->delayed_state != INVALID_STATE) { - if (time_is_after_jiffies(port->delayed_runtime)) { - mod_delayed_work(port->wq, &port->state_machine, - port->delayed_runtime - jiffies); + if (ktime_after(port->delayed_runtime, ktime_get())) { + mod_tcpm_delayed_work(port, ktime_to_ms(ktime_sub(port->delayed_runtime, + ktime_get()))); return true; } port->delayed_state = INVALID_STATE; @@ -2783,6 +2880,10 @@ static void tcpm_reset_port(struct tcpm_port *port) port->try_src_count = 0; port->try_snk_count = 0; port->usb_type = POWER_SUPPLY_USB_TYPE_C; + port->nr_sink_caps = 0; + port->sink_cap_done = false; + if (port->tcpc->enable_frs) + port->tcpc->enable_frs(port->tcpc, false); power_supply_changed(port->psy); } @@ -3258,10 +3359,9 @@ static void run_state_machine(struct tcpm_port *port) case SNK_DISCOVERY_DEBOUNCE_DONE: if (!tcpm_port_is_disconnected(port) && tcpm_port_is_sink(port) && - time_is_after_jiffies(port->delayed_runtime)) { + ktime_after(port->delayed_runtime, ktime_get())) { tcpm_set_state(port, SNK_DISCOVERY, - jiffies_to_msecs(port->delayed_runtime - - jiffies)); + ktime_to_ms(ktime_sub(port->delayed_runtime, ktime_get()))); break; } tcpm_set_state(port, unattached_state(port), 0); @@ -3334,10 +3434,9 @@ static void run_state_machine(struct tcpm_port *port) tcpm_swap_complete(port, 0); tcpm_typec_connect(port); tcpm_check_send_discover(port); + mod_enable_frs_delayed_work(port, 0); tcpm_pps_complete(port, port->pps_status); - power_supply_changed(port->psy); - break; /* Accessory states */ @@ -3361,9 +3460,13 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, HARD_RESET_START, 0); break; case HARD_RESET_START: + port->sink_cap_done = false; + if (port->tcpc->enable_frs) + port->tcpc->enable_frs(port->tcpc, false); port->hard_reset_count++; port->tcpc->set_pd_rx(port->tcpc, false); tcpm_unregister_altmodes(port); + port->nr_sink_caps = 0; port->send_discover = true; if (port->pwr_role == TYPEC_SOURCE) tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF, @@ -3495,6 +3598,35 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, ready_state(port), 0); break; + case FR_SWAP_SEND: + if (tcpm_pd_send_control(port, PD_CTRL_FR_SWAP)) { + tcpm_set_state(port, ERROR_RECOVERY, 0); + break; + } + tcpm_set_state_cond(port, FR_SWAP_SEND_TIMEOUT, PD_T_SENDER_RESPONSE); + break; + case FR_SWAP_SEND_TIMEOUT: + tcpm_set_state(port, ERROR_RECOVERY, 0); + break; + case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF: + tcpm_set_state(port, ERROR_RECOVERY, PD_T_PS_SOURCE_OFF); + break; + case FR_SWAP_SNK_SRC_NEW_SINK_READY: + if (port->vbus_source) + tcpm_set_state(port, FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED, 0); + else + tcpm_set_state(port, ERROR_RECOVERY, PD_T_RECEIVER_RESPONSE); + break; + case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED: + tcpm_set_pwr_role(port, TYPEC_SOURCE); + if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY)) { + tcpm_set_state(port, ERROR_RECOVERY, 0); + break; + } + tcpm_set_cc(port, tcpm_rp_cc(port)); + tcpm_set_state(port, SRC_STARTUP, PD_T_SWAP_SRC_START); + break; + /* PR_Swap states */ case PR_SWAP_ACCEPT: tcpm_pd_send_control(port, PD_CTRL_ACCEPT); @@ -3573,7 +3705,7 @@ static void run_state_machine(struct tcpm_port *port) */ tcpm_set_pwr_role(port, TYPEC_SOURCE); tcpm_pd_send_control(port, PD_CTRL_PS_RDY); - tcpm_set_state(port, SRC_STARTUP, 0); + tcpm_set_state(port, SRC_STARTUP, PD_T_SWAP_SRC_START); break; case VCONN_SWAP_ACCEPT: @@ -3618,6 +3750,12 @@ static void run_state_machine(struct tcpm_port *port) else tcpm_set_state(port, SNK_READY, 0); break; + case FR_SWAP_CANCEL: + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, SRC_READY, 0); + else + tcpm_set_state(port, SNK_READY, 0); + break; case BIST_RX: switch (BDO_MODE_MASK(port->bist_request)) { @@ -3652,6 +3790,14 @@ static void run_state_machine(struct tcpm_port *port) case GET_PPS_STATUS_SEND_TIMEOUT: tcpm_set_state(port, ready_state(port), 0); break; + case GET_SINK_CAP: + tcpm_pd_send_control(port, PD_CTRL_GET_SINK_CAP); + tcpm_set_state(port, GET_SINK_CAP_TIMEOUT, PD_T_SENDER_RESPONSE); + break; + case GET_SINK_CAP_TIMEOUT: + port->sink_cap_done = true; + tcpm_set_state(port, ready_state(port), 0); + break; case ERROR_RECOVERY: tcpm_swap_complete(port, -EPROTO); tcpm_pps_complete(port, -EPROTO); @@ -3674,10 +3820,9 @@ static void run_state_machine(struct tcpm_port *port) } } -static void tcpm_state_machine_work(struct work_struct *work) +static void tcpm_state_machine_work(struct kthread_work *work) { - struct tcpm_port *port = container_of(work, struct tcpm_port, - state_machine.work); + struct tcpm_port *port = container_of(work, struct tcpm_port, state_machine); enum tcpm_state prev_state; mutex_lock(&port->lock); @@ -3868,6 +4013,13 @@ static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1, * Ignore it. */ break; + case FR_SWAP_SEND: + case FR_SWAP_SEND_TIMEOUT: + case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF: + case FR_SWAP_SNK_SRC_NEW_SINK_READY: + case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED: + /* Do nothing, CC change expected */ + break; case PORT_RESET: case PORT_RESET_WAIT_OFF: @@ -3938,6 +4090,9 @@ static void _tcpm_pd_vbus_on(struct tcpm_port *port) case SRC_TRY_DEBOUNCE: /* Do nothing, waiting for sink detection */ break; + case FR_SWAP_SNK_SRC_NEW_SINK_READY: + tcpm_set_state(port, FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED, 0); + break; case PORT_RESET: case PORT_RESET_WAIT_OFF: @@ -4017,6 +4172,14 @@ static void _tcpm_pd_vbus_off(struct tcpm_port *port) */ break; + case FR_SWAP_SEND: + case FR_SWAP_SEND_TIMEOUT: + case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF: + case FR_SWAP_SNK_SRC_NEW_SINK_READY: + case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED: + /* Do nothing, vbus drop expected */ + break; + default: if (port->pwr_role == TYPEC_SINK && port->attached) @@ -4041,7 +4204,7 @@ static void _tcpm_pd_hard_reset(struct tcpm_port *port) 0); } -static void tcpm_pd_event_handler(struct work_struct *work) +static void tcpm_pd_event_handler(struct kthread_work *work) { struct tcpm_port *port = container_of(work, struct tcpm_port, event_work); @@ -4071,6 +4234,25 @@ static void tcpm_pd_event_handler(struct work_struct *work) if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0) _tcpm_cc_change(port, cc1, cc2); } + if (events & TCPM_FRS_EVENT) { + if (port->state == SNK_READY) + tcpm_set_state(port, FR_SWAP_SEND, 0); + else + tcpm_log(port, "Discarding FRS_SIGNAL! Not in sink ready"); + } + if (events & TCPM_SOURCING_VBUS) { + tcpm_log(port, "sourcing vbus"); + /* + * In fast role swap case TCPC autonomously sources vbus. Set vbus_source + * true as TCPM wouldn't have called tcpm_set_vbus. + * + * When vbus is sourced on the command on TCPM i.e. TCPM called + * tcpm_set_vbus to source vbus, vbus_source would already be true. + */ + port->vbus_source = true; + _tcpm_pd_vbus_on(port); + } + spin_lock(&port->pd_event_lock); } spin_unlock(&port->pd_event_lock); @@ -4082,7 +4264,7 @@ void tcpm_cc_change(struct tcpm_port *port) spin_lock(&port->pd_event_lock); port->pd_events |= TCPM_CC_EVENT; spin_unlock(&port->pd_event_lock); - queue_work(port->wq, &port->event_work); + kthread_queue_work(port->wq, &port->event_work); } EXPORT_SYMBOL_GPL(tcpm_cc_change); @@ -4091,7 +4273,7 @@ void tcpm_vbus_change(struct tcpm_port *port) spin_lock(&port->pd_event_lock); port->pd_events |= TCPM_VBUS_EVENT; spin_unlock(&port->pd_event_lock); - queue_work(port->wq, &port->event_work); + kthread_queue_work(port->wq, &port->event_work); } EXPORT_SYMBOL_GPL(tcpm_vbus_change); @@ -4100,10 +4282,54 @@ void tcpm_pd_hard_reset(struct tcpm_port *port) spin_lock(&port->pd_event_lock); port->pd_events = TCPM_RESET_EVENT; spin_unlock(&port->pd_event_lock); - queue_work(port->wq, &port->event_work); + kthread_queue_work(port->wq, &port->event_work); } EXPORT_SYMBOL_GPL(tcpm_pd_hard_reset); +void tcpm_sink_frs(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events = TCPM_FRS_EVENT; + spin_unlock(&port->pd_event_lock); + kthread_queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_sink_frs); + +void tcpm_sourcing_vbus(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events = TCPM_SOURCING_VBUS; + spin_unlock(&port->pd_event_lock); + kthread_queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_sourcing_vbus); + +static void tcpm_enable_frs_work(struct kthread_work *work) +{ + struct tcpm_port *port = container_of(work, struct tcpm_port, enable_frs); + + mutex_lock(&port->lock); + /* Not FRS capable */ + if (!port->connected || port->port_type != TYPEC_PORT_DRP || + port->pwr_opmode != TYPEC_PWR_MODE_PD || + !port->tcpc->enable_frs || + /* Sink caps queried */ + port->sink_cap_done || port->negotiated_rev < PD_REV30) + goto unlock; + + /* Send when the state machine is idle */ + if (port->state != SNK_READY || port->vdm_state != VDM_STATE_DONE || port->send_discover) + goto resched; + + tcpm_set_state(port, GET_SINK_CAP, 0); + port->sink_cap_done = true; + +resched: + mod_enable_frs_delayed_work(port, GET_SINK_CAP_RETRY_MS); +unlock: + mutex_unlock(&port->lock); +} + static int tcpm_dr_set(struct typec_port *p, enum typec_data_role data) { struct tcpm_port *port = typec_get_drvdata(p); @@ -4511,7 +4737,7 @@ static int tcpm_fw_get_caps(struct tcpm_port *port, { const char *cap_str; int ret; - u32 mw; + u32 mw, frs_current; if (!fwnode) return -EINVAL; @@ -4580,6 +4806,13 @@ sink: port->self_powered = fwnode_property_read_bool(fwnode, "self-powered"); + /* FRS can only be supported byb DRP ports */ + if (port->port_type == TYPEC_PORT_DRP) { + ret = fwnode_property_read_u32(fwnode, "frs-typec-current", &frs_current); + if (ret >= 0 && frs_current <= FRS_5V_3A) + port->frs_current = frs_current; + } + return 0; } @@ -4808,6 +5041,30 @@ static int devm_tcpm_psy_register(struct tcpm_port *port) return PTR_ERR_OR_ZERO(port->psy); } +static enum hrtimer_restart state_machine_timer_handler(struct hrtimer *timer) +{ + struct tcpm_port *port = container_of(timer, struct tcpm_port, state_machine_timer); + + kthread_queue_work(port->wq, &port->state_machine); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart vdm_state_machine_timer_handler(struct hrtimer *timer) +{ + struct tcpm_port *port = container_of(timer, struct tcpm_port, vdm_state_machine_timer); + + kthread_queue_work(port->wq, &port->vdm_state_machine); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart enable_frs_timer_handler(struct hrtimer *timer) +{ + struct tcpm_port *port = container_of(timer, struct tcpm_port, enable_frs_timer); + + kthread_queue_work(port->wq, &port->enable_frs); + return HRTIMER_NORESTART; +} + struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) { struct tcpm_port *port; @@ -4829,12 +5086,21 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) mutex_init(&port->lock); mutex_init(&port->swap_lock); - port->wq = create_singlethread_workqueue(dev_name(dev)); - if (!port->wq) - return ERR_PTR(-ENOMEM); - INIT_DELAYED_WORK(&port->state_machine, tcpm_state_machine_work); - INIT_DELAYED_WORK(&port->vdm_state_machine, vdm_state_machine_work); - INIT_WORK(&port->event_work, tcpm_pd_event_handler); + port->wq = kthread_create_worker(0, dev_name(dev)); + if (IS_ERR(port->wq)) + return ERR_CAST(port->wq); + sched_set_fifo(port->wq->task); + + kthread_init_work(&port->state_machine, tcpm_state_machine_work); + kthread_init_work(&port->vdm_state_machine, vdm_state_machine_work); + kthread_init_work(&port->event_work, tcpm_pd_event_handler); + kthread_init_work(&port->enable_frs, tcpm_enable_frs_work); + hrtimer_init(&port->state_machine_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + port->state_machine_timer.function = state_machine_timer_handler; + hrtimer_init(&port->vdm_state_machine_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + port->vdm_state_machine_timer.function = vdm_state_machine_timer_handler; + hrtimer_init(&port->enable_frs_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + port->enable_frs_timer.function = enable_frs_timer_handler; spin_lock_init(&port->pd_event_lock); @@ -4886,7 +5152,7 @@ out_role_sw_put: usb_role_switch_put(port->role_sw); out_destroy_wq: tcpm_debugfs_exit(port); - destroy_workqueue(port->wq); + kthread_destroy_worker(port->wq); return ERR_PTR(err); } EXPORT_SYMBOL_GPL(tcpm_register_port); @@ -4901,7 +5167,7 @@ void tcpm_unregister_port(struct tcpm_port *port) typec_unregister_port(port->typec_port); usb_role_switch_put(port->role_sw); tcpm_debugfs_exit(port); - destroy_workqueue(port->wq); + kthread_destroy_worker(port->wq); } EXPORT_SYMBOL_GPL(tcpm_unregister_port); diff --git a/drivers/usb/usbip/usbip_common.c b/drivers/usb/usbip/usbip_common.c index e4b96674c405..4ce6c6a45eb1 100644 --- a/drivers/usb/usbip/usbip_common.c +++ b/drivers/usb/usbip/usbip_common.c @@ -755,13 +755,7 @@ EXPORT_SYMBOL_GPL(usbip_recv_xbuff); static int __init usbip_core_init(void) { - int ret; - - ret = usbip_init_eh(); - if (ret) - return ret; - - return 0; + return usbip_init_eh(); } static void __exit usbip_core_exit(void) diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index 1b598db5d8b9..66cde5e5f796 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -797,8 +797,14 @@ no_need_xmit: usb_hcd_unlink_urb_from_ep(hcd, urb); no_need_unlink: spin_unlock_irqrestore(&vhci->lock, flags); - if (!ret) + if (!ret) { + /* usb_hcd_giveback_urb() should be called with + * irqs disabled + */ + local_irq_disable(); usb_hcd_giveback_urb(hcd, urb, urb->status); + local_irq_enable(); + } return ret; } |