From 31c6bf70955dda6ef92ab40624f289576cff97d3 Mon Sep 17 00:00:00 2001 From: Bjørn Mork Date: Sat, 11 Jan 2014 02:04:00 +0100 Subject: usb: core: let dynamic ids override static ids MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This modifies the probing order so that any matching dynamic entry always will be used, even if the driver has a matching static entry. It is sometimes useful to dynamically update existing device entries. With the new ability to set the dynamic entry driver_info field, this can be used to test new additions to class driver exception lists or proposed changes to existing static per-device driver_info entries. Signed-off-by: Bjørn Mork Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/driver.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 5d01558cef6..9cd21813508 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -302,9 +302,9 @@ static int usb_probe_interface(struct device *dev) return error; } - id = usb_match_id(intf, driver->id_table); + id = usb_match_dynamic_id(intf, driver); if (!id) - id = usb_match_dynamic_id(intf, driver); + id = usb_match_id(intf, driver->id_table); if (!id) return error; -- cgit v1.2.3-70-g09d2 From ca52a17ba975dbf47e87c9bc63086aca0cf92806 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 7 Feb 2014 16:36:40 +0100 Subject: ohci-platform: Add support for devicetree instantiation Add support for ohci-platform instantiation from devicetree, including optionally getting clks and a phy from devicetree, and enabling / disabling those on power_on / off. This should allow using ohci-platform from devicetree in various cases. Specifically after this commit it can be used for the ohci controller found on Allwinner sunxi SoCs. Signed-off-by: Hans de Goede Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-ohci.txt | 22 +++ drivers/usb/host/ohci-platform.c | 162 ++++++++++++++++++--- 2 files changed, 162 insertions(+), 22 deletions(-) create mode 100644 Documentation/devicetree/bindings/usb/usb-ohci.txt diff --git a/Documentation/devicetree/bindings/usb/usb-ohci.txt b/Documentation/devicetree/bindings/usb/usb-ohci.txt new file mode 100644 index 00000000000..6ba38d990d1 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/usb-ohci.txt @@ -0,0 +1,22 @@ +USB OHCI controllers + +Required properties: +- compatible : "usb-ohci" +- reg : ohci controller register range (address and length) +- interrupts : ohci controller interrupt + +Optional properties: +- clocks : a list of phandle + clock specifier pairs +- phys : phandle + phy specifier pair +- phy-names : "usb" + +Example: + + ohci0: usb@01c14400 { + compatible = "allwinner,sun4i-a10-ohci", "usb-ohci"; + reg = <0x01c14400 0x100>; + interrupts = <64>; + clocks = <&usb_clk 6>, <&ahb_gates 2>; + phys = <&usbphy 1>; + phy-names = "usb"; + }; diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index 68f674cd095..49304ddde42 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -3,6 +3,7 @@ * * Copyright 2007 Michael Buesch * Copyright 2011-2012 Hauke Mehrtens + * Copyright 2014 Hans de Goede * * Derived from the OCHI-SSB driver * Derived from the OHCI-PCI driver @@ -14,11 +15,14 @@ * Licensed under the GNU/GPL. See COPYING for details. */ +#include +#include #include #include #include #include #include +#include #include #include #include @@ -27,6 +31,13 @@ #include "ohci.h" #define DRIVER_DESC "OHCI generic platform driver" +#define OHCI_MAX_CLKS 3 +#define hcd_to_ohci_priv(h) ((struct ohci_platform_priv *)hcd_to_ohci(h)->priv) + +struct ohci_platform_priv { + struct clk *clks[OHCI_MAX_CLKS]; + struct phy *phy; +}; static const char hcd_name[] = "ohci-platform"; @@ -48,11 +59,67 @@ static int ohci_platform_reset(struct usb_hcd *hcd) return ohci_setup(hcd); } +static int ohci_platform_power_on(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); + int clk, ret; + + for (clk = 0; clk < OHCI_MAX_CLKS && priv->clks[clk]; clk++) { + ret = clk_prepare_enable(priv->clks[clk]); + if (ret) + goto err_disable_clks; + } + + if (priv->phy) { + ret = phy_init(priv->phy); + if (ret) + goto err_disable_clks; + + ret = phy_power_on(priv->phy); + if (ret) + goto err_exit_phy; + } + + return 0; + +err_exit_phy: + phy_exit(priv->phy); +err_disable_clks: + while (--clk >= 0) + clk_disable_unprepare(priv->clks[clk]); + + return ret; +} + +static void ohci_platform_power_off(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); + int clk; + + if (priv->phy) { + phy_power_off(priv->phy); + phy_exit(priv->phy); + } + + for (clk = OHCI_MAX_CLKS - 1; clk >= 0; clk--) + if (priv->clks[clk]) + clk_disable_unprepare(priv->clks[clk]); +} + static struct hc_driver __read_mostly ohci_platform_hc_driver; static const struct ohci_driver_overrides platform_overrides __initconst = { - .product_desc = "Generic Platform OHCI controller", - .reset = ohci_platform_reset, + .product_desc = "Generic Platform OHCI controller", + .reset = ohci_platform_reset, + .extra_priv_size = sizeof(struct ohci_platform_priv), +}; + +static struct usb_ohci_pdata ohci_platform_defaults = { + .power_on = ohci_platform_power_on, + .power_suspend = ohci_platform_power_off, + .power_off = ohci_platform_power_off, }; static int ohci_platform_probe(struct platform_device *dev) @@ -60,17 +127,23 @@ static int ohci_platform_probe(struct platform_device *dev) struct usb_hcd *hcd; struct resource *res_mem; struct usb_ohci_pdata *pdata = dev_get_platdata(&dev->dev); - int irq; - int err = -ENOMEM; - - if (!pdata) { - WARN_ON(1); - return -ENODEV; - } + struct ohci_platform_priv *priv; + int err, irq, clk = 0; if (usb_disabled()) return -ENODEV; + /* + * Use reasonable defaults so platforms don't have to provide these + * with DT probing on ARM. + */ + if (!pdata) + pdata = &ohci_platform_defaults; + + err = dma_coerce_mask_and_coherent(&dev->dev, DMA_BIT_MASK(32)); + if (err) + return err; + irq = platform_get_irq(dev, 0); if (irq < 0) { dev_err(&dev->dev, "no irq provided"); @@ -83,17 +156,40 @@ static int ohci_platform_probe(struct platform_device *dev) return -ENXIO; } + hcd = usb_create_hcd(&ohci_platform_hc_driver, &dev->dev, + dev_name(&dev->dev)); + if (!hcd) + return -ENOMEM; + + platform_set_drvdata(dev, hcd); + dev->dev.platform_data = pdata; + priv = hcd_to_ohci_priv(hcd); + + if (pdata == &ohci_platform_defaults && dev->dev.of_node) { + priv->phy = devm_phy_get(&dev->dev, "usb"); + if (IS_ERR(priv->phy)) { + err = PTR_ERR(priv->phy); + if (err == -EPROBE_DEFER) + goto err_put_hcd; + priv->phy = NULL; + } + + for (clk = 0; clk < OHCI_MAX_CLKS; clk++) { + priv->clks[clk] = of_clk_get(dev->dev.of_node, clk); + if (IS_ERR(priv->clks[clk])) { + err = PTR_ERR(priv->clks[clk]); + if (err == -EPROBE_DEFER) + goto err_put_clks; + priv->clks[clk] = NULL; + break; + } + } + } + if (pdata->power_on) { err = pdata->power_on(dev); if (err < 0) - return err; - } - - hcd = usb_create_hcd(&ohci_platform_hc_driver, &dev->dev, - dev_name(&dev->dev)); - if (!hcd) { - err = -ENOMEM; - goto err_power; + goto err_put_clks; } hcd->rsrc_start = res_mem->start; @@ -102,11 +198,11 @@ static int ohci_platform_probe(struct platform_device *dev) hcd->regs = devm_ioremap_resource(&dev->dev, res_mem); if (IS_ERR(hcd->regs)) { err = PTR_ERR(hcd->regs); - goto err_put_hcd; + goto err_power; } err = usb_add_hcd(hcd, irq, IRQF_SHARED); if (err) - goto err_put_hcd; + goto err_power; device_wakeup_enable(hcd->self.controller); @@ -114,11 +210,17 @@ static int ohci_platform_probe(struct platform_device *dev) return err; -err_put_hcd: - usb_put_hcd(hcd); err_power: if (pdata->power_off) pdata->power_off(dev); +err_put_clks: + while (--clk >= 0) + clk_put(priv->clks[clk]); +err_put_hcd: + if (pdata == &ohci_platform_defaults) + dev->dev.platform_data = NULL; + + usb_put_hcd(hcd); return err; } @@ -127,13 +229,22 @@ static int ohci_platform_remove(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct usb_ohci_pdata *pdata = dev_get_platdata(&dev->dev); + struct ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); + int clk; usb_remove_hcd(hcd); - usb_put_hcd(hcd); if (pdata->power_off) pdata->power_off(dev); + for (clk = 0; clk < OHCI_MAX_CLKS && priv->clks[clk]; clk++) + clk_put(priv->clks[clk]); + + usb_put_hcd(hcd); + + if (pdata == &ohci_platform_defaults) + dev->dev.platform_data = NULL; + return 0; } @@ -180,6 +291,12 @@ static int ohci_platform_resume(struct device *dev) #define ohci_platform_resume NULL #endif /* CONFIG_PM */ +static const struct of_device_id ohci_platform_ids[] = { + { .compatible = "usb-ohci", }, + { } +}; +MODULE_DEVICE_TABLE(of, ohci_platform_ids); + static const struct platform_device_id ohci_platform_table[] = { { "ohci-platform", 0 }, { } @@ -200,6 +317,7 @@ static struct platform_driver ohci_platform_driver = { .owner = THIS_MODULE, .name = "ohci-platform", .pm = &ohci_platform_pm_ops, + .of_match_table = ohci_platform_ids, } }; -- cgit v1.2.3-70-g09d2 From a4aeb2117571292f4e002c54b3f91e138722bf7a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 7 Feb 2014 16:36:41 +0100 Subject: ehci-platform: Add support for clks and phy passed through devicetree Currently ehci-platform is only used in combination with devicetree when used with some Via socs. By extending it to (optionally) get clks and a phy from devicetree, and enabling / disabling those on power_on / off, it can be used more generically. Specifically after this commit it can be used for the ehci controller on Allwinner sunxi SoCs. Since ehci-platform is intended to handle any generic enough non pci ehci device, add a "usb-ehci" compatibility string. There already is a usb-ehci device-tree bindings document, update this with clks and phy bindings info. Although actually quite generic so far the via,vt8500 compatibilty string had its own bindings document. Somehow we even ended up with 2 of them. Since these provide no extra information over the generic usb-ehci documentation, this patch removes them. The ehci-ppc-of.c driver also claims the usb-ehci compatibility string, even though it mostly is ibm,usb-ehci-440epx specific. ehci-platform.c is not needed on ppc platforms, so add a !PPC_OF dependency to it to avoid 2 drivers claiming the same compatibility string getting build on ppc. Signed-off-by: Hans de Goede Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-ehci.txt | 25 +++- .../devicetree/bindings/usb/via,vt8500-ehci.txt | 15 --- .../devicetree/bindings/usb/vt8500-ehci.txt | 12 -- drivers/usb/host/Kconfig | 1 + drivers/usb/host/ehci-platform.c | 147 +++++++++++++++++---- 5 files changed, 142 insertions(+), 58 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/via,vt8500-ehci.txt delete mode 100644 Documentation/devicetree/bindings/usb/vt8500-ehci.txt diff --git a/Documentation/devicetree/bindings/usb/usb-ehci.txt b/Documentation/devicetree/bindings/usb/usb-ehci.txt index fa18612f757..2c1aeebe12b 100644 --- a/Documentation/devicetree/bindings/usb/usb-ehci.txt +++ b/Documentation/devicetree/bindings/usb/usb-ehci.txt @@ -7,13 +7,14 @@ Required properties: (debug-port or other) can be also specified here, but only after definition of standard EHCI registers. - interrupts : one EHCI interrupt should be described here. -If device registers are implemented in big endian mode, the device -node should have "big-endian-regs" property. -If controller implementation operates with big endian descriptors, -"big-endian-desc" property should be specified. -If both big endian registers and descriptors are used by the controller -implementation, "big-endian" property can be specified instead of having -both "big-endian-regs" and "big-endian-desc". + +Optional properties: + - big-endian-regs : boolean, set this for hcds with big-endian registers + - big-endian-desc : boolean, set this for hcds with big-endian descriptors + - big-endian : boolean, for hcds with big-endian-regs + big-endian-desc + - clocks : a list of phandle + clock specifier pairs + - phys : phandle + phy specifier pair + - phy-names : "usb" Example (Sequoia 440EPx): ehci@e0000300 { @@ -23,3 +24,13 @@ Example (Sequoia 440EPx): reg = <0 e0000300 90 0 e0000390 70>; big-endian; }; + +Example (Allwinner sun4i A10 SoC): + ehci0: usb@01c14000 { + compatible = "allwinner,sun4i-a10-ehci", "usb-ehci"; + reg = <0x01c14000 0x100>; + interrupts = <39>; + clocks = <&ahb_gates 1>; + phys = <&usbphy 1>; + phy-names = "usb"; + }; diff --git a/Documentation/devicetree/bindings/usb/via,vt8500-ehci.txt b/Documentation/devicetree/bindings/usb/via,vt8500-ehci.txt deleted file mode 100644 index 17b3ad1d97e..00000000000 --- a/Documentation/devicetree/bindings/usb/via,vt8500-ehci.txt +++ /dev/null @@ -1,15 +0,0 @@ -VIA/Wondermedia VT8500 EHCI Controller ------------------------------------------------------ - -Required properties: -- compatible : "via,vt8500-ehci" -- reg : Should contain 1 register ranges(address and length) -- interrupts : ehci controller interrupt - -Example: - - ehci@d8007900 { - compatible = "via,vt8500-ehci"; - reg = <0xd8007900 0x200>; - interrupts = <43>; - }; diff --git a/Documentation/devicetree/bindings/usb/vt8500-ehci.txt b/Documentation/devicetree/bindings/usb/vt8500-ehci.txt deleted file mode 100644 index 5fb8fd6e250..00000000000 --- a/Documentation/devicetree/bindings/usb/vt8500-ehci.txt +++ /dev/null @@ -1,12 +0,0 @@ -VIA VT8500 and Wondermedia WM8xxx SoC USB controllers. - -Required properties: - - compatible: Should be "via,vt8500-ehci" or "wm,prizm-ehci". - - reg: Address range of the ehci registers. size should be 0x200 - - interrupts: Should contain the ehci interrupt. - -usb: ehci@D8007100 { - compatible = "wm,prizm-ehci", "usb-ehci"; - reg = <0xD8007100 0x200>; - interrupts = <1>; -}; diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index a9707da7da0..e28cbe0e1f5 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -255,6 +255,7 @@ config USB_EHCI_ATH79 config USB_EHCI_HCD_PLATFORM tristate "Generic EHCI driver for a platform device" + depends on !PPC_OF default n ---help--- Adds an EHCI host driver for a generic platform device, which diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index 01536cfd361..5ebd0b75b58 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -3,6 +3,7 @@ * * Copyright 2007 Steven Brown * Copyright 2010-2012 Hauke Mehrtens + * Copyright 2014 Hans de Goede * * Derived from the ohci-ssb driver * Copyright 2007 Michael Buesch @@ -18,6 +19,7 @@ * * Licensed under the GNU/GPL. See COPYING for details. */ +#include #include #include #include @@ -25,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +36,13 @@ #include "ehci.h" #define DRIVER_DESC "EHCI generic platform driver" +#define EHCI_MAX_CLKS 3 +#define hcd_to_ehci_priv(h) ((struct ehci_platform_priv *)hcd_to_ehci(h)->priv) + +struct ehci_platform_priv { + struct clk *clks[EHCI_MAX_CLKS]; + struct phy *phy; +}; static const char hcd_name[] = "ehci-platform"; @@ -64,38 +74,90 @@ static int ehci_platform_reset(struct usb_hcd *hcd) return 0; } +static int ehci_platform_power_on(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); + int clk, ret; + + for (clk = 0; clk < EHCI_MAX_CLKS && priv->clks[clk]; clk++) { + ret = clk_prepare_enable(priv->clks[clk]); + if (ret) + goto err_disable_clks; + } + + if (priv->phy) { + ret = phy_init(priv->phy); + if (ret) + goto err_disable_clks; + + ret = phy_power_on(priv->phy); + if (ret) + goto err_exit_phy; + } + + return 0; + +err_exit_phy: + phy_exit(priv->phy); +err_disable_clks: + while (--clk >= 0) + clk_disable_unprepare(priv->clks[clk]); + + return ret; +} + +static void ehci_platform_power_off(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); + int clk; + + if (priv->phy) { + phy_power_off(priv->phy); + phy_exit(priv->phy); + } + + for (clk = EHCI_MAX_CLKS - 1; clk >= 0; clk--) + if (priv->clks[clk]) + clk_disable_unprepare(priv->clks[clk]); +} + static struct hc_driver __read_mostly ehci_platform_hc_driver; static const struct ehci_driver_overrides platform_overrides __initconst = { - .reset = ehci_platform_reset, + .reset = ehci_platform_reset, + .extra_priv_size = sizeof(struct ehci_platform_priv), }; -static struct usb_ehci_pdata ehci_platform_defaults; +static struct usb_ehci_pdata ehci_platform_defaults = { + .power_on = ehci_platform_power_on, + .power_suspend = ehci_platform_power_off, + .power_off = ehci_platform_power_off, +}; static int ehci_platform_probe(struct platform_device *dev) { struct usb_hcd *hcd; struct resource *res_mem; - struct usb_ehci_pdata *pdata; - int irq; - int err; + struct usb_ehci_pdata *pdata = dev_get_platdata(&dev->dev); + struct ehci_platform_priv *priv; + int err, irq, clk = 0; if (usb_disabled()) return -ENODEV; /* - * use reasonable defaults so platforms don't have to provide these. - * with DT probing on ARM, none of these are set. + * Use reasonable defaults so platforms don't have to provide these + * with DT probing on ARM. */ - if (!dev_get_platdata(&dev->dev)) - dev->dev.platform_data = &ehci_platform_defaults; + if (!pdata) + pdata = &ehci_platform_defaults; err = dma_coerce_mask_and_coherent(&dev->dev, DMA_BIT_MASK(32)); if (err) return err; - pdata = dev_get_platdata(&dev->dev); - irq = platform_get_irq(dev, 0); if (irq < 0) { dev_err(&dev->dev, "no irq provided"); @@ -107,17 +169,40 @@ static int ehci_platform_probe(struct platform_device *dev) return -ENXIO; } + hcd = usb_create_hcd(&ehci_platform_hc_driver, &dev->dev, + dev_name(&dev->dev)); + if (!hcd) + return -ENOMEM; + + platform_set_drvdata(dev, hcd); + dev->dev.platform_data = pdata; + priv = hcd_to_ehci_priv(hcd); + + if (pdata == &ehci_platform_defaults && dev->dev.of_node) { + priv->phy = devm_phy_get(&dev->dev, "usb"); + if (IS_ERR(priv->phy)) { + err = PTR_ERR(priv->phy); + if (err == -EPROBE_DEFER) + goto err_put_hcd; + priv->phy = NULL; + } + + for (clk = 0; clk < EHCI_MAX_CLKS; clk++) { + priv->clks[clk] = of_clk_get(dev->dev.of_node, clk); + if (IS_ERR(priv->clks[clk])) { + err = PTR_ERR(priv->clks[clk]); + if (err == -EPROBE_DEFER) + goto err_put_clks; + priv->clks[clk] = NULL; + break; + } + } + } + if (pdata->power_on) { err = pdata->power_on(dev); if (err < 0) - return err; - } - - hcd = usb_create_hcd(&ehci_platform_hc_driver, &dev->dev, - dev_name(&dev->dev)); - if (!hcd) { - err = -ENOMEM; - goto err_power; + goto err_put_clks; } hcd->rsrc_start = res_mem->start; @@ -126,22 +211,28 @@ static int ehci_platform_probe(struct platform_device *dev) hcd->regs = devm_ioremap_resource(&dev->dev, res_mem); if (IS_ERR(hcd->regs)) { err = PTR_ERR(hcd->regs); - goto err_put_hcd; + goto err_power; } err = usb_add_hcd(hcd, irq, IRQF_SHARED); if (err) - goto err_put_hcd; + goto err_power; device_wakeup_enable(hcd->self.controller); platform_set_drvdata(dev, hcd); return err; -err_put_hcd: - usb_put_hcd(hcd); err_power: if (pdata->power_off) pdata->power_off(dev); +err_put_clks: + while (--clk >= 0) + clk_put(priv->clks[clk]); +err_put_hcd: + if (pdata == &ehci_platform_defaults) + dev->dev.platform_data = NULL; + + usb_put_hcd(hcd); return err; } @@ -150,13 +241,19 @@ static int ehci_platform_remove(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct usb_ehci_pdata *pdata = dev_get_platdata(&dev->dev); + struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); + int clk; usb_remove_hcd(hcd); - usb_put_hcd(hcd); if (pdata->power_off) pdata->power_off(dev); + for (clk = 0; clk < EHCI_MAX_CLKS && priv->clks[clk]; clk++) + clk_put(priv->clks[clk]); + + usb_put_hcd(hcd); + if (pdata == &ehci_platform_defaults) dev->dev.platform_data = NULL; @@ -207,8 +304,10 @@ static int ehci_platform_resume(struct device *dev) static const struct of_device_id vt8500_ehci_ids[] = { { .compatible = "via,vt8500-ehci", }, { .compatible = "wm,prizm-ehci", }, + { .compatible = "usb-ehci", }, {} }; +MODULE_DEVICE_TABLE(of, vt8500_ehci_ids); static const struct platform_device_id ehci_platform_table[] = { { "ehci-platform", 0 }, -- cgit v1.2.3-70-g09d2 From b1034412570f38d32b88dfd5da5cb2f86b5c321c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 7 Feb 2014 16:36:42 +0100 Subject: ohci-platform: Add support for controllers with big-endian regs / descriptors Note this commit uses the same devicetree booleans for this as the ones already existing in the usb-ehci bindings, see: Documentation/devicetree/bindings/usb/usb-ehci.txt Signed-off-by: Hans de Goede Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-ohci.txt | 3 +++ drivers/usb/host/ohci-platform.c | 27 ++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/usb-ohci.txt b/Documentation/devicetree/bindings/usb/usb-ohci.txt index 6ba38d990d1..6933b0c6487 100644 --- a/Documentation/devicetree/bindings/usb/usb-ohci.txt +++ b/Documentation/devicetree/bindings/usb/usb-ohci.txt @@ -6,6 +6,9 @@ Required properties: - interrupts : ohci controller interrupt Optional properties: +- big-endian-regs : boolean, set this for hcds with big-endian registers +- big-endian-desc : boolean, set this for hcds with big-endian descriptors +- big-endian : boolean, for hcds with big-endian-regs + big-endian-desc - clocks : a list of phandle + clock specifier pairs - phys : phandle + phy specifier pair - phy-names : "usb" diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index 49304ddde42..e2c28fd0348 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -128,6 +128,7 @@ static int ohci_platform_probe(struct platform_device *dev) struct resource *res_mem; struct usb_ohci_pdata *pdata = dev_get_platdata(&dev->dev); struct ohci_platform_priv *priv; + struct ohci_hcd *ohci; int err, irq, clk = 0; if (usb_disabled()) @@ -164,8 +165,34 @@ static int ohci_platform_probe(struct platform_device *dev) platform_set_drvdata(dev, hcd); dev->dev.platform_data = pdata; priv = hcd_to_ohci_priv(hcd); + ohci = hcd_to_ohci(hcd); if (pdata == &ohci_platform_defaults && dev->dev.of_node) { + if (of_property_read_bool(dev->dev.of_node, "big-endian-regs")) + ohci->flags |= OHCI_QUIRK_BE_MMIO; + + if (of_property_read_bool(dev->dev.of_node, "big-endian-desc")) + ohci->flags |= OHCI_QUIRK_BE_DESC; + + if (of_property_read_bool(dev->dev.of_node, "big-endian")) + ohci->flags |= OHCI_QUIRK_BE_MMIO | OHCI_QUIRK_BE_DESC; + +#ifndef CONFIG_USB_OHCI_BIG_ENDIAN_MMIO + if (ohci->flags & OHCI_QUIRK_BE_MMIO) { + dev_err(&dev->dev, + "Error big-endian-regs not compiled in\n"); + err = -EINVAL; + goto err_put_hcd; + } +#endif +#ifndef CONFIG_USB_OHCI_BIG_ENDIAN_DESC + if (ohci->flags & OHCI_QUIRK_BE_DESC) { + dev_err(&dev->dev, + "Error big-endian-desc not compiled in\n"); + err = -EINVAL; + goto err_put_hcd; + } +#endif priv->phy = devm_phy_get(&dev->dev, "usb"); if (IS_ERR(priv->phy)) { err = PTR_ERR(priv->phy); -- cgit v1.2.3-70-g09d2 From ad3db5dabad169bfffcd36b94a2f65ae88a3405a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 7 Feb 2014 16:36:43 +0100 Subject: ehci-platform: Add support for controllers with big-endian regs / descriptors This uses the already documented devicetree booleans for this, see: Documentation/devicetree/bindings/usb/usb-ehci.txt Signed-off-by: Hans de Goede Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-platform.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index 5ebd0b75b58..8fde6490234 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -55,8 +55,10 @@ static int ehci_platform_reset(struct usb_hcd *hcd) hcd->has_tt = pdata->has_tt; ehci->has_synopsys_hc_bug = pdata->has_synopsys_hc_bug; - ehci->big_endian_desc = pdata->big_endian_desc; - ehci->big_endian_mmio = pdata->big_endian_mmio; + if (pdata->big_endian_desc) + ehci->big_endian_desc = 1; + if (pdata->big_endian_mmio) + ehci->big_endian_mmio = 1; if (pdata->pre_setup) { retval = pdata->pre_setup(hcd); @@ -142,6 +144,7 @@ static int ehci_platform_probe(struct platform_device *dev) struct resource *res_mem; struct usb_ehci_pdata *pdata = dev_get_platdata(&dev->dev); struct ehci_platform_priv *priv; + struct ehci_hcd *ehci; int err, irq, clk = 0; if (usb_disabled()) @@ -177,8 +180,34 @@ static int ehci_platform_probe(struct platform_device *dev) platform_set_drvdata(dev, hcd); dev->dev.platform_data = pdata; priv = hcd_to_ehci_priv(hcd); + ehci = hcd_to_ehci(hcd); if (pdata == &ehci_platform_defaults && dev->dev.of_node) { + if (of_property_read_bool(dev->dev.of_node, "big-endian-regs")) + ehci->big_endian_mmio = 1; + + if (of_property_read_bool(dev->dev.of_node, "big-endian-desc")) + ehci->big_endian_desc = 1; + + if (of_property_read_bool(dev->dev.of_node, "big-endian")) + ehci->big_endian_mmio = ehci->big_endian_desc = 1; + +#ifndef CONFIG_USB_EHCI_BIG_ENDIAN_MMIO + if (ehci->big_endian_mmio) { + dev_err(&dev->dev, + "Error big-endian-regs not compiled in\n"); + err = -EINVAL; + goto err_put_hcd; + } +#endif +#ifndef CONFIG_USB_EHCI_BIG_ENDIAN_DESC + if (ehci->big_endian_desc) { + dev_err(&dev->dev, + "Error big-endian-desc not compiled in\n"); + err = -EINVAL; + goto err_put_hcd; + } +#endif priv->phy = devm_phy_get(&dev->dev, "usb"); if (IS_ERR(priv->phy)) { err = PTR_ERR(priv->phy); -- cgit v1.2.3-70-g09d2 From 93571adbb132c47d4d3a8e42d47276862f9635a2 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Tue, 4 Feb 2014 15:19:40 -0800 Subject: usb: dwc2: handle the Host Port Interrupt when it occurs in device mode According to the spec for the DWC2 controller, when the PRTINT interrupt fires, the application must clear the appropriate status bit in the Host Port Control and Status register to clear this bit. When disconnecting an A-cable when the dwc2 host driver, the PRTINT fires, but only the GINTSTS_PRTINT status is cleared, no action is done with the HPRT0 register. The HPRT0_ENACHG bit in the HPRT0 must also be poked to correctly clear the GINTSTS_PRTINT interrupt. I am seeing this behavoir on v2.93 of the DWC2 IP. When I disconnect an OTG A-cable adapter, the PRTINT interrupt fires when the DWC2 is in device mode and is never cleared. This patch adds the function to read the HPRT0 register when the PRTINT fires and the dwc2 IP has already transitioned to device mode. This function is only clearing the HPRT0_ENACHG bit for now, but can be modified to handle more. Signed-off-by: Dinh Nguyen [ paulz: modified patch to preserve HPRT0_ENA bit ] Signed-off-by: Paul Zimmerman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/core_intr.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c index 8205799e6db..c93918b70d0 100644 --- a/drivers/usb/dwc2/core_intr.c +++ b/drivers/usb/dwc2/core_intr.c @@ -71,6 +71,26 @@ static const char *dwc2_op_state_str(struct dwc2_hsotg *hsotg) } } +/** + * dwc2_handle_usb_port_intr - handles OTG PRTINT interrupts. + * When the PRTINT interrupt fires, there are certain status bits in the Host + * Port that needs to get cleared. + * + * @hsotg: Programming view of DWC_otg controller + */ +static void dwc2_handle_usb_port_intr(struct dwc2_hsotg *hsotg) +{ + u32 hprt0 = readl(hsotg->regs + HPRT0); + + if (hprt0 & HPRT0_ENACHG) { + hprt0 &= ~HPRT0_ENA; + writel(hprt0, hsotg->regs + HPRT0); + } + + /* Clear interrupt */ + writel(GINTSTS_PRTINT, hsotg->regs + GINTSTS); +} + /** * dwc2_handle_mode_mismatch_intr() - Logs a mode mismatch warning message * @@ -479,9 +499,8 @@ irqreturn_t dwc2_handle_common_intr(int irq, void *dev) if (dwc2_is_device_mode(hsotg)) { dev_dbg(hsotg->dev, " --Port interrupt received in Device mode--\n"); - gintsts = GINTSTS_PRTINT; - writel(gintsts, hsotg->regs + GINTSTS); - retval = 1; + dwc2_handle_usb_port_intr(hsotg); + retval = IRQ_HANDLED; } } -- cgit v1.2.3-70-g09d2 From 5c73e74034820ca2a85101a52622f1c92237edc0 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 21 Jan 2014 09:50:51 +0300 Subject: usb: phy: msm: tiny leak on error in probe() Free "motog" on error. This is more to appease the static checkers than a real worry. Signed-off-by: Dan Carpenter Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-msm-usb.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/phy/phy-msm-usb.c b/drivers/usb/phy/phy-msm-usb.c index 8546c8dccd5..64c9d14ea77 100644 --- a/drivers/usb/phy/phy-msm-usb.c +++ b/drivers/usb/phy/phy-msm-usb.c @@ -1429,7 +1429,8 @@ static int __init msm_otg_probe(struct platform_device *pdev) motg->phy.otg = kzalloc(sizeof(struct usb_otg), GFP_KERNEL); if (!motg->phy.otg) { dev_err(&pdev->dev, "unable to allocate msm_otg\n"); - return -ENOMEM; + ret = -ENOMEM; + goto free_motg; } motg->pdata = dev_get_platdata(&pdev->dev); -- cgit v1.2.3-70-g09d2 From 22f6a0f0e3549b73b3319e26e1689f72b1f1284b Mon Sep 17 00:00:00 2001 From: Shaibal Dutta Date: Sat, 1 Feb 2014 19:16:46 -0800 Subject: usb: move hub init and LED blink work to power efficient workqueue Allow the scheduler to select the best CPU to handle hub initalization and LED blinking work. This extends idle residency times on idle CPUs and conserves power. This functionality is enabled when CONFIG_WQ_POWER_EFFICIENT is selected. [zoran.markovic@linaro.org: Rebased to latest kernel. Added commit message. Changed reference from system to power efficient workqueue for LEDs in check_highspeed() and hub_port_connect_change().] Acked-by: Alan Stern Cc: Sarah Sharp Cc: Xenia Ragiadakou Cc: Julius Werner Cc: Krzysztof Mazur Cc: Matthias Beyer Cc: Dan Williams Cc: Mathias Nyman Cc: Thomas Pugliese Signed-off-by: Shaibal Dutta Signed-off-by: Zoran Markovic Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 64ea21971be..519f2c3594b 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -499,7 +499,8 @@ static void led_work (struct work_struct *work) changed++; } if (changed) - schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD); + queue_delayed_work(system_power_efficient_wq, + &hub->leds, LED_CYCLE_PERIOD); } /* use a short timeout for hub/port status fetches */ @@ -1041,7 +1042,8 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) if (type == HUB_INIT) { delay = hub_power_on(hub, false); PREPARE_DELAYED_WORK(&hub->init_work, hub_init_func2); - schedule_delayed_work(&hub->init_work, + queue_delayed_work(system_power_efficient_wq, + &hub->init_work, msecs_to_jiffies(delay)); /* Suppress autosuspend until init is done */ @@ -1195,7 +1197,8 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) /* Don't do a long sleep inside a workqueue routine */ if (type == HUB_INIT2) { PREPARE_DELAYED_WORK(&hub->init_work, hub_init_func3); - schedule_delayed_work(&hub->init_work, + queue_delayed_work(system_power_efficient_wq, + &hub->init_work, msecs_to_jiffies(delay)); return; /* Continues at init3: below */ } else { @@ -1209,7 +1212,8 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) if (status < 0) dev_err(hub->intfdev, "activate --> %d\n", status); if (hub->has_indicators && blinkenlights) - schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD); + queue_delayed_work(system_power_efficient_wq, + &hub->leds, LED_CYCLE_PERIOD); /* Scan all ports that need attention */ kick_khubd(hub); @@ -4311,7 +4315,8 @@ check_highspeed (struct usb_hub *hub, struct usb_device *udev, int port1) /* hub LEDs are probably harder to miss than syslog */ if (hub->has_indicators) { hub->indicator[port1-1] = INDICATOR_GREEN_BLINK; - schedule_delayed_work (&hub->leds, 0); + queue_delayed_work(system_power_efficient_wq, + &hub->leds, 0); } } kfree(qual); @@ -4540,7 +4545,9 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, if (hub->has_indicators) { hub->indicator[port1-1] = INDICATOR_AMBER_BLINK; - schedule_delayed_work (&hub->leds, 0); + queue_delayed_work( + system_power_efficient_wq, + &hub->leds, 0); } status = -ENOTCONN; /* Don't retry */ goto loop_disable; -- cgit v1.2.3-70-g09d2 From ce149c30b9f89d0c9addd1d71ccdb57c1051553b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 11 Feb 2014 17:35:28 +0100 Subject: ohci-platform: Change compatible string from usb-ohci to generic-ohci The initial versions of the devicetree enablement patches for ohci-platform used "ohci-platform" as compatible string. However this was disliked by various reviewers because the platform bus is a Linux invention and devicetree is supposed to be OS agnostic. After much discussion I gave up and went with the generic usb-ohci as requested. In retro-spect I should have chosen something different, the dts files for many existing boards already claim to be compatible with "usb-ohci", ie they have: compatible = "ti,ohci-omap3", "usb-ohci"; In theory this should not be a problem since the "ti,ohci-omap3" entry takes presedence, but in practice using a conflicting compatible string is an issue, because it makes which driver gets used depend on driver registration order. This patch changes the compatible string claimed by ohci-platform to "generic-ohci", avoiding the driver registration / module loading ordering problems. Signed-off-by: Hans de Goede Tested-by: Kevin Hilman Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-ohci.txt | 4 ++-- drivers/usb/host/ohci-platform.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/usb-ohci.txt b/Documentation/devicetree/bindings/usb/usb-ohci.txt index 6933b0c6487..45f67d91e88 100644 --- a/Documentation/devicetree/bindings/usb/usb-ohci.txt +++ b/Documentation/devicetree/bindings/usb/usb-ohci.txt @@ -1,7 +1,7 @@ USB OHCI controllers Required properties: -- compatible : "usb-ohci" +- compatible : "generic-ohci" - reg : ohci controller register range (address and length) - interrupts : ohci controller interrupt @@ -16,7 +16,7 @@ Optional properties: Example: ohci0: usb@01c14400 { - compatible = "allwinner,sun4i-a10-ohci", "usb-ohci"; + compatible = "allwinner,sun4i-a10-ohci", "generic-ohci"; reg = <0x01c14400 0x100>; interrupts = <64>; clocks = <&usb_clk 6>, <&ahb_gates 2>; diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index e2c28fd0348..b6ca0b25259 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -319,7 +319,7 @@ static int ohci_platform_resume(struct device *dev) #endif /* CONFIG_PM */ static const struct of_device_id ohci_platform_ids[] = { - { .compatible = "usb-ohci", }, + { .compatible = "generic-ohci", }, { } }; MODULE_DEVICE_TABLE(of, ohci_platform_ids); -- cgit v1.2.3-70-g09d2 From 915974c34ee056be918b7ea287a870766a0db6ba Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 11 Feb 2014 17:35:29 +0100 Subject: ehci-platform: Change compatible string from usb-ehci to generic-ehci The initial versions of the devicetree enablement patches for ehci-platform used "ehci-platform" as compatible string. However this was disliked by various reviewers because the platform bus is a Linux invention and devicetree is supposed to be OS agnostic. After much discussion I gave up, added a: "depends on !PPC_OF" to Kconfig to avoid a known conflict with PPC-OF platforms and went with the generic usb-ehci as requested. In retro-spect I should have chosen something different, the dts files for many existing boards already claim to be compatible with "usb-ehci", ie they have: compatible = "ti,ehci-omap", "usb-ehci"; In theory this should not be a problem since the "ti,ehci-omap" entry takes presedence, but in practice using a conflicting compatible string is an issue, because it makes which driver gets used depend on driver registration order. This patch changes the compatible string claimed by ehci-platform to "generic-ehci", avoiding the driver registration / module loading ordering problems, and removes the "depends on !PPC_OF" workaround. Signed-off-by: Hans de Goede Acked-by: Alan Stern Tested-by: Kevin Hilman Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-ehci.txt | 4 ++-- drivers/usb/host/Kconfig | 1 - drivers/usb/host/ehci-platform.c | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/usb-ehci.txt b/Documentation/devicetree/bindings/usb/usb-ehci.txt index 2c1aeebe12b..ff151ec084c 100644 --- a/Documentation/devicetree/bindings/usb/usb-ehci.txt +++ b/Documentation/devicetree/bindings/usb/usb-ehci.txt @@ -1,7 +1,7 @@ USB EHCI controllers Required properties: - - compatible : should be "usb-ehci". + - compatible : should be "generic-ehci". - reg : should contain at least address and length of the standard EHCI register set for the device. Optional platform-dependent registers (debug-port or other) can be also specified here, but only after @@ -27,7 +27,7 @@ Example (Sequoia 440EPx): Example (Allwinner sun4i A10 SoC): ehci0: usb@01c14000 { - compatible = "allwinner,sun4i-a10-ehci", "usb-ehci"; + compatible = "allwinner,sun4i-a10-ehci", "generic-ehci"; reg = <0x01c14000 0x100>; interrupts = <39>; clocks = <&ahb_gates 1>; diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index e28cbe0e1f5..a9707da7da0 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -255,7 +255,6 @@ config USB_EHCI_ATH79 config USB_EHCI_HCD_PLATFORM tristate "Generic EHCI driver for a platform device" - depends on !PPC_OF default n ---help--- Adds an EHCI host driver for a generic platform device, which diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index 8fde6490234..117873033d0 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -333,7 +333,7 @@ static int ehci_platform_resume(struct device *dev) static const struct of_device_id vt8500_ehci_ids[] = { { .compatible = "via,vt8500-ehci", }, { .compatible = "wm,prizm-ehci", }, - { .compatible = "usb-ehci", }, + { .compatible = "generic-ehci", }, {} }; MODULE_DEVICE_TABLE(of, vt8500_ehci_ids); -- cgit v1.2.3-70-g09d2 From 843d5e036419bddb4aaf21d60c7ffe437e963166 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 11 Feb 2014 11:26:10 -0500 Subject: USB: ehci-platform: check for platform data misconfiguration The ehci-platform driver checks for misconfigurations in cases where the Device Tree data specifies big-endian registers or descriptors but the corresponding driver config settings have not been enabled. As Jonas Gorski suggested, we may as well apply the same check to general platform data too. This requires moving the code that sets the big-endian quirk flags from the ehci_platform_reset() routine into ehci_platform_probe(), and moving the checks out of the DT-specific "if" statement clause. The patch also changes the text of the error messages in an attempt to make the nature of the error more clear. Signed-off-by: Alan Stern Reported-by: Jonas Gorski Acked-by: Hans de Goede Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-platform.c | 42 +++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index 117873033d0..b3a0e11073a 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -55,10 +55,6 @@ static int ehci_platform_reset(struct usb_hcd *hcd) hcd->has_tt = pdata->has_tt; ehci->has_synopsys_hc_bug = pdata->has_synopsys_hc_bug; - if (pdata->big_endian_desc) - ehci->big_endian_desc = 1; - if (pdata->big_endian_mmio) - ehci->big_endian_mmio = 1; if (pdata->pre_setup) { retval = pdata->pre_setup(hcd); @@ -192,22 +188,6 @@ static int ehci_platform_probe(struct platform_device *dev) if (of_property_read_bool(dev->dev.of_node, "big-endian")) ehci->big_endian_mmio = ehci->big_endian_desc = 1; -#ifndef CONFIG_USB_EHCI_BIG_ENDIAN_MMIO - if (ehci->big_endian_mmio) { - dev_err(&dev->dev, - "Error big-endian-regs not compiled in\n"); - err = -EINVAL; - goto err_put_hcd; - } -#endif -#ifndef CONFIG_USB_EHCI_BIG_ENDIAN_DESC - if (ehci->big_endian_desc) { - dev_err(&dev->dev, - "Error big-endian-desc not compiled in\n"); - err = -EINVAL; - goto err_put_hcd; - } -#endif priv->phy = devm_phy_get(&dev->dev, "usb"); if (IS_ERR(priv->phy)) { err = PTR_ERR(priv->phy); @@ -228,6 +208,28 @@ static int ehci_platform_probe(struct platform_device *dev) } } + if (pdata->big_endian_desc) + ehci->big_endian_desc = 1; + if (pdata->big_endian_mmio) + ehci->big_endian_mmio = 1; + +#ifndef CONFIG_USB_EHCI_BIG_ENDIAN_MMIO + if (ehci->big_endian_mmio) { + dev_err(&dev->dev, + "Error: CONFIG_USB_EHCI_BIG_ENDIAN_MMIO not set\n"); + err = -EINVAL; + goto err_put_clks; + } +#endif +#ifndef CONFIG_USB_EHCI_BIG_ENDIAN_DESC + if (ehci->big_endian_desc) { + dev_err(&dev->dev, + "Error: CONFIG_USB_EHCI_BIG_ENDIAN_DESC not set\n"); + err = -EINVAL; + goto err_put_clks; + } +#endif + if (pdata->power_on) { err = pdata->power_on(dev); if (err < 0) -- cgit v1.2.3-70-g09d2 From adff52952ef52c4dbfb930727f6f8cfe14d9967c Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 11 Feb 2014 11:26:00 -0500 Subject: USB: ohci-platform: check for platform data misconfiguration The ohci-platform driver checks for misconfigurations in cases where the Device Tree data specifies big-endian registers or descriptors but the corresponding driver config settings have not been enabled. As Jonas Gorski suggested, we may as well apply the same check to general platform data too. This requires moving the code that sets the big-endian quirk flags from the ohci_platform_reset() routine into ohci_platform_probe(), and moving the checks out of the DT-specific "if" statement clause. The patch also changes the text of the error messages in an attempt to make the nature of the error more clear. Signed-off-by: Alan Stern Reported-by: Jonas Gorski Acked-by: Hans de Goede Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-platform.c | 42 +++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index b6ca0b25259..b6002c951c5 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -47,10 +47,6 @@ static int ohci_platform_reset(struct usb_hcd *hcd) struct usb_ohci_pdata *pdata = dev_get_platdata(&pdev->dev); struct ohci_hcd *ohci = hcd_to_ohci(hcd); - if (pdata->big_endian_desc) - ohci->flags |= OHCI_QUIRK_BE_DESC; - if (pdata->big_endian_mmio) - ohci->flags |= OHCI_QUIRK_BE_MMIO; if (pdata->no_big_frame_no) ohci->flags |= OHCI_QUIRK_FRAME_NO; if (pdata->num_ports) @@ -177,22 +173,6 @@ static int ohci_platform_probe(struct platform_device *dev) if (of_property_read_bool(dev->dev.of_node, "big-endian")) ohci->flags |= OHCI_QUIRK_BE_MMIO | OHCI_QUIRK_BE_DESC; -#ifndef CONFIG_USB_OHCI_BIG_ENDIAN_MMIO - if (ohci->flags & OHCI_QUIRK_BE_MMIO) { - dev_err(&dev->dev, - "Error big-endian-regs not compiled in\n"); - err = -EINVAL; - goto err_put_hcd; - } -#endif -#ifndef CONFIG_USB_OHCI_BIG_ENDIAN_DESC - if (ohci->flags & OHCI_QUIRK_BE_DESC) { - dev_err(&dev->dev, - "Error big-endian-desc not compiled in\n"); - err = -EINVAL; - goto err_put_hcd; - } -#endif priv->phy = devm_phy_get(&dev->dev, "usb"); if (IS_ERR(priv->phy)) { err = PTR_ERR(priv->phy); @@ -213,6 +193,28 @@ static int ohci_platform_probe(struct platform_device *dev) } } + if (pdata->big_endian_desc) + ohci->flags |= OHCI_QUIRK_BE_DESC; + if (pdata->big_endian_mmio) + ohci->flags |= OHCI_QUIRK_BE_MMIO; + +#ifndef CONFIG_USB_OHCI_BIG_ENDIAN_MMIO + if (ohci->flags & OHCI_QUIRK_BE_MMIO) { + dev_err(&dev->dev, + "Error: CONFIG_USB_OHCI_BIG_ENDIAN_MMIO not set\n"); + err = -EINVAL; + goto err_put_clks; + } +#endif +#ifndef CONFIG_USB_OHCI_BIG_ENDIAN_DESC + if (ohci->flags & OHCI_QUIRK_BE_DESC) { + dev_err(&dev->dev, + "Error: CONFIG_USB_OHCI_BIG_ENDIAN_DESC not set\n"); + err = -EINVAL; + goto err_put_clks; + } +#endif + if (pdata->power_on) { err = pdata->power_on(dev); if (err < 0) -- cgit v1.2.3-70-g09d2 From 275200b38d7f996c7a658e62ca38b0a788dfc489 Mon Sep 17 00:00:00 2001 From: Richard Weinberger Date: Sun, 9 Feb 2014 19:48:01 +0100 Subject: Remove MACH_OMAP_H4_OTG The symbol is an orphan, get rid of it. Signed-off-by: Richard Weinberger Acked-by: Aaro Koskinen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 8154165aa60..42f017afd36 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -226,7 +226,7 @@ config USB_GR_UDC config USB_OMAP tristate "OMAP USB Device Controller" depends on ARCH_OMAP1 - select ISP1301_OMAP if MACH_OMAP_H2 || MACH_OMAP_H3 || MACH_OMAP_H4_OTG + select ISP1301_OMAP if MACH_OMAP_H2 || MACH_OMAP_H3 help Many Texas Instruments OMAP processors have flexible full speed USB device controllers, with support for up to 30 -- cgit v1.2.3-70-g09d2 From ea17c7c6ad3176fede61b07d5dc6a9abe665fa1a Mon Sep 17 00:00:00 2001 From: Paul Bolle Date: Tue, 11 Feb 2014 13:23:07 +0100 Subject: USB: ELAN: Remove useless "default M" lines The Kconfig entries for USB_U132_HCD and USB_FTDI_ELAN default to (uppercase) "M". But in Kconfig (lowercase) "m" is a magic symbol. "M" is an ordinary symbol. As "M" is never set these Kconfig symbols will also not be set by default. Since I'm not aware of a reason why these driver should be set by default, let's just drop these lines (that basically do nothing). Signed-off-by: Paul Bolle Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 1 - drivers/usb/misc/Kconfig | 1 - 2 files changed, 2 deletions(-) diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index a9707da7da0..e22b8266083 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -584,7 +584,6 @@ config FHCI_DEBUG config USB_U132_HCD tristate "Elan U132 Adapter Host Controller" depends on USB_FTDI_ELAN - default M help The U132 adapter is a USB to CardBus adapter specifically designed for PC cards that contain an OHCI host controller. Typical PC cards diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index ba5f70f9288..1bca274dc3b 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -128,7 +128,6 @@ config USB_IDMOUSE config USB_FTDI_ELAN tristate "Elan PCMCIA CardBus Adapter USB Client" - default M help ELAN's Uxxx series of adapters are USB to PCMCIA CardBus adapters. Currently only the U132 adapter is available. -- cgit v1.2.3-70-g09d2 From e8fcbb61405997f03b9e127806db620c7cfb9909 Mon Sep 17 00:00:00 2001 From: Christian Vogel Date: Mon, 10 Feb 2014 18:49:43 +0100 Subject: usb/misc/usbled: Add Riso Kagaku Webmail Notifier Add support for the "Webmail Notifier" (USB powered LED for signaling new emails) made by Riso Kagaku Corp. which displays 7 distinct colors. USB Protocol initially reverse engineered by https://code.google.com/p/usbmailnotifier/. Signed-off-by: Christian Vogel Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/usbled.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/usb/misc/usbled.c b/drivers/usb/misc/usbled.c index 78eb4ff3326..bdef0d6eb91 100644 --- a/drivers/usb/misc/usbled.c +++ b/drivers/usb/misc/usbled.c @@ -22,8 +22,27 @@ enum led_type { DELCOM_VISUAL_SIGNAL_INDICATOR, DREAM_CHEEKY_WEBMAIL_NOTIFIER, + RISO_KAGAKU_LED }; +/* the Webmail LED made by RISO KAGAKU CORP. decodes a color index + internally, we want to keep the red+green+blue sysfs api, so we decode + from 1-bit RGB to the riso kagaku color index according to this table... */ + +static unsigned const char riso_kagaku_tbl[] = { +/* R+2G+4B -> riso kagaku color index */ + [0] = 0, /* black */ + [1] = 2, /* red */ + [2] = 1, /* green */ + [3] = 5, /* yellow */ + [4] = 3, /* blue */ + [5] = 6, /* magenta */ + [6] = 4, /* cyan */ + [7] = 7 /* white */ +}; + +#define RISO_KAGAKU_IX(r,g,b) riso_kagaku_tbl[((r)?1:0)+((g)?2:0)+((b)?4:0)] + /* table of devices that work with this driver */ static const struct usb_device_id id_table[] = { { USB_DEVICE(0x0fc5, 0x1223), @@ -32,6 +51,8 @@ static const struct usb_device_id id_table[] = { .driver_info = DREAM_CHEEKY_WEBMAIL_NOTIFIER }, { USB_DEVICE(0x1d34, 0x000a), .driver_info = DREAM_CHEEKY_WEBMAIL_NOTIFIER }, + { USB_DEVICE(0x1294, 0x1320), + .driver_info = RISO_KAGAKU_LED }, { }, }; MODULE_DEVICE_TABLE(usb, id_table); @@ -48,6 +69,7 @@ static void change_color(struct usb_led *led) { int retval = 0; unsigned char *buffer; + int actlength; buffer = kmalloc(8, GFP_KERNEL); if (!buffer) { @@ -104,6 +126,18 @@ static void change_color(struct usb_led *led) 2000); break; + case RISO_KAGAKU_LED: + buffer[0] = RISO_KAGAKU_IX(led->red, led->green, led->blue); + buffer[1] = 0; + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 0; + + retval = usb_interrupt_msg(led->udev, + usb_sndctrlpipe(led->udev, 2), + buffer, 5, &actlength, 1000 /*ms timeout*/); + break; + default: dev_err(&led->udev->dev, "unknown device type %d\n", led->type); } -- cgit v1.2.3-70-g09d2 From e16fa44b39189cf31baaa8e880bc1c23a458c669 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 11 Feb 2014 17:54:45 +0100 Subject: uhci-platform: Change compatible string from platform-uhci to generic-uhci This brings the uhci-platform bindings in sync with what we've done for the ohci- and ehci-platform drivers. As discussed there using platform as a prefix is a bit weird as the platform bus is a Linux specific thing and the bindings are supposed to be OS agnostic. Note that the old platform-uhci compatible string is kept around for, well, compatibility reasons. While at it rename the bindings txt file to match the name of all the other ?hci-platform bindings docs. Signed-off-by: Hans de Goede Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/platform-uhci.txt | 15 --------------- Documentation/devicetree/bindings/usb/usb-uhci.txt | 15 +++++++++++++++ drivers/usb/host/uhci-platform.c | 1 + 3 files changed, 16 insertions(+), 15 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/platform-uhci.txt create mode 100644 Documentation/devicetree/bindings/usb/usb-uhci.txt diff --git a/Documentation/devicetree/bindings/usb/platform-uhci.txt b/Documentation/devicetree/bindings/usb/platform-uhci.txt deleted file mode 100644 index a4fb0719d15..00000000000 --- a/Documentation/devicetree/bindings/usb/platform-uhci.txt +++ /dev/null @@ -1,15 +0,0 @@ -Generic Platform UHCI Controller ------------------------------------------------------ - -Required properties: -- compatible : "platform-uhci" -- reg : Should contain 1 register ranges(address and length) -- interrupts : UHCI controller interrupt - -Example: - - uhci@d8007b00 { - compatible = "platform-uhci"; - reg = <0xd8007b00 0x200>; - interrupts = <43>; - }; diff --git a/Documentation/devicetree/bindings/usb/usb-uhci.txt b/Documentation/devicetree/bindings/usb/usb-uhci.txt new file mode 100644 index 00000000000..298133416c9 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/usb-uhci.txt @@ -0,0 +1,15 @@ +Generic Platform UHCI Controller +----------------------------------------------------- + +Required properties: +- compatible : "generic-uhci" (deprecated: "platform-uhci") +- reg : Should contain 1 register ranges(address and length) +- interrupts : UHCI controller interrupt + +Example: + + uhci@d8007b00 { + compatible = "generic-uhci"; + reg = <0xd8007b00 0x200>; + interrupts = <43>; + }; diff --git a/drivers/usb/host/uhci-platform.c b/drivers/usb/host/uhci-platform.c index 44e6c9da889..01833ab2b5c 100644 --- a/drivers/usb/host/uhci-platform.c +++ b/drivers/usb/host/uhci-platform.c @@ -148,6 +148,7 @@ static void uhci_hcd_platform_shutdown(struct platform_device *op) } static const struct of_device_id platform_uhci_ids[] = { + { .compatible = "generic-uhci", }, { .compatible = "platform-uhci", }, {} }; -- cgit v1.2.3-70-g09d2 From 0f94388b27c599015b74eedf1a32126a3f5fc0f9 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 11 Feb 2014 17:54:46 +0100 Subject: xhci-platform: Change compatible string from xhci-platform to generic-xhci This brings the xhci-platform bindings in sync with what we've done for the ohci- and ehci-platform drivers. As discussed there using platform as a postfix is a bit weird as the platform bus is a Linux specific thing and the bindings are supposed to be OS agnostic. Note that the old xhci-platform compatible string is kept around for, well, compatibility reasons. Signed-off-by: Hans de Goede Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-xhci.txt | 4 ++-- drivers/usb/host/xhci-plat.c | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/usb-xhci.txt b/Documentation/devicetree/bindings/usb/usb-xhci.txt index 5752df0e17a..90f8f607d12 100644 --- a/Documentation/devicetree/bindings/usb/usb-xhci.txt +++ b/Documentation/devicetree/bindings/usb/usb-xhci.txt @@ -1,14 +1,14 @@ USB xHCI controllers Required properties: - - compatible: should be "xhci-platform". + - compatible: should be "generic-xhci" (deprecated: "xhci-platform"). - reg: should contain address and length of the standard XHCI register set for the device. - interrupts: one XHCI interrupt should be described here. Example: usb@f0931000 { - compatible = "xhci-platform"; + compatible = "generic-xhci"; reg = <0xf0931000 0x8c8>; interrupts = <0x0 0x4e 0x0>; }; diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 8abda5c73ca..8affef91078 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -226,6 +226,7 @@ static const struct dev_pm_ops xhci_plat_pm_ops = { #ifdef CONFIG_OF static const struct of_device_id usb_xhci_of_match[] = { + { .compatible = "generic-xhci" }, { .compatible = "xhci-platform" }, { }, }; -- cgit v1.2.3-70-g09d2 From 2b54fa6bbef4dbc0beabcc989625204b608d062d Mon Sep 17 00:00:00 2001 From: Paul Zimmerman Date: Wed, 12 Feb 2014 17:44:35 -0800 Subject: usb: dwc2: fix dereference before NULL check In a couple of places, we were checking qtd->urb for NULL after we had already dereferenced it. Fix this by moving the check to before the dereference. Signed-off-by: Paul Zimmerman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/hcd_intr.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/usb/dwc2/hcd_intr.c b/drivers/usb/dwc2/hcd_intr.c index 012f17ec1a3..47b9eb5389b 100644 --- a/drivers/usb/dwc2/hcd_intr.c +++ b/drivers/usb/dwc2/hcd_intr.c @@ -975,8 +975,8 @@ static void dwc2_hc_xfercomp_intr(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd) { struct dwc2_hcd_urb *urb = qtd->urb; - int pipe_type = dwc2_hcd_get_pipe_type(&urb->pipe_info); enum dwc2_halt_status halt_status = DWC2_HC_XFER_COMPLETE; + int pipe_type; int urb_xfer_done; if (dbg_hc(chan)) @@ -984,6 +984,11 @@ static void dwc2_hc_xfercomp_intr(struct dwc2_hsotg *hsotg, "--Host Channel %d Interrupt: Transfer Complete--\n", chnum); + if (!urb) + goto handle_xfercomp_done; + + pipe_type = dwc2_hcd_get_pipe_type(&urb->pipe_info); + if (hsotg->core_params->dma_desc_enable > 0) { dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, halt_status); if (pipe_type == USB_ENDPOINT_XFER_ISOC) @@ -1005,9 +1010,6 @@ static void dwc2_hc_xfercomp_intr(struct dwc2_hsotg *hsotg, } } - if (!urb) - goto handle_xfercomp_done; - /* Update the QTD and URB states */ switch (pipe_type) { case USB_ENDPOINT_XFER_CONTROL: @@ -1105,7 +1107,7 @@ static void dwc2_hc_stall_intr(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd) { struct dwc2_hcd_urb *urb = qtd->urb; - int pipe_type = dwc2_hcd_get_pipe_type(&urb->pipe_info); + int pipe_type; dev_dbg(hsotg->dev, "--Host Channel %d Interrupt: STALL Received--\n", chnum); @@ -1119,6 +1121,8 @@ static void dwc2_hc_stall_intr(struct dwc2_hsotg *hsotg, if (!urb) goto handle_stall_halt; + pipe_type = dwc2_hcd_get_pipe_type(&urb->pipe_info); + if (pipe_type == USB_ENDPOINT_XFER_CONTROL) dwc2_host_complete(hsotg, qtd, -EPIPE); -- cgit v1.2.3-70-g09d2 From 759db9ead2c064203e9cb46bfff7c8ef25417b65 Mon Sep 17 00:00:00 2001 From: Christian Vogel Date: Wed, 12 Feb 2014 20:56:00 +0100 Subject: usbhid/quirks: Ignore Riso Kagaku Webmail Notifier The "Webmail Notifier" is a USB controlled LED that appears as a HID device. When trying to change the LED via hidraw it returns malformed reports. As "usbled" supports it, we blacklist it in usbhid. Signed-off-by: Christian Vogel Signed-off-by: Greg Kroah-Hartman --- drivers/hid/hid-core.c | 1 + drivers/hid/hid-ids.h | 3 +++ 2 files changed, 4 insertions(+) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 3bfac3accd2..5a86198da3a 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2275,6 +2275,7 @@ static const struct hid_device_id hid_ignore_list[] = { { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_1_PHIDGETSERVO_20) }, { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_8_8_4_IF_KIT) }, { HID_USB_DEVICE(USB_VENDOR_ID_YEALINK, USB_DEVICE_ID_YEALINK_P1K_P4K_B2K) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RISO_KAGAKU, USB_DEVICE_ID_RI_KA_WEBMAIL) }, { } }; diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 5a5248f2cc0..b0027f7a46b 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -953,4 +953,7 @@ #define USB_DEVICE_ID_PRIMAX_KEYBOARD 0x4e05 +#define USB_VENDOR_ID_RISO_KAGAKU 0x1294 /* Riso Kagaku Corp. */ +#define USB_DEVICE_ID_RI_KA_WEBMAIL 0x1320 /* Webmail Notifier */ + #endif -- cgit v1.2.3-70-g09d2 From 6d36b6f313738e99fc8b903697f8ecf06add29aa Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 11 Feb 2014 17:03:51 +0100 Subject: phy-core: Don't allow building phy-core as a module include/phy/phy.h has stub code in there for when building without the phy-core enabled. This is useful for generic drivers such as ahci-platform, ehci-platoform and ohci-platform which have support for driving an optional phy passed to them through the devicetree. Since on some boards this phy functionality is not needed, being able to disable the phy subsystem without needing a lot of #ifdef magic in the driver using it is quite useful. However this breaks when the module using the phy subsystem is build-in and the phy-core is not, which leads to the build failing with missing symbol errors in the linking stage of the zImage. Which leads to gems such as this being added to the Kconfig for achi_platform: depends on GENERIC_PHY || !GENERIC_PHY Rather then duplicating this code in a lot of places using the phy-core, I believe it is better to simply not allow the phy-core to be built as a module. The phy core is quite small and has no external dependencies, so always building it in when enabling it should not be an issue. Signed-off-by: Hans de Goede Acked-by: Andrew Lunn Acked-by: Roger Quadros Signed-off-by: Greg Kroah-Hartman --- drivers/phy/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index afa2354f660..4ef8755733c 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -5,7 +5,7 @@ menu "PHY Subsystem" config GENERIC_PHY - tristate "PHY Core" + bool "PHY Core" help Generic PHY support. -- cgit v1.2.3-70-g09d2 From c57c41d2c6a1fdb3cfecb5edcee386d5db67ac02 Mon Sep 17 00:00:00 2001 From: George Cherian Date: Mon, 27 Jan 2014 15:07:24 +0530 Subject: usb: musb: musb_host: Enable ISOCH IN handling for AM335x host Enable the isochrounous IN handling for AM335x HOST. Reprogram CPPI to receive consecutive ISOCH frames in the same URB. Signed-off-by: George Cherian Signed-off-by: Felipe Balbi --- drivers/usb/musb/musb_host.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index ed455724017..79b75106f27 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -1691,7 +1691,8 @@ void musb_host_rx(struct musb *musb, u8 epnum) | MUSB_RXCSR_RXPKTRDY); musb_writew(hw_ep->regs, MUSB_RXCSR, val); -#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_UX500_DMA) +#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_UX500_DMA) || \ + defined(CONFIG_USB_TI_CPPI41_DMA) if (usb_pipeisoc(pipe)) { struct usb_iso_packet_descriptor *d; @@ -1704,10 +1705,30 @@ void musb_host_rx(struct musb *musb, u8 epnum) if (d->status != -EILSEQ && d->status != -EOVERFLOW) d->status = 0; - if (++qh->iso_idx >= urb->number_of_packets) + if (++qh->iso_idx >= urb->number_of_packets) { done = true; - else + } else { +#if defined(CONFIG_USB_TI_CPPI41_DMA) + struct dma_controller *c; + dma_addr_t *buf; + u32 length, ret; + + c = musb->dma_controller; + buf = (void *) + urb->iso_frame_desc[qh->iso_idx].offset + + (u32)urb->transfer_dma; + + length = + urb->iso_frame_desc[qh->iso_idx].length; + + val |= MUSB_RXCSR_DMAENAB; + musb_writew(hw_ep->regs, MUSB_RXCSR, val); + + ret = c->channel_program(dma, qh->maxpacket, + 0, (u32) buf, length); +#endif done = false; + } } else { /* done if urb buffer is full or short packet is recd */ @@ -1747,7 +1768,8 @@ void musb_host_rx(struct musb *musb, u8 epnum) } /* we are expecting IN packets */ -#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_UX500_DMA) +#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_UX500_DMA) || \ + defined(CONFIG_USB_TI_CPPI41_DMA) if (dma) { struct dma_controller *c; u16 rx_count; -- cgit v1.2.3-70-g09d2 From f82503f549c70c15e283511270e6713a912fef37 Mon Sep 17 00:00:00 2001 From: George Cherian Date: Mon, 27 Jan 2014 15:07:25 +0530 Subject: usb: musb: musb_cppi41: Make CPPI aware of high bandwidth transfers Enable CPPI to handle high bandwidth transfers, especially to support webcam captures. Use a single bd to get the whole of the data in case of high bandwidth transfers. Signed-off-by: George Cherian Signed-off-by: Felipe Balbi --- drivers/usb/musb/musb_cppi41.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/usb/musb/musb_cppi41.c b/drivers/usb/musb/musb_cppi41.c index f88929609ba..39ee516c8cb 100644 --- a/drivers/usb/musb/musb_cppi41.c +++ b/drivers/usb/musb/musb_cppi41.c @@ -448,12 +448,25 @@ static int cppi41_dma_channel_program(struct dma_channel *channel, dma_addr_t dma_addr, u32 len) { int ret; + struct cppi41_dma_channel *cppi41_channel = channel->private_data; + int hb_mult = 0; BUG_ON(channel->status == MUSB_DMA_STATUS_UNKNOWN || channel->status == MUSB_DMA_STATUS_BUSY); + if (is_host_active(cppi41_channel->controller->musb)) { + if (cppi41_channel->is_tx) + hb_mult = cppi41_channel->hw_ep->out_qh->hb_mult; + else + hb_mult = cppi41_channel->hw_ep->in_qh->hb_mult; + } + channel->status = MUSB_DMA_STATUS_BUSY; channel->actual_len = 0; + + if (hb_mult) + packet_sz = hb_mult * (packet_sz & 0x7FF); + ret = cppi41_configure_channel(channel, packet_sz, mode, dma_addr, len); if (!ret) channel->status = MUSB_DMA_STATUS_FREE; -- cgit v1.2.3-70-g09d2 From 1af54b7a40ca9bbd549e626be01870caa3f0299d Mon Sep 17 00:00:00 2001 From: George Cherian Date: Mon, 27 Jan 2014 15:07:26 +0530 Subject: usb: musb: musb_cppi41: Handle ISOCH differently and not use the hrtimer. In case of ISOCH transfers the hrtimer workaround for the hardware issue is not very reliable. Instead of checking musb_is_tx_fifo_empty() in hrtimer routine, schedule a completion work and check the same in completion work. Signed-off-by: George Cherian Signed-off-by: Felipe Balbi --- drivers/usb/musb/musb_cppi41.c | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/drivers/usb/musb/musb_cppi41.c b/drivers/usb/musb/musb_cppi41.c index 39ee516c8cb..5b0f2a58f8c 100644 --- a/drivers/usb/musb/musb_cppi41.c +++ b/drivers/usb/musb/musb_cppi41.c @@ -39,6 +39,7 @@ struct cppi41_dma_channel { u32 transferred; u32 packet_sz; struct list_head tx_check; + struct work_struct dma_completion; }; #define MUSB_DMA_NUM_CHANNELS 15 @@ -112,6 +113,18 @@ static bool musb_is_tx_fifo_empty(struct musb_hw_ep *hw_ep) return true; } +static bool is_isoc(struct musb_hw_ep *hw_ep, bool in) +{ + if (in && hw_ep->in_qh) { + if (hw_ep->in_qh->type == USB_ENDPOINT_XFER_ISOC) + return true; + } else if (hw_ep->out_qh) { + if (hw_ep->out_qh->type == USB_ENDPOINT_XFER_ISOC) + return true; + } + return false; +} + static void cppi41_dma_callback(void *private_data); static void cppi41_trans_done(struct cppi41_dma_channel *cppi41_channel) @@ -165,6 +178,32 @@ static void cppi41_trans_done(struct cppi41_dma_channel *cppi41_channel) } } +static void cppi_trans_done_work(struct work_struct *work) +{ + unsigned long flags; + struct cppi41_dma_channel *cppi41_channel = + container_of(work, struct cppi41_dma_channel, dma_completion); + struct cppi41_dma_controller *controller = cppi41_channel->controller; + struct musb *musb = controller->musb; + struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep; + bool empty; + + if (!cppi41_channel->is_tx && is_isoc(hw_ep, 1)) { + spin_lock_irqsave(&musb->lock, flags); + cppi41_trans_done(cppi41_channel); + spin_unlock_irqrestore(&musb->lock, flags); + } else { + empty = musb_is_tx_fifo_empty(hw_ep); + if (empty) { + spin_lock_irqsave(&musb->lock, flags); + cppi41_trans_done(cppi41_channel); + spin_unlock_irqrestore(&musb->lock, flags); + } else { + schedule_work(&cppi41_channel->dma_completion); + } + } +} + static enum hrtimer_restart cppi41_recheck_tx_req(struct hrtimer *timer) { struct cppi41_dma_controller *controller; @@ -228,6 +267,14 @@ static void cppi41_dma_callback(void *private_data) transferred < cppi41_channel->packet_sz) cppi41_channel->prog_len = 0; + if (!cppi41_channel->is_tx) { + if (is_isoc(hw_ep, 1)) + schedule_work(&cppi41_channel->dma_completion); + else + cppi41_trans_done(cppi41_channel); + goto out; + } + empty = musb_is_tx_fifo_empty(hw_ep); if (empty) { cppi41_trans_done(cppi41_channel); @@ -264,6 +311,10 @@ static void cppi41_dma_callback(void *private_data) goto out; } } + if (is_isoc(hw_ep, 0)) { + schedule_work(&cppi41_channel->dma_completion); + goto out; + } list_add_tail(&cppi41_channel->tx_check, &controller->early_tx_list); if (!hrtimer_active(&controller->early_tx)) { @@ -620,6 +671,8 @@ static int cppi41_dma_controller_start(struct cppi41_dma_controller *controller) cppi41_channel->port_num = port; cppi41_channel->is_tx = is_tx; INIT_LIST_HEAD(&cppi41_channel->tx_check); + INIT_WORK(&cppi41_channel->dma_completion, + cppi_trans_done_work); musb_dma = &cppi41_channel->channel; musb_dma->private_data = cppi41_channel; -- cgit v1.2.3-70-g09d2 From 1a7ed5bec407478b9ce5e3267708110277851614 Mon Sep 17 00:00:00 2001 From: Matt Porter Date: Mon, 3 Feb 2014 10:29:09 -0500 Subject: usb: gadget: s3c-hsotg: fix build on x86 and other architectures The readsl and writesl I/O accessors are only defined on some architectures. The driver currently depends on CONFIG_ARM because the build breaks on x86, in particular. Switch to use of ioread32_rep and iowrite32_rep to fix build on all architectures and remove the CONFIG_ARM dependency. Also update printk formatting to handle a long long dma_addr_t to avoid warnings on !32-bit architectures. Signed-off-by: Matt Porter Signed-off-by: Felipe Balbi --- drivers/usb/gadget/Kconfig | 1 - drivers/usb/gadget/s3c-hsotg.c | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 42f017afd36..3557c7e5040 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -301,7 +301,6 @@ config USB_PXA27X gadget drivers to also be dynamically linked. config USB_S3C_HSOTG - depends on ARM tristate "Designware/S3C HS/OtG USB Device controller" help The Designware USB2.0 high-speed gadget controller diff --git a/drivers/usb/gadget/s3c-hsotg.c b/drivers/usb/gadget/s3c-hsotg.c index 1172eaeddd8..0449b768ac0 100644 --- a/drivers/usb/gadget/s3c-hsotg.c +++ b/drivers/usb/gadget/s3c-hsotg.c @@ -617,7 +617,7 @@ static int s3c_hsotg_write_fifo(struct s3c_hsotg *hsotg, to_write = DIV_ROUND_UP(to_write, 4); data = hs_req->req.buf + buf_pos; - writesl(hsotg->regs + EPFIFO(hs_ep->index), data, to_write); + iowrite32_rep(hsotg->regs + EPFIFO(hs_ep->index), data, to_write); return (to_write >= can_write) ? -ENOSPC : 0; } @@ -720,8 +720,8 @@ static void s3c_hsotg_start_req(struct s3c_hsotg *hsotg, ureq->length, ureq->actual); if (0) dev_dbg(hsotg->dev, - "REQ buf %p len %d dma 0x%08x noi=%d zp=%d snok=%d\n", - ureq->buf, length, ureq->dma, + "REQ buf %p len %d dma 0x%08llx noi=%d zp=%d snok=%d\n", + ureq->buf, length, (unsigned long long)ureq->dma, ureq->no_interrupt, ureq->zero, ureq->short_not_ok); maxreq = get_ep_limit(hs_ep); @@ -789,8 +789,8 @@ static void s3c_hsotg_start_req(struct s3c_hsotg *hsotg, dma_reg = dir_in ? DIEPDMA(index) : DOEPDMA(index); writel(ureq->dma, hsotg->regs + dma_reg); - dev_dbg(hsotg->dev, "%s: 0x%08x => 0x%08x\n", - __func__, ureq->dma, dma_reg); + dev_dbg(hsotg->dev, "%s: 0x%08llx => 0x%08x\n", + __func__, (unsigned long long)ureq->dma, dma_reg); } ctrl |= DxEPCTL_EPEna; /* ensure ep enabled */ @@ -1488,7 +1488,7 @@ static void s3c_hsotg_rx_data(struct s3c_hsotg *hsotg, int ep_idx, int size) * note, we might over-write the buffer end by 3 bytes depending on * alignment of the data. */ - readsl(fifo, hs_req->req.buf + read_ptr, to_read); + ioread32_rep(fifo, hs_req->req.buf + read_ptr, to_read); } /** -- cgit v1.2.3-70-g09d2 From 8b3bc14f2d841eaabaaad6aba05b7657dd0b5c7e Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Tue, 4 Feb 2014 14:25:29 +0900 Subject: usb: gadget: s3c-hsotg: use %pad for dma_addr_t Use %pad for dma_addr_t to avoid the following build warnings in printks. drivers/usb/gadget/s3c-hsotg.c: In function 's3c_hsotg_start_req' drivers/usb/gadget/s3c-hsotg.c:722:3: warning: format '%x' expects argument of type 'unsigned int' but argument 6 has type 'dma_addr_t' [-Wformat] drivers/usb/gadget/s3c-hsotg.c:792:3: warning: format '%x' expects argument of type 'unsigned int' but argument 5 has type 'dma_addr_t' [-Wformat] Signed-off-by: Jingoo Han Signed-off-by: Felipe Balbi --- drivers/usb/gadget/s3c-hsotg.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/s3c-hsotg.c b/drivers/usb/gadget/s3c-hsotg.c index 0449b768ac0..930c490dd83 100644 --- a/drivers/usb/gadget/s3c-hsotg.c +++ b/drivers/usb/gadget/s3c-hsotg.c @@ -720,8 +720,8 @@ static void s3c_hsotg_start_req(struct s3c_hsotg *hsotg, ureq->length, ureq->actual); if (0) dev_dbg(hsotg->dev, - "REQ buf %p len %d dma 0x%08llx noi=%d zp=%d snok=%d\n", - ureq->buf, length, (unsigned long long)ureq->dma, + "REQ buf %p len %d dma 0x%pad noi=%d zp=%d snok=%d\n", + ureq->buf, length, &ureq->dma, ureq->no_interrupt, ureq->zero, ureq->short_not_ok); maxreq = get_ep_limit(hs_ep); @@ -789,8 +789,8 @@ static void s3c_hsotg_start_req(struct s3c_hsotg *hsotg, dma_reg = dir_in ? DIEPDMA(index) : DOEPDMA(index); writel(ureq->dma, hsotg->regs + dma_reg); - dev_dbg(hsotg->dev, "%s: 0x%08llx => 0x%08x\n", - __func__, (unsigned long long)ureq->dma, dma_reg); + dev_dbg(hsotg->dev, "%s: 0x%pad => 0x%08x\n", + __func__, &ureq->dma, dma_reg); } ctrl |= DxEPCTL_EPEna; /* ensure ep enabled */ -- cgit v1.2.3-70-g09d2 From abcdcc29c6aed12c137c8c06ee914d08605bf521 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 29 Dec 2013 23:47:25 +0100 Subject: usb: gadget: fix error return code Set the return variable to an error code as done elsewhere in the function. A simplified version of the semantic match that finds this problem is as follows: (http://coccinelle.lip6.fr/) // ( if@p1 (\(ret < 0\|ret != 0\)) { ... return ret; } | ret@p1 = 0 ) ... when != ret = e1 when != &ret *if(...) { ... when != ret = e2 when forall return ret; } // Signed-off-by: Julia Lawall Signed-off-by: Felipe Balbi --- drivers/usb/gadget/printer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/gadget/printer.c b/drivers/usb/gadget/printer.c index bf7a56b6d48..92b55bdda56 100644 --- a/drivers/usb/gadget/printer.c +++ b/drivers/usb/gadget/printer.c @@ -1133,6 +1133,7 @@ static int __init printer_bind_config(struct usb_configuration *c) NULL, "g_printer"); if (IS_ERR(dev->pdev)) { ERROR(dev, "Failed to create device: g_printer\n"); + status = PTR_ERR(dev->pdev); goto fail; } -- cgit v1.2.3-70-g09d2 From e362115a07eaf3509b12853c2047d19720501fd9 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Fri, 3 Jan 2014 10:58:59 +0530 Subject: usb: gadget: s3c2410_udc: Fix build error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pass value instead of address as expected by 'usb_ep_set_maxpacket_limit'. Fixes the following compilation error introduced by commit e117e742d310 ("usb: gadget: add "maxpacket_limit" field to struct usb_ep"): drivers/usb/gadget/s3c2410_udc.c: In function ‘s3c2410_udc_reinit’: drivers/usb/gadget/s3c2410_udc.c:1632:3: error: cannot take address of bit-field ‘maxpacket’ usb_ep_set_maxpacket_limit(&ep->ep, &ep->ep.maxpacket); Signed-off-by: Sachin Kamat Reviewed-by: Robert Baldyga Signed-off-by: Felipe Balbi --- drivers/usb/gadget/s3c2410_udc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/s3c2410_udc.c b/drivers/usb/gadget/s3c2410_udc.c index f04b2c3154d..dd9678f85c5 100644 --- a/drivers/usb/gadget/s3c2410_udc.c +++ b/drivers/usb/gadget/s3c2410_udc.c @@ -1629,7 +1629,7 @@ static void s3c2410_udc_reinit(struct s3c2410_udc *dev) ep->ep.desc = NULL; ep->halted = 0; INIT_LIST_HEAD(&ep->queue); - usb_ep_set_maxpacket_limit(&ep->ep, &ep->ep.maxpacket); + usb_ep_set_maxpacket_limit(&ep->ep, ep->ep.maxpacket); } } -- cgit v1.2.3-70-g09d2 From 236064c253581367a62d67083a620318670683d0 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Fri, 3 Jan 2014 10:59:00 +0530 Subject: usb: gadget: s3c-hsudc: remove unused label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the following compilation warning: drivers/usb/gadget/s3c-hsudc.c: In function ‘s3c_hsudc_probe’: drivers/usb/gadget/s3c-hsudc.c:1347:1: warning: label ‘err_add_device’ defined but not used [-Wunused-label] Signed-off-by: Sachin Kamat Signed-off-by: Felipe Balbi --- drivers/usb/gadget/s3c-hsudc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/gadget/s3c-hsudc.c b/drivers/usb/gadget/s3c-hsudc.c index ea4bbfe72ec..10c6a128250 100644 --- a/drivers/usb/gadget/s3c-hsudc.c +++ b/drivers/usb/gadget/s3c-hsudc.c @@ -1344,7 +1344,6 @@ static int s3c_hsudc_probe(struct platform_device *pdev) return 0; err_add_udc: -err_add_device: clk_disable(hsudc->uclk); err_res: if (!IS_ERR_OR_NULL(hsudc->transceiver)) -- cgit v1.2.3-70-g09d2 From 2c2b04253bfc7ac67a2ab71a6e771cd42e9418fe Mon Sep 17 00:00:00 2001 From: "wenlin.kang" Date: Wed, 8 Jan 2014 14:22:11 +0800 Subject: usb: gadget: printer: fix possible deadlock The problem occurs in follow path. printer_read() | +---setup_rx_reqs() | +---usb_ep_queue() | +---... | +---rx_complete() Although it is clear from code, we can't get it normally. only when we enable some spin_lock debug config option, we can find it. eg: BUG: spinlock lockup on CPU#0, g_printer_test_/584 lock: bf05e158, .magic: dead4ead, .owner: g_printer_test_/584, .owner_cpu: 0 [] (unwind_backtrace+0x0/0x104) from [] (dump_stack+0x20/0x24) [] (dump_stack+0x20/0x24) from [] (spin_dump+0x8c/0x94) [] (spin_dump+0x8c/0x94) from [] (do_raw_spin_lock+0x128/0x154) [] (do_raw_spin_lock+0x128/0x154) from [] (_raw_spin_lock_irqsave+0x64/0x70) [] (_raw_spin_lock_irqsave+0x64/0x70) from [] (rx_complete+0x54/0x10c [g_printer]) [] (rx_complete+0x54/0x10c [g_printer]) from [] (musb_g_giveback+0x78/0x88) [] (musb_g_giveback+0x78/0x88) from [] (rxstate+0xa0/0x10c) [] (rxstate+0xa0/0x10c) from [] (musb_ep_restart+0x44/0x70) [] (musb_ep_restart+0x44/0x70) from [] (musb_gadget_queue+0xe8/0xf8) [] (musb_gadget_queue+0xe8/0xf8) from [] (setup_rx_reqs+0xa4/0x178 [g_printer]) [] (setup_rx_reqs+0xa4/0x178 [g_printer]) from [] (printer_read+0x9c/0x3f4 [g_printer]) [] (printer_read+0x9c/0x3f4 [g_printer]) from [] (vfs_read+0xb4/0x144) [] (vfs_read+0xb4/0x144) from [] (sys_read+0x50/0x124) [] (sys_read+0x50/0x124) from [] (ret_fast_syscall+0x0/0x3c) The root cause is that we use the same lock two time in a path, so to avoid the deadlock, we need to unlock in setup_rx_reqs(), and only unlock. Signed-off-by: wenlin.kang Signed-off-by: Felipe Balbi --- drivers/usb/gadget/printer.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/gadget/printer.c b/drivers/usb/gadget/printer.c index 92b55bdda56..96ffdac330b 100644 --- a/drivers/usb/gadget/printer.c +++ b/drivers/usb/gadget/printer.c @@ -427,7 +427,10 @@ setup_rx_reqs(struct printer_dev *dev) req->length = USB_BUFSIZE; req->complete = rx_complete; + /* here, we unlock, and only unlock, to avoid deadlock. */ + spin_unlock(&dev->lock); error = usb_ep_queue(dev->out_ep, req, GFP_ATOMIC); + spin_lock(&dev->lock); if (error) { DBG(dev, "rx submit --> %d\n", error); list_add(&req->list, &dev->rx_reqs); -- cgit v1.2.3-70-g09d2 From 7e98f60003df98026edd66916f282501eee075c4 Mon Sep 17 00:00:00 2001 From: "wenlin.kang" Date: Wed, 8 Jan 2014 14:22:12 +0800 Subject: usb: gadget: printer: fix memory leak When read data from g_printer, we see a Segmentation fault. eg: Unable to handle kernel paging request at virtual address bf048000 pgd = cf038000 [bf048000] *pgd=8e8cf811, *pte=00000000, *ppte=00000000 Internal error: Oops: 7 [#1] PREEMPT ARM Modules linked in: bluetooth rfcomm g_printer CPU: 0 Not tainted (3.4.43-WR5.0.1.9_standard #1) PC is at __copy_to_user_std+0x310/0x3a8 LR is at 0x4c808010 pc : [] lr : [<4c808010>] psr: 20000013 sp : cf883ea8 ip : 80801018 fp : cf883f24 r10: bf04706c r9 : 18a21205 r8 : 21953888 r7 : 201588aa r6 : 5109aa16 r5 : 0705aaa2 r4 : 5140aa8a r3 : 0000004c r2 : 00000fdc r1 : bf048000 r0 : bef5fc3c Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user Control: 10c5387d Table: 8f038019 DAC: 00000015 Process g_printer_test. (pid: 661, stack limit = 0xcf8822e8) Stack: (0xcf883ea8 to 0xcf884000) 3ea0: bf047068 00001fff bef5ecb9 cf882000 00001fff bef5ecb9 3ec0: 00001fff 00000000 cf2e8724 bf044d3c 80000013 80000013 00000001 bf04706c 3ee0: cf883f24 cf883ef0 c012e5ac c0324388 c007c8ac c0046298 00008180 cf29b900 3f00: 00002000 bef5ecb8 cf883f68 00000003 cf882000 cf29b900 cf883f54 cf883f28 3f20: c012ea08 bf044b0c c000eb88 00000000 cf883f7c 00000000 00000000 00002000 3f40: bef5ecb8 00000003 cf883fa4 cf883f58 c012eae8 c012e960 00000001 bef60cb8 3f60: 000000a8 c000eb88 00000000 00000000 cf883fa4 00000000 c014329c 00000000 3f80: 000000d4 41af63f0 00000003 c000eb88 cf882000 00000000 00000000 cf883fa8 3fa0: c000e920 c012eaa4 00000000 000000d4 00000003 bef5ecb8 00002000 bef5ecb8 3fc0: 00000000 000000d4 41af63f0 00000003 b6f534c0 00000000 419f9000 00000000 3fe0: 00000000 bef5ecac 000086d9 41a986bc 60000010 00000003 0109608a 0088828a Code: f5d1f07c e8b100f0 e1a03c2e e2522020 (e8b15300) ---[ end trace 97e2618e250e3377 ]--- Segmentation fault The root cause is the dev->rx_buffers list has been broken. When we call printer_read(), the following call tree is triggered: printer_read() | +---setup_rx_reqs(req) | | | +---usb_ep_queue(req) | | | | | +---... | | | | | +---rx_complete(req). | | | +---add the req to dev->rx_reqs_active | +---while(!list_empty(&dev->rx_buffers))) The route happens when we don't use DMA or fail to start DMA in USB driver. We can see: in the case, in rx_complete() it will add the req to dev->rx_buffers. meanwhile we see that we will also add the req to dev->rx_reqs_active after usb_ep_queue() return, so this adding will break the dev->rx_buffers out. After, when we call list_empty() to check dev->rx_buffers in while(), due to can't check correctly dev->rx_buffers, so the Segmentation fault occurs when copy_to_user() is called. Signed-off-by: wenlin.kang Signed-off-by: Felipe Balbi --- drivers/usb/gadget/printer.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/printer.c b/drivers/usb/gadget/printer.c index 96ffdac330b..0fd459e724b 100644 --- a/drivers/usb/gadget/printer.c +++ b/drivers/usb/gadget/printer.c @@ -435,7 +435,9 @@ setup_rx_reqs(struct printer_dev *dev) DBG(dev, "rx submit --> %d\n", error); list_add(&req->list, &dev->rx_reqs); break; - } else { + } + /* if the req is empty, then add it into dev->rx_reqs_active. */ + else if (list_empty(&req->list)) { list_add(&req->list, &dev->rx_reqs_active); } } -- cgit v1.2.3-70-g09d2 From c9f721b2f3168a0a3b1cc29e92ad1f6f3d62e376 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Tue, 14 Jan 2014 08:36:00 +0100 Subject: usb: gadget: s3c-hsotg: stall ep0 in set_halt function When s3c_hsotg_ep_sethalt() function is called for ep0 it should be stalled in the same way that it is in s3c_hsotg_process_control() function, because SET_HALT for ep0 is delayed response for setup request. Endpoint 0, if halted, it doesn't need CLEAR_HALT because it clears "stalled" state automatically when next setup request is received. For this reason this patch moves code setting ep0 to "stalled" state to new function named s3c_hsotg_stall_ep0() which is called in s3c_hsotg_process_control() function as an immediate response for setup request, and in s3c_hsotg_ep_sethalt() function as a delayed response for setup request. Signed-off-by: Robert Baldyga Signed-off-by: Felipe Balbi --- drivers/usb/gadget/s3c-hsotg.c | 78 +++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/drivers/usb/gadget/s3c-hsotg.c b/drivers/usb/gadget/s3c-hsotg.c index 930c490dd83..99c8e3ca6a3 100644 --- a/drivers/usb/gadget/s3c-hsotg.c +++ b/drivers/usb/gadget/s3c-hsotg.c @@ -1185,6 +1185,41 @@ static int s3c_hsotg_process_req_feature(struct s3c_hsotg *hsotg, static void s3c_hsotg_enqueue_setup(struct s3c_hsotg *hsotg); static void s3c_hsotg_disconnect(struct s3c_hsotg *hsotg); +/** + * s3c_hsotg_stall_ep0 - stall ep0 + * @hsotg: The device state + * + * Set stall for ep0 as response for setup request. + */ +static void s3c_hsotg_stall_ep0(struct s3c_hsotg *hsotg) { + struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; + u32 reg; + u32 ctrl; + + dev_dbg(hsotg->dev, "ep0 stall (dir=%d)\n", ep0->dir_in); + reg = (ep0->dir_in) ? DIEPCTL0 : DOEPCTL0; + + /* + * DxEPCTL_Stall will be cleared by EP once it has + * taken effect, so no need to clear later. + */ + + ctrl = readl(hsotg->regs + reg); + ctrl |= DxEPCTL_Stall; + ctrl |= DxEPCTL_CNAK; + writel(ctrl, hsotg->regs + reg); + + dev_dbg(hsotg->dev, + "written DxEPCTL=0x%08x to %08x (DxEPCTL=0x%08x)\n", + ctrl, reg, readl(hsotg->regs + reg)); + + /* + * complete won't be called, so we enqueue + * setup request here + */ + s3c_hsotg_enqueue_setup(hsotg); +} + /** * s3c_hsotg_process_control - process a control request * @hsotg: The device state @@ -1262,38 +1297,8 @@ static void s3c_hsotg_process_control(struct s3c_hsotg *hsotg, * so respond with a STALL for the status stage to indicate failure. */ - if (ret < 0) { - u32 reg; - u32 ctrl; - - dev_dbg(hsotg->dev, "ep0 stall (dir=%d)\n", ep0->dir_in); - reg = (ep0->dir_in) ? DIEPCTL0 : DOEPCTL0; - - /* - * DxEPCTL_Stall will be cleared by EP once it has - * taken effect, so no need to clear later. - */ - - ctrl = readl(hsotg->regs + reg); - ctrl |= DxEPCTL_Stall; - ctrl |= DxEPCTL_CNAK; - writel(ctrl, hsotg->regs + reg); - - dev_dbg(hsotg->dev, - "written DxEPCTL=0x%08x to %08x (DxEPCTL=0x%08x)\n", - ctrl, reg, readl(hsotg->regs + reg)); - - /* - * don't believe we need to anything more to get the EP - * to reply with a STALL packet - */ - - /* - * complete won't be called, so we enqueue - * setup request here - */ - s3c_hsotg_enqueue_setup(hsotg); - } + if (ret < 0) + s3c_hsotg_stall_ep0(hsotg); } /** @@ -2832,6 +2837,15 @@ static int s3c_hsotg_ep_sethalt(struct usb_ep *ep, int value) dev_info(hs->dev, "%s(ep %p %s, %d)\n", __func__, ep, ep->name, value); + if (index == 0) { + if (value) + s3c_hsotg_stall_ep0(hs); + else + dev_warn(hs->dev, + "%s: can't clear halt on ep0\n", __func__); + return 0; + } + /* write both IN and OUT control registers */ epreg = DIEPCTL(index); -- cgit v1.2.3-70-g09d2 From 30bbae9fad15ee25686e62f97d734624b86d3405 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Tue, 14 Jan 2014 12:58:56 +0100 Subject: usb: dwc3: omap: don't check resource with devm_ioremap_resource devm_ioremap_resource does sanity checks on the given resource. No need to duplicate this in the driver. Signed-off-by: Wolfram Sang Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/dwc3-omap.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-omap.c b/drivers/usb/dwc3/dwc3-omap.c index b269dbd47fc..80b3357034b 100644 --- a/drivers/usb/dwc3/dwc3-omap.c +++ b/drivers/usb/dwc3/dwc3-omap.c @@ -424,11 +424,6 @@ static int dwc3_omap_probe(struct platform_device *pdev) } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(dev, "missing memory base resource\n"); - return -EINVAL; - } - base = devm_ioremap_resource(dev, res); if (IS_ERR(base)) return PTR_ERR(base); -- cgit v1.2.3-70-g09d2 From de9db572fe5135bb2cf5bb2d8520f99a56b21d9b Mon Sep 17 00:00:00 2001 From: Markus Pargmann Date: Fri, 17 Jan 2014 10:22:36 +0100 Subject: usb: musb: dsps, use devm_kzalloc Replace kzalloc by devm_kzalloc and remove the kfree() calls. Signed-off-by: Markus Pargmann Signed-off-by: Felipe Balbi --- drivers/usb/musb/musb_dsps.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/usb/musb/musb_dsps.c b/drivers/usb/musb/musb_dsps.c index 7a109eae9b9..68c14e1eac6 100644 --- a/drivers/usb/musb/musb_dsps.c +++ b/drivers/usb/musb/musb_dsps.c @@ -616,7 +616,7 @@ static int dsps_probe(struct platform_device *pdev) wrp = match->data; /* allocate glue */ - glue = kzalloc(sizeof(*glue), GFP_KERNEL); + glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL); if (!glue) { dev_err(&pdev->dev, "unable to allocate glue memory\n"); return -ENOMEM; @@ -644,7 +644,6 @@ err3: pm_runtime_put(&pdev->dev); err2: pm_runtime_disable(&pdev->dev); - kfree(glue); return ret; } @@ -657,7 +656,6 @@ static int dsps_remove(struct platform_device *pdev) /* disable usbss clocks */ pm_runtime_put(&pdev->dev); pm_runtime_disable(&pdev->dev); - kfree(glue); return 0; } -- cgit v1.2.3-70-g09d2 From ea365922f9eb2fb6fc0486f521748f19bfd5c502 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Mon, 13 Jan 2014 16:49:35 +0100 Subject: usb: gadget: FunctionFS: dereference ffs_dev conditionally ffs_dev->ffs_release_dev_callback should be accessed only if ffs_dev is not NULL. Acked-by: Michal Nazarewicz Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi --- drivers/usb/gadget/f_fs.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index 306a2b52125..78333f033e9 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -2590,11 +2590,12 @@ static void ffs_release_dev(struct ffs_data *ffs_data) ffs_dev_lock(); ffs_dev = ffs_data->private_data; - if (ffs_dev) + if (ffs_dev) { ffs_dev->mounted = false; - - if (ffs_dev->ffs_release_dev_callback) - ffs_dev->ffs_release_dev_callback(ffs_dev); + + if (ffs_dev->ffs_release_dev_callback) + ffs_dev->ffs_release_dev_callback(ffs_dev); + } ffs_dev_unlock(); } -- cgit v1.2.3-70-g09d2 From ab13cb0c0da6d728d87924339db4feaa598483ad Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Mon, 13 Jan 2014 16:49:36 +0100 Subject: usb: gadget: code cleanup Remove trailing whitespace Acked-by: Michal Nazarewicz Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi --- drivers/usb/gadget/f_fs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index 78333f033e9..f0c657d2c77 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -2509,7 +2509,7 @@ static int _ffs_name_dev(struct ffs_dev *dev, const char *name) existing = _ffs_find_dev(name); if (existing) return -EBUSY; - + dev->name = name; return 0; -- cgit v1.2.3-70-g09d2 From 10b69ce08ca7b9cf1c3c73122c0d489427fab5fd Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Mon, 13 Jan 2014 16:49:37 +0100 Subject: usb: gadget: FunctionFS: staticize functions used only in f_fs.c ffs_alloc_dev and ffs_free_dev are used only in f_fs.c, so make them static. Acked-by: Michal Nazarewicz Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi --- drivers/usb/gadget/f_fs.c | 6 ++++-- drivers/usb/gadget/u_fs.h | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index f0c657d2c77..96bea089ef2 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -162,7 +162,9 @@ DEFINE_MUTEX(ffs_lock); EXPORT_SYMBOL(ffs_lock); static struct ffs_dev *ffs_find_dev(const char *name); +static struct ffs_dev *ffs_alloc_dev(void); static int _ffs_name_dev(struct ffs_dev *dev, const char *name); +static void ffs_free_dev(struct ffs_dev *dev); static void *ffs_acquire_dev(const char *dev_name); static void ffs_release_dev(struct ffs_data *ffs_data); static int ffs_ready(struct ffs_data *ffs); @@ -2473,7 +2475,7 @@ static struct usb_function *ffs_alloc(struct usb_function_instance *fi) /* * ffs_lock must be taken by the caller of this function */ -struct ffs_dev *ffs_alloc_dev(void) +static struct ffs_dev *ffs_alloc_dev(void) { struct ffs_dev *dev; int ret; @@ -2550,7 +2552,7 @@ EXPORT_SYMBOL(ffs_single_dev); /* * ffs_lock must be taken by the caller of this function */ -void ffs_free_dev(struct ffs_dev *dev) +static void ffs_free_dev(struct ffs_dev *dev) { list_del(&dev->entry); if (dev->name_allocated) diff --git a/drivers/usb/gadget/u_fs.h b/drivers/usb/gadget/u_fs.h index bc2d3718219..f418c25f851 100644 --- a/drivers/usb/gadget/u_fs.h +++ b/drivers/usb/gadget/u_fs.h @@ -65,10 +65,8 @@ static inline void ffs_dev_unlock(void) mutex_unlock(&ffs_lock); } -struct ffs_dev *ffs_alloc_dev(void); int ffs_name_dev(struct ffs_dev *dev, const char *name); int ffs_single_dev(struct ffs_dev *dev); -void ffs_free_dev(struct ffs_dev *dev); struct ffs_epfile; struct ffs_function; -- cgit v1.2.3-70-g09d2 From da13a7738e87a5adecbd8741191ceabfc056767b Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Mon, 13 Jan 2014 16:49:38 +0100 Subject: usb: gadget: FunctionFS: use consistent naming with regard to ffs_lock Consistently prefix function name with underscore if the function has to be called with ffs_lock taken. Acked-by: Michal Nazarewicz Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi --- drivers/usb/gadget/f_fs.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index 96bea089ef2..fffda6113b0 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -161,10 +161,10 @@ ffs_sb_create_file(struct super_block *sb, const char *name, void *data, DEFINE_MUTEX(ffs_lock); EXPORT_SYMBOL(ffs_lock); -static struct ffs_dev *ffs_find_dev(const char *name); -static struct ffs_dev *ffs_alloc_dev(void); +static struct ffs_dev *_ffs_find_dev(const char *name); +static struct ffs_dev *_ffs_alloc_dev(void); static int _ffs_name_dev(struct ffs_dev *dev, const char *name); -static void ffs_free_dev(struct ffs_dev *dev); +static void _ffs_free_dev(struct ffs_dev *dev); static void *ffs_acquire_dev(const char *dev_name); static void ffs_release_dev(struct ffs_data *ffs_data); static int ffs_ready(struct ffs_data *ffs); @@ -2255,7 +2255,7 @@ static int ffs_func_revmap_intf(struct ffs_function *func, u8 intf) static LIST_HEAD(ffs_devices); -static struct ffs_dev *_ffs_find_dev(const char *name) +static struct ffs_dev *_ffs_do_find_dev(const char *name) { struct ffs_dev *dev; @@ -2272,7 +2272,7 @@ static struct ffs_dev *_ffs_find_dev(const char *name) /* * ffs_lock must be taken by the caller of this function */ -static struct ffs_dev *ffs_get_single_dev(void) +static struct ffs_dev *_ffs_get_single_dev(void) { struct ffs_dev *dev; @@ -2288,15 +2288,15 @@ static struct ffs_dev *ffs_get_single_dev(void) /* * ffs_lock must be taken by the caller of this function */ -static struct ffs_dev *ffs_find_dev(const char *name) +static struct ffs_dev *_ffs_find_dev(const char *name) { struct ffs_dev *dev; - dev = ffs_get_single_dev(); + dev = _ffs_get_single_dev(); if (dev) return dev; - return _ffs_find_dev(name); + return _ffs_do_find_dev(name); } /* Configfs support *********************************************************/ @@ -2332,7 +2332,7 @@ static void ffs_free_inst(struct usb_function_instance *f) opts = to_f_fs_opts(f); ffs_dev_lock(); - ffs_free_dev(opts->dev); + _ffs_free_dev(opts->dev); ffs_dev_unlock(); kfree(opts); } @@ -2387,7 +2387,7 @@ static struct usb_function_instance *ffs_alloc_inst(void) opts->func_inst.set_inst_name = ffs_set_inst_name; opts->func_inst.free_func_inst = ffs_free_inst; ffs_dev_lock(); - dev = ffs_alloc_dev(); + dev = _ffs_alloc_dev(); ffs_dev_unlock(); if (IS_ERR(dev)) { kfree(opts); @@ -2475,12 +2475,12 @@ static struct usb_function *ffs_alloc(struct usb_function_instance *fi) /* * ffs_lock must be taken by the caller of this function */ -static struct ffs_dev *ffs_alloc_dev(void) +static struct ffs_dev *_ffs_alloc_dev(void) { struct ffs_dev *dev; int ret; - if (ffs_get_single_dev()) + if (_ffs_get_single_dev()) return ERR_PTR(-EBUSY); dev = kzalloc(sizeof(*dev), GFP_KERNEL); @@ -2508,7 +2508,7 @@ static int _ffs_name_dev(struct ffs_dev *dev, const char *name) { struct ffs_dev *existing; - existing = _ffs_find_dev(name); + existing = _ffs_do_find_dev(name); if (existing) return -EBUSY; @@ -2552,7 +2552,7 @@ EXPORT_SYMBOL(ffs_single_dev); /* * ffs_lock must be taken by the caller of this function */ -static void ffs_free_dev(struct ffs_dev *dev) +static void _ffs_free_dev(struct ffs_dev *dev) { list_del(&dev->entry); if (dev->name_allocated) @@ -2569,7 +2569,7 @@ static void *ffs_acquire_dev(const char *dev_name) ENTER(); ffs_dev_lock(); - ffs_dev = ffs_find_dev(dev_name); + ffs_dev = _ffs_find_dev(dev_name); if (!ffs_dev) ffs_dev = ERR_PTR(-ENODEV); else if (ffs_dev->mounted) -- cgit v1.2.3-70-g09d2 From e46318a00091e3e009363a516acc44a4a80e2ebb Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Mon, 10 Feb 2014 10:42:40 +0100 Subject: usb: gadget: functionfs: fix typo in the enum variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since “cancelled” is spelled with two “l”s, rename FFS_SETUP_CANCELED to FFS_SETUP_CANCELLED. Signed-off-by: Michal Nazarewicz Signed-off-by: Robert Baldyga Signed-off-by: Felipe Balbi --- drivers/usb/gadget/f_fs.c | 16 ++++++++-------- drivers/usb/gadget/u_fs.h | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index fffda6113b0..eb5cb2b4b90 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -246,7 +246,7 @@ static ssize_t ffs_ep0_write(struct file *file, const char __user *buf, ENTER(); /* Fast check if setup was canceled */ - if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELED) + if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELLED) return -EIDRM; /* Acquire mutex */ @@ -313,7 +313,7 @@ static ssize_t ffs_ep0_write(struct file *file, const char __user *buf, */ spin_lock_irq(&ffs->ev.waitq.lock); switch (FFS_SETUP_STATE(ffs)) { - case FFS_SETUP_CANCELED: + case FFS_SETUP_CANCELLED: ret = -EIDRM; goto done_spin; @@ -348,7 +348,7 @@ static ssize_t ffs_ep0_write(struct file *file, const char __user *buf, /* * We are guaranteed to be still in FFS_ACTIVE state * but the state of setup could have changed from - * FFS_SETUP_PENDING to FFS_SETUP_CANCELED so we need + * FFS_SETUP_PENDING to FFS_SETUP_CANCELLED so we need * to check for that. If that happened we copied data * from user space in vain but it's unlikely. * @@ -357,7 +357,7 @@ static ssize_t ffs_ep0_write(struct file *file, const char __user *buf, * transition can be performed and it's protected by * mutex. */ - if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELED) { + if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELLED) { ret = -EIDRM; done_spin: spin_unlock_irq(&ffs->ev.waitq.lock); @@ -423,7 +423,7 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, ENTER(); /* Fast check if setup was canceled */ - if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELED) + if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELLED) return -EIDRM; /* Acquire mutex */ @@ -444,7 +444,7 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, spin_lock_irq(&ffs->ev.waitq.lock); switch (FFS_SETUP_STATE(ffs)) { - case FFS_SETUP_CANCELED: + case FFS_SETUP_CANCELLED: ret = -EIDRM; break; @@ -491,7 +491,7 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, spin_lock_irq(&ffs->ev.waitq.lock); /* See ffs_ep0_write() */ - if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELED) { + if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELLED) { ret = -EIDRM; break; } @@ -1786,7 +1786,7 @@ static void __ffs_event_add(struct ffs_data *ffs, * the source does nothing. */ if (ffs->setup_state == FFS_SETUP_PENDING) - ffs->setup_state = FFS_SETUP_CANCELED; + ffs->setup_state = FFS_SETUP_CANCELLED; switch (type) { case FUNCTIONFS_RESUME: diff --git a/drivers/usb/gadget/u_fs.h b/drivers/usb/gadget/u_fs.h index f418c25f851..38012bcfe9e 100644 --- a/drivers/usb/gadget/u_fs.h +++ b/drivers/usb/gadget/u_fs.h @@ -123,7 +123,7 @@ enum ffs_setup_state { * setup. If this state is set read/write on ep0 return * -EIDRM. This state is only set when adding event. */ - FFS_SETUP_CANCELED + FFS_SETUP_CANCELLED }; struct ffs_data { @@ -166,18 +166,18 @@ struct ffs_data { /* * Possible transitions: - * + FFS_NO_SETUP -> FFS_SETUP_PENDING -- P: ev.waitq.lock + * + FFS_NO_SETUP -> FFS_SETUP_PENDING -- P: ev.waitq.lock * happens only in ep0 read which is P: mutex - * + FFS_SETUP_PENDING -> FFS_NO_SETUP -- P: ev.waitq.lock + * + FFS_SETUP_PENDING -> FFS_NO_SETUP -- P: ev.waitq.lock * happens only in ep0 i/o which is P: mutex - * + FFS_SETUP_PENDING -> FFS_SETUP_CANCELED -- P: ev.waitq.lock - * + FFS_SETUP_CANCELED -> FFS_NO_SETUP -- cmpxchg + * + FFS_SETUP_PENDING -> FFS_SETUP_CANCELLED -- P: ev.waitq.lock + * + FFS_SETUP_CANCELLED -> FFS_NO_SETUP -- cmpxchg */ enum ffs_setup_state setup_state; #define FFS_SETUP_STATE(ffs) \ ((enum ffs_setup_state)cmpxchg(&(ffs)->setup_state, \ - FFS_SETUP_CANCELED, FFS_NO_SETUP)) + FFS_SETUP_CANCELLED, FFS_NO_SETUP)) /* Events & such. */ struct { -- cgit v1.2.3-70-g09d2 From a7ecf0544f0fc710ba6e2ff751d328a4190c4a1f Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Mon, 10 Feb 2014 10:42:41 +0100 Subject: usb: gadget: functionfs: replace FFS_SETUP_STATUS with an inline function The FFS_SETUP_STATUS macro could be trivialy replaced with an static inline function but more importantly its name was tad confusing. The name suggested it was a simple accessor macro but it actually did change the state of the ffs_data structure perfomring a FFS_SETUP_CANCELLED -> FFS_NO_SETUP transition. The name of the function -- ffs_setup_state_clear_cancelled -- should better describe what the function actually does. Signed-off-by: Michal Nazarewicz Signed-off-by: Robert Baldyga Signed-off-by: Felipe Balbi --- drivers/usb/gadget/f_fs.c | 22 ++++++++++++++++------ drivers/usb/gadget/u_fs.h | 7 +++---- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index eb5cb2b4b90..5bfacf8b9e6 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -99,6 +99,14 @@ static struct ffs_function *ffs_func_from_usb(struct usb_function *f) } +static inline enum ffs_setup_state +ffs_setup_state_clear_cancelled(struct ffs_data *ffs) +{ + return (enum ffs_setup_state) + cmpxchg(&ffs->setup_state, FFS_SETUP_CANCELLED, FFS_NO_SETUP); +} + + static void ffs_func_eps_disable(struct ffs_function *func); static int __must_check ffs_func_eps_enable(struct ffs_function *func); @@ -246,7 +254,7 @@ static ssize_t ffs_ep0_write(struct file *file, const char __user *buf, ENTER(); /* Fast check if setup was canceled */ - if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELLED) + if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED) return -EIDRM; /* Acquire mutex */ @@ -312,7 +320,7 @@ static ssize_t ffs_ep0_write(struct file *file, const char __user *buf, * rather then _irqsave */ spin_lock_irq(&ffs->ev.waitq.lock); - switch (FFS_SETUP_STATE(ffs)) { + switch (ffs_setup_state_clear_cancelled(ffs)) { case FFS_SETUP_CANCELLED: ret = -EIDRM; goto done_spin; @@ -357,7 +365,8 @@ static ssize_t ffs_ep0_write(struct file *file, const char __user *buf, * transition can be performed and it's protected by * mutex. */ - if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELLED) { + if (ffs_setup_state_clear_cancelled(ffs) == + FFS_SETUP_CANCELLED) { ret = -EIDRM; done_spin: spin_unlock_irq(&ffs->ev.waitq.lock); @@ -423,7 +432,7 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, ENTER(); /* Fast check if setup was canceled */ - if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELLED) + if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED) return -EIDRM; /* Acquire mutex */ @@ -443,7 +452,7 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, */ spin_lock_irq(&ffs->ev.waitq.lock); - switch (FFS_SETUP_STATE(ffs)) { + switch (ffs_setup_state_clear_cancelled(ffs)) { case FFS_SETUP_CANCELLED: ret = -EIDRM; break; @@ -491,7 +500,8 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, spin_lock_irq(&ffs->ev.waitq.lock); /* See ffs_ep0_write() */ - if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELLED) { + if (ffs_setup_state_clear_cancelled(ffs) == + FFS_SETUP_CANCELLED) { ret = -EIDRM; break; } diff --git a/drivers/usb/gadget/u_fs.h b/drivers/usb/gadget/u_fs.h index 38012bcfe9e..78263cc36df 100644 --- a/drivers/usb/gadget/u_fs.h +++ b/drivers/usb/gadget/u_fs.h @@ -172,13 +172,12 @@ struct ffs_data { * happens only in ep0 i/o which is P: mutex * + FFS_SETUP_PENDING -> FFS_SETUP_CANCELLED -- P: ev.waitq.lock * + FFS_SETUP_CANCELLED -> FFS_NO_SETUP -- cmpxchg + * + * This field should never be accessed directly and instead + * ffs_setup_state_clear_cancelled function should be used. */ enum ffs_setup_state setup_state; -#define FFS_SETUP_STATE(ffs) \ - ((enum ffs_setup_state)cmpxchg(&(ffs)->setup_state, \ - FFS_SETUP_CANCELLED, FFS_NO_SETUP)) - /* Events & such. */ struct { u8 types[4]; -- cgit v1.2.3-70-g09d2 From 0a7b1f8a70a59fd8215a937f77da20e003b9fc49 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Mon, 10 Feb 2014 10:42:42 +0100 Subject: usb: gadget: f_fs: fix setup request handling This patch fixes __ffs_ep0_queue_wait() function, which now returns number of bytes transferred in USB request or error code in case of failure. This is needed by ffs_ep0_read() function, when read data is copied to userspace. It also cleans up code by removing usused variable ep0req_status. Signed-off-by: Robert Baldyga Acked-by: Michal Nazarewicz Signed-off-by: Felipe Balbi --- drivers/usb/gadget/f_fs.c | 2 +- drivers/usb/gadget/u_fs.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index 5bfacf8b9e6..b65a5029dee 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -228,7 +228,7 @@ static int __ffs_ep0_queue_wait(struct ffs_data *ffs, char *data, size_t len) } ffs->setup_state = FFS_NO_SETUP; - return ffs->ep0req_status; + return req->status ? req->status : req->actual; } static int __ffs_ep0_stall(struct ffs_data *ffs) diff --git a/drivers/usb/gadget/u_fs.h b/drivers/usb/gadget/u_fs.h index 78263cc36df..c39e805025b 100644 --- a/drivers/usb/gadget/u_fs.h +++ b/drivers/usb/gadget/u_fs.h @@ -154,7 +154,6 @@ struct ffs_data { */ struct usb_request *ep0req; /* P: mutex */ struct completion ep0req_completion; /* P: mutex */ - int ep0req_status; /* P: mutex */ /* reference counter */ atomic_t ref; -- cgit v1.2.3-70-g09d2 From 23de91e970a410a30b5927b9a749ed1dfd914140 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Mon, 10 Feb 2014 10:42:43 +0100 Subject: usb: gadget: f_fs: add poll for endpoint 0 This patch adds poll function for file representing ep0. Ability of read from or write to ep0 file is related with actual state of ffs: - When desctiptors or strings are not written yet, POLLOUT flag is set. - If there is any event to read, POLLIN flag is set. - If setup request was read, POLLIN and POLLOUT flag is set, to allow send response (by performing I/O operation consistent with setup request direction) or set stall (by performing I/O operation opposite setup request direction). Signed-off-by: Robert Baldyga Acked-by: Michal Nazarewicz Signed-off-by: Felipe Balbi --- drivers/usb/gadget/f_fs.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index b65a5029dee..20321631f0f 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -28,6 +28,8 @@ #include #include +#include + #include "u_fs.h" #include "configfs.h" @@ -570,6 +572,45 @@ static long ffs_ep0_ioctl(struct file *file, unsigned code, unsigned long value) return ret; } +static unsigned int ffs_ep0_poll(struct file *file, poll_table *wait) +{ + struct ffs_data *ffs = file->private_data; + unsigned int mask = POLLWRNORM; + int ret; + + poll_wait(file, &ffs->ev.waitq, wait); + + ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); + if (unlikely(ret < 0)) + return mask; + + switch (ffs->state) { + case FFS_READ_DESCRIPTORS: + case FFS_READ_STRINGS: + mask |= POLLOUT; + break; + + case FFS_ACTIVE: + switch (ffs->setup_state) { + case FFS_NO_SETUP: + if (ffs->ev.count) + mask |= POLLIN; + break; + + case FFS_SETUP_PENDING: + case FFS_SETUP_CANCELLED: + mask |= (POLLIN | POLLOUT); + break; + } + case FFS_CLOSING: + break; + } + + mutex_unlock(&ffs->mutex); + + return mask; +} + static const struct file_operations ffs_ep0_operations = { .llseek = no_llseek, @@ -578,6 +619,7 @@ static const struct file_operations ffs_ep0_operations = { .read = ffs_ep0_read, .release = ffs_ep0_release, .unlocked_ioctl = ffs_ep0_ioctl, + .poll = ffs_ep0_poll, }; -- cgit v1.2.3-70-g09d2 From 2e4c7553cd6f9c68bb741582dcb614edcbeca70f Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Mon, 10 Feb 2014 10:42:44 +0100 Subject: usb: gadget: f_fs: add aio support This patch adds asynchronous I/O support for FunctionFS endpoint files. It adds ffs_epfile_aio_write() and ffs_epfile_aio_read() functions responsible for preparing AIO operations. It also modifies ffs_epfile_io() function, adding aio handling code. Instead of extending list of parameters of this function, there is new struct ffs_io_data which contains all information needed to perform I/O operation. Pointer to this struct replaces "buf" and "len" parameters of ffs_epfile_io() function. Allocated buffer is freed immediately only after sync operation, because in async IO it's freed in complete funcion. For each async operation an USB request is allocated, because it allows to have more than one request queued on single endpoint. According to changes in ffs_epfile_io() function, functions ffs_epfile_write() and ffs_epfile_read() are updated to use new API. For asynchronous I/O operations there is new request complete function named ffs_epfile_async_io_complete(), which completes AIO operation, and frees used memory. Signed-off-by: Robert Baldyga Acked-by: Michal Nazarewicz Signed-off-by: Felipe Balbi --- drivers/usb/gadget/f_fs.c | 265 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 239 insertions(+), 26 deletions(-) diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index 20321631f0f..1ae741fdace 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -28,6 +28,8 @@ #include #include +#include +#include #include #include "u_fs.h" @@ -158,6 +160,25 @@ struct ffs_epfile { unsigned char _pad; }; +/* ffs_io_data structure ***************************************************/ + +struct ffs_io_data { + bool aio; + bool read; + + struct kiocb *kiocb; + const struct iovec *iovec; + unsigned long nr_segs; + char __user *buf; + size_t len; + + struct mm_struct *mm; + struct work_struct work; + + struct usb_ep *ep; + struct usb_request *req; +}; + static int __must_check ffs_epfiles_create(struct ffs_data *ffs); static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count); @@ -635,8 +656,52 @@ static void ffs_epfile_io_complete(struct usb_ep *_ep, struct usb_request *req) } } -static ssize_t ffs_epfile_io(struct file *file, - char __user *buf, size_t len, int read) +static void ffs_user_copy_worker(struct work_struct *work) +{ + struct ffs_io_data *io_data = container_of(work, struct ffs_io_data, + work); + int ret = io_data->req->status ? io_data->req->status : + io_data->req->actual; + + if (io_data->read && ret > 0) { + int i; + size_t pos = 0; + use_mm(io_data->mm); + for (i = 0; i < io_data->nr_segs; i++) { + if (unlikely(copy_to_user(io_data->iovec[i].iov_base, + &io_data->buf[pos], + io_data->iovec[i].iov_len))) { + ret = -EFAULT; + break; + } + pos += io_data->iovec[i].iov_len; + } + unuse_mm(io_data->mm); + } + + aio_complete(io_data->kiocb, ret, ret); + + usb_ep_free_request(io_data->ep, io_data->req); + + io_data->kiocb->private = NULL; + if (io_data->read) + kfree(io_data->iovec); + kfree(io_data->buf); + kfree(io_data); +} + +static void ffs_epfile_async_io_complete(struct usb_ep *_ep, + struct usb_request *req) +{ + struct ffs_io_data *io_data = req->context; + + ENTER(); + + INIT_WORK(&io_data->work, ffs_user_copy_worker); + schedule_work(&io_data->work); +} + +static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) { struct ffs_epfile *epfile = file->private_data; struct usb_gadget *gadget = epfile->ffs->gadget; @@ -667,7 +732,7 @@ static ssize_t ffs_epfile_io(struct file *file, } /* Do we halt? */ - halt = !read == !epfile->in; + halt = (!io_data->read == !epfile->in); if (halt && epfile->isoc) { ret = -EINVAL; goto error; @@ -679,15 +744,32 @@ static ssize_t ffs_epfile_io(struct file *file, * Controller may require buffer size to be aligned to * maxpacketsize of an out endpoint. */ - data_len = read ? usb_ep_align_maybe(gadget, ep->ep, len) : len; + data_len = io_data->read ? + usb_ep_align_maybe(gadget, ep->ep, io_data->len) : + io_data->len; data = kmalloc(data_len, GFP_KERNEL); if (unlikely(!data)) return -ENOMEM; - - if (!read && unlikely(copy_from_user(data, buf, len))) { - ret = -EFAULT; - goto error; + if (io_data->aio && !io_data->read) { + int i; + size_t pos = 0; + for (i = 0; i < io_data->nr_segs; i++) { + if (unlikely(copy_from_user(&data[pos], + io_data->iovec[i].iov_base, + io_data->iovec[i].iov_len))) { + ret = -EFAULT; + goto error; + } + pos += io_data->iovec[i].iov_len; + } + } else { + if (!io_data->read && + unlikely(__copy_from_user(data, io_data->buf, + io_data->len))) { + ret = -EFAULT; + goto error; + } } } @@ -710,24 +792,52 @@ static ssize_t ffs_epfile_io(struct file *file, ret = -EBADMSG; } else { /* Fire the request */ - DECLARE_COMPLETION_ONSTACK(done); + struct usb_request *req; - struct usb_request *req = ep->req; - req->context = &done; - req->complete = ffs_epfile_io_complete; - req->buf = data; - req->length = data_len; + if (io_data->aio) { + req = usb_ep_alloc_request(ep->ep, GFP_KERNEL); + if (unlikely(!req)) + goto error; - ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC); + req->buf = data; + req->length = io_data->len; - spin_unlock_irq(&epfile->ffs->eps_lock); + io_data->buf = data; + io_data->ep = ep->ep; + io_data->req = req; - if (unlikely(ret < 0)) { - /* nop */ - } else if (unlikely(wait_for_completion_interruptible(&done))) { - ret = -EINTR; - usb_ep_dequeue(ep->ep, req); + req->context = io_data; + req->complete = ffs_epfile_async_io_complete; + + ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC); + if (unlikely(ret)) { + usb_ep_free_request(ep->ep, req); + goto error; + } + ret = -EIOCBQUEUED; + + spin_unlock_irq(&epfile->ffs->eps_lock); } else { + DECLARE_COMPLETION_ONSTACK(done); + + req = ep->req; + req->buf = data; + req->length = io_data->len; + + req->context = &done; + req->complete = ffs_epfile_io_complete; + + ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC); + + spin_unlock_irq(&epfile->ffs->eps_lock); + + if (unlikely(ret < 0)) { + /* nop */ + } else if (unlikely( + wait_for_completion_interruptible(&done))) { + ret = -EINTR; + usb_ep_dequeue(ep->ep, req); + } else { /* * XXX We may end up silently droping data here. * Since data_len (i.e. req->length) may be bigger @@ -736,14 +846,18 @@ static ssize_t ffs_epfile_io(struct file *file, * space for. */ ret = ep->status; - if (read && ret > 0 && - unlikely(copy_to_user(buf, data, - min_t(size_t, ret, len)))) + if (io_data->read && ret > 0 && + unlikely(copy_to_user(io_data->buf, data, + min_t(size_t, ret, + io_data->len)))) ret = -EFAULT; + } + kfree(data); } } mutex_unlock(&epfile->mutex); + return ret; error: kfree(data); return ret; @@ -753,17 +867,31 @@ static ssize_t ffs_epfile_write(struct file *file, const char __user *buf, size_t len, loff_t *ptr) { + struct ffs_io_data io_data; + ENTER(); - return ffs_epfile_io(file, (char __user *)buf, len, 0); + io_data.aio = false; + io_data.read = false; + io_data.buf = (char * __user)buf; + io_data.len = len; + + return ffs_epfile_io(file, &io_data); } static ssize_t ffs_epfile_read(struct file *file, char __user *buf, size_t len, loff_t *ptr) { + struct ffs_io_data io_data; + ENTER(); - return ffs_epfile_io(file, buf, len, 1); + io_data.aio = false; + io_data.read = true; + io_data.buf = buf; + io_data.len = len; + + return ffs_epfile_io(file, &io_data); } static int @@ -782,6 +910,89 @@ ffs_epfile_open(struct inode *inode, struct file *file) return 0; } +static int ffs_aio_cancel(struct kiocb *kiocb) +{ + struct ffs_io_data *io_data = kiocb->private; + struct ffs_epfile *epfile = kiocb->ki_filp->private_data; + int value; + + ENTER(); + + spin_lock_irq(&epfile->ffs->eps_lock); + + if (likely(io_data && io_data->ep && io_data->req)) + value = usb_ep_dequeue(io_data->ep, io_data->req); + else + value = -EINVAL; + + spin_unlock_irq(&epfile->ffs->eps_lock); + + return value; +} + +static ssize_t ffs_epfile_aio_write(struct kiocb *kiocb, + const struct iovec *iovec, + unsigned long nr_segs, loff_t loff) +{ + struct ffs_io_data *io_data; + + ENTER(); + + io_data = kmalloc(sizeof(*io_data), GFP_KERNEL); + if (unlikely(!io_data)) + return -ENOMEM; + + io_data->aio = true; + io_data->read = false; + io_data->kiocb = kiocb; + io_data->iovec = iovec; + io_data->nr_segs = nr_segs; + io_data->len = kiocb->ki_nbytes; + io_data->mm = current->mm; + + kiocb->private = io_data; + + kiocb_set_cancel_fn(kiocb, ffs_aio_cancel); + + return ffs_epfile_io(kiocb->ki_filp, io_data); +} + +static ssize_t ffs_epfile_aio_read(struct kiocb *kiocb, + const struct iovec *iovec, + unsigned long nr_segs, loff_t loff) +{ + struct ffs_io_data *io_data; + struct iovec *iovec_copy; + + ENTER(); + + iovec_copy = kmalloc_array(nr_segs, sizeof(*iovec_copy), GFP_KERNEL); + if (unlikely(!iovec_copy)) + return -ENOMEM; + + memcpy(iovec_copy, iovec, sizeof(struct iovec)*nr_segs); + + io_data = kmalloc(sizeof(*io_data), GFP_KERNEL); + if (unlikely(!io_data)) { + kfree(iovec_copy); + return -ENOMEM; + } + + io_data->aio = true; + io_data->read = true; + io_data->kiocb = kiocb; + io_data->iovec = iovec_copy; + io_data->nr_segs = nr_segs; + io_data->len = kiocb->ki_nbytes; + io_data->mm = current->mm; + + kiocb->private = io_data; + + kiocb_set_cancel_fn(kiocb, ffs_aio_cancel); + + return ffs_epfile_io(kiocb->ki_filp, io_data); +} + static int ffs_epfile_release(struct inode *inode, struct file *file) { @@ -838,6 +1049,8 @@ static const struct file_operations ffs_epfile_operations = { .open = ffs_epfile_open, .write = ffs_epfile_write, .read = ffs_epfile_read, + .aio_write = ffs_epfile_aio_write, + .aio_read = ffs_epfile_aio_read, .release = ffs_epfile_release, .unlocked_ioctl = ffs_epfile_ioctl, }; -- cgit v1.2.3-70-g09d2 From b797b76fb464ed6939ce71386bee7fdda4b68632 Mon Sep 17 00:00:00 2001 From: Paul Bolle Date: Sun, 16 Feb 2014 20:45:41 +0100 Subject: usb: host: remove USB_ARCH_HAS_?HCI USB_ARCH_HAS_EHCI, USB_ARCH_HAS_OHCI, and USB_ARCH_HAS_XHCI were made obsolete in v3.11. They have not been used ever since. Setting them has no effect. They can safely be removed. Signed-off-by: Paul Bolle Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/Kconfig | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 2e6b832e004..e0cad441808 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -2,10 +2,6 @@ # USB device configuration # -# These are unused now, remove them once they are no longer selected -config USB_ARCH_HAS_OHCI - bool - config USB_OHCI_BIG_ENDIAN_DESC bool @@ -17,18 +13,12 @@ config USB_OHCI_LITTLE_ENDIAN default n if STB03xxx || PPC_MPC52xx default y -config USB_ARCH_HAS_EHCI - bool - config USB_EHCI_BIG_ENDIAN_MMIO bool config USB_EHCI_BIG_ENDIAN_DESC bool -config USB_ARCH_HAS_XHCI - bool - menuconfig USB_SUPPORT bool "USB support" depends on HAS_IOMEM -- cgit v1.2.3-70-g09d2 From f6723b569a67aa4042b4ddc72822bf7f4395f3d2 Mon Sep 17 00:00:00 2001 From: Paul Bolle Date: Sun, 16 Feb 2014 20:46:20 +0100 Subject: usb: host: remove selects of USB_ARCH_HAS_?HCI USB_ARCH_HAS_EHCI, USB_ARCH_HAS_OHCI, and USB_ARCH_HAS_XHCI were just removed. Selecting them is a nop. The select statements for these symbols can be removed too. Signed-off-by: Paul Bolle Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- arch/arm/Kconfig | 3 --- arch/arm/mach-exynos/Kconfig | 1 - arch/arm/mach-omap2/Kconfig | 2 -- arch/arm/mach-shmobile/Kconfig | 4 ---- arch/arm/mach-tegra/Kconfig | 1 - arch/mips/Kconfig | 7 ------- arch/mips/ath79/Kconfig | 8 -------- arch/mips/ralink/Kconfig | 6 ------ arch/powerpc/platforms/44x/Kconfig | 1 - arch/powerpc/platforms/ps3/Kconfig | 2 -- arch/sh/Kconfig | 9 --------- 11 files changed, 44 deletions(-) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index e2541981779..e607f08b30f 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -534,7 +534,6 @@ config ARCH_DOVE select PINCTRL select PINCTRL_DOVE select PLAT_ORION_LEGACY - select USB_ARCH_HAS_EHCI help Support for the Marvell Dove SoC 88AP510 @@ -633,7 +632,6 @@ config ARCH_LPC32XX select GENERIC_CLOCKEVENTS select HAVE_IDE select HAVE_PWM - select USB_ARCH_HAS_OHCI select USE_OF help Support for the NXP LPC32XX family of processors @@ -770,7 +768,6 @@ config ARCH_S3C64XX select SAMSUNG_ATAGS select SAMSUNG_WAKEMASK select SAMSUNG_WDT_RESET - select USB_ARCH_HAS_OHCI help Samsung S3C64XX series based systems diff --git a/arch/arm/mach-exynos/Kconfig b/arch/arm/mach-exynos/Kconfig index 4c414af75ef..8d197dcdd2c 100644 --- a/arch/arm/mach-exynos/Kconfig +++ b/arch/arm/mach-exynos/Kconfig @@ -36,7 +36,6 @@ config ARCH_EXYNOS5 select HAVE_ARM_SCU if SMP select HAVE_SMP select PINCTRL - select USB_ARCH_HAS_XHCI help Samsung EXYNOS5 (Cortex-A15) SoC based systems diff --git a/arch/arm/mach-omap2/Kconfig b/arch/arm/mach-omap2/Kconfig index e2ce4f8366a..5463b2521da 100644 --- a/arch/arm/mach-omap2/Kconfig +++ b/arch/arm/mach-omap2/Kconfig @@ -21,7 +21,6 @@ config ARCH_OMAP3 select PM_OPP if PM select PM_RUNTIME if CPU_IDLE select SOC_HAS_OMAP2_SDRC - select USB_ARCH_HAS_EHCI if USB_SUPPORT config ARCH_OMAP4 bool "TI OMAP4" @@ -42,7 +41,6 @@ config ARCH_OMAP4 select PL310_ERRATA_727915 select PM_OPP if PM select PM_RUNTIME if CPU_IDLE - select USB_ARCH_HAS_EHCI if USB_SUPPORT select ARM_ERRATA_754322 select ARM_ERRATA_775420 diff --git a/arch/arm/mach-shmobile/Kconfig b/arch/arm/mach-shmobile/Kconfig index 05fa505df58..a0659023720 100644 --- a/arch/arm/mach-shmobile/Kconfig +++ b/arch/arm/mach-shmobile/Kconfig @@ -102,8 +102,6 @@ config ARCH_R8A7778 select CPU_V7 select SH_CLK_CPG select ARM_GIC - select USB_ARCH_HAS_EHCI - select USB_ARCH_HAS_OHCI config ARCH_R8A7779 bool "R-Car H1 (R8A77790)" @@ -111,8 +109,6 @@ config ARCH_R8A7779 select ARM_GIC select CPU_V7 select SH_CLK_CPG - select USB_ARCH_HAS_EHCI - select USB_ARCH_HAS_OHCI select RENESAS_INTC_IRQPIN config ARCH_R8A7790 diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig index b1232d8be6f..4926bd11f19 100644 --- a/arch/arm/mach-tegra/Kconfig +++ b/arch/arm/mach-tegra/Kconfig @@ -19,7 +19,6 @@ config ARCH_TEGRA select RESET_CONTROLLER select SOC_BUS select SPARSE_IRQ - select USB_ARCH_HAS_EHCI if USB_SUPPORT select USB_ULPI if USB_PHY select USB_ULPI_VIEWPORT if USB_PHY select USE_OF diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index dcae3a7035d..ba7fe400365 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -67,8 +67,6 @@ config MIPS_ALCHEMY select SYS_SUPPORTS_APM_EMULATION select ARCH_REQUIRE_GPIOLIB select SYS_SUPPORTS_ZBOOT - select USB_ARCH_HAS_OHCI - select USB_ARCH_HAS_EHCI config AR7 bool "Texas Instruments AR7" @@ -360,7 +358,6 @@ config MIPS_SEAD3 select SYS_SUPPORTS_LITTLE_ENDIAN select SYS_SUPPORTS_SMARTMIPS select SYS_SUPPORTS_MICROMIPS - select USB_ARCH_HAS_EHCI select USB_EHCI_BIG_ENDIAN_DESC select USB_EHCI_BIG_ENDIAN_MMIO select USE_OF @@ -718,8 +715,6 @@ config CAVIUM_OCTEON_SOC select SWAP_IO_SPACE select HW_HAS_PCI select ZONE_DMA32 - select USB_ARCH_HAS_OHCI - select USB_ARCH_HAS_EHCI select HOLES_IN_ZONE select ARCH_REQUIRE_GPIOLIB help @@ -756,8 +751,6 @@ config NLM_XLR_BOARD select ZONE_DMA32 if 64BIT select SYNC_R4K select SYS_HAS_EARLY_PRINTK - select USB_ARCH_HAS_OHCI if USB_SUPPORT - select USB_ARCH_HAS_EHCI if USB_SUPPORT select SYS_SUPPORTS_ZBOOT select SYS_SUPPORTS_ZBOOT_UART16550 help diff --git a/arch/mips/ath79/Kconfig b/arch/mips/ath79/Kconfig index 3995e31a73e..dfc60209dc6 100644 --- a/arch/mips/ath79/Kconfig +++ b/arch/mips/ath79/Kconfig @@ -74,34 +74,26 @@ config ATH79_MACH_UBNT_XM endmenu config SOC_AR71XX - select USB_ARCH_HAS_EHCI - select USB_ARCH_HAS_OHCI select HW_HAS_PCI def_bool n config SOC_AR724X - select USB_ARCH_HAS_EHCI - select USB_ARCH_HAS_OHCI select HW_HAS_PCI select PCI_AR724X if PCI def_bool n config SOC_AR913X - select USB_ARCH_HAS_EHCI def_bool n config SOC_AR933X - select USB_ARCH_HAS_EHCI def_bool n config SOC_AR934X - select USB_ARCH_HAS_EHCI select HW_HAS_PCI select PCI_AR724X if PCI def_bool n config SOC_QCA955X - select USB_ARCH_HAS_EHCI select HW_HAS_PCI select PCI_AR724X if PCI def_bool n diff --git a/arch/mips/ralink/Kconfig b/arch/mips/ralink/Kconfig index 1bfd1c17b3c..4a296655f44 100644 --- a/arch/mips/ralink/Kconfig +++ b/arch/mips/ralink/Kconfig @@ -20,19 +20,13 @@ choice config SOC_RT305X bool "RT305x" select USB_ARCH_HAS_HCD - select USB_ARCH_HAS_OHCI - select USB_ARCH_HAS_EHCI config SOC_RT3883 bool "RT3883" - select USB_ARCH_HAS_OHCI - select USB_ARCH_HAS_EHCI select HW_HAS_PCI config SOC_MT7620 bool "MT7620" - select USB_ARCH_HAS_OHCI - select USB_ARCH_HAS_EHCI endchoice diff --git a/arch/powerpc/platforms/44x/Kconfig b/arch/powerpc/platforms/44x/Kconfig index d6c7506ec7d..dc1a264ec6e 100644 --- a/arch/powerpc/platforms/44x/Kconfig +++ b/arch/powerpc/platforms/44x/Kconfig @@ -265,7 +265,6 @@ config 440EP select PPC_FPU select IBM440EP_ERR42 select IBM_EMAC_ZMII - select USB_ARCH_HAS_OHCI config 440EPX bool diff --git a/arch/powerpc/platforms/ps3/Kconfig b/arch/powerpc/platforms/ps3/Kconfig index e87c1947397..56f274064d6 100644 --- a/arch/powerpc/platforms/ps3/Kconfig +++ b/arch/powerpc/platforms/ps3/Kconfig @@ -2,10 +2,8 @@ config PPC_PS3 bool "Sony PS3" depends on PPC64 && PPC_BOOK3S select PPC_CELL - select USB_ARCH_HAS_OHCI select USB_OHCI_LITTLE_ENDIAN select USB_OHCI_BIG_ENDIAN_MMIO - select USB_ARCH_HAS_EHCI select USB_EHCI_BIG_ENDIAN_MMIO select PPC_PCI_CHOICE help diff --git a/arch/sh/Kconfig b/arch/sh/Kconfig index 6357710753d..64455daa63a 100644 --- a/arch/sh/Kconfig +++ b/arch/sh/Kconfig @@ -356,7 +356,6 @@ config CPU_SUBTYPE_SH7720 select CPU_HAS_DSP select SYS_SUPPORTS_CMT select ARCH_WANT_OPTIONAL_GPIOLIB - select USB_ARCH_HAS_OHCI select USB_OHCI_SH if USB_OHCI_HCD select PINCTRL help @@ -367,7 +366,6 @@ config CPU_SUBTYPE_SH7721 select CPU_SH3 select CPU_HAS_DSP select SYS_SUPPORTS_CMT - select USB_ARCH_HAS_OHCI select USB_OHCI_SH if USB_OHCI_HCD help Select SH7721 if you have a SH3-DSP SH7721 CPU. @@ -445,8 +443,6 @@ config CPU_SUBTYPE_SH7734 select CPU_SH4A select CPU_SHX2 select ARCH_WANT_OPTIONAL_GPIOLIB - select USB_ARCH_HAS_OHCI - select USB_ARCH_HAS_EHCI select PINCTRL help Select SH7734 if you have a SH4A SH7734 CPU. @@ -456,8 +452,6 @@ config CPU_SUBTYPE_SH7757 select CPU_SH4A select CPU_SHX2 select ARCH_WANT_OPTIONAL_GPIOLIB - select USB_ARCH_HAS_OHCI - select USB_ARCH_HAS_EHCI select PINCTRL help Select SH7757 if you have a SH4A SH7757 CPU. @@ -465,7 +459,6 @@ config CPU_SUBTYPE_SH7757 config CPU_SUBTYPE_SH7763 bool "Support SH7763 processor" select CPU_SH4A - select USB_ARCH_HAS_OHCI select USB_OHCI_SH if USB_OHCI_HCD help Select SH7763 if you have a SH4A SH7763(R5S77631) CPU. @@ -494,9 +487,7 @@ config CPU_SUBTYPE_SH7786 select CPU_HAS_PTEAEX select GENERIC_CLOCKEVENTS_BROADCAST if SMP select ARCH_WANT_OPTIONAL_GPIOLIB - select USB_ARCH_HAS_OHCI select USB_OHCI_SH if USB_OHCI_HCD - select USB_ARCH_HAS_EHCI select USB_EHCI_SH if USB_EHCI_HCD select PINCTRL -- cgit v1.2.3-70-g09d2 From aa7be0f8f7fbd446c7cd5352480d0d10d78f742b Mon Sep 17 00:00:00 2001 From: Bo Shen Date: Wed, 19 Feb 2014 10:07:43 +0800 Subject: usb: gadget: at91: fix the number of endpoint parameter In sama5d3 SoC, there are 16 endpoints, which is different with earlier SoCs (only have 7 endpoints). The USBA_NR_ENDPOINTS macro is not suitable for sama5d3. So, get the endpoints number through the udc->num_ep, which get from platform data for non-dt kernel, or parse from dt node. Acked-by: Nicolas Ferre Signed-off-by: Bo Shen Signed-off-by: Felipe Balbi --- drivers/usb/gadget/atmel_usba_udc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/atmel_usba_udc.c b/drivers/usb/gadget/atmel_usba_udc.c index 52771d4c44b..53eea030fc1 100644 --- a/drivers/usb/gadget/atmel_usba_udc.c +++ b/drivers/usb/gadget/atmel_usba_udc.c @@ -1670,7 +1670,7 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) if (ep_status) { int i; - for (i = 0; i < USBA_NR_ENDPOINTS; i++) + for (i = 0; i < udc->num_ep; i++) if (ep_status & (1 << i)) { if (ep_is_control(&udc->usba_ep[i])) usba_control_irq(udc, &udc->usba_ep[i]); -- cgit v1.2.3-70-g09d2 From 3fcba0d87bfb5f880d34868090e05571dca80f78 Mon Sep 17 00:00:00 2001 From: Bo Shen Date: Wed, 19 Feb 2014 10:07:44 +0800 Subject: usb: gadget: at91: using USBA_NR_DMAS for DMA channels The SoCs earlier than sama5d3, they have the same number endpoints and DMA channels. In driver code, they use the same definition USBA_NR_ENDPOINTS for both endpoints and dma channels. However, in sama5d3, it has different number for endpoints and DMA channels. So, define a new macro USBA_NR_DMAs for DMA channels. And the USBA_NR_ENDPOINS is not used anymore, remove it at the same time. Acked-by: Nicolas Ferre Signed-off-by: Bo Shen Signed-off-by: Felipe Balbi --- drivers/usb/gadget/atmel_usba_udc.c | 2 +- drivers/usb/gadget/atmel_usba_udc.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/atmel_usba_udc.c b/drivers/usb/gadget/atmel_usba_udc.c index 53eea030fc1..18a44919ae9 100644 --- a/drivers/usb/gadget/atmel_usba_udc.c +++ b/drivers/usb/gadget/atmel_usba_udc.c @@ -1661,7 +1661,7 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) if (dma_status) { int i; - for (i = 1; i < USBA_NR_ENDPOINTS; i++) + for (i = 1; i < USBA_NR_DMAS; i++) if (dma_status & (1 << i)) usba_dma_irq(udc, &udc->usba_ep[i]); } diff --git a/drivers/usb/gadget/atmel_usba_udc.h b/drivers/usb/gadget/atmel_usba_udc.h index 2922db50bef..a70706e8cb0 100644 --- a/drivers/usb/gadget/atmel_usba_udc.h +++ b/drivers/usb/gadget/atmel_usba_udc.h @@ -210,7 +210,7 @@ #define USBA_FIFO_BASE(x) ((x) << 16) /* Synth parameters */ -#define USBA_NR_ENDPOINTS 7 +#define USBA_NR_DMAS 7 #define EP0_FIFO_SIZE 64 #define EP0_EPT_SIZE USBA_EPT_SIZE_64 -- cgit v1.2.3-70-g09d2 From 798a2468e026ab6a922f2c6c70ca4406b8e8dbc1 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 17 Jan 2014 16:34:11 +0300 Subject: usb: gadget: gr_udc: remove some unneeded error handling Debugfs function return an ERR_PTR if they compiled out. We don't need to test for that here because if the debugfs file are compiled out then it is ok to pass an ERR_PTR to debugfs_create_file() since it will just be a no-op stub. Debugfs return NULLs on error, but we don't need to test for that either because debugfs_create_file() will accept NULL pointers. Signed-off-by: Dan Carpenter Signed-off-by: Felipe Balbi --- drivers/usb/gadget/gr_udc.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/usb/gadget/gr_udc.c b/drivers/usb/gadget/gr_udc.c index 914cbd84ee4..f984ee75324 100644 --- a/drivers/usb/gadget/gr_udc.c +++ b/drivers/usb/gadget/gr_udc.c @@ -225,14 +225,8 @@ static void gr_dfs_create(struct gr_udc *dev) const char *name = "gr_udc_state"; dev->dfs_root = debugfs_create_dir(dev_name(dev->dev), NULL); - if (IS_ERR(dev->dfs_root)) { - dev_err(dev->dev, "Failed to create debugfs directory\n"); - return; - } - dev->dfs_state = debugfs_create_file(name, 0444, dev->dfs_root, - dev, &gr_dfs_fops); - if (IS_ERR(dev->dfs_state)) - dev_err(dev->dev, "Failed to create debugfs file %s\n", name); + dev->dfs_state = debugfs_create_file(name, 0444, dev->dfs_root, dev, + &gr_dfs_fops); } static void gr_dfs_delete(struct gr_udc *dev) -- cgit v1.2.3-70-g09d2 From 06f9b6e59661cee510b04513b13ea7927727d758 Mon Sep 17 00:00:00 2001 From: Huang Rui Date: Tue, 7 Jan 2014 17:45:50 +0800 Subject: usb: dwc3: fix wrong bit mask in dwc3_event_devt Around DWC USB3 2.30a release another bit has been added to the Device-Specific Event (DEVT) Event Information (EvtInfo) bitfield. Because of that, what used to be 8 bits long, has become 9 bits long. Per dwc3 2.30a+ spec in the Device-Specific Event (DEVT), the field of Event Information Bits(EvtInfo) uses [24:16] bits, and it has 9 bits not 8 bits. And the following reserved field uses [31:25] bits not [31:24] bits, and it has 7 bits. So in dwc3_event_devt, the bit mask should be: event_info [24:16] 9 bits reserved31_25 [31:25] 7 bits This patch makes sure that newer core releases will work fine with Linux and that we will decode the event information properly on new core releases. [ balbi@ti.com : improve commit log a bit ] Cc: Signed-off-by: Huang Rui Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index f8af8d44af8..69c4583933d 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -815,15 +815,15 @@ struct dwc3_event_depevt { * 12 - VndrDevTstRcved * @reserved15_12: Reserved, not used * @event_info: Information about this event - * @reserved31_24: Reserved, not used + * @reserved31_25: Reserved, not used */ struct dwc3_event_devt { u32 one_bit:1; u32 device_event:7; u32 type:4; u32 reserved15_12:4; - u32 event_info:8; - u32 reserved31_24:8; + u32 event_info:9; + u32 reserved31_25:7; } __packed; /** -- cgit v1.2.3-70-g09d2 From 40f099e32c2a06bad7d75683421e30fcc74924cd Mon Sep 17 00:00:00 2001 From: Markus Pargmann Date: Fri, 17 Jan 2014 10:22:35 +0100 Subject: usb: musb: dsps, debugfs files debugfs files to show the contents of important dsps registers. Signed-off-by: Markus Pargmann Signed-off-by: Felipe Balbi --- drivers/usb/musb/musb_dsps.c | 54 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/drivers/usb/musb/musb_dsps.c b/drivers/usb/musb/musb_dsps.c index 68c14e1eac6..3372ded5def 100644 --- a/drivers/usb/musb/musb_dsps.c +++ b/drivers/usb/musb/musb_dsps.c @@ -45,6 +45,8 @@ #include #include +#include + #include "musb_core.h" static const struct of_device_id musb_dsps_of_match[]; @@ -136,6 +138,26 @@ struct dsps_glue { unsigned long last_timer; /* last timer data for each instance */ struct dsps_context context; + struct debugfs_regset32 regset; + struct dentry *dbgfs_root; +}; + +static const struct debugfs_reg32 dsps_musb_regs[] = { + { "revision", 0x00 }, + { "control", 0x14 }, + { "status", 0x18 }, + { "eoi", 0x24 }, + { "intr0_stat", 0x30 }, + { "intr1_stat", 0x34 }, + { "intr0_set", 0x38 }, + { "intr1_set", 0x3c }, + { "txmode", 0x70 }, + { "rxmode", 0x74 }, + { "autoreq", 0xd0 }, + { "srpfixtime", 0xd4 }, + { "tdown", 0xd8 }, + { "phy_utmi", 0xe0 }, + { "mode", 0xe8 }, }; static void dsps_musb_try_idle(struct musb *musb, unsigned long timeout) @@ -368,6 +390,30 @@ out: return ret; } +static int dsps_musb_dbg_init(struct musb *musb, struct dsps_glue *glue) +{ + struct dentry *root; + struct dentry *file; + char buf[128]; + + sprintf(buf, "%s.dsps", dev_name(musb->controller)); + root = debugfs_create_dir(buf, NULL); + if (!root) + return -ENOMEM; + glue->dbgfs_root = root; + + glue->regset.regs = dsps_musb_regs; + glue->regset.nregs = ARRAY_SIZE(dsps_musb_regs); + glue->regset.base = musb->ctrl_base; + + file = debugfs_create_regset32("regdump", S_IRUGO, root, &glue->regset); + if (!file) { + debugfs_remove_recursive(root); + return -ENOMEM; + } + return 0; +} + static int dsps_musb_init(struct musb *musb) { struct device *dev = musb->controller; @@ -377,6 +423,7 @@ static int dsps_musb_init(struct musb *musb) void __iomem *reg_base; struct resource *r; u32 rev, val; + int ret; r = platform_get_resource_byname(parent, IORESOURCE_MEM, "control"); if (!r) @@ -410,6 +457,10 @@ static int dsps_musb_init(struct musb *musb) val &= ~(1 << wrp->otg_disable); dsps_writel(musb->ctrl_base, wrp->phy_utmi, val); + ret = dsps_musb_dbg_init(musb, glue); + if (ret) + return ret; + return 0; } @@ -656,6 +707,9 @@ static int dsps_remove(struct platform_device *pdev) /* disable usbss clocks */ pm_runtime_put(&pdev->dev); pm_runtime_disable(&pdev->dev); + + debugfs_remove_recursive(glue->dbgfs_root); + return 0; } -- cgit v1.2.3-70-g09d2 From c859aa65a7ec40c02f435f14fa71de2a87c64513 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 19 Feb 2014 13:41:40 +0800 Subject: usb: chipidea: refine PHY operation - Delete global_phy due to we can get the phy from phy layer now - using devm_usb_get_phy to instead of usb_get_phy - delete the otg_set_peripheral, which should be handled by otg layer Signed-off-by: Peter Chen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci.h | 2 -- drivers/usb/chipidea/core.c | 70 +++++++++++++++++---------------------------- drivers/usb/chipidea/udc.c | 6 ---- 3 files changed, 26 insertions(+), 52 deletions(-) diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 88b80f7728e..e206406ae1d 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -196,8 +196,6 @@ struct ci_hdrc { struct ci_hdrc_platform_data *platdata; int vbus_active; - /* FIXME: some day, we'll not use global phy */ - bool global_phy; struct usb_phy *transceiver; struct usb_hcd *hcd; struct dentry *debugfs; diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 33f22bc6ad7..75aaa9c3cc4 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -496,33 +496,6 @@ static void ci_get_otg_capable(struct ci_hdrc *ci) } } -static int ci_usb_phy_init(struct ci_hdrc *ci) -{ - if (ci->platdata->phy) { - ci->transceiver = ci->platdata->phy; - return usb_phy_init(ci->transceiver); - } else { - ci->global_phy = true; - ci->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); - if (IS_ERR(ci->transceiver)) - ci->transceiver = NULL; - - return 0; - } -} - -static void ci_usb_phy_destroy(struct ci_hdrc *ci) -{ - if (!ci->transceiver) - return; - - otg_set_peripheral(ci->transceiver->otg, NULL); - if (ci->global_phy) - usb_put_phy(ci->transceiver); - else - usb_phy_shutdown(ci->transceiver); -} - static int ci_hdrc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -561,7 +534,26 @@ static int ci_hdrc_probe(struct platform_device *pdev) hw_phymode_configure(ci); - ret = ci_usb_phy_init(ci); + if (ci->platdata->phy) + ci->transceiver = ci->platdata->phy; + else + ci->transceiver = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + + if (IS_ERR(ci->transceiver)) { + ret = PTR_ERR(ci->transceiver); + /* + * if -ENXIO is returned, it means PHY layer wasn't + * enabled, so it makes no sense to return -EPROBE_DEFER + * in that case, since no PHY driver will ever probe. + */ + if (ret == -ENXIO) + return ret; + + dev_err(dev, "no usb2 phy configured\n"); + return -EPROBE_DEFER; + } + + ret = usb_phy_init(ci->transceiver); if (ret) { dev_err(dev, "unable to init phy: %d\n", ret); return ret; @@ -573,7 +565,7 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (ci->irq < 0) { dev_err(dev, "missing IRQ\n"); ret = -ENODEV; - goto destroy_phy; + goto deinit_phy; } ci_get_otg_capable(ci); @@ -590,23 +582,12 @@ static int ci_hdrc_probe(struct platform_device *pdev) ret = ci_hdrc_gadget_init(ci); if (ret) dev_info(dev, "doesn't support gadget\n"); - if (!ret && ci->transceiver) { - ret = otg_set_peripheral(ci->transceiver->otg, - &ci->gadget); - /* - * If we implement all USB functions using chipidea drivers, - * it doesn't need to call above API, meanwhile, if we only - * use gadget function, calling above API is useless. - */ - if (ret && ret != -ENOTSUPP) - goto destroy_phy; - } } if (!ci->roles[CI_ROLE_HOST] && !ci->roles[CI_ROLE_GADGET]) { dev_err(dev, "no supported roles\n"); ret = -ENODEV; - goto destroy_phy; + goto deinit_phy; } if (ci->is_otg) { @@ -663,8 +644,8 @@ static int ci_hdrc_probe(struct platform_device *pdev) free_irq(ci->irq, ci); stop: ci_role_destroy(ci); -destroy_phy: - ci_usb_phy_destroy(ci); +deinit_phy: + usb_phy_shutdown(ci->transceiver); return ret; } @@ -677,7 +658,8 @@ static int ci_hdrc_remove(struct platform_device *pdev) free_irq(ci->irq, ci); ci_role_destroy(ci); ci_hdrc_enter_lpm(ci, true); - ci_usb_phy_destroy(ci); + usb_phy_shutdown(ci->transceiver); + kfree(ci->hw_bank.regmap); return 0; } diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 4ab2cb62dfc..0a61c6688bc 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -1837,12 +1837,6 @@ void ci_hdrc_gadget_destroy(struct ci_hdrc *ci) dma_pool_destroy(ci->td_pool); dma_pool_destroy(ci->qh_pool); - - if (ci->transceiver) { - otg_set_peripheral(ci->transceiver->otg, NULL); - if (ci->global_phy) - usb_put_phy(ci->transceiver); - } } static int udc_id_switch_for_device(struct ci_hdrc *ci) -- cgit v1.2.3-70-g09d2 From 64fc06c40e01aa8129f5bc4c51567b5e205126b0 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 19 Feb 2014 13:41:41 +0800 Subject: usb: chipidea: udc: refine ep operation at isr_tr_complete_handler - delete the warning message at interrupt handler, and adds judgement at ep_enable, if non-ep0 requests ctrl transfer, it will indicate an error. - delete hw_test_and_clear_setup_status which is a broken code - Tested with g_mass_storage, g_ncm, g_ether Cc: matthieu.castet@parrot.com Reported-by: Michael Grzeschik Acked-by: Michael Grzeschik Tested-by: Michael Grzeschik Signed-off-by: Peter Chen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/udc.c | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 0a61c6688bc..0dc56aebb80 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -177,19 +177,6 @@ static int hw_ep_get_halt(struct ci_hdrc *ci, int num, int dir) return hw_read(ci, OP_ENDPTCTRL + num, mask) ? 1 : 0; } -/** - * hw_test_and_clear_setup_status: test & clear setup status (execute without - * interruption) - * @n: endpoint number - * - * This function returns setup status - */ -static int hw_test_and_clear_setup_status(struct ci_hdrc *ci, int n) -{ - n = ep_to_bit(ci, n); - return hw_test_and_clear(ci, OP_ENDPTSETUPSTAT, BIT(n)); -} - /** * hw_ep_prime: primes endpoint (execute without interruption) * @num: endpoint number @@ -997,14 +984,10 @@ __acquires(ci->lock) } } - if (hwep->type != USB_ENDPOINT_XFER_CONTROL || - !hw_test_and_clear_setup_status(ci, i)) - continue; - - if (i != 0) { - dev_warn(ci->dev, "ctrl traffic at endpoint %d\n", i); + /* Only handle setup packet below */ + if (i != 0 || + !hw_test_and_clear(ci, OP_ENDPTSETUPSTAT, BIT(0))) continue; - } /* * Flush data and handshake transactions of previous @@ -1193,6 +1176,11 @@ static int ep_enable(struct usb_ep *ep, hwep->qh.ptr->td.next |= cpu_to_le32(TD_TERMINATE); /* needed? */ + if (hwep->num != 0 && hwep->type == USB_ENDPOINT_XFER_CONTROL) { + dev_err(hwep->ci->dev, "Set control xfer at non-ep0\n"); + retval = -EINVAL; + } + /* * Enable endpoints in the HW other than ep0 as ep0 * is always enabled -- cgit v1.2.3-70-g09d2 From fad56745a641750745d85f59d5cc640a1a4c1719 Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Wed, 19 Feb 2014 13:41:42 +0800 Subject: usb: chipidea: use dev_get_platdata() Use the wrapper function for retrieving the platform data instead of accessing dev->platform_data directly. This is a cosmetic change to make the code simpler and enhance the readability. Signed-off-by: Peter Chen Signed-off-by: Jingoo Han Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 75aaa9c3cc4..47b4bd860b6 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -505,7 +505,7 @@ static int ci_hdrc_probe(struct platform_device *pdev) int ret; enum usb_dr_mode dr_mode; - if (!dev->platform_data) { + if (!dev_get_platdata(dev)) { dev_err(dev, "platform data missing\n"); return -ENODEV; } @@ -522,7 +522,7 @@ static int ci_hdrc_probe(struct platform_device *pdev) } ci->dev = dev; - ci->platdata = dev->platform_data; + ci->platdata = dev_get_platdata(dev); ci->imx28_write_fix = !!(ci->platdata->flags & CI_HDRC_IMX28_WRITE_FIX); -- cgit v1.2.3-70-g09d2 From 4f6743d5ca97ac66831add302cc5467db4ee3809 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Wed, 19 Feb 2014 13:41:43 +0800 Subject: usb: chipidea: udc: add maximum-speed = full-speed option This patch makes it possible to set the chipidea udc into full-speed only mode. It is set by the oftree property "maximum-speed = full-speed". Signed-off-by: Peter Chen Signed-off-by: Michael Grzeschik Signed-off-by: Marc Kleine-Budde Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/ci-hdrc-imx.txt | 2 ++ drivers/usb/chipidea/bits.h | 2 ++ drivers/usb/chipidea/core.c | 11 +++++++++++ include/linux/usb/chipidea.h | 1 + 4 files changed, 16 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/ci-hdrc-imx.txt b/Documentation/devicetree/bindings/usb/ci-hdrc-imx.txt index b4b5b7906c8..a6a32cb7f77 100644 --- a/Documentation/devicetree/bindings/usb/ci-hdrc-imx.txt +++ b/Documentation/devicetree/bindings/usb/ci-hdrc-imx.txt @@ -18,6 +18,7 @@ Optional properties: - vbus-supply: regulator for vbus - disable-over-current: disable over current detect - external-vbus-divider: enables off-chip resistor divider for Vbus +- maximum-speed: limit the maximum connection speed to "full-speed". Examples: usb@02184000 { /* USB OTG */ @@ -28,4 +29,5 @@ usb@02184000 { /* USB OTG */ fsl,usbmisc = <&usbmisc 0>; disable-over-current; external-vbus-divider; + maximum-speed = "full-speed"; }; diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h index a8571316568..83d06c1455b 100644 --- a/drivers/usb/chipidea/bits.h +++ b/drivers/usb/chipidea/bits.h @@ -50,12 +50,14 @@ #define PORTSC_PTC (0x0FUL << 16) #define PORTSC_PHCD(d) ((d) ? BIT(22) : BIT(23)) /* PTS and PTW for non lpm version only */ +#define PORTSC_PFSC BIT(24) #define PORTSC_PTS(d) \ (u32)((((d) & 0x3) << 30) | (((d) & 0x4) ? BIT(25) : 0)) #define PORTSC_PTW BIT(28) #define PORTSC_STS BIT(29) /* DEVLC */ +#define DEVLC_PFSC BIT(23) #define DEVLC_PSPD (0x03UL << 25) #define DEVLC_PSPD_HS (0x02UL << 25) #define DEVLC_PTW BIT(27) diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 47b4bd860b6..65aeaacda6c 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -64,6 +64,7 @@ #include #include #include +#include #include #include @@ -298,6 +299,13 @@ int hw_device_reset(struct ci_hdrc *ci, u32 mode) if (ci->platdata->flags & CI_HDRC_DISABLE_STREAMING) hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, USBMODE_CI_SDIS); + if (ci->platdata->flags & CI_HDRC_FORCE_FULLSPEED) { + if (ci->hw_bank.lpm) + hw_write(ci, OP_DEVLC, DEVLC_PFSC, DEVLC_PFSC); + else + hw_write(ci, OP_PORTSC, PORTSC_PFSC, PORTSC_PFSC); + } + /* USBMODE should be configured step by step */ hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE); hw_write(ci, OP_USBMODE, USBMODE_CM, mode); @@ -412,6 +420,9 @@ static int ci_get_platdata(struct device *dev, } } + if (of_usb_get_maximum_speed(dev->of_node) == USB_SPEED_FULL) + platdata->flags |= CI_HDRC_FORCE_FULLSPEED; + return 0; } diff --git a/include/linux/usb/chipidea.h b/include/linux/usb/chipidea.h index 708bd119627..bbe779f640b 100644 --- a/include/linux/usb/chipidea.h +++ b/include/linux/usb/chipidea.h @@ -25,6 +25,7 @@ struct ci_hdrc_platform_data { */ #define CI_HDRC_DUAL_ROLE_NOT_OTG BIT(4) #define CI_HDRC_IMX28_WRITE_FIX BIT(5) +#define CI_HDRC_FORCE_FULLSPEED BIT(6) enum usb_dr_mode dr_mode; #define CI_HDRC_CONTROLLER_RESET_EVENT 0 #define CI_HDRC_CONTROLLER_STOPPED_EVENT 1 -- cgit v1.2.3-70-g09d2 From 42d182124801573e06284200d81c3963962e753d Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Wed, 19 Feb 2014 13:41:44 +0800 Subject: usb: chipidea: Propagate the real error code on platform_get_irq() failure No need to return a 'fake' return value on platform_get_irq() failure. Just return the error code itself instead. Signed-off-by: Peter Chen Signed-off-by: Fabio Estevam Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 65aeaacda6c..ca6831c5b76 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -575,7 +575,7 @@ static int ci_hdrc_probe(struct platform_device *pdev) ci->irq = platform_get_irq(pdev, 0); if (ci->irq < 0) { dev_err(dev, "missing IRQ\n"); - ret = -ENODEV; + ret = ci->irq; goto deinit_phy; } -- cgit v1.2.3-70-g09d2 From f080a51bef2caa9b0f647dc430bc608d5723ac29 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Thu, 20 Feb 2014 10:49:30 -0500 Subject: USB: complain if userspace resets an active endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is an error for a driver to call usb_clear_halt() or usb_reset_endpoint() while there are URBs queued for the endpoint, because the end result is not well defined. At the time the endpoint gets reset, it may or may not be actively running. As far as I know, no kernel drivers do this. But some userspace drivers do, and it seems like a good idea to bring this error to their attention. This patch adds a warning to the kernel log whenever a program invokes the USBDEVFS_CLEAR_HALT or USBDEVFS_RESETEP ioctls at an inappropriate time, and includes the name of the program. This will make it clear that any subsequent errors are not due to the misbehavior of a kernel driver. Signed-off-by: Alan Stern Suggested-by: Bjørn Mork CC: Stanislaw Gruszka Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/devio.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 90e18f6fa2b..f3ba2e076ee 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -1043,6 +1043,20 @@ static int proc_bulk(struct dev_state *ps, void __user *arg) return ret; } +static void check_reset_of_active_ep(struct usb_device *udev, + unsigned int epnum, char *ioctl_name) +{ + struct usb_host_endpoint **eps; + struct usb_host_endpoint *ep; + + eps = (epnum & USB_DIR_IN) ? udev->ep_in : udev->ep_out; + ep = eps[epnum & 0x0f]; + if (ep && !list_empty(&ep->urb_list)) + dev_warn(&udev->dev, "Process %d (%s) called USBDEVFS_%s for active endpoint 0x%02x\n", + task_pid_nr(current), current->comm, + ioctl_name, epnum); +} + static int proc_resetep(struct dev_state *ps, void __user *arg) { unsigned int ep; @@ -1056,6 +1070,7 @@ static int proc_resetep(struct dev_state *ps, void __user *arg) ret = checkintf(ps, ret); if (ret) return ret; + check_reset_of_active_ep(ps->dev, ep, "RESETEP"); usb_reset_endpoint(ps->dev, ep); return 0; } @@ -1074,6 +1089,7 @@ static int proc_clearhalt(struct dev_state *ps, void __user *arg) ret = checkintf(ps, ret); if (ret) return ret; + check_reset_of_active_ep(ps->dev, ep, "CLEAR_HALT"); if (ep & USB_DIR_IN) pipe = usb_rcvbulkpipe(ps->dev, ep & 0x7f); else -- cgit v1.2.3-70-g09d2 From 618836cc3478aec3dc9a60488bfd43ca93a322bd Mon Sep 17 00:00:00 2001 From: Thomas Pugliese Date: Fri, 28 Feb 2014 14:31:55 -0600 Subject: usb: wusbcore: fix kernel panic on HWA unplug This patch adds ref counting to sections of code that operate on struct wa_xfer objects that were missing it. Specifically, error handling cases need to be protected from freeing the xfer while it is still in use elsewhere. This fixes a kernel panic that can occur when pulling the HWA dongle while data is being transferred to a wireless device. Signed-off-by: Thomas Pugliese Signed-off-by: Greg Kroah-Hartman --- drivers/usb/wusbcore/wa-xfer.c | 48 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c index 3cd96e936d7..3ca05555510 100644 --- a/drivers/usb/wusbcore/wa-xfer.c +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -733,6 +733,8 @@ static void wa_seg_dto_cb(struct urb *urb) seg->isoc_frame_offset + seg->isoc_frame_index); /* resubmit the URB with the next isoc frame. */ + /* take a ref on resubmit. */ + wa_xfer_get(xfer); result = usb_submit_urb(seg->dto_urb, GFP_ATOMIC); if (result < 0) { dev_err(dev, "xfer 0x%08X#%u: DTO submit failed: %d\n", @@ -760,9 +762,13 @@ static void wa_seg_dto_cb(struct urb *urb) goto error_default; } + /* taken when this URB was submitted. */ + wa_xfer_put(xfer); return; error_dto_submit: + /* taken on resubmit attempt. */ + wa_xfer_put(xfer); error_default: spin_lock_irqsave(&xfer->lock, flags); rpipe = xfer->ep->hcpriv; @@ -788,7 +794,8 @@ error_default: wa_xfer_completion(xfer); if (rpipe_ready) wa_xfer_delayed_run(rpipe); - + /* taken when this URB was submitted. */ + wa_xfer_put(xfer); } /* @@ -855,6 +862,8 @@ static void wa_seg_iso_pack_desc_cb(struct urb *urb) if (rpipe_ready) wa_xfer_delayed_run(rpipe); } + /* taken when this URB was submitted. */ + wa_xfer_put(xfer); } /* @@ -931,6 +940,8 @@ static void wa_seg_tr_cb(struct urb *urb) if (rpipe_ready) wa_xfer_delayed_run(rpipe); } + /* taken when this URB was submitted. */ + wa_xfer_put(xfer); } /* @@ -1318,30 +1329,41 @@ static int __wa_seg_submit(struct wa_rpipe *rpipe, struct wa_xfer *xfer, /* default to done unless we encounter a multi-frame isoc segment. */ *dto_done = 1; + /* + * Take a ref for each segment urb so the xfer cannot disappear until + * all of the callbacks run. + */ + wa_xfer_get(xfer); /* submit the transfer request. */ + seg->status = WA_SEG_SUBMITTED; result = usb_submit_urb(&seg->tr_urb, GFP_ATOMIC); if (result < 0) { pr_err("%s: xfer %p#%u: REQ submit failed: %d\n", __func__, xfer, seg->index, result); - goto error_seg_submit; + wa_xfer_put(xfer); + goto error_tr_submit; } /* submit the isoc packet descriptor if present. */ if (seg->isoc_pack_desc_urb) { + wa_xfer_get(xfer); result = usb_submit_urb(seg->isoc_pack_desc_urb, GFP_ATOMIC); seg->isoc_frame_index = 0; if (result < 0) { pr_err("%s: xfer %p#%u: ISO packet descriptor submit failed: %d\n", __func__, xfer, seg->index, result); + wa_xfer_put(xfer); goto error_iso_pack_desc_submit; } } /* submit the out data if this is an out request. */ if (seg->dto_urb) { struct wahc *wa = xfer->wa; + wa_xfer_get(xfer); result = usb_submit_urb(seg->dto_urb, GFP_ATOMIC); if (result < 0) { pr_err("%s: xfer %p#%u: DTO submit failed: %d\n", __func__, xfer, seg->index, result); + wa_xfer_put(xfer); goto error_dto_submit; } /* @@ -1353,7 +1375,6 @@ static int __wa_seg_submit(struct wa_rpipe *rpipe, struct wa_xfer *xfer, && (seg->isoc_frame_count > 1)) *dto_done = 0; } - seg->status = WA_SEG_SUBMITTED; rpipe_avail_dec(rpipe); return 0; @@ -1361,7 +1382,7 @@ error_dto_submit: usb_unlink_urb(seg->isoc_pack_desc_urb); error_iso_pack_desc_submit: usb_unlink_urb(&seg->tr_urb); -error_seg_submit: +error_tr_submit: seg->status = WA_SEG_ERROR; seg->result = result; *dto_done = 1; @@ -1393,6 +1414,12 @@ static int __wa_xfer_delayed_run(struct wa_rpipe *rpipe, int *dto_waiting) list_node); list_del(&seg->list_node); xfer = seg->xfer; + /* + * Get a reference to the xfer in case the callbacks for the + * URBs submitted by __wa_seg_submit attempt to complete + * the xfer before this function completes. + */ + wa_xfer_get(xfer); result = __wa_seg_submit(rpipe, xfer, seg, &dto_done); /* release the dto resource if this RPIPE is done with it. */ if (dto_done) @@ -1404,10 +1431,15 @@ static int __wa_xfer_delayed_run(struct wa_rpipe *rpipe, int *dto_waiting) spin_unlock_irqrestore(&rpipe->seg_lock, flags); spin_lock_irqsave(&xfer->lock, flags); __wa_xfer_abort(xfer); + /* + * This seg was marked as submitted when it was put on + * the RPIPE seg_list. Mark it done. + */ xfer->segs_done++; spin_unlock_irqrestore(&xfer->lock, flags); spin_lock_irqsave(&rpipe->seg_lock, flags); } + wa_xfer_put(xfer); } /* * Mark this RPIPE as waiting if dto was not acquired, there are @@ -1592,12 +1624,19 @@ static int wa_urb_enqueue_b(struct wa_xfer *xfer) dev_err(&(urb->dev->dev), "%s: error_xfer_setup\n", __func__); goto error_xfer_setup; } + /* + * Get a xfer reference since __wa_xfer_submit starts asynchronous + * operations that may try to complete the xfer before this function + * exits. + */ + wa_xfer_get(xfer); result = __wa_xfer_submit(xfer); if (result < 0) { dev_err(&(urb->dev->dev), "%s: error_xfer_submit\n", __func__); goto error_xfer_submit; } spin_unlock_irqrestore(&xfer->lock, flags); + wa_xfer_put(xfer); return 0; /* @@ -1623,6 +1662,7 @@ error_xfer_submit: spin_unlock_irqrestore(&xfer->lock, flags); if (done) wa_xfer_completion(xfer); + wa_xfer_put(xfer); /* return success since the completion routine will run. */ return 0; } -- cgit v1.2.3-70-g09d2 From acfadcea2adaa52048c6b3c8a3c75105a5540707 Mon Sep 17 00:00:00 2001 From: Thomas Pugliese Date: Fri, 28 Feb 2014 14:31:56 -0600 Subject: usb: wusbcore: fix stranded URB after HWA unplug This patch adds error checking to the abort request callback to forcibly clean up the dequeued transfers if the abort request failed. The wa_complete_remaining_xfer_segs was modified so that it could be used in this situation as well. This fixes a stranded URB/PNP hang when the HWA is unplugged while playing audio to a wireless audio device. Signed-off-by: Thomas Pugliese Signed-off-by: Greg Kroah-Hartman --- drivers/usb/wusbcore/wa-xfer.c | 56 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c index 3ca05555510..cb061915f05 100644 --- a/drivers/usb/wusbcore/wa-xfer.c +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -167,6 +167,8 @@ struct wa_xfer { static void __wa_populate_dto_urb_isoc(struct wa_xfer *xfer, struct wa_seg *seg, int curr_iso_frame); +static void wa_complete_remaining_xfer_segs(struct wa_xfer *xfer, + int starting_index, enum wa_seg_status status); static inline void wa_xfer_init(struct wa_xfer *xfer) { @@ -416,12 +418,51 @@ out: struct wa_xfer_abort_buffer { struct urb urb; + struct wahc *wa; struct wa_xfer_abort cmd; }; static void __wa_xfer_abort_cb(struct urb *urb) { struct wa_xfer_abort_buffer *b = urb->context; + struct wahc *wa = b->wa; + + /* + * If the abort request URB failed, then the HWA did not get the abort + * command. Forcibly clean up the xfer without waiting for a Transfer + * Result from the HWA. + */ + if (urb->status < 0) { + struct wa_xfer *xfer; + struct device *dev = &wa->usb_iface->dev; + + xfer = wa_xfer_get_by_id(wa, le32_to_cpu(b->cmd.dwTransferID)); + dev_err(dev, "%s: Transfer Abort request failed. result: %d\n", + __func__, urb->status); + if (xfer) { + unsigned long flags; + int done; + struct wa_rpipe *rpipe = xfer->ep->hcpriv; + + dev_err(dev, "%s: cleaning up xfer %p ID 0x%08X.\n", + __func__, xfer, wa_xfer_id(xfer)); + spin_lock_irqsave(&xfer->lock, flags); + /* mark all segs as aborted. */ + wa_complete_remaining_xfer_segs(xfer, 0, + WA_SEG_ABORTED); + done = __wa_xfer_is_done(xfer); + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + wa_xfer_delayed_run(rpipe); + wa_xfer_put(xfer); + } else { + dev_err(dev, "%s: xfer ID 0x%08X already gone.\n", + __func__, le32_to_cpu(b->cmd.dwTransferID)); + } + } + + wa_put(wa); /* taken in __wa_xfer_abort */ usb_put_urb(&b->urb); } @@ -449,6 +490,7 @@ static int __wa_xfer_abort(struct wa_xfer *xfer) b->cmd.bRequestType = WA_XFER_ABORT; b->cmd.wRPipe = rpipe->descr.wRPipeIndex; b->cmd.dwTransferID = wa_xfer_id_le32(xfer); + b->wa = wa_get(xfer->wa); usb_init_urb(&b->urb); usb_fill_bulk_urb(&b->urb, xfer->wa->usb_dev, @@ -462,6 +504,7 @@ static int __wa_xfer_abort(struct wa_xfer *xfer) error_submit: + wa_put(xfer->wa); if (printk_ratelimit()) dev_err(dev, "xfer %p: Can't submit abort request: %d\n", xfer, result); @@ -2036,15 +2079,17 @@ static int wa_xfer_status_to_errno(u8 status) * no other segment transfer results will be returned from the device. * Mark the remaining submitted or pending xfers as completed so that * the xfer will complete cleanly. + * + * xfer->lock must be held + * */ static void wa_complete_remaining_xfer_segs(struct wa_xfer *xfer, - struct wa_seg *incoming_seg, enum wa_seg_status status) + int starting_index, enum wa_seg_status status) { int index; struct wa_rpipe *rpipe = xfer->ep->hcpriv; - for (index = incoming_seg->index + 1; index < xfer->segs_submitted; - index++) { + for (index = starting_index; index < xfer->segs_submitted; index++) { struct wa_seg *current_seg = xfer->seg[index]; BUG_ON(current_seg == NULL); @@ -2202,7 +2247,8 @@ static void wa_xfer_result_chew(struct wahc *wa, struct wa_xfer *xfer, * transfers with data or below for no data, the xfer will complete. */ if (xfer_result->bTransferSegment & 0x80) - wa_complete_remaining_xfer_segs(xfer, seg, WA_SEG_DONE); + wa_complete_remaining_xfer_segs(xfer, seg->index + 1, + WA_SEG_DONE); if (usb_pipeisoc(xfer->urb->pipe) && (le32_to_cpu(xfer_result->dwNumOfPackets) > 0)) { /* set up WA state to read the isoc packet status next. */ @@ -2253,7 +2299,7 @@ error_buf_in_populate: error_complete: xfer->segs_done++; rpipe_ready = rpipe_avail_inc(rpipe); - wa_complete_remaining_xfer_segs(xfer, seg, seg->status); + wa_complete_remaining_xfer_segs(xfer, seg->index + 1, seg->status); done = __wa_xfer_is_done(xfer); /* * queue work item to clear STALL for control endpoints. -- cgit v1.2.3-70-g09d2 From 5da43afc2b73795e82c4bc3e53a4a177a02637d0 Mon Sep 17 00:00:00 2001 From: Thomas Pugliese Date: Fri, 28 Feb 2014 14:31:57 -0600 Subject: usb: wusbcore: prevent urb dequeue and giveback race This patch takes a reference to the wa_xfer object in wa_urb_dequeue to prevent the urb giveback code from completing the xfer and freeing it while wa_urb_dequeue is executing. It also checks for done at the start to avoid a double completion scenario. Adding the check for done in urb_dequeue means that any other place where a submitted transfer segment is marked as done must complete the transfer if it is done. __wa_xfer_delayed_run was not checking this case so that check was added as well. Signed-off-by: Thomas Pugliese Signed-off-by: Greg Kroah-Hartman --- drivers/usb/wusbcore/wa-xfer.c | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c index cb061915f05..5e5343e6991 100644 --- a/drivers/usb/wusbcore/wa-xfer.c +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -1471,6 +1471,8 @@ static int __wa_xfer_delayed_run(struct wa_rpipe *rpipe, int *dto_waiting) xfer, wa_xfer_id(xfer), seg->index, atomic_read(&rpipe->segs_available), result); if (unlikely(result < 0)) { + int done; + spin_unlock_irqrestore(&rpipe->seg_lock, flags); spin_lock_irqsave(&xfer->lock, flags); __wa_xfer_abort(xfer); @@ -1479,7 +1481,10 @@ static int __wa_xfer_delayed_run(struct wa_rpipe *rpipe, int *dto_waiting) * the RPIPE seg_list. Mark it done. */ xfer->segs_done++; + done = __wa_xfer_is_done(xfer); spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); spin_lock_irqsave(&rpipe->seg_lock, flags); } wa_xfer_put(xfer); @@ -1915,20 +1920,20 @@ int wa_urb_dequeue(struct wahc *wa, struct urb *urb, int status) /* check if it is safe to unlink. */ spin_lock_irqsave(&wa->xfer_list_lock, flags); result = usb_hcd_check_unlink_urb(&(wa->wusb->usb_hcd), urb, status); + if ((result == 0) && urb->hcpriv) { + /* + * Get a xfer ref to prevent a race with wa_xfer_giveback + * cleaning up the xfer while we are working with it. + */ + wa_xfer_get(urb->hcpriv); + } spin_unlock_irqrestore(&wa->xfer_list_lock, flags); if (result) return result; xfer = urb->hcpriv; - if (xfer == NULL) { - /* - * Nothing setup yet enqueue will see urb->status != - * -EINPROGRESS (by hcd layer) and bail out with - * error, no need to do completion - */ - BUG_ON(urb->status == -EINPROGRESS); - goto out; - } + if (xfer == NULL) + return -ENOENT; spin_lock_irqsave(&xfer->lock, flags); pr_debug("%s: DEQUEUE xfer id 0x%08X\n", __func__, wa_xfer_id(xfer)); rpipe = xfer->ep->hcpriv; @@ -1939,6 +1944,16 @@ int wa_urb_dequeue(struct wahc *wa, struct urb *urb, int status) result = -ENOENT; goto out_unlock; } + /* + * Check for done to avoid racing with wa_xfer_giveback and completing + * twice. + */ + if (__wa_xfer_is_done(xfer)) { + pr_debug("%s: xfer %p id 0x%08X already done.\n", __func__, + xfer, wa_xfer_id(xfer)); + result = -ENOENT; + goto out_unlock; + } /* Check the delayed list -> if there, release and complete */ spin_lock_irqsave(&wa->xfer_list_lock, flags2); if (!list_empty(&xfer->list_node) && xfer->seg == NULL) @@ -2007,11 +2022,12 @@ int wa_urb_dequeue(struct wahc *wa, struct urb *urb, int status) wa_xfer_completion(xfer); if (rpipe_ready) wa_xfer_delayed_run(rpipe); + wa_xfer_put(xfer); return result; out_unlock: spin_unlock_irqrestore(&xfer->lock, flags); -out: + wa_xfer_put(xfer); return result; dequeue_delayed: @@ -2020,6 +2036,7 @@ dequeue_delayed: xfer->result = urb->status; spin_unlock_irqrestore(&xfer->lock, flags); wa_xfer_giveback(xfer); + wa_xfer_put(xfer); usb_put_urb(urb); /* we got a ref in enqueue() */ return 0; } -- cgit v1.2.3-70-g09d2 From e500d526f968f184462912334b74b80dc905fca0 Mon Sep 17 00:00:00 2001 From: Thomas Pugliese Date: Fri, 28 Feb 2014 14:31:58 -0600 Subject: usb: wusbcore: add a convenience function for completing a transfer segment This patch adds a convenience function for the commonly performed task of marking a transfer segment as done. It combines the 3 steps of setting the segment status, incrementing the segs_done field of the transfer and checking if the completed segment results in the transfer also being done. Signed-off-by: Thomas Pugliese Signed-off-by: Greg Kroah-Hartman --- drivers/usb/wusbcore/wa-xfer.c | 48 ++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c index 5e5343e6991..3d6b30d8520 100644 --- a/drivers/usb/wusbcore/wa-xfer.c +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -391,6 +391,24 @@ out: return result; } +/* + * Mark the given segment as done. Return true if this completes the xfer. + * This should only be called for segs that have been submitted to an RPIPE. + * Delayed segs are not marked as submitted so they do not need to be marked + * as done when cleaning up. + * + * xfer->lock has to be locked + */ +static unsigned __wa_xfer_mark_seg_as_done(struct wa_xfer *xfer, + struct wa_seg *seg, enum wa_seg_status status) +{ + seg->status = status; + xfer->segs_done++; + + /* check for done. */ + return __wa_xfer_is_done(xfer); +} + /* * Search for a transfer list ID on the HCD's URB list * @@ -821,12 +839,10 @@ error_default: wa_reset_all(wa); } if (seg->status != WA_SEG_ERROR) { - seg->status = WA_SEG_ERROR; seg->result = urb->status; - xfer->segs_done++; __wa_xfer_abort(xfer); rpipe_ready = rpipe_avail_inc(rpipe); - done = __wa_xfer_is_done(xfer); + done = __wa_xfer_mark_seg_as_done(xfer, seg, WA_SEG_ERROR); } spin_unlock_irqrestore(&xfer->lock, flags); if (holding_dto) { @@ -892,12 +908,11 @@ static void wa_seg_iso_pack_desc_cb(struct urb *urb) } if (seg->status != WA_SEG_ERROR) { usb_unlink_urb(seg->dto_urb); - seg->status = WA_SEG_ERROR; seg->result = urb->status; - xfer->segs_done++; __wa_xfer_abort(xfer); rpipe_ready = rpipe_avail_inc(rpipe); - done = __wa_xfer_is_done(xfer); + done = __wa_xfer_mark_seg_as_done(xfer, seg, + WA_SEG_ERROR); } spin_unlock_irqrestore(&xfer->lock, flags); if (done) @@ -971,12 +986,10 @@ static void wa_seg_tr_cb(struct urb *urb) } usb_unlink_urb(seg->isoc_pack_desc_urb); usb_unlink_urb(seg->dto_urb); - seg->status = WA_SEG_ERROR; seg->result = urb->status; - xfer->segs_done++; __wa_xfer_abort(xfer); rpipe_ready = rpipe_avail_inc(rpipe); - done = __wa_xfer_is_done(xfer); + done = __wa_xfer_mark_seg_as_done(xfer, seg, WA_SEG_ERROR); spin_unlock_irqrestore(&xfer->lock, flags); if (done) wa_xfer_completion(xfer); @@ -2285,11 +2298,9 @@ static void wa_xfer_result_chew(struct wahc *wa, struct wa_xfer *xfer, goto error_submit_buf_in; } else { /* OUT data phase or no data, complete it -- */ - seg->status = WA_SEG_DONE; seg->result = bytes_transferred; - xfer->segs_done++; rpipe_ready = rpipe_avail_inc(rpipe); - done = __wa_xfer_is_done(xfer); + done = __wa_xfer_mark_seg_as_done(xfer, seg, WA_SEG_DONE); } spin_unlock_irqrestore(&xfer->lock, flags); if (done) @@ -2453,10 +2464,8 @@ static int wa_process_iso_packet_status(struct wahc *wa, struct urb *urb) dti_busy = 1; } else { /* OUT transfer or no more IN data, complete it -- */ - seg->status = WA_SEG_DONE; - xfer->segs_done++; rpipe_ready = rpipe_avail_inc(rpipe); - done = __wa_xfer_is_done(xfer); + done = __wa_xfer_mark_seg_as_done(xfer, seg, WA_SEG_DONE); } spin_unlock_irqrestore(&xfer->lock, flags); wa->dti_state = WA_DTI_TRANSFER_RESULT_PENDING; @@ -2547,12 +2556,11 @@ static void wa_buf_in_cb(struct urb *urb) } } else { rpipe = xfer->ep->hcpriv; - seg->status = WA_SEG_DONE; dev_dbg(dev, "xfer %p#%u: data in done (%zu bytes)\n", xfer, seg->index, seg->result); - xfer->segs_done++; rpipe_ready = rpipe_avail_inc(rpipe); - done = __wa_xfer_is_done(xfer); + done = __wa_xfer_mark_seg_as_done(xfer, seg, + WA_SEG_DONE); } spin_unlock_irqrestore(&xfer->lock, flags); if (done) @@ -2575,12 +2583,10 @@ static void wa_buf_in_cb(struct urb *urb) "exceeded, resetting device\n"); wa_reset_all(wa); } - seg->status = WA_SEG_ERROR; seg->result = urb->status; - xfer->segs_done++; rpipe_ready = rpipe_avail_inc(rpipe); __wa_xfer_abort(xfer); - done = __wa_xfer_is_done(xfer); + done = __wa_xfer_mark_seg_as_done(xfer, seg, WA_SEG_ERROR); spin_unlock_irqrestore(&xfer->lock, flags); if (done) wa_xfer_completion(xfer); -- cgit v1.2.3-70-g09d2 From 4659a2452baa7d89324fda097158d7f8fe71e0cb Mon Sep 17 00:00:00 2001 From: Thomas Pugliese Date: Fri, 28 Feb 2014 15:15:19 -0600 Subject: usb: wusbcore: adjust iterator correctly when searching for ep comp descriptor If the endpoint companion descriptor is not the first descriptor in the extra descriptor buffer of a usb_host_endpoint, the loop in rpipe_epc_find will get its buffer pointer and remaining size values out of sync. The buffer ptr 'itr' is advanced by the descriptor's bLength field but the remaining size value 'itr_size' is decremented by the bDescriptorType field which is incorrect. This patch fixes the loop to decrement itr_size by bLength as it should. Signed-off-by: Thomas Pugliese Signed-off-by: Greg Kroah-Hartman --- drivers/usb/wusbcore/wa-rpipe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/wusbcore/wa-rpipe.c b/drivers/usb/wusbcore/wa-rpipe.c index 6ca80a4efc1..6d6da127f6d 100644 --- a/drivers/usb/wusbcore/wa-rpipe.c +++ b/drivers/usb/wusbcore/wa-rpipe.c @@ -298,7 +298,7 @@ static struct usb_wireless_ep_comp_descriptor *rpipe_epc_find( break; } itr += hdr->bLength; - itr_size -= hdr->bDescriptorType; + itr_size -= hdr->bLength; } out: return epcd; -- cgit v1.2.3-70-g09d2 From ecf3701cede840476b012ac4796f77c6dd9ee623 Mon Sep 17 00:00:00 2001 From: Thomas Pugliese Date: Fri, 28 Feb 2014 15:10:26 -0600 Subject: usb: wusbcore: read actual_length bytes isoc in segments Use the iso_frame_desc.actual_length field instead of length when reading isoc in data segments from the HWA. This fixes a case where the isoc in read URB would never complete because it expected the HWA to send more data than it actually did. When this happened the URB would be stuck in the driver preventing module unload and clean shutdown. Signed-off-by: Thomas Pugliese Signed-off-by: Greg Kroah-Hartman --- drivers/usb/wusbcore/wa-xfer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c index 3d6b30d8520..ff1de5e396a 100644 --- a/drivers/usb/wusbcore/wa-xfer.c +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -2159,7 +2159,7 @@ static void __wa_populate_buf_in_urb_isoc(struct wahc *wa, struct wa_xfer *xfer, wa->buf_in_urb->transfer_dma = xfer->urb->transfer_dma + xfer->urb->iso_frame_desc[curr_iso_frame].offset; wa->buf_in_urb->transfer_buffer_length = - xfer->urb->iso_frame_desc[curr_iso_frame].length; + xfer->urb->iso_frame_desc[curr_iso_frame].actual_length; wa->buf_in_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; wa->buf_in_urb->transfer_buffer = NULL; wa->buf_in_urb->sg = NULL; -- cgit v1.2.3-70-g09d2 From d1c5dd6f8edf16d2ed5a9a3d023b6f3f091cc42d Mon Sep 17 00:00:00 2001 From: Thomas Pugliese Date: Fri, 28 Feb 2014 15:06:51 -0600 Subject: usb: wusbcore: add info to HWA debug prints This patch adds a debug print in the transfer dequeue case where a transfer result arrives for a transfer that has already been cleaned up. It also adds the transfer ID to some debug prints and prints error codes as signed integers in a couple of others. Signed-off-by: Thomas Pugliese Signed-off-by: Greg Kroah-Hartman --- drivers/usb/wusbcore/wa-xfer.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c index ff1de5e396a..0636206f75a 100644 --- a/drivers/usb/wusbcore/wa-xfer.c +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -369,13 +369,13 @@ static unsigned __wa_xfer_is_done(struct wa_xfer *xfer) break; case WA_SEG_ERROR: xfer->result = seg->result; - dev_dbg(dev, "xfer %p ID %08X#%u: ERROR result %zu(0x%08zX)\n", + dev_dbg(dev, "xfer %p ID %08X#%u: ERROR result %zi(0x%08zX)\n", xfer, wa_xfer_id(xfer), seg->index, seg->result, seg->result); goto out; case WA_SEG_ABORTED: xfer->result = seg->result; - dev_dbg(dev, "xfer %p ID %08X#%u: ABORTED result %zu(0x%08zX)\n", + dev_dbg(dev, "xfer %p ID %08X#%u: ABORTED result %zi(0x%08zX)\n", xfer, wa_xfer_id(xfer), seg->index, seg->result, seg->result); goto out; @@ -2262,7 +2262,7 @@ static void wa_xfer_result_chew(struct wahc *wa, struct wa_xfer *xfer, } if (usb_status & 0x80) { seg->result = wa_xfer_status_to_errno(usb_status); - dev_err(dev, "DTI: xfer %p#:%08X:%u failed (0x%02x)\n", + dev_err(dev, "DTI: xfer %p 0x%08X:#%u failed (0x%02x)\n", xfer, xfer->id, seg->index, usb_status); seg->status = ((usb_status & 0x7F) == WA_XFER_STATUS_ABORTED) ? WA_SEG_ABORTED : WA_SEG_ERROR; @@ -2556,8 +2556,10 @@ static void wa_buf_in_cb(struct urb *urb) } } else { rpipe = xfer->ep->hcpriv; - dev_dbg(dev, "xfer %p#%u: data in done (%zu bytes)\n", - xfer, seg->index, seg->result); + dev_dbg(dev, + "xfer %p 0x%08X#%u: data in done (%zu bytes)\n", + xfer, wa_xfer_id(xfer), seg->index, + seg->result); rpipe_ready = rpipe_avail_inc(rpipe); done = __wa_xfer_mark_seg_as_done(xfer, seg, WA_SEG_DONE); @@ -2575,8 +2577,9 @@ static void wa_buf_in_cb(struct urb *urb) spin_lock_irqsave(&xfer->lock, flags); rpipe = xfer->ep->hcpriv; if (printk_ratelimit()) - dev_err(dev, "xfer %p#%u: data in error %d\n", - xfer, seg->index, urb->status); + dev_err(dev, "xfer %p 0x%08X#%u: data in error %d\n", + xfer, wa_xfer_id(xfer), seg->index, + urb->status); if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)){ dev_err(dev, "DTO: URB max acceptable errors " @@ -2670,11 +2673,15 @@ static void wa_dti_cb(struct urb *urb) xfer_result->hdr.bNotifyType); break; } + xfer_id = le32_to_cpu(xfer_result->dwTransferID); usb_status = xfer_result->bTransferStatus & 0x3f; - if (usb_status == WA_XFER_STATUS_NOT_FOUND) + if (usb_status == WA_XFER_STATUS_NOT_FOUND) { /* taken care of already */ + dev_dbg(dev, "%s: xfer 0x%08X#%u not found.\n", + __func__, xfer_id, + xfer_result->bTransferSegment & 0x7f); break; - xfer_id = le32_to_cpu(xfer_result->dwTransferID); + } xfer = wa_xfer_get_by_id(wa, xfer_id); if (xfer == NULL) { /* FIXME: transaction not found. */ -- cgit v1.2.3-70-g09d2 From 938569eb75b329fee9f2634900ad7e12caf13fd2 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 27 Feb 2014 10:57:10 +0100 Subject: hub: debug message for failing to enable device This error case isn't reported during enumeration. Signed-off-by: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 519f2c3594b..69687de9de6 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4111,8 +4111,12 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, did_new_scheme = true; retval = hub_enable_device(udev); - if (retval < 0) + if (retval < 0) { + dev_err(&udev->dev, + "hub failed to enable device, error %d\n", + retval); goto fail; + } #define GET_DESCRIPTOR_BUFSIZE 64 buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO); -- cgit v1.2.3-70-g09d2 From 23c058201fe36adf1a225281739b3ec31ec4e858 Mon Sep 17 00:00:00 2001 From: Bjørn Mork Date: Thu, 27 Feb 2014 14:23:55 +0100 Subject: usb: hub: usb_ext_cap_descriptor.bmAttributes is le32 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Better check the correct bit on big endian systems too. Shuts up the following sparse __CHECK_ENDIAN__ warning: .../hub.c:3965:32: warning: restricted __le32 degrades to integer Signed-off-by: Bjørn Mork Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 69687de9de6..f6c6b6f7cc9 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -3962,7 +3962,7 @@ static void hub_set_initial_usb2_lpm_policy(struct usb_device *udev) connect_type = usb_get_hub_port_connect_type(udev->parent, udev->portnum); - if ((udev->bos->ext_cap->bmAttributes & USB_BESL_SUPPORT) || + if ((udev->bos->ext_cap->bmAttributes & cpu_to_le32(USB_BESL_SUPPORT)) || connect_type == USB_PORT_CONNECT_TYPE_HARD_WIRED) { udev->usb2_hw_lpm_allowed = 1; usb_set_usb2_hardware_lpm(udev, 1); -- cgit v1.2.3-70-g09d2 From 927c4dac34cf465089b8d3ff570147ae3ce1a984 Mon Sep 17 00:00:00 2001 From: Thomas Pugliese Date: Fri, 28 Feb 2014 21:54:37 -0600 Subject: usb: wusbcore: fix compile warnings Fix "pointer targets differ in signedness" and "variable set but not used" warnings Signed-off-by: Thomas Pugliese Signed-off-by: Greg Kroah-Hartman --- drivers/usb/wusbcore/devconnect.c | 4 +--- drivers/usb/wusbcore/wa-xfer.c | 6 ++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/usb/wusbcore/devconnect.c b/drivers/usb/wusbcore/devconnect.c index 3b959e83b28..0677139c606 100644 --- a/drivers/usb/wusbcore/devconnect.c +++ b/drivers/usb/wusbcore/devconnect.c @@ -284,7 +284,7 @@ void wusbhc_devconnect_ack(struct wusbhc *wusbhc, struct wusb_dn_connect *dnc, struct device *dev = wusbhc->dev; struct wusb_dev *wusb_dev; struct wusb_port *port; - unsigned idx, devnum; + unsigned idx; mutex_lock(&wusbhc->mutex); @@ -312,8 +312,6 @@ void wusbhc_devconnect_ack(struct wusbhc *wusbhc, struct wusb_dn_connect *dnc, goto error_unlock; } - devnum = idx + 2; - /* Make sure we are using no crypto on that "virtual port" */ wusbhc->set_ptk(wusbhc, idx, 0, NULL, 0); diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c index 0636206f75a..404ce81b286 100644 --- a/drivers/usb/wusbcore/wa-xfer.c +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -1007,7 +1007,7 @@ static void wa_seg_tr_cb(struct urb *urb) */ static struct scatterlist *wa_xfer_create_subset_sg(struct scatterlist *in_sg, const unsigned int bytes_transferred, - const unsigned int bytes_to_transfer, unsigned int *out_num_sgs) + const unsigned int bytes_to_transfer, int *out_num_sgs) { struct scatterlist *out_sg; unsigned int bytes_processed = 0, offset_into_current_page_data = 0, @@ -1161,14 +1161,13 @@ static int __wa_populate_dto_urb(struct wa_xfer *xfer, */ static int __wa_xfer_setup_segs(struct wa_xfer *xfer, size_t xfer_hdr_size) { - int result, cnt, iso_frame_offset; + int result, cnt, isoc_frame_offset = 0; size_t alloc_size = sizeof(*xfer->seg[0]) - sizeof(xfer->seg[0]->xfer_hdr) + xfer_hdr_size; struct usb_device *usb_dev = xfer->wa->usb_dev; const struct usb_endpoint_descriptor *dto_epd = xfer->wa->dto_epd; struct wa_seg *seg; size_t buf_itr, buf_size, buf_itr_size; - int isoc_frame_offset = 0; result = -ENOMEM; xfer->seg = kcalloc(xfer->segs, sizeof(xfer->seg[0]), GFP_ATOMIC); @@ -1176,7 +1175,6 @@ static int __wa_xfer_setup_segs(struct wa_xfer *xfer, size_t xfer_hdr_size) goto error_segs_kzalloc; buf_itr = 0; buf_size = xfer->urb->transfer_buffer_length; - iso_frame_offset = 0; for (cnt = 0; cnt < xfer->segs; cnt++) { size_t iso_pkt_descr_size = 0; int seg_isoc_frame_count = 0, seg_isoc_size = 0; -- cgit v1.2.3-70-g09d2 From b6d790f793f0082df18f3f6c8a7b1b3fb255c88e Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Mon, 3 Feb 2014 13:55:03 +0530 Subject: usb: phy: twl4030-usb: Silence checkpatch warnings Silences the following warnings: WARNING: sizeof *twl should be sizeof(*twl) WARNING: sizeof *otg should be sizeof(*otg) Signed-off-by: Sachin Kamat Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-twl4030-usb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/phy/phy-twl4030-usb.c b/drivers/phy/phy-twl4030-usb.c index c3ace1db813..6c7cec7d0a1 100644 --- a/drivers/phy/phy-twl4030-usb.c +++ b/drivers/phy/phy-twl4030-usb.c @@ -661,7 +661,7 @@ static int twl4030_usb_probe(struct platform_device *pdev) struct phy_provider *phy_provider; struct phy_init_data *init_data = NULL; - twl = devm_kzalloc(&pdev->dev, sizeof *twl, GFP_KERNEL); + twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL); if (!twl) return -ENOMEM; @@ -676,7 +676,7 @@ static int twl4030_usb_probe(struct platform_device *pdev) return -EINVAL; } - otg = devm_kzalloc(&pdev->dev, sizeof *otg, GFP_KERNEL); + otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); if (!otg) return -ENOMEM; -- cgit v1.2.3-70-g09d2 From ed093e6109865f8ee62c98fc002c04ae25fcd109 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Mon, 3 Feb 2014 13:55:04 +0530 Subject: usb: phy: twl4030-usb: Remove redundant semicolon Semicolon after switch statement is not needed. Signed-off-by: Sachin Kamat Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-twl4030-usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/phy-twl4030-usb.c b/drivers/phy/phy-twl4030-usb.c index 6c7cec7d0a1..2e0e9b3774c 100644 --- a/drivers/phy/phy-twl4030-usb.c +++ b/drivers/phy/phy-twl4030-usb.c @@ -338,7 +338,7 @@ static void twl4030_usb_set_mode(struct twl4030_usb *twl, int mode) dev_err(twl->dev, "unsupported T2 transceiver mode %d\n", mode); break; - }; + } } static void twl4030_i2c_access(struct twl4030_usb *twl, int on) -- cgit v1.2.3-70-g09d2 From e4b9f782fe923e75cf47d369b770c49c33dfb39d Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Mon, 3 Feb 2014 13:55:05 +0530 Subject: usb: phy: bcm-kona-usb2: Use PTR_ERR_OR_ZERO PTR_ERR_OR_ZERO simplifies the code. Signed-off-by: Sachin Kamat Cc: Matt Porter Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-bcm-kona-usb2.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/phy/phy-bcm-kona-usb2.c b/drivers/phy/phy-bcm-kona-usb2.c index efc5c1a13a5..e94f5a6a564 100644 --- a/drivers/phy/phy-bcm-kona-usb2.c +++ b/drivers/phy/phy-bcm-kona-usb2.c @@ -128,10 +128,8 @@ static int bcm_kona_usb2_probe(struct platform_device *pdev) phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - if (IS_ERR(phy_provider)) - return PTR_ERR(phy_provider); - return 0; + return PTR_ERR_OR_ZERO(phy_provider); } static const struct of_device_id bcm_kona_usb2_dt_ids[] = { -- cgit v1.2.3-70-g09d2 From 13454e5e08450fe500bef240aa98976dcb06ff63 Mon Sep 17 00:00:00 2001 From: Sylwester Nawrocki Date: Fri, 21 Feb 2014 17:44:01 +0100 Subject: phy: Select PHY_EXYNOS_MIPI_VIDEO by default for ARCH_EXYNOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of requiring user to figure out when PHY_EXYNOS_MIPI_VIDEO needs to be selected select it by default for Exynos SoCs. Also enable it when COMPILE_TEST is selected. If required the MIPI CSIS/DPHY driver can be then disabled or compiled in as module. Signed-off-by: Sylwester Nawrocki Acked-by: Kyungmin Park Acked-by: Felipe Balbi Acked-by: Uwe Kleine-König Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/Kconfig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index c7a551c2d5f..f0fb4290fc0 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -16,8 +16,11 @@ config GENERIC_PHY framework should select this config. config PHY_EXYNOS_MIPI_VIDEO - depends on HAS_IOMEM tristate "S5P/EXYNOS SoC series MIPI CSI-2/DSI PHY driver" + depends on HAS_IOMEM + depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST + select GENERIC_PHY + default y if ARCH_S5PV210 || ARCH_EXYNOS help Support for MIPI CSI-2 and MIPI DSI DPHY found on Samsung S5P and EXYNOS SoCs. -- cgit v1.2.3-70-g09d2 From a50ce20ecabdb9310fde90af31a8adb79cf0805e Mon Sep 17 00:00:00 2001 From: Sylwester Nawrocki Date: Fri, 21 Feb 2014 17:44:02 +0100 Subject: phy: Select PHY_EXYNOS_DP_VIDEO by default for ARCH_EXYNOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of requiring user to figure out when PHY_EXYNOS_DP_VIDEO needs to be selected select it by default for Exynos SoCs. Also enable it when COMPILE_TEST is selected. If required the display port PHY driver can be then disabled or compiled in as module. Signed-off-by: Sylwester Nawrocki Acked-by: Kyungmin Park Acked-by: Jingoo Han Acked-by: Felipe Balbi Acked-by: Uwe Kleine-König Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index f0fb4290fc0..5bf9ed33e73 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -57,6 +57,8 @@ config TWL4030_USB config PHY_EXYNOS_DP_VIDEO tristate "EXYNOS SoC series Display Port PHY driver" depends on OF + depends on ARCH_EXYNOS || COMPILE_TEST + default ARCH_EXYNOS select GENERIC_PHY help Support for Display Port PHY found on Samsung EXYNOS SoCs. -- cgit v1.2.3-70-g09d2 From bcff4cba41bcd7dfe535a2b7278b9fb8214a2a8f Mon Sep 17 00:00:00 2001 From: Yuvaraj Kumar C D Date: Mon, 3 Mar 2014 10:52:39 +0530 Subject: PHY: Exynos: Add Exynos5250 SATA PHY driver This patch adds the SATA PHY driver for Exynos5250.This driver uses the generic PHY framework to deal with SATA PHY.Exynos5250 SATA PHY comprises of CMU and TRSV blocks which are of I2C register Map.So this driver configures the CMU and TRSV block of exynos5250 SATA PHY using i2c. Signed-off-by: Yuvaraj Kumar C D Signed-off-by: Girish K S Signed-off-by: Vasanth Ananthan Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/Kconfig | 15 +++ drivers/phy/Makefile | 1 + drivers/phy/phy-exynos5250-sata.c | 251 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 267 insertions(+) create mode 100644 drivers/phy/phy-exynos5250-sata.c diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 5bf9ed33e73..04312849501 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -70,4 +70,19 @@ config BCM_KONA_USB2_PHY help Enable this to support the Broadcom Kona USB 2.0 PHY. +config PHY_EXYNOS5250_SATA + tristate "Exynos5250 Sata SerDes/PHY driver" + depends on SOC_EXYNOS5250 + depends on HAS_IOMEM + depends on OF + select GENERIC_PHY + select I2C + select I2C_S3C2410 + select MFD_SYSCON + help + Enable this to support SATA SerDes/Phy found on Samsung's + Exynos5250 based SoCs.This SerDes/Phy supports SATA 1.5 Gb/s, + SATA 3.0 Gb/s, SATA 6.0 Gb/s speeds. It supports one SATA host + port to accept one SATA device. + endmenu diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index b57c25371cc..0d038224a10 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_PHY_EXYNOS_MIPI_VIDEO) += phy-exynos-mipi-video.o obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.o obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o +obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o diff --git a/drivers/phy/phy-exynos5250-sata.c b/drivers/phy/phy-exynos5250-sata.c new file mode 100644 index 00000000000..c9361b731fa --- /dev/null +++ b/drivers/phy/phy-exynos5250-sata.c @@ -0,0 +1,251 @@ +/* + * Samsung SATA SerDes(PHY) driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Authors: Girish K S + * Yuvaraj Kumar C D + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SATAPHY_CONTROL_OFFSET 0x0724 +#define EXYNOS5_SATAPHY_PMU_ENABLE BIT(0) +#define EXYNOS5_SATA_RESET 0x4 +#define RESET_GLOBAL_RST_N BIT(0) +#define RESET_CMN_RST_N BIT(1) +#define RESET_CMN_BLOCK_RST_N BIT(2) +#define RESET_CMN_I2C_RST_N BIT(3) +#define RESET_TX_RX_PIPE_RST_N BIT(4) +#define RESET_TX_RX_BLOCK_RST_N BIT(5) +#define RESET_TX_RX_I2C_RST_N (BIT(6) | BIT(7)) +#define LINK_RESET 0xf0000 +#define EXYNOS5_SATA_MODE0 0x10 +#define SATA_SPD_GEN3 BIT(1) +#define EXYNOS5_SATA_CTRL0 0x14 +#define CTRL0_P0_PHY_CALIBRATED_SEL BIT(9) +#define CTRL0_P0_PHY_CALIBRATED BIT(8) +#define EXYNOS5_SATA_PHSATA_CTRLM 0xe0 +#define PHCTRLM_REF_RATE BIT(1) +#define PHCTRLM_HIGH_SPEED BIT(0) +#define EXYNOS5_SATA_PHSATA_STATM 0xf0 +#define PHSTATM_PLL_LOCKED BIT(0) + +#define PHY_PLL_TIMEOUT (usecs_to_jiffies(1000)) + +struct exynos_sata_phy { + struct phy *phy; + struct clk *phyclk; + void __iomem *regs; + struct regmap *pmureg; + struct i2c_client *client; +}; + +static int wait_for_reg_status(void __iomem *base, u32 reg, u32 checkbit, + u32 status) +{ + unsigned long timeout = jiffies + PHY_PLL_TIMEOUT; + + while (time_before(jiffies, timeout)) { + if ((readl(base + reg) & checkbit) == status) + return 0; + } + + return -EFAULT; +} + +static int exynos_sata_phy_power_on(struct phy *phy) +{ + struct exynos_sata_phy *sata_phy = phy_get_drvdata(phy); + + return regmap_update_bits(sata_phy->pmureg, SATAPHY_CONTROL_OFFSET, + EXYNOS5_SATAPHY_PMU_ENABLE, true); + +} + +static int exynos_sata_phy_power_off(struct phy *phy) +{ + struct exynos_sata_phy *sata_phy = phy_get_drvdata(phy); + + return regmap_update_bits(sata_phy->pmureg, SATAPHY_CONTROL_OFFSET, + EXYNOS5_SATAPHY_PMU_ENABLE, false); + +} + +static int exynos_sata_phy_init(struct phy *phy) +{ + u32 val = 0; + int ret = 0; + u8 buf[] = { 0x3a, 0x0b }; + struct exynos_sata_phy *sata_phy = phy_get_drvdata(phy); + + ret = regmap_update_bits(sata_phy->pmureg, SATAPHY_CONTROL_OFFSET, + EXYNOS5_SATAPHY_PMU_ENABLE, true); + if (ret != 0) + dev_err(&sata_phy->phy->dev, "phy init failed\n"); + + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); + val |= RESET_GLOBAL_RST_N | RESET_CMN_RST_N | RESET_CMN_BLOCK_RST_N + | RESET_CMN_I2C_RST_N | RESET_TX_RX_PIPE_RST_N + | RESET_TX_RX_BLOCK_RST_N | RESET_TX_RX_I2C_RST_N; + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); + val |= LINK_RESET; + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); + val |= RESET_CMN_RST_N; + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM); + val &= ~PHCTRLM_REF_RATE; + writel(val, sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM); + + /* High speed enable for Gen3 */ + val = readl(sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM); + val |= PHCTRLM_HIGH_SPEED; + writel(val, sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM); + + val = readl(sata_phy->regs + EXYNOS5_SATA_CTRL0); + val |= CTRL0_P0_PHY_CALIBRATED_SEL | CTRL0_P0_PHY_CALIBRATED; + writel(val, sata_phy->regs + EXYNOS5_SATA_CTRL0); + + val = readl(sata_phy->regs + EXYNOS5_SATA_MODE0); + val |= SATA_SPD_GEN3; + writel(val, sata_phy->regs + EXYNOS5_SATA_MODE0); + + ret = i2c_master_send(sata_phy->client, buf, sizeof(buf)); + if (ret < 0) + return ret; + + /* release cmu reset */ + val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); + val &= ~RESET_CMN_RST_N; + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); + val |= RESET_CMN_RST_N; + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + ret = wait_for_reg_status(sata_phy->regs, + EXYNOS5_SATA_PHSATA_STATM, + PHSTATM_PLL_LOCKED, 1); + if (ret < 0) + dev_err(&sata_phy->phy->dev, + "PHY PLL locking failed\n"); + return ret; +} + +static struct phy_ops exynos_sata_phy_ops = { + .init = exynos_sata_phy_init, + .power_on = exynos_sata_phy_power_on, + .power_off = exynos_sata_phy_power_off, + .owner = THIS_MODULE, +}; + +static int exynos_sata_phy_probe(struct platform_device *pdev) +{ + struct exynos_sata_phy *sata_phy; + struct device *dev = &pdev->dev; + struct resource *res; + struct phy_provider *phy_provider; + struct device_node *node; + int ret = 0; + + sata_phy = devm_kzalloc(dev, sizeof(*sata_phy), GFP_KERNEL); + if (!sata_phy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + sata_phy->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(sata_phy->regs)) + return PTR_ERR(sata_phy->regs); + + sata_phy->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node, + "samsung,syscon-phandle"); + if (IS_ERR(sata_phy->pmureg)) { + dev_err(dev, "syscon regmap lookup failed.\n"); + return PTR_ERR(sata_phy->pmureg); + } + + node = of_parse_phandle(dev->of_node, + "samsung,exynos-sataphy-i2c-phandle", 0); + if (!node) + return -EINVAL; + + sata_phy->client = of_find_i2c_device_by_node(node); + if (!sata_phy->client) + return -EPROBE_DEFER; + + dev_set_drvdata(dev, sata_phy); + + sata_phy->phyclk = devm_clk_get(dev, "sata_phyctrl"); + if (IS_ERR(sata_phy->phyclk)) { + dev_err(dev, "failed to get clk for PHY\n"); + return PTR_ERR(sata_phy->phyclk); + } + + ret = clk_prepare_enable(sata_phy->phyclk); + if (ret < 0) { + dev_err(dev, "failed to enable source clk\n"); + return ret; + } + + sata_phy->phy = devm_phy_create(dev, &exynos_sata_phy_ops, NULL); + if (IS_ERR(sata_phy->phy)) { + clk_disable_unprepare(sata_phy->phyclk); + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(sata_phy->phy); + } + + phy_set_drvdata(sata_phy->phy, sata_phy); + + phy_provider = devm_of_phy_provider_register(dev, + of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + clk_disable_unprepare(sata_phy->phyclk); + return PTR_ERR(phy_provider); + } + + return 0; +} + +static const struct of_device_id exynos_sata_phy_of_match[] = { + { .compatible = "samsung,exynos5250-sata-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, exynos_sata_phy_of_match); + +static struct platform_driver exynos_sata_phy_driver = { + .probe = exynos_sata_phy_probe, + .driver = { + .of_match_table = exynos_sata_phy_of_match, + .name = "samsung,sata-phy", + .owner = THIS_MODULE, + } +}; +module_platform_driver(exynos_sata_phy_driver); + +MODULE_DESCRIPTION("Samsung SerDes PHY driver"); +MODULE_LICENSE("GPL V2"); +MODULE_AUTHOR("Girish K S "); +MODULE_AUTHOR("Yuvaraj C D "); -- cgit v1.2.3-70-g09d2 From ba4bdc9e1dc01300490e4f5315b0ac8576bd4c7a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 1 Mar 2014 18:09:26 +0100 Subject: PHY: sunxi: Add driver for sunxi usb phy The Allwinner A1x / A2x SoCs have 2 or 3 usb phys which are all accessed through a single set of registers. Besides this there are also some other phy related bits which need poking, which are per phy, but shared between the ohci and ehci controllers, so these are also controlled from this new phy driver. Signed-off-by: Hans de Goede Acked-by: Maxime Ripard Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/sun4i-usb-phy.txt | 26 ++ drivers/phy/Kconfig | 11 + drivers/phy/Makefile | 1 + drivers/phy/phy-sun4i-usb.c | 331 +++++++++++++++++++++ 4 files changed, 369 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt create mode 100644 drivers/phy/phy-sun4i-usb.c diff --git a/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt b/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt new file mode 100644 index 00000000000..a82361b6201 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt @@ -0,0 +1,26 @@ +Allwinner sun4i USB PHY +----------------------- + +Required properties: +- compatible : should be one of "allwinner,sun4i-a10-usb-phy", + "allwinner,sun5i-a13-usb-phy" or "allwinner,sun7i-a20-usb-phy" +- reg : a list of offset + length pairs +- reg-names : "phy_ctrl", "pmu1" and for sun4i or sun7i "pmu2" +- #phy-cells : from the generic phy bindings, must be 1 +- clocks : phandle + clock specifier for the phy clock +- clock-names : "usb_phy" +- resets : a list of phandle + reset specifier pairs +- reset-names : "usb0_reset", "usb1_reset" and for sun4i or sun7i "usb2_reset" + +Example: + usbphy: phy@0x01c13400 { + #phy-cells = <1>; + compatible = "allwinner,sun4i-a10-usb-phy"; + /* phy base regs, phy1 pmu reg, phy2 pmu reg */ + reg = <0x01c13400 0x10 0x01c14800 0x4 0x01c1c800 0x4>; + reg-names = "phy_ctrl", "pmu1", "pmu2"; + clocks = <&usb_clk 8>; + clock-names = "usb_phy"; + resets = <&usb_clk 1>, <&usb_clk 2>; + reset-names = "usb1_reset", "usb2_reset"; + }; diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 04312849501..e677ee01fe8 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -85,4 +85,15 @@ config PHY_EXYNOS5250_SATA SATA 3.0 Gb/s, SATA 6.0 Gb/s speeds. It supports one SATA host port to accept one SATA device. +config PHY_SUN4I_USB + tristate "Allwinner sunxi SoC USB PHY driver" + depends on ARCH_SUNXI && HAS_IOMEM && OF + select GENERIC_PHY + help + Enable this to support the transceiver that is part of Allwinner + sunxi SoCs. + + This driver controls the entire USB PHY block, both the USB OTG + parts, as well as the 2 regular USB 2 host PHYs. + endmenu diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 0d038224a10..5d0b59edaf8 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.o obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o +obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o diff --git a/drivers/phy/phy-sun4i-usb.c b/drivers/phy/phy-sun4i-usb.c new file mode 100644 index 00000000000..e6e6c4ba714 --- /dev/null +++ b/drivers/phy/phy-sun4i-usb.c @@ -0,0 +1,331 @@ +/* + * Allwinner sun4i USB phy driver + * + * Copyright (C) 2014 Hans de Goede + * + * Based on code from + * Allwinner Technology Co., Ltd. + * + * Modelled after: Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Sylwester Nawrocki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REG_ISCR 0x00 +#define REG_PHYCTL 0x04 +#define REG_PHYBIST 0x08 +#define REG_PHYTUNE 0x0c + +#define PHYCTL_DATA BIT(7) + +#define SUNXI_AHB_ICHR8_EN BIT(10) +#define SUNXI_AHB_INCR4_BURST_EN BIT(9) +#define SUNXI_AHB_INCRX_ALIGN_EN BIT(8) +#define SUNXI_ULPI_BYPASS_EN BIT(0) + +/* Common Control Bits for Both PHYs */ +#define PHY_PLL_BW 0x03 +#define PHY_RES45_CAL_EN 0x0c + +/* Private Control Bits for Each PHY */ +#define PHY_TX_AMPLITUDE_TUNE 0x20 +#define PHY_TX_SLEWRATE_TUNE 0x22 +#define PHY_VBUSVALID_TH_SEL 0x25 +#define PHY_PULLUP_RES_SEL 0x27 +#define PHY_OTG_FUNC_EN 0x28 +#define PHY_VBUS_DET_EN 0x29 +#define PHY_DISCON_TH_SEL 0x2a + +#define MAX_PHYS 3 + +struct sun4i_usb_phy_data { + struct clk *clk; + void __iomem *base; + struct mutex mutex; + int num_phys; + u32 disc_thresh; + struct sun4i_usb_phy { + struct phy *phy; + void __iomem *pmu; + struct regulator *vbus; + struct reset_control *reset; + int index; + } phys[MAX_PHYS]; +}; + +#define to_sun4i_usb_phy_data(phy) \ + container_of((phy), struct sun4i_usb_phy_data, phys[(phy)->index]) + +static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data, + int len) +{ + struct sun4i_usb_phy_data *phy_data = to_sun4i_usb_phy_data(phy); + u32 temp, usbc_bit = BIT(phy->index * 2); + int i; + + mutex_lock(&phy_data->mutex); + + for (i = 0; i < len; i++) { + temp = readl(phy_data->base + REG_PHYCTL); + + /* clear the address portion */ + temp &= ~(0xff << 8); + + /* set the address */ + temp |= ((addr + i) << 8); + writel(temp, phy_data->base + REG_PHYCTL); + + /* set the data bit and clear usbc bit*/ + temp = readb(phy_data->base + REG_PHYCTL); + if (data & 0x1) + temp |= PHYCTL_DATA; + else + temp &= ~PHYCTL_DATA; + temp &= ~usbc_bit; + writeb(temp, phy_data->base + REG_PHYCTL); + + /* pulse usbc_bit */ + temp = readb(phy_data->base + REG_PHYCTL); + temp |= usbc_bit; + writeb(temp, phy_data->base + REG_PHYCTL); + + temp = readb(phy_data->base + REG_PHYCTL); + temp &= ~usbc_bit; + writeb(temp, phy_data->base + REG_PHYCTL); + + data >>= 1; + } + mutex_unlock(&phy_data->mutex); +} + +static void sun4i_usb_phy_passby(struct sun4i_usb_phy *phy, int enable) +{ + u32 bits, reg_value; + + if (!phy->pmu) + return; + + bits = SUNXI_AHB_ICHR8_EN | SUNXI_AHB_INCR4_BURST_EN | + SUNXI_AHB_INCRX_ALIGN_EN | SUNXI_ULPI_BYPASS_EN; + + reg_value = readl(phy->pmu); + + if (enable) + reg_value |= bits; + else + reg_value &= ~bits; + + writel(reg_value, phy->pmu); +} + +static int sun4i_usb_phy_init(struct phy *_phy) +{ + struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); + struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); + int ret; + + ret = clk_prepare_enable(data->clk); + if (ret) + return ret; + + ret = reset_control_deassert(phy->reset); + if (ret) { + clk_disable_unprepare(data->clk); + return ret; + } + + /* Adjust PHY's magnitude and rate */ + sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5); + + /* Disconnect threshold adjustment */ + sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, data->disc_thresh, 2); + + sun4i_usb_phy_passby(phy, 1); + + return 0; +} + +static int sun4i_usb_phy_exit(struct phy *_phy) +{ + struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); + struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); + + sun4i_usb_phy_passby(phy, 0); + reset_control_assert(phy->reset); + clk_disable_unprepare(data->clk); + + return 0; +} + +static int sun4i_usb_phy_power_on(struct phy *_phy) +{ + struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); + int ret = 0; + + if (phy->vbus) + ret = regulator_enable(phy->vbus); + + return ret; +} + +static int sun4i_usb_phy_power_off(struct phy *_phy) +{ + struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); + + if (phy->vbus) + regulator_disable(phy->vbus); + + return 0; +} + +static struct phy_ops sun4i_usb_phy_ops = { + .init = sun4i_usb_phy_init, + .exit = sun4i_usb_phy_exit, + .power_on = sun4i_usb_phy_power_on, + .power_off = sun4i_usb_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct phy *sun4i_usb_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct sun4i_usb_phy_data *data = dev_get_drvdata(dev); + + if (WARN_ON(args->args[0] == 0 || args->args[0] >= data->num_phys)) + return ERR_PTR(-ENODEV); + + return data->phys[args->args[0]].phy; +} + +static int sun4i_usb_phy_probe(struct platform_device *pdev) +{ + struct sun4i_usb_phy_data *data; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + void __iomem *pmu = NULL; + struct phy_provider *phy_provider; + struct reset_control *reset; + struct regulator *vbus; + struct resource *res; + struct phy *phy; + char name[16]; + int i; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->mutex); + + if (of_device_is_compatible(np, "allwinner,sun5i-a13-usb-phy")) + data->num_phys = 2; + else + data->num_phys = 3; + + if (of_device_is_compatible(np, "allwinner,sun4i-a10-usb-phy")) + data->disc_thresh = 3; + else + data->disc_thresh = 2; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_ctrl"); + data->base = devm_ioremap_resource(dev, res); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + + data->clk = devm_clk_get(dev, "usb_phy"); + if (IS_ERR(data->clk)) { + dev_err(dev, "could not get usb_phy clock\n"); + return PTR_ERR(data->clk); + } + + /* Skip 0, 0 is the phy for otg which is not yet supported. */ + for (i = 1; i < data->num_phys; i++) { + snprintf(name, sizeof(name), "usb%d_vbus", i); + vbus = devm_regulator_get_optional(dev, name); + if (IS_ERR(vbus)) { + if (PTR_ERR(vbus) == -EPROBE_DEFER) + return -EPROBE_DEFER; + vbus = NULL; + } + + snprintf(name, sizeof(name), "usb%d_reset", i); + reset = devm_reset_control_get(dev, name); + if (IS_ERR(reset)) { + dev_err(dev, "failed to get reset %s\n", name); + return PTR_ERR(reset); + } + + if (i) { /* No pmu for usbc0 */ + snprintf(name, sizeof(name), "pmu%d", i); + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, name); + pmu = devm_ioremap_resource(dev, res); + if (IS_ERR(pmu)) + return PTR_ERR(pmu); + } + + phy = devm_phy_create(dev, &sun4i_usb_phy_ops, NULL); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create PHY %d\n", i); + return PTR_ERR(phy); + } + + data->phys[i].phy = phy; + data->phys[i].pmu = pmu; + data->phys[i].vbus = vbus; + data->phys[i].reset = reset; + data->phys[i].index = i; + phy_set_drvdata(phy, &data->phys[i]); + } + + dev_set_drvdata(dev, data); + phy_provider = devm_of_phy_provider_register(dev, sun4i_usb_phy_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + return 0; +} + +static const struct of_device_id sun4i_usb_phy_of_match[] = { + { .compatible = "allwinner,sun4i-a10-usb-phy" }, + { .compatible = "allwinner,sun5i-a13-usb-phy" }, + { .compatible = "allwinner,sun7i-a20-usb-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, sun4i_usb_phy_of_match); + +static struct platform_driver sun4i_usb_phy_driver = { + .probe = sun4i_usb_phy_probe, + .driver = { + .of_match_table = sun4i_usb_phy_of_match, + .name = "sun4i-usb-phy", + .owner = THIS_MODULE, + } +}; +module_platform_driver(sun4i_usb_phy_driver); + +MODULE_DESCRIPTION("Allwinner sun4i USB phy driver"); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-70-g09d2 From 5d188d6df3f461788edb86d8ba8f6e6b05203012 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 25 Feb 2014 17:09:53 +0100 Subject: USB: EHCI: tegra: Drop unused defines Since commit 2d22b42db02f "usb: phy: registering Tegra USB PHY as platform driver" the driver no longer relies on the hard-coded physical addresses to determine the association between PHY and EHCI port, so these defines can be dropped. Signed-off-by: Thierry Reding Reviewed-by: Stephen Warren Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-tegra.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c index af28b748e87..27ac6ad53c3 100644 --- a/drivers/usb/host/ehci-tegra.c +++ b/drivers/usb/host/ehci-tegra.c @@ -38,10 +38,6 @@ #include "ehci.h" -#define TEGRA_USB_BASE 0xC5000000 -#define TEGRA_USB2_BASE 0xC5004000 -#define TEGRA_USB3_BASE 0xC5008000 - #define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) #define TEGRA_USB_DMA_ALIGN 32 -- cgit v1.2.3-70-g09d2 From 3c1b2c3ecd3122fe6dded7b012f74021144d95b2 Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Mon, 24 Feb 2014 10:01:16 -0800 Subject: USB: sisusb: Use static const, fix typo Convert 1 char * array to 2 char arrays to reduce size. Use static const to avoid array reloads on function entry. Fix asymmetric typo. $ size drivers/usb/misc/sisusbvga/sisusb.o* text data bss dec hex filename 29971 4841 9180 43992 abd8 drivers/usb/misc/sisusbvga/sisusb.o.new 30083 4841 9180 44104 ac48 drivers/usb/misc/sisusbvga/sisusb.o.old Signed-off-by: Joe Perches Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/sisusbvga/sisusb.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/usb/misc/sisusbvga/sisusb.c b/drivers/usb/misc/sisusbvga/sisusb.c index de98906f786..06b5d77cd9a 100644 --- a/drivers/usb/misc/sisusbvga/sisusb.c +++ b/drivers/usb/misc/sisusbvga/sisusb.c @@ -2123,8 +2123,8 @@ sisusb_get_ramconfig(struct sisusb_usb_data *sisusb) u8 tmp8, tmp82, ramtype; int bw = 0; char *ramtypetext1 = NULL; - const char *ramtypetext2[] = { "SDR SDRAM", "SDR SGRAM", - "DDR SDRAM", "DDR SGRAM" }; + static const char ram_datarate[4] = {'S', 'S', 'D', 'D'}; + static const char ram_dynamictype[4] = {'D', 'G', 'D', 'G'}; static const int busSDR[4] = {64, 64, 128, 128}; static const int busDDR[4] = {32, 32, 64, 64}; static const int busDDRA[4] = {64+32, 64+32 , (64+32)*2, (64+32)*2}; @@ -2156,8 +2156,10 @@ sisusb_get_ramconfig(struct sisusb_usb_data *sisusb) break; } - dev_info(&sisusb->sisusb_dev->dev, "%dMB %s %s, bus width %d\n", (sisusb->vramsize >> 20), ramtypetext1, - ramtypetext2[ramtype], bw); + + dev_info(&sisusb->sisusb_dev->dev, "%dMB %s %cDR S%cRAM, bus width %d\n", + sisusb->vramsize >> 20, ramtypetext1, + ram_datarate[ramtype], ram_dynamictype[ramtype], bw); } static int -- cgit v1.2.3-70-g09d2 From 06c886a95cbb533fb3c4178ec0362460b17926ed Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Sat, 1 Mar 2014 09:33:21 +0100 Subject: phy: mvebu-sata: prepare new Dove DT Kconfig variable DT-enabled Dove will move over from ARCH_DOVE in mach-dove to MACH_DOVE in mach-mvebu. As non-DT ARCH_DOVE will stay to rot for a while, add a new DT-only MACH_DOVE Kconfig. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Kishon Vijay Abraham I Acked-by: Jason Cooper --- drivers/phy/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index e677ee01fe8..dc1756cf5d7 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -27,7 +27,7 @@ config PHY_EXYNOS_MIPI_VIDEO config PHY_MVEBU_SATA def_bool y - depends on ARCH_KIRKWOOD || ARCH_DOVE + depends on ARCH_KIRKWOOD || ARCH_DOVE || MACH_DOVE depends on OF select GENERIC_PHY -- cgit v1.2.3-70-g09d2 From 25cd2882e2fc8bd8ed7acaee0ec979f11feda6d7 Mon Sep 17 00:00:00 2001 From: Sarah Sharp Date: Fri, 17 Jan 2014 14:15:44 -0800 Subject: usb/xhci: Change how we indicate a host supports Link PM. The xHCI driver currently uses a USB core internal field, udev->lpm_capable, to indicate the xHCI driver knows how to calculate the LPM timeout values. If this value is set for the host controller udev, it means Link PM can be enabled for child devices under that host. Change the code so the xHCI driver isn't mucking with USB core internal fields. Instead, indicate the xHCI driver doesn't support Link PM on this host by clearing the U1 and U2 exit latencies in the roothub SuperSpeed Extended Capabilities BOS descriptor. The code to check for the roothub setting U1 and U2 exit latencies to zero will also disable LPM for external devices that do that same. This was already effectively done with commit ae8963adb4ad8c5f2a89ca1d99fb7bb721e7599f "usb: Don't enable LPM if the exit latency is zero." Leave that code in place, so that if a device sets one exit latency value to zero, but the other is set to a valid value, LPM is only enabled for the U1 or U2 state that had the valid value. This is the same behavior the code had before. Also, change messages about missing Link PM information from warning level to info level. Only print a warning about the first device that doesn't support LPM, to avoid log spam. Further, cleanup some unnecessary line breaks to help people to grep for the error messages. Signed-off-by: Sarah Sharp Cc: Alan Stern --- drivers/usb/core/hub.c | 24 ++++++++++++++++-------- drivers/usb/host/xhci-hub.c | 8 +++++--- drivers/usb/host/xhci-pci.c | 6 ------ 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index f6c6b6f7cc9..763c3134dd7 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -141,19 +141,27 @@ static int usb_device_supports_lpm(struct usb_device *udev) return 0; } - /* All USB 3.0 must support LPM, but we need their max exit latency - * information from the SuperSpeed Extended Capabilities BOS descriptor. + /* + * According to the USB 3.0 spec, all USB 3.0 devices must support LPM. + * However, there are some that don't, and they set the U1/U2 exit + * latencies to zero. */ if (!udev->bos->ss_cap) { - dev_warn(&udev->dev, "No LPM exit latency info found. " - "Power management will be impacted.\n"); + dev_info(&udev->dev, "No LPM exit latency info found, disabling LPM.\n"); return 0; } - if (udev->parent->lpm_capable) - return 1; - dev_warn(&udev->dev, "Parent hub missing LPM exit latency info. " - "Power management will be impacted.\n"); + if (udev->bos->ss_cap->bU1devExitLat == 0 && + udev->bos->ss_cap->bU2DevExitLat == 0) { + if (udev->parent) + dev_info(&udev->dev, "LPM exit latency is zeroed, disabling LPM.\n"); + else + dev_info(&udev->dev, "We don't know the algorithms for LPM for this host, disabling LPM.\n"); + return 0; + } + + if (!udev->parent || udev->parent->lpm_capable) + return 1; return 0; } diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 9992fbfec85..1ad6bc1951c 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -732,9 +732,11 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, /* Set the U1 and U2 exit latencies. */ memcpy(buf, &usb_bos_descriptor, USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE); - temp = readl(&xhci->cap_regs->hcs_params3); - buf[12] = HCS_U1_LATENCY(temp); - put_unaligned_le16(HCS_U2_LATENCY(temp), &buf[13]); + if ((xhci->quirks & XHCI_LPM_SUPPORT)) { + temp = readl(&xhci->cap_regs->hcs_params3); + buf[12] = HCS_U1_LATENCY(temp); + put_unaligned_le16(HCS_U2_LATENCY(temp), &buf[13]); + } /* Indicate whether the host has LTM support. */ temp = readl(&xhci->cap_regs->hcc_params); diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 04f986d9234..6c03584ac15 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -222,12 +222,6 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) goto put_usb3_hcd; /* Roothub already marked as USB 3.0 speed */ - /* We know the LPM timeout algorithms for this host, let the USB core - * enable and disable LPM for devices under the USB 3.0 roothub. - */ - if (xhci->quirks & XHCI_LPM_SUPPORT) - hcd_to_bus(xhci->shared_hcd)->root_hub->lpm_capable = 1; - return 0; put_usb3_hcd: -- cgit v1.2.3-70-g09d2 From e587b8b270d3706147c806d42cc4ac78232caac7 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Wed, 8 Jan 2014 17:13:11 +0100 Subject: xhci: make warnings greppable This changes debug messages and warnings in xhci-ring.c to be on a single line so grep can find them. grep must have precedence over the 80 column limit. [Sarah fixed two checkpatch.pl issues with split lines introduced by this commit.] Signed-off-by: Oliver Neukum Signed-off-by: Sarah Sharp --- drivers/usb/host/xhci-ring.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 0ed64eb68e4..3ec1d8fe06f 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1082,8 +1082,7 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, ep_ring = xhci_stream_id_to_ring(dev, ep_index, stream_id); if (!ep_ring) { - xhci_warn(xhci, "WARN Set TR deq ptr command for " - "freed stream ID %u\n", + xhci_warn(xhci, "WARN Set TR deq ptr command for freed stream ID %u\n", stream_id); /* XXX: Harmless??? */ dev->eps[ep_index].ep_state &= ~SET_DEQ_PENDING; @@ -1099,12 +1098,10 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, switch (cmd_comp_code) { case COMP_TRB_ERR: - xhci_warn(xhci, "WARN Set TR Deq Ptr cmd invalid because " - "of stream ID configuration\n"); + xhci_warn(xhci, "WARN Set TR Deq Ptr cmd invalid because of stream ID configuration\n"); break; case COMP_CTX_STATE: - xhci_warn(xhci, "WARN Set TR Deq Ptr cmd failed due " - "to incorrect slot or ep state.\n"); + xhci_warn(xhci, "WARN Set TR Deq Ptr cmd failed due to incorrect slot or ep state.\n"); ep_state = le32_to_cpu(ep_ctx->ep_info); ep_state &= EP_STATE_MASK; slot_state = le32_to_cpu(slot_ctx->dev_state); @@ -1114,13 +1111,12 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, slot_state, ep_state); break; case COMP_EBADSLT: - xhci_warn(xhci, "WARN Set TR Deq Ptr cmd failed because " - "slot %u was not enabled.\n", slot_id); + xhci_warn(xhci, "WARN Set TR Deq Ptr cmd failed because slot %u was not enabled.\n", + slot_id); break; default: - xhci_warn(xhci, "WARN Set TR Deq Ptr cmd with unknown " - "completion code of %u.\n", - cmd_comp_code); + xhci_warn(xhci, "WARN Set TR Deq Ptr cmd with unknown completion code of %u.\n", + cmd_comp_code); break; } /* OK what do we do now? The endpoint state is hosed, and we @@ -1142,8 +1138,7 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, update_ring_for_set_deq_completion(xhci, dev, ep_ring, ep_index); } else { - xhci_warn(xhci, "Mismatch between completed Set TR Deq " - "Ptr command & xHCI internal state.\n"); + xhci_warn(xhci, "Mismatch between completed Set TR Deq Ptr command & xHCI internal state.\n"); xhci_warn(xhci, "ep deq seg = %p, deq ptr = %p\n", dev->eps[ep_index].queued_deq_seg, dev->eps[ep_index].queued_deq_ptr); -- cgit v1.2.3-70-g09d2 From 153413032c6ea624fccc6732aba27a57688a7f91 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 4 Oct 2013 00:29:44 +0200 Subject: xhci: fix usb3 streams xhci maintains a radix tree for each stream endpoint because it must be able to map a trb address to the stream ring. Each ring segment must be added to the ring for this to work. Currently xhci sticks only the first segment of each stream ring into the radix tree. Result is that things work initially, but as soon as the first segment is full xhci can't map the trb address from the completion event to the stream ring any more -> BOOM. You'll find this message in the logs: ERROR Transfer event for disabled endpoint or incorrect stream ring This patch adds a helper function to update the radix tree, and a function to remove ring segments from the tree. Both functions loop over the segment list and handles all segments instead of just the first. [Note: Sarah changed this patch to add radix_tree_maybe_preload() and radix_tree_preload_end() calls around the radix tree insert, since we can now insert entries in interrupt context. There are now two helper functions to make the code cleaner, and those functions are moved to make them static.] Signed-off-by: Gerd Hoffmann Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/host/xhci-mem.c | 132 +++++++++++++++++++++++++++++--------------- drivers/usb/host/xhci.h | 1 + 2 files changed, 90 insertions(+), 43 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index bce4391a0e7..39f9a99a503 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -149,14 +149,95 @@ static void xhci_link_rings(struct xhci_hcd *xhci, struct xhci_ring *ring, } } +/* + * We need a radix tree for mapping physical addresses of TRBs to which stream + * ID they belong to. We need to do this because the host controller won't tell + * us which stream ring the TRB came from. We could store the stream ID in an + * event data TRB, but that doesn't help us for the cancellation case, since the + * endpoint may stop before it reaches that event data TRB. + * + * The radix tree maps the upper portion of the TRB DMA address to a ring + * segment that has the same upper portion of DMA addresses. For example, say I + * have segments of size 1KB, that are always 64-byte aligned. A segment may + * start at 0x10c91000 and end at 0x10c913f0. If I use the upper 10 bits, the + * key to the stream ID is 0x43244. I can use the DMA address of the TRB to + * pass the radix tree a key to get the right stream ID: + * + * 0x10c90fff >> 10 = 0x43243 + * 0x10c912c0 >> 10 = 0x43244 + * 0x10c91400 >> 10 = 0x43245 + * + * Obviously, only those TRBs with DMA addresses that are within the segment + * will make the radix tree return the stream ID for that ring. + * + * Caveats for the radix tree: + * + * The radix tree uses an unsigned long as a key pair. On 32-bit systems, an + * unsigned long will be 32-bits; on a 64-bit system an unsigned long will be + * 64-bits. Since we only request 32-bit DMA addresses, we can use that as the + * key on 32-bit or 64-bit systems (it would also be fine if we asked for 64-bit + * PCI DMA addresses on a 64-bit system). There might be a problem on 32-bit + * extended systems (where the DMA address can be bigger than 32-bits), + * if we allow the PCI dma mask to be bigger than 32-bits. So don't do that. + */ +static int xhci_update_stream_mapping(struct xhci_ring *ring, gfp_t mem_flags) +{ + struct xhci_segment *seg; + unsigned long key; + int ret; + + if (WARN_ON_ONCE(ring->trb_address_map == NULL)) + return 0; + + seg = ring->first_seg; + do { + key = (unsigned long)(seg->dma >> TRB_SEGMENT_SHIFT); + /* Skip any segments that were already added. */ + if (radix_tree_lookup(ring->trb_address_map, key)) + continue; + + ret = radix_tree_maybe_preload(mem_flags); + if (ret) + return ret; + ret = radix_tree_insert(ring->trb_address_map, + key, ring); + radix_tree_preload_end(); + if (ret) + return ret; + seg = seg->next; + } while (seg != ring->first_seg); + + return 0; +} + +static void xhci_remove_stream_mapping(struct xhci_ring *ring) +{ + struct xhci_segment *seg; + unsigned long key; + + if (WARN_ON_ONCE(ring->trb_address_map == NULL)) + return; + + seg = ring->first_seg; + do { + key = (unsigned long)(seg->dma >> TRB_SEGMENT_SHIFT); + if (radix_tree_lookup(ring->trb_address_map, key)) + radix_tree_delete(ring->trb_address_map, key); + seg = seg->next; + } while (seg != ring->first_seg); +} + /* XXX: Do we need the hcd structure in all these functions? */ void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring) { if (!ring) return; - if (ring->first_seg) + if (ring->first_seg) { + if (ring->type == TYPE_STREAM) + xhci_remove_stream_mapping(ring); xhci_free_segments_for_ring(xhci, ring->first_seg); + } kfree(ring); } @@ -354,6 +435,11 @@ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, "ring expansion succeed, now has %d segments", ring->num_segs); + if (ring->type == TYPE_STREAM) { + ret = xhci_update_stream_mapping(ring, flags); + WARN_ON(ret); /* FIXME */ + } + return 0; } @@ -510,36 +596,6 @@ struct xhci_ring *xhci_stream_id_to_ring( * The number of stream contexts in the stream context array may be bigger than * the number of streams the driver wants to use. This is because the number of * stream context array entries must be a power of two. - * - * We need a radix tree for mapping physical addresses of TRBs to which stream - * ID they belong to. We need to do this because the host controller won't tell - * us which stream ring the TRB came from. We could store the stream ID in an - * event data TRB, but that doesn't help us for the cancellation case, since the - * endpoint may stop before it reaches that event data TRB. - * - * The radix tree maps the upper portion of the TRB DMA address to a ring - * segment that has the same upper portion of DMA addresses. For example, say I - * have segments of size 1KB, that are always 64-byte aligned. A segment may - * start at 0x10c91000 and end at 0x10c913f0. If I use the upper 10 bits, the - * key to the stream ID is 0x43244. I can use the DMA address of the TRB to - * pass the radix tree a key to get the right stream ID: - * - * 0x10c90fff >> 10 = 0x43243 - * 0x10c912c0 >> 10 = 0x43244 - * 0x10c91400 >> 10 = 0x43245 - * - * Obviously, only those TRBs with DMA addresses that are within the segment - * will make the radix tree return the stream ID for that ring. - * - * Caveats for the radix tree: - * - * The radix tree uses an unsigned long as a key pair. On 32-bit systems, an - * unsigned long will be 32-bits; on a 64-bit system an unsigned long will be - * 64-bits. Since we only request 32-bit DMA addresses, we can use that as the - * key on 32-bit or 64-bit systems (it would also be fine if we asked for 64-bit - * PCI DMA addresses on a 64-bit system). There might be a problem on 32-bit - * extended systems (where the DMA address can be bigger than 32-bits), - * if we allow the PCI dma mask to be bigger than 32-bits. So don't do that. */ struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci, unsigned int num_stream_ctxs, @@ -548,7 +604,6 @@ struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci, struct xhci_stream_info *stream_info; u32 cur_stream; struct xhci_ring *cur_ring; - unsigned long key; u64 addr; int ret; @@ -603,6 +658,7 @@ struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci, if (!cur_ring) goto cleanup_rings; cur_ring->stream_id = cur_stream; + cur_ring->trb_address_map = &stream_info->trb_address_map; /* Set deq ptr, cycle bit, and stream context type */ addr = cur_ring->first_seg->dma | SCT_FOR_CTX(SCT_PRI_TR) | @@ -612,10 +668,7 @@ struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci, xhci_dbg(xhci, "Setting stream %d ring ptr to 0x%08llx\n", cur_stream, (unsigned long long) addr); - key = (unsigned long) - (cur_ring->first_seg->dma >> TRB_SEGMENT_SHIFT); - ret = radix_tree_insert(&stream_info->trb_address_map, - key, cur_ring); + ret = xhci_update_stream_mapping(cur_ring, mem_flags); if (ret) { xhci_ring_free(xhci, cur_ring); stream_info->stream_rings[cur_stream] = NULL; @@ -635,9 +688,6 @@ cleanup_rings: for (cur_stream = 1; cur_stream < num_streams; cur_stream++) { cur_ring = stream_info->stream_rings[cur_stream]; if (cur_ring) { - addr = cur_ring->first_seg->dma; - radix_tree_delete(&stream_info->trb_address_map, - addr >> TRB_SEGMENT_SHIFT); xhci_ring_free(xhci, cur_ring); stream_info->stream_rings[cur_stream] = NULL; } @@ -698,7 +748,6 @@ void xhci_free_stream_info(struct xhci_hcd *xhci, { int cur_stream; struct xhci_ring *cur_ring; - dma_addr_t addr; if (!stream_info) return; @@ -707,9 +756,6 @@ void xhci_free_stream_info(struct xhci_hcd *xhci, cur_stream++) { cur_ring = stream_info->stream_rings[cur_stream]; if (cur_ring) { - addr = cur_ring->first_seg->dma; - radix_tree_delete(&stream_info->trb_address_map, - addr >> TRB_SEGMENT_SHIFT); xhci_ring_free(xhci, cur_ring); stream_info->stream_rings[cur_stream] = NULL; } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 58ed9d088e6..a6aa98f19a1 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1341,6 +1341,7 @@ struct xhci_ring { unsigned int num_trbs_free_temp; enum xhci_ring_type type; bool last_td_was_short; + struct radix_tree_root *trb_address_map; }; struct xhci_erst_entry { -- cgit v1.2.3-70-g09d2 From df6138347bcde316e4d4b24ae4d9d0296461c79a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Oct 2013 00:29:45 +0200 Subject: xhci: Free streams when they are still allocated on a set_interface call And warn about this, as that would be a driver bug. Like wise drivers should ensure that streams are properly free-ed before a device is reset. So lets warn about that too. This already causes warnings in the form of: [ 96.982398] xhci_hcd 0000:01:00.0: WARN Can't disable streams for endpoint 0x81 , streams are already disabled! [ 96.982400] xhci_hcd 0000:01:00.0: WARN xhci_free_streams() called with non-streams endpoint But it is better to also warn about the actual cause of this later warnings. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/host/xhci.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 6fe577d46fa..c3fa32a905e 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -2678,6 +2678,20 @@ static int xhci_configure_endpoint(struct xhci_hcd *xhci, return ret; } +static void xhci_check_bw_drop_ep_streams(struct xhci_hcd *xhci, + struct xhci_virt_device *vdev, int i) +{ + struct xhci_virt_ep *ep = &vdev->eps[i]; + + if (ep->ep_state & EP_HAS_STREAMS) { + xhci_warn(xhci, "WARN: endpoint 0x%02x has streams on set_interface, freeing streams.\n", + xhci_get_endpoint_address(i)); + xhci_free_stream_info(xhci, ep->stream_info); + ep->stream_info = NULL; + ep->ep_state &= ~EP_HAS_STREAMS; + } +} + /* Called after one or more calls to xhci_add_endpoint() or * xhci_drop_endpoint(). If this call fails, the USB core is expected * to call xhci_reset_bandwidth(). @@ -2742,8 +2756,10 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) /* Free any rings that were dropped, but not changed. */ for (i = 1; i < 31; ++i) { if ((le32_to_cpu(ctrl_ctx->drop_flags) & (1 << (i + 1))) && - !(le32_to_cpu(ctrl_ctx->add_flags) & (1 << (i + 1)))) + !(le32_to_cpu(ctrl_ctx->add_flags) & (1 << (i + 1)))) { xhci_free_or_cache_endpoint_ring(xhci, virt_dev, i); + xhci_check_bw_drop_ep_streams(xhci, virt_dev, i); + } } xhci_zero_in_ctx(xhci, virt_dev); /* @@ -2759,6 +2775,7 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) if (virt_dev->eps[i].ring) { xhci_free_or_cache_endpoint_ring(xhci, virt_dev, i); } + 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; } @@ -3519,6 +3536,8 @@ int xhci_discover_or_reset_device(struct usb_hcd *hcd, struct usb_device *udev) struct xhci_virt_ep *ep = &virt_dev->eps[i]; if (ep->ep_state & EP_HAS_STREAMS) { + xhci_warn(xhci, "WARN: endpoint 0x%02x has streams on device reset, freeing streams.\n", + xhci_get_endpoint_address(i)); xhci_free_stream_info(xhci, ep->stream_info); ep->stream_info = NULL; ep->ep_state &= ~EP_HAS_STREAMS; -- cgit v1.2.3-70-g09d2 From ee4aa54bce2ae27c36998da3d32d9f55cb48ca21 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Oct 2013 00:29:46 +0200 Subject: xhci: Check size rather then number of streams when allocating stream ctxs Before this a device needing ie 32 stream ctxs would end up with an entry from the small_streams_pool which has 256 bytes entries, where as 32 stream ctxs need 512 bytes. Things actually keep running for a surprisingly long time before crashing because of this. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/host/xhci-mem.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 39f9a99a503..be7b7b6b551 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -520,12 +520,12 @@ static void xhci_free_stream_ctx(struct xhci_hcd *xhci, struct xhci_stream_ctx *stream_ctx, dma_addr_t dma) { struct device *dev = xhci_to_hcd(xhci)->self.controller; + size_t size = sizeof(struct xhci_stream_ctx) * num_stream_ctxs; - if (num_stream_ctxs > MEDIUM_STREAM_ARRAY_SIZE) - dma_free_coherent(dev, - sizeof(struct xhci_stream_ctx)*num_stream_ctxs, + if (size > MEDIUM_STREAM_ARRAY_SIZE) + dma_free_coherent(dev, size, stream_ctx, dma); - else if (num_stream_ctxs <= SMALL_STREAM_ARRAY_SIZE) + else if (size <= SMALL_STREAM_ARRAY_SIZE) return dma_pool_free(xhci->small_streams_pool, stream_ctx, dma); else @@ -548,12 +548,12 @@ static struct xhci_stream_ctx *xhci_alloc_stream_ctx(struct xhci_hcd *xhci, gfp_t mem_flags) { struct device *dev = xhci_to_hcd(xhci)->self.controller; + size_t size = sizeof(struct xhci_stream_ctx) * num_stream_ctxs; - if (num_stream_ctxs > MEDIUM_STREAM_ARRAY_SIZE) - return dma_alloc_coherent(dev, - sizeof(struct xhci_stream_ctx)*num_stream_ctxs, + if (size > MEDIUM_STREAM_ARRAY_SIZE) + return dma_alloc_coherent(dev, size, dma, mem_flags); - else if (num_stream_ctxs <= SMALL_STREAM_ARRAY_SIZE) + else if (size <= SMALL_STREAM_ARRAY_SIZE) return dma_pool_alloc(xhci->small_streams_pool, mem_flags, dma); else -- cgit v1.2.3-70-g09d2 From c4bedb77ec4cb42f37cae4cbfddda8283161f7c8 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Oct 2013 00:29:47 +0200 Subject: xhci: For streams the css flag most be read from the stream-ctx on ep stop Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/host/xhci-ring.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 3ec1d8fe06f..51c5109bbef 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -546,9 +546,9 @@ void xhci_find_new_dequeue_state(struct xhci_hcd *xhci, struct xhci_dequeue_state *state) { struct xhci_virt_device *dev = xhci->devs[slot_id]; + struct xhci_virt_ep *ep = &dev->eps[ep_index]; struct xhci_ring *ep_ring; struct xhci_generic_trb *trb; - struct xhci_ep_ctx *ep_ctx; dma_addr_t addr; ep_ring = xhci_triad_to_transfer_ring(xhci, slot_id, @@ -573,8 +573,16 @@ void xhci_find_new_dequeue_state(struct xhci_hcd *xhci, /* Dig out the cycle state saved by the xHC during the stop ep cmd */ xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, "Finding endpoint context"); - ep_ctx = xhci_get_ep_ctx(xhci, dev->out_ctx, ep_index); - state->new_cycle_state = 0x1 & le64_to_cpu(ep_ctx->deq); + /* 4.6.9 the css flag is written to the stream context for streams */ + if (ep->ep_state & EP_HAS_STREAMS) { + struct xhci_stream_ctx *ctx = + &ep->stream_info->stream_ctx_array[stream_id]; + state->new_cycle_state = 0x1 & le64_to_cpu(ctx->stream_ring); + } else { + struct xhci_ep_ctx *ep_ctx + = xhci_get_ep_ctx(xhci, dev->out_ctx, ep_index); + state->new_cycle_state = 0x1 & le64_to_cpu(ep_ctx->deq); + } state->new_deq_ptr = cur_td->last_trb; xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, -- cgit v1.2.3-70-g09d2 From 95241dbdf8281ece1355b8673b882d6a182f3c7d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Oct 2013 00:29:48 +0200 Subject: xhci: Set SCT field for Set TR dequeue on streams Nec XHCI controllers don't seem to care, but without this Intel XHCI controllers reject Set TR dequeue commands with a COMP_TRB_ERR, leading to the following warning: WARN Set TR Deq Ptr cmd invalid because of stream ID configuration And very shortly after this the system completely freezes. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/host/xhci-ring.c | 5 ++++- drivers/usb/host/xhci.h | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 51c5109bbef..f17e5c8c1cb 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -4073,6 +4073,7 @@ static int queue_set_tr_deq(struct xhci_hcd *xhci, int slot_id, u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id); u32 trb_ep_index = EP_ID_FOR_TRB(ep_index); u32 trb_stream_id = STREAM_ID_FOR_TRB(stream_id); + u32 trb_sct = 0; u32 type = TRB_TYPE(TRB_SET_DEQ); struct xhci_virt_ep *ep; @@ -4091,7 +4092,9 @@ static int queue_set_tr_deq(struct xhci_hcd *xhci, int slot_id, } ep->queued_deq_seg = deq_seg; ep->queued_deq_ptr = deq_ptr; - return queue_command(xhci, lower_32_bits(addr) | cycle_state, + if (stream_id) + trb_sct = SCT_FOR_TRB(SCT_PRI_TR); + return queue_command(xhci, lower_32_bits(addr) | trb_sct | cycle_state, upper_32_bits(addr), trb_stream_id, trb_slot_id | trb_ep_index | type, false); } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index a6aa98f19a1..2c77bf7ab9e 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1118,9 +1118,10 @@ enum xhci_setup_dev { #define TRB_TO_SUSPEND_PORT(p) (((p) & (1 << 23)) >> 23) #define LAST_EP_INDEX 30 -/* Set TR Dequeue Pointer command TRB fields */ +/* Set TR Dequeue Pointer command TRB fields, 6.4.3.9 */ #define TRB_TO_STREAM_ID(p) ((((p) & (0xffff << 16)) >> 16)) #define STREAM_ID_FOR_TRB(p) ((((p)) & 0xffff) << 16) +#define SCT_FOR_TRB(p) (((p) << 1) & 0x7) /* Port Status Change Event TRB fields */ -- cgit v1.2.3-70-g09d2 From 9aad95e292f58d00aa0f2e30c7f7dafd7fc7491c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Oct 2013 00:29:49 +0200 Subject: xhci: For streams the dequeue ptr must be read from the stream ctx This fixes TR dequeue validation failing on Intel XHCI controllers with the following warning: Mismatch between completed Set TR Deq Ptr command & xHCI internal state. Interestingly enough reading the deq ptr from the ep ctx after a TR Deq Ptr command does work on a Nec XHCI controller, it seems the Nec writes the ptr to both the ep and stream contexts when streams are used. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/host/xhci-ring.c | 22 +++++++++++++++------- drivers/usb/host/xhci.h | 1 + 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index f17e5c8c1cb..b3d27e6467e 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1081,12 +1081,14 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, unsigned int stream_id; struct xhci_ring *ep_ring; struct xhci_virt_device *dev; + struct xhci_virt_ep *ep; struct xhci_ep_ctx *ep_ctx; struct xhci_slot_ctx *slot_ctx; ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3])); stream_id = TRB_TO_STREAM_ID(le32_to_cpu(trb->generic.field[2])); dev = xhci->devs[slot_id]; + ep = &dev->eps[ep_index]; ep_ring = xhci_stream_id_to_ring(dev, ep_index, stream_id); if (!ep_ring) { @@ -1134,12 +1136,19 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, * cancelling URBs, which might not be an error... */ } else { + u64 deq; + /* 4.6.10 deq ptr is written to the stream ctx for streams */ + if (ep->ep_state & EP_HAS_STREAMS) { + struct xhci_stream_ctx *ctx = + &ep->stream_info->stream_ctx_array[stream_id]; + deq = le64_to_cpu(ctx->stream_ring) & SCTX_DEQ_MASK; + } else { + deq = le64_to_cpu(ep_ctx->deq) & ~EP_CTX_CYCLE_MASK; + } xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, - "Successful Set TR Deq Ptr cmd, deq = @%08llx", - le64_to_cpu(ep_ctx->deq)); - if (xhci_trb_virt_to_dma(dev->eps[ep_index].queued_deq_seg, - dev->eps[ep_index].queued_deq_ptr) == - (le64_to_cpu(ep_ctx->deq) & ~(EP_CTX_CYCLE_MASK))) { + "Successful Set TR Deq Ptr cmd, deq = @%08llx", deq); + if (xhci_trb_virt_to_dma(ep->queued_deq_seg, + ep->queued_deq_ptr) == deq) { /* Update the ring's dequeue segment and dequeue pointer * to reflect the new position. */ @@ -1148,8 +1157,7 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, } else { xhci_warn(xhci, "Mismatch between completed Set TR Deq Ptr command & xHCI internal state.\n"); xhci_warn(xhci, "ep deq seg = %p, deq ptr = %p\n", - dev->eps[ep_index].queued_deq_seg, - dev->eps[ep_index].queued_deq_ptr); + ep->queued_deq_seg, ep->queued_deq_ptr); } } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 2c77bf7ab9e..d280e9213d0 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -703,6 +703,7 @@ struct xhci_ep_ctx { /* deq bitmasks */ #define EP_CTX_CYCLE_MASK (1 << 0) +#define SCTX_DEQ_MASK (~0xfL) /** -- cgit v1.2.3-70-g09d2 From a3901538611f8d5180df44f5b61293e70903958f Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Oct 2013 17:05:55 +0200 Subject: xhci: use usb_ss_max_streams in xhci_check_streams_endpoint The ss_ep_comp bmAttributes filed can contain more info then just the streams, use usb_ss_max_streams to properly get max streams. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/host/xhci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index c3fa32a905e..6cb9c2235a0 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -2971,7 +2971,7 @@ static int xhci_check_streams_endpoint(struct xhci_hcd *xhci, ret = xhci_check_args(xhci_to_hcd(xhci), udev, ep, 1, true, __func__); if (ret <= 0) return -EINVAL; - if (ep->ss_ep_comp.bmAttributes == 0) { + if (usb_ss_max_streams(&ep->ss_ep_comp) == 0) { xhci_warn(xhci, "WARN: SuperSpeed Endpoint Companion" " descriptor for ep 0x%x does not support streams\n", ep->desc.bEndpointAddress); -- cgit v1.2.3-70-g09d2 From d57342232db459bf820b90e39496190965d7a775 Mon Sep 17 00:00:00 2001 From: Sarah Sharp Date: Thu, 17 Oct 2013 12:44:58 -0700 Subject: xhci: Remove segments from radix tree on failed insert. If we're expanding a stream ring, we want to make sure we can add those ring segments to the radix tree that maps segments to ring pointers. Try the radix tree insert after the new ring segments have been allocated (the last segment in the new ring chunk will point to the first newly allocated segment), but before the new ring segments are linked into the old ring. If insert fails on any one segment, remove each segment from the radix tree, deallocate the new segments, and return. Otherwise, link the new segments into the tree. HdG: Add a check to only update stream mappings in xhci_ring_expansion when the ring is a stream ring. Signed-off-by: Sarah Sharp Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/host/xhci-mem.c | 107 +++++++++++++++++++++++++++++++++----------- 1 file changed, 81 insertions(+), 26 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index be7b7b6b551..edfb31ad594 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -180,53 +180,98 @@ static void xhci_link_rings(struct xhci_hcd *xhci, struct xhci_ring *ring, * extended systems (where the DMA address can be bigger than 32-bits), * if we allow the PCI dma mask to be bigger than 32-bits. So don't do that. */ -static int xhci_update_stream_mapping(struct xhci_ring *ring, gfp_t mem_flags) +static int xhci_insert_segment_mapping(struct radix_tree_root *trb_address_map, + struct xhci_ring *ring, + struct xhci_segment *seg, + gfp_t mem_flags) { - struct xhci_segment *seg; unsigned long key; int ret; - if (WARN_ON_ONCE(ring->trb_address_map == NULL)) + key = (unsigned long)(seg->dma >> TRB_SEGMENT_SHIFT); + /* Skip any segments that were already added. */ + if (radix_tree_lookup(trb_address_map, key)) return 0; - seg = ring->first_seg; - do { - key = (unsigned long)(seg->dma >> TRB_SEGMENT_SHIFT); - /* Skip any segments that were already added. */ - if (radix_tree_lookup(ring->trb_address_map, key)) - continue; + ret = radix_tree_maybe_preload(mem_flags); + if (ret) + return ret; + ret = radix_tree_insert(trb_address_map, + key, ring); + radix_tree_preload_end(); + return ret; +} - ret = radix_tree_maybe_preload(mem_flags); - if (ret) - return ret; - ret = radix_tree_insert(ring->trb_address_map, - key, ring); - radix_tree_preload_end(); +static void xhci_remove_segment_mapping(struct radix_tree_root *trb_address_map, + struct xhci_segment *seg) +{ + unsigned long key; + + key = (unsigned long)(seg->dma >> TRB_SEGMENT_SHIFT); + if (radix_tree_lookup(trb_address_map, key)) + radix_tree_delete(trb_address_map, key); +} + +static int xhci_update_stream_segment_mapping( + struct radix_tree_root *trb_address_map, + struct xhci_ring *ring, + struct xhci_segment *first_seg, + struct xhci_segment *last_seg, + gfp_t mem_flags) +{ + struct xhci_segment *seg; + struct xhci_segment *failed_seg; + int ret; + + if (WARN_ON_ONCE(trb_address_map == NULL)) + return 0; + + seg = first_seg; + do { + ret = xhci_insert_segment_mapping(trb_address_map, + ring, seg, mem_flags); if (ret) - return ret; + goto remove_streams; + if (seg == last_seg) + return 0; seg = seg->next; - } while (seg != ring->first_seg); + } while (seg != first_seg); return 0; + +remove_streams: + failed_seg = seg; + seg = first_seg; + do { + xhci_remove_segment_mapping(trb_address_map, seg); + if (seg == failed_seg) + return ret; + seg = seg->next; + } while (seg != first_seg); + + return ret; } static void xhci_remove_stream_mapping(struct xhci_ring *ring) { struct xhci_segment *seg; - unsigned long key; if (WARN_ON_ONCE(ring->trb_address_map == NULL)) return; seg = ring->first_seg; do { - key = (unsigned long)(seg->dma >> TRB_SEGMENT_SHIFT); - if (radix_tree_lookup(ring->trb_address_map, key)) - radix_tree_delete(ring->trb_address_map, key); + xhci_remove_segment_mapping(ring->trb_address_map, seg); seg = seg->next; } while (seg != ring->first_seg); } +static int xhci_update_stream_mapping(struct xhci_ring *ring, gfp_t mem_flags) +{ + return xhci_update_stream_segment_mapping(ring->trb_address_map, ring, + ring->first_seg, ring->last_seg, mem_flags); +} + /* XXX: Do we need the hcd structure in all these functions? */ void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring) { @@ -430,16 +475,26 @@ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, if (ret) return -ENOMEM; + if (ring->type == TYPE_STREAM) + ret = xhci_update_stream_segment_mapping(ring->trb_address_map, + ring, first, last, flags); + if (ret) { + struct xhci_segment *next; + do { + next = first->next; + xhci_segment_free(xhci, first); + if (first == last) + break; + first = next; + } while (true); + return ret; + } + xhci_link_rings(xhci, ring, first, last, num_segs); xhci_dbg_trace(xhci, trace_xhci_dbg_ring_expansion, "ring expansion succeed, now has %d segments", ring->num_segs); - if (ring->type == TYPE_STREAM) { - ret = xhci_update_stream_mapping(ring, flags); - WARN_ON(ret); /* FIXME */ - } - return 0; } -- cgit v1.2.3-70-g09d2 From 12d4bbcea727710bbd04de3e1de05957a0675e60 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Oct 2013 17:19:23 +0200 Subject: usb-core: Fix usb_free_streams return value documentation Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/core/hcd.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 2518c325075..9b445f43f82 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -2078,8 +2078,7 @@ EXPORT_SYMBOL_GPL(usb_alloc_streams); * Reverts a group of bulk endpoints back to not using stream IDs. * Can fail if we are given bad arguments, or HCD is broken. * - * Return: On success, the number of allocated streams. On failure, a negative - * error code. + * Return: 0 on success. On failure, a negative error code. */ int usb_free_streams(struct usb_interface *interface, struct usb_host_endpoint **eps, unsigned int num_eps, -- cgit v1.2.3-70-g09d2 From 8f5d35441ff26b31e3812556ce468c76f1eb216b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Oct 2013 17:19:24 +0200 Subject: usb-core: Move USB_MAXENDPOINTS definitions to usb.h So that it can be used in other places too. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/core/config.c | 1 - include/linux/usb.h | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 8d72f0c6593..14ba398d6de 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -10,7 +10,6 @@ #define USB_MAXALTSETTING 128 /* Hard limit */ -#define USB_MAXENDPOINTS 30 /* Hard limit */ #define USB_MAXCONFIG 8 /* Arbitrary limit */ diff --git a/include/linux/usb.h b/include/linux/usb.h index 7f6eb859873..9b73dd782c0 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -202,6 +202,8 @@ static inline void usb_set_intfdata(struct usb_interface *intf, void *data) struct usb_interface *usb_get_intf(struct usb_interface *intf); void usb_put_intf(struct usb_interface *intf); +/* Hard limit */ +#define USB_MAXENDPOINTS 30 /* this maximum is arbitrary */ #define USB_MAXINTERFACES 32 #define USB_MAXIADS (USB_MAXINTERFACES/2) -- cgit v1.2.3-70-g09d2 From 8d4f70b2fa52ca80f74faebc2471f74ee374cf61 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Oct 2013 17:19:25 +0200 Subject: usb-core: Track if an endpoint has streams This is a preparation patch for adding support for bulk streams to usbfs. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/core/hcd.c | 34 ++++++++++++++++++++++++++-------- include/linux/usb.h | 2 ++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 9b445f43f82..9c4e2922b04 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -2049,7 +2049,7 @@ int usb_alloc_streams(struct usb_interface *interface, { struct usb_hcd *hcd; struct usb_device *dev; - int i; + int i, ret; dev = interface_to_usbdev(interface); hcd = bus_to_hcd(dev->bus); @@ -2058,13 +2058,24 @@ int usb_alloc_streams(struct usb_interface *interface, if (dev->speed != USB_SPEED_SUPER) return -EINVAL; - /* Streams only apply to bulk endpoints. */ - for (i = 0; i < num_eps; i++) + for (i = 0; i < num_eps; i++) { + /* Streams only apply to bulk endpoints. */ if (!usb_endpoint_xfer_bulk(&eps[i]->desc)) return -EINVAL; + /* Re-alloc is not allowed */ + if (eps[i]->streams) + return -EINVAL; + } - return hcd->driver->alloc_streams(hcd, dev, eps, num_eps, + ret = hcd->driver->alloc_streams(hcd, dev, eps, num_eps, num_streams, mem_flags); + if (ret < 0) + return ret; + + for (i = 0; i < num_eps; i++) + eps[i]->streams = ret; + + return ret; } EXPORT_SYMBOL_GPL(usb_alloc_streams); @@ -2086,19 +2097,26 @@ int usb_free_streams(struct usb_interface *interface, { struct usb_hcd *hcd; struct usb_device *dev; - int i; + int i, ret; dev = interface_to_usbdev(interface); hcd = bus_to_hcd(dev->bus); if (dev->speed != USB_SPEED_SUPER) return -EINVAL; - /* Streams only apply to bulk endpoints. */ + /* Double-free is not allowed */ for (i = 0; i < num_eps; i++) - if (!eps[i] || !usb_endpoint_xfer_bulk(&eps[i]->desc)) + if (!eps[i] || !eps[i]->streams) return -EINVAL; - return hcd->driver->free_streams(hcd, dev, eps, num_eps, mem_flags); + ret = hcd->driver->free_streams(hcd, dev, eps, num_eps, mem_flags); + if (ret < 0) + return ret; + + for (i = 0; i < num_eps; i++) + eps[i]->streams = 0; + + return ret; } EXPORT_SYMBOL_GPL(usb_free_streams); diff --git a/include/linux/usb.h b/include/linux/usb.h index 9b73dd782c0..f1015cee594 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -57,6 +57,7 @@ struct ep_device; * @extra: descriptors following this endpoint in the configuration * @extralen: how many bytes of "extra" are valid * @enabled: URBs may be submitted to this endpoint + * @streams: number of USB-3 streams allocated on the endpoint * * USB requests are always queued to a given endpoint, identified by a * descriptor within an active interface in a given USB configuration. @@ -71,6 +72,7 @@ struct usb_host_endpoint { unsigned char *extra; /* Extra descriptors */ int extralen; int enabled; + int streams; }; /* host-side wrapper for one interface setting's parsed descriptors */ -- cgit v1.2.3-70-g09d2 From 6343e8bf09de16ab4dcae2c6ca1a0e8dbd4dd770 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Oct 2013 17:19:26 +0200 Subject: usb-core: Free bulk streams on interface release Documentation/usb/bulk-streams.txt says: All stream IDs will be deallocated when the driver releases the interface, to ensure that drivers that don't support streams will be able to use the endpoint This commit actually implements this. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/core/driver.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 85e0450a2bc..08283d40616 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -400,8 +400,9 @@ static int usb_unbind_interface(struct device *dev) { struct usb_driver *driver = to_usb_driver(dev->driver); struct usb_interface *intf = to_usb_interface(dev); + struct usb_host_endpoint *ep, **eps = NULL; struct usb_device *udev; - int error, r, lpm_disable_error; + int i, j, error, r, lpm_disable_error; intf->condition = USB_INTERFACE_UNBINDING; @@ -425,6 +426,26 @@ static int usb_unbind_interface(struct device *dev) driver->disconnect(intf); usb_cancel_queued_reset(intf); + /* Free streams */ + for (i = 0, j = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) { + ep = &intf->cur_altsetting->endpoint[i]; + if (ep->streams == 0) + continue; + if (j == 0) { + eps = kmalloc(USB_MAXENDPOINTS * sizeof(void *), + GFP_KERNEL); + if (!eps) { + dev_warn(dev, "oom, leaking streams\n"); + break; + } + } + eps[j++] = ep; + } + if (j) { + usb_free_streams(intf, eps, j, GFP_KERNEL); + kfree(eps); + } + /* Reset other interface state. * We cannot do a Set-Interface if the device is suspended or * if it is prepared for a system sleep (since installing a new -- cgit v1.2.3-70-g09d2 From 5ec9c1771ce83a1e2b7ec96ed9f29a9f1b25e71e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Oct 2013 17:19:27 +0200 Subject: usbfs: Kill urbs on interface before doing a set_interface The usb_set_interface documentation says: * Also, drivers must not change altsettings while urbs are scheduled for * endpoints in that interface; all such urbs must first be completed * (perhaps forced by unlinking). For in kernel drivers we trust the drivers to get this right, but we cannot trust userspace to get this right, so enforce it by killing any urbs still pending on the interface. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/core/devio.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index f3ba2e076ee..2a95e4e574b 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -1143,6 +1143,9 @@ static int proc_setintf(struct dev_state *ps, void __user *arg) return -EFAULT; if ((ret = checkintf(ps, setintf.interface))) return ret; + + destroy_async_on_interface(ps, setintf.interface); + return usb_set_interface(ps->dev, setintf.interface, setintf.altsetting); } -- cgit v1.2.3-70-g09d2 From b2d03eb56e66620a9b27f1a0c2795722087effc9 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Oct 2013 17:19:28 +0200 Subject: usbfs: proc_do_submiturb use a local variable for number_of_packets This is a preparation patch for adding support for bulk streams. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/core/devio.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 2a95e4e574b..c88d8bfaca8 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -1208,6 +1208,7 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, struct usb_ctrlrequest *dr = NULL; unsigned int u, totlen, isofrmlen; int i, ret, is_in, num_sgs = 0, ifnum = -1; + int number_of_packets = 0; void *buf; if (uurb->flags & ~(USBDEVFS_URB_ISO_ASAP | @@ -1261,7 +1262,6 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, le16_to_cpup(&dr->wIndex)); if (ret) goto error; - uurb->number_of_packets = 0; uurb->buffer_length = le16_to_cpup(&dr->wLength); uurb->buffer += 8; if ((dr->bRequestType & USB_DIR_IN) && uurb->buffer_length) { @@ -1291,7 +1291,6 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, uurb->type = USBDEVFS_URB_TYPE_INTERRUPT; goto interrupt_urb; } - uurb->number_of_packets = 0; num_sgs = DIV_ROUND_UP(uurb->buffer_length, USB_SG_SIZE); if (num_sgs == 1 || num_sgs > ps->dev->bus->sg_tablesize) num_sgs = 0; @@ -1301,7 +1300,6 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, if (!usb_endpoint_xfer_int(&ep->desc)) return -EINVAL; interrupt_urb: - uurb->number_of_packets = 0; break; case USBDEVFS_URB_TYPE_ISO: @@ -1311,15 +1309,16 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, return -EINVAL; if (!usb_endpoint_xfer_isoc(&ep->desc)) return -EINVAL; + number_of_packets = uurb->number_of_packets; isofrmlen = sizeof(struct usbdevfs_iso_packet_desc) * - uurb->number_of_packets; + number_of_packets; if (!(isopkt = kmalloc(isofrmlen, GFP_KERNEL))) return -ENOMEM; if (copy_from_user(isopkt, iso_frame_desc, isofrmlen)) { ret = -EFAULT; goto error; } - for (totlen = u = 0; u < uurb->number_of_packets; u++) { + for (totlen = u = 0; u < number_of_packets; u++) { /* * arbitrary limit need for USB 3.0 * bMaxBurst (0~15 allowed, 1~16 packets) @@ -1350,7 +1349,7 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, ret = -EFAULT; goto error; } - as = alloc_async(uurb->number_of_packets); + as = alloc_async(number_of_packets); if (!as) { ret = -ENOMEM; goto error; @@ -1444,7 +1443,7 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, as->urb->setup_packet = (unsigned char *)dr; dr = NULL; as->urb->start_frame = uurb->start_frame; - as->urb->number_of_packets = uurb->number_of_packets; + as->urb->number_of_packets = number_of_packets; if (uurb->type == USBDEVFS_URB_TYPE_ISO || ps->dev->speed == USB_SPEED_HIGH) as->urb->interval = 1 << min(15, ep->desc.bInterval - 1); @@ -1452,7 +1451,7 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, as->urb->interval = ep->desc.bInterval; as->urb->context = as; as->urb->complete = async_completed; - for (totlen = u = 0; u < uurb->number_of_packets; u++) { + for (totlen = u = 0; u < number_of_packets; u++) { as->urb->iso_frame_desc[u].offset = totlen; as->urb->iso_frame_desc[u].length = isopkt[u].length; totlen += isopkt[u].length; -- cgit v1.2.3-70-g09d2 From 948cd8c18c466fdcbe707bb2a42a148796bfccdd Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Oct 2013 17:19:29 +0200 Subject: usbfs: Add support for bulk stream ids This patch makes it possible to specify a bulk stream id when submitting an urb using the async usbfs API. It overloads the number_of_packets usbdevfs_urb field for this. This is not pretty, but given other constraints it is the best we can do. The reasoning leading to this goes as follows: 1) We want to support bulk streams in the usbfs API 2) We do not want to extend the usbdevfs_urb struct with a new member, as that would mean defining new ioctl numbers for all async API ioctls + adding compat versions for the old ones (times 2 for 32 bit support) 3) 1 + 2 means we need to re-use an existing field 4) number_of_packets is only used for isoc urbs, and streams are bulk only so it is the best (and only) candidate for re-using Note that: 1) This patch only uses number_of_packets as stream_id if the app has actually allocated streams on the ep, so that old apps which may have garbage in there (as it was unused until now in the bulk case), will not break 2) This patch does not add support for allocating / freeing bulk-streams, that is done in a follow up patch Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/core/devio.c | 4 ++++ include/uapi/linux/usbdevice_fs.h | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index c88d8bfaca8..d7571a63181 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -1209,6 +1209,7 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, unsigned int u, totlen, isofrmlen; int i, ret, is_in, num_sgs = 0, ifnum = -1; int number_of_packets = 0; + unsigned int stream_id = 0; void *buf; if (uurb->flags & ~(USBDEVFS_URB_ISO_ASAP | @@ -1294,6 +1295,8 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, num_sgs = DIV_ROUND_UP(uurb->buffer_length, USB_SG_SIZE); if (num_sgs == 1 || num_sgs > ps->dev->bus->sg_tablesize) num_sgs = 0; + if (ep->streams) + stream_id = uurb->stream_id; break; case USBDEVFS_URB_TYPE_INTERRUPT: @@ -1444,6 +1447,7 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, dr = NULL; as->urb->start_frame = uurb->start_frame; as->urb->number_of_packets = number_of_packets; + as->urb->stream_id = stream_id; if (uurb->type == USBDEVFS_URB_TYPE_ISO || ps->dev->speed == USB_SPEED_HIGH) as->urb->interval = 1 << min(15, ep->desc.bInterval - 1); diff --git a/include/uapi/linux/usbdevice_fs.h b/include/uapi/linux/usbdevice_fs.h index 0c65e4b1261..cbf122db56b 100644 --- a/include/uapi/linux/usbdevice_fs.h +++ b/include/uapi/linux/usbdevice_fs.h @@ -102,7 +102,10 @@ struct usbdevfs_urb { int buffer_length; int actual_length; int start_frame; - int number_of_packets; + union { + int number_of_packets; /* Only used for isoc urbs */ + unsigned int stream_id; /* Only used with bulk streams */ + }; int error_count; unsigned int signr; /* signal to be sent on completion, or 0 if none should be sent. */ -- cgit v1.2.3-70-g09d2 From 2fec32b06e374642802f7fb4f5350317cd14732b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Oct 2013 17:19:30 +0200 Subject: usbfs: Add ep_to_host_endpoint helper function Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/core/devio.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index d7571a63181..502974b4deb 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -769,6 +769,15 @@ static int check_ctrlrecip(struct dev_state *ps, unsigned int requesttype, return ret; } +static struct usb_host_endpoint *ep_to_host_endpoint(struct usb_device *dev, + unsigned char ep) +{ + if (ep & USB_ENDPOINT_DIR_MASK) + return dev->ep_in[ep & USB_ENDPOINT_NUMBER_MASK]; + else + return dev->ep_out[ep & USB_ENDPOINT_NUMBER_MASK]; +} + static int match_devt(struct device *dev, void *data) { return dev->devt == (dev_t) (unsigned long) data; @@ -1230,15 +1239,10 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, if (ret) return ret; } - if ((uurb->endpoint & USB_ENDPOINT_DIR_MASK) != 0) { - is_in = 1; - ep = ps->dev->ep_in[uurb->endpoint & USB_ENDPOINT_NUMBER_MASK]; - } else { - is_in = 0; - ep = ps->dev->ep_out[uurb->endpoint & USB_ENDPOINT_NUMBER_MASK]; - } + ep = ep_to_host_endpoint(ps->dev, uurb->endpoint); if (!ep) return -ENOENT; + is_in = (uurb->endpoint & USB_ENDPOINT_DIR_MASK) != 0; u = 0; switch(uurb->type) { -- cgit v1.2.3-70-g09d2 From bcf7f6e39335af4f03da8c26a98185fd49754fcc Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Oct 2013 17:19:31 +0200 Subject: usbfs: Add support for allocating / freeing streams This allows userspace to use bulk-streams, just like in kernel drivers, see Documentation/usb/bulk-streams.txt for details on the in kernel API. This is exported pretty much one on one to userspace. To use streams an app must first make a USBDEVFS_ALLOC_STREAMS ioctl, on success this will return the number of streams available (which may be less then requested). If there are n streams the app can then submit usbdevfs_urb-s with their stream_id member set to 1-n to use a specific stream. IE if USBDEVFS_ALLOC_STREAMS returns 4 then stream_id 1-4 can be used. When the app is done using streams it should call USBDEVFS_FREE_STREAMS Note applications are advised to use libusb rather then using the usbdevfs api directly. The latest version of libusb has support for streams. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/core/devio.c | 118 ++++++++++++++++++++++++++++++++++++++ include/uapi/linux/usbdevice_fs.h | 7 +++ 2 files changed, 125 insertions(+) diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 502974b4deb..12401ee4ba0 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -778,6 +778,79 @@ static struct usb_host_endpoint *ep_to_host_endpoint(struct usb_device *dev, return dev->ep_out[ep & USB_ENDPOINT_NUMBER_MASK]; } +static int parse_usbdevfs_streams(struct dev_state *ps, + struct usbdevfs_streams __user *streams, + unsigned int *num_streams_ret, + unsigned int *num_eps_ret, + struct usb_host_endpoint ***eps_ret, + struct usb_interface **intf_ret) +{ + unsigned int i, num_streams, num_eps; + struct usb_host_endpoint **eps; + struct usb_interface *intf = NULL; + unsigned char ep; + int ifnum, ret; + + if (get_user(num_streams, &streams->num_streams) || + get_user(num_eps, &streams->num_eps)) + return -EFAULT; + + if (num_eps < 1 || num_eps > USB_MAXENDPOINTS) + return -EINVAL; + + /* The XHCI controller allows max 2 ^ 16 streams */ + if (num_streams_ret && (num_streams < 2 || num_streams > 65536)) + return -EINVAL; + + eps = kmalloc(num_eps * sizeof(*eps), GFP_KERNEL); + if (!eps) + return -ENOMEM; + + for (i = 0; i < num_eps; i++) { + if (get_user(ep, &streams->eps[i])) { + ret = -EFAULT; + goto error; + } + eps[i] = ep_to_host_endpoint(ps->dev, ep); + if (!eps[i]) { + ret = -EINVAL; + goto error; + } + + /* usb_alloc/free_streams operate on an usb_interface */ + ifnum = findintfep(ps->dev, ep); + if (ifnum < 0) { + ret = ifnum; + goto error; + } + + if (i == 0) { + ret = checkintf(ps, ifnum); + if (ret < 0) + goto error; + intf = usb_ifnum_to_if(ps->dev, ifnum); + } else { + /* Verify all eps belong to the same interface */ + if (ifnum != intf->altsetting->desc.bInterfaceNumber) { + ret = -EINVAL; + goto error; + } + } + } + + if (num_streams_ret) + *num_streams_ret = num_streams; + *num_eps_ret = num_eps; + *eps_ret = eps; + *intf_ret = intf; + + return 0; + +error: + kfree(eps); + return ret; +} + static int match_devt(struct device *dev, void *data) { return dev->devt == (dev_t) (unsigned long) data; @@ -2009,6 +2082,45 @@ static int proc_disconnect_claim(struct dev_state *ps, void __user *arg) return claimintf(ps, dc.interface); } +static int proc_alloc_streams(struct dev_state *ps, void __user *arg) +{ + unsigned num_streams, num_eps; + struct usb_host_endpoint **eps; + struct usb_interface *intf; + int r; + + r = parse_usbdevfs_streams(ps, arg, &num_streams, &num_eps, + &eps, &intf); + if (r) + return r; + + destroy_async_on_interface(ps, + intf->altsetting[0].desc.bInterfaceNumber); + + r = usb_alloc_streams(intf, eps, num_eps, num_streams, GFP_KERNEL); + kfree(eps); + return r; +} + +static int proc_free_streams(struct dev_state *ps, void __user *arg) +{ + unsigned num_eps; + struct usb_host_endpoint **eps; + struct usb_interface *intf; + int r; + + r = parse_usbdevfs_streams(ps, arg, NULL, &num_eps, &eps, &intf); + if (r) + return r; + + destroy_async_on_interface(ps, + intf->altsetting[0].desc.bInterfaceNumber); + + r = usb_free_streams(intf, eps, num_eps, GFP_KERNEL); + kfree(eps); + return r; +} + /* * NOTE: All requests here that have interface numbers as parameters * are assuming that somehow the configuration has been prevented from @@ -2185,6 +2297,12 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, case USBDEVFS_DISCONNECT_CLAIM: ret = proc_disconnect_claim(ps, p); break; + case USBDEVFS_ALLOC_STREAMS: + ret = proc_alloc_streams(ps, p); + break; + case USBDEVFS_FREE_STREAMS: + ret = proc_free_streams(ps, p); + break; } usb_unlock_device(dev); if (ret >= 0) diff --git a/include/uapi/linux/usbdevice_fs.h b/include/uapi/linux/usbdevice_fs.h index cbf122db56b..abe5f4bd4d8 100644 --- a/include/uapi/linux/usbdevice_fs.h +++ b/include/uapi/linux/usbdevice_fs.h @@ -147,6 +147,11 @@ struct usbdevfs_disconnect_claim { char driver[USBDEVFS_MAXDRIVERNAME + 1]; }; +struct usbdevfs_streams { + unsigned int num_streams; /* Not used by USBDEVFS_FREE_STREAMS */ + unsigned int num_eps; + unsigned char eps[0]; +}; #define USBDEVFS_CONTROL _IOWR('U', 0, struct usbdevfs_ctrltransfer) #define USBDEVFS_CONTROL32 _IOWR('U', 0, struct usbdevfs_ctrltransfer32) @@ -179,5 +184,7 @@ struct usbdevfs_disconnect_claim { #define USBDEVFS_RELEASE_PORT _IOR('U', 25, unsigned int) #define USBDEVFS_GET_CAPABILITIES _IOR('U', 26, __u32) #define USBDEVFS_DISCONNECT_CLAIM _IOR('U', 27, struct usbdevfs_disconnect_claim) +#define USBDEVFS_ALLOC_STREAMS _IOR('U', 28, struct usbdevfs_streams) +#define USBDEVFS_FREE_STREAMS _IOR('U', 29, struct usbdevfs_streams) #endif /* _UAPI_LINUX_USBDEVICE_FS_H */ -- cgit v1.2.3-70-g09d2 From d89bd835326947e6618b97469159d3266016fe0a Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 13 Sep 2013 13:27:11 +0200 Subject: uas: properly reinitialize in uas_eh_bus_reset_handler Signed-off-by: Gerd Hoffmann Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index d966b59f7d7..fc08ee91943 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -85,6 +85,8 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, struct uas_dev_info *devinfo, gfp_t gfp); static void uas_do_work(struct work_struct *work); static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller); +static void uas_configure_endpoints(struct uas_dev_info *devinfo); +static void uas_free_streams(struct uas_dev_info *devinfo); static DECLARE_WORK(uas_work, uas_do_work); static DEFINE_SPINLOCK(uas_work_lock); @@ -800,7 +802,10 @@ static int uas_eh_bus_reset_handler(struct scsi_cmnd *cmnd) usb_kill_anchored_urbs(&devinfo->cmd_urbs); usb_kill_anchored_urbs(&devinfo->sense_urbs); usb_kill_anchored_urbs(&devinfo->data_urbs); + uas_free_streams(devinfo); err = usb_reset_device(udev); + if (!err) + uas_configure_endpoints(devinfo); devinfo->resetting = 0; if (err) { -- cgit v1.2.3-70-g09d2 From 1bf8198e6b2da3e30960e95f8d215f44572515ce Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 13 Sep 2013 13:27:12 +0200 Subject: uas: make work list per-device Simplifies locking, we'll protect the list with the device spin lock. Also plugs races which can happen when two devices operate on the global list. While being at it rename the list head from "list" to "work", preparing for the addition of a second list. Signed-off-by: Gerd Hoffmann Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 106 +++++++++++++++++++--------------------------- 1 file changed, 44 insertions(+), 62 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index fc08ee91943..3cf5a5ff3d5 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -51,6 +51,8 @@ struct uas_dev_info { unsigned uas_sense_old:1; struct scsi_cmnd *cmnd; spinlock_t lock; + struct work_struct work; + struct list_head work_list; }; enum { @@ -77,7 +79,7 @@ struct uas_cmd_info { struct urb *cmd_urb; struct urb *data_in_urb; struct urb *data_out_urb; - struct list_head list; + struct list_head work; }; /* I hate forward declarations, but I actually have a loop */ @@ -88,10 +90,6 @@ static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller); static void uas_configure_endpoints(struct uas_dev_info *devinfo); static void uas_free_streams(struct uas_dev_info *devinfo); -static DECLARE_WORK(uas_work, uas_do_work); -static DEFINE_SPINLOCK(uas_work_lock); -static LIST_HEAD(uas_work_list); - static void uas_unlink_data_urbs(struct uas_dev_info *devinfo, struct uas_cmd_info *cmdinfo) { @@ -118,75 +116,66 @@ static void uas_unlink_data_urbs(struct uas_dev_info *devinfo, static void uas_do_work(struct work_struct *work) { + struct uas_dev_info *devinfo = + container_of(work, struct uas_dev_info, work); struct uas_cmd_info *cmdinfo; struct uas_cmd_info *temp; - struct list_head list; unsigned long flags; int err; - spin_lock_irq(&uas_work_lock); - list_replace_init(&uas_work_list, &list); - spin_unlock_irq(&uas_work_lock); - - list_for_each_entry_safe(cmdinfo, temp, &list, list) { + spin_lock_irqsave(&devinfo->lock, flags); + list_for_each_entry_safe(cmdinfo, temp, &devinfo->work_list, work) { struct scsi_pointer *scp = (void *)cmdinfo; - struct scsi_cmnd *cmnd = container_of(scp, - struct scsi_cmnd, SCp); - struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; - spin_lock_irqsave(&devinfo->lock, flags); + struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, + SCp); err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_ATOMIC); - if (!err) + if (!err) { cmdinfo->state &= ~IS_IN_WORK_LIST; - spin_unlock_irqrestore(&devinfo->lock, flags); - if (err) { - list_del(&cmdinfo->list); - spin_lock_irq(&uas_work_lock); - list_add_tail(&cmdinfo->list, &uas_work_list); - spin_unlock_irq(&uas_work_lock); - schedule_work(&uas_work); + list_del(&cmdinfo->work); + } else { + schedule_work(&devinfo->work); } } + spin_unlock_irqrestore(&devinfo->lock, flags); } static void uas_abort_work(struct uas_dev_info *devinfo) { struct uas_cmd_info *cmdinfo; struct uas_cmd_info *temp; - struct list_head list; unsigned long flags; - spin_lock_irq(&uas_work_lock); - list_replace_init(&uas_work_list, &list); - spin_unlock_irq(&uas_work_lock); - spin_lock_irqsave(&devinfo->lock, flags); - list_for_each_entry_safe(cmdinfo, temp, &list, list) { + list_for_each_entry_safe(cmdinfo, temp, &devinfo->work_list, work) { struct scsi_pointer *scp = (void *)cmdinfo; - struct scsi_cmnd *cmnd = container_of(scp, - struct scsi_cmnd, SCp); - struct uas_dev_info *di = (void *)cmnd->device->hostdata; - - if (di == devinfo) { - cmdinfo->state |= COMMAND_ABORTED; - cmdinfo->state &= ~IS_IN_WORK_LIST; - if (devinfo->resetting) { - /* uas_stat_cmplt() will not do that - * when a device reset is in - * progress */ - cmdinfo->state &= ~COMMAND_INFLIGHT; - } - uas_try_complete(cmnd, __func__); - } else { - /* not our uas device, relink into list */ - list_del(&cmdinfo->list); - spin_lock_irq(&uas_work_lock); - list_add_tail(&cmdinfo->list, &uas_work_list); - spin_unlock_irq(&uas_work_lock); + struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, + SCp); + cmdinfo->state |= COMMAND_ABORTED; + cmdinfo->state &= ~IS_IN_WORK_LIST; + if (devinfo->resetting) { + /* uas_stat_cmplt() will not do that + * when a device reset is in + * progress */ + cmdinfo->state &= ~COMMAND_INFLIGHT; } + uas_try_complete(cmnd, __func__); + list_del(&cmdinfo->work); } spin_unlock_irqrestore(&devinfo->lock, flags); } +static void uas_add_work(struct uas_cmd_info *cmdinfo) +{ + struct scsi_pointer *scp = (void *)cmdinfo; + struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, SCp); + struct uas_dev_info *devinfo = cmnd->device->hostdata; + + WARN_ON(!spin_is_locked(&devinfo->lock)); + list_add_tail(&cmdinfo->work, &devinfo->work_list); + cmdinfo->state |= IS_IN_WORK_LIST; + schedule_work(&devinfo->work); +} + static void uas_sense(struct urb *urb, struct scsi_cmnd *cmnd) { struct sense_iu *sense_iu = urb->transfer_buffer; @@ -288,11 +277,7 @@ static void uas_xfer_data(struct urb *urb, struct scsi_cmnd *cmnd, cmdinfo->state |= direction | SUBMIT_STATUS_URB; err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_ATOMIC); if (err) { - spin_lock(&uas_work_lock); - list_add_tail(&cmdinfo->list, &uas_work_list); - cmdinfo->state |= IS_IN_WORK_LIST; - spin_unlock(&uas_work_lock); - schedule_work(&uas_work); + uas_add_work(cmdinfo); } } @@ -694,11 +679,7 @@ static int uas_queuecommand_lck(struct scsi_cmnd *cmnd, spin_unlock_irqrestore(&devinfo->lock, flags); return SCSI_MLQUEUE_DEVICE_BUSY; } - spin_lock(&uas_work_lock); - list_add_tail(&cmdinfo->list, &uas_work_list); - cmdinfo->state |= IS_IN_WORK_LIST; - spin_unlock(&uas_work_lock); - schedule_work(&uas_work); + uas_add_work(cmdinfo); } spin_unlock_irqrestore(&devinfo->lock, flags); @@ -764,10 +745,8 @@ static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) spin_lock_irqsave(&devinfo->lock, flags); cmdinfo->state |= COMMAND_ABORTED; if (cmdinfo->state & IS_IN_WORK_LIST) { - spin_lock(&uas_work_lock); - list_del(&cmdinfo->list); + list_del(&cmdinfo->work); cmdinfo->state &= ~IS_IN_WORK_LIST; - spin_unlock(&uas_work_lock); } if (cmdinfo->state & COMMAND_INFLIGHT) { spin_unlock_irqrestore(&devinfo->lock, flags); @@ -1007,6 +986,8 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) init_usb_anchor(&devinfo->sense_urbs); init_usb_anchor(&devinfo->data_urbs); spin_lock_init(&devinfo->lock); + INIT_WORK(&devinfo->work, uas_do_work); + INIT_LIST_HEAD(&devinfo->work_list); uas_configure_endpoints(devinfo); result = scsi_init_shared_tag_map(shost, devinfo->qdepth - 3); @@ -1050,6 +1031,7 @@ static void uas_disconnect(struct usb_interface *intf) struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; devinfo->resetting = 1; + cancel_work_sync(&devinfo->work); uas_abort_work(devinfo); usb_kill_anchored_urbs(&devinfo->cmd_urbs); usb_kill_anchored_urbs(&devinfo->sense_urbs); -- cgit v1.2.3-70-g09d2 From 326349f82461918830ed59913a3ccd8ffa9ac46f Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 13 Sep 2013 13:27:13 +0200 Subject: uas: add dead request list This patch adds a new list where all requests which are canceled are added to, so we don't loose them. Then, after killing all inflight urbs on bus reset (and disconnect) we'll walk over the list and clean them up. Without this we can end up with aborted requests lingering around in case of status pipe transfer errors. Signed-off-by: Gerd Hoffmann Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 50 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 3cf5a5ff3d5..f0490382351 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -53,6 +53,7 @@ struct uas_dev_info { spinlock_t lock; struct work_struct work; struct list_head work_list; + struct list_head dead_list; }; enum { @@ -80,6 +81,7 @@ struct uas_cmd_info { struct urb *data_in_urb; struct urb *data_out_urb; struct list_head work; + struct list_head dead; }; /* I hate forward declarations, but I actually have a loop */ @@ -89,6 +91,7 @@ static void uas_do_work(struct work_struct *work); static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller); static void uas_configure_endpoints(struct uas_dev_info *devinfo); static void uas_free_streams(struct uas_dev_info *devinfo); +static void uas_log_cmd_state(struct scsi_cmnd *cmnd, const char *caller); static void uas_unlink_data_urbs(struct uas_dev_info *devinfo, struct uas_cmd_info *cmdinfo) @@ -150,16 +153,12 @@ static void uas_abort_work(struct uas_dev_info *devinfo) struct scsi_pointer *scp = (void *)cmdinfo; struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, SCp); + uas_log_cmd_state(cmnd, __func__); + WARN_ON(cmdinfo->state & COMMAND_ABORTED); cmdinfo->state |= COMMAND_ABORTED; cmdinfo->state &= ~IS_IN_WORK_LIST; - if (devinfo->resetting) { - /* uas_stat_cmplt() will not do that - * when a device reset is in - * progress */ - cmdinfo->state &= ~COMMAND_INFLIGHT; - } - uas_try_complete(cmnd, __func__); list_del(&cmdinfo->work); + list_add_tail(&cmdinfo->dead, &devinfo->dead_list); } spin_unlock_irqrestore(&devinfo->lock, flags); } @@ -176,6 +175,28 @@ static void uas_add_work(struct uas_cmd_info *cmdinfo) schedule_work(&devinfo->work); } +static void uas_zap_dead(struct uas_dev_info *devinfo) +{ + struct uas_cmd_info *cmdinfo; + struct uas_cmd_info *temp; + unsigned long flags; + + spin_lock_irqsave(&devinfo->lock, flags); + list_for_each_entry_safe(cmdinfo, temp, &devinfo->dead_list, dead) { + struct scsi_pointer *scp = (void *)cmdinfo; + struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, + SCp); + uas_log_cmd_state(cmnd, __func__); + WARN_ON(!(cmdinfo->state & COMMAND_ABORTED)); + /* all urbs are killed, clear inflight bits */ + cmdinfo->state &= ~(COMMAND_INFLIGHT | + DATA_IN_URB_INFLIGHT | + DATA_OUT_URB_INFLIGHT); + uas_try_complete(cmnd, __func__); + } + spin_unlock_irqrestore(&devinfo->lock, flags); +} + static void uas_sense(struct urb *urb, struct scsi_cmnd *cmnd) { struct sense_iu *sense_iu = urb->transfer_buffer; @@ -263,6 +284,7 @@ static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller) if (cmdinfo->state & COMMAND_ABORTED) { scmd_printk(KERN_INFO, cmnd, "abort completed\n"); cmnd->result = DID_ABORT << 16; + list_del(&cmdinfo->dead); } cmnd->scsi_done(cmnd); return 0; @@ -292,7 +314,13 @@ static void uas_stat_cmplt(struct urb *urb) u16 tag; if (urb->status) { - dev_err(&urb->dev->dev, "URB BAD STATUS %d\n", urb->status); + if (urb->status == -ENOENT) { + dev_err(&urb->dev->dev, "stat urb: killed, stream %d\n", + urb->stream_id); + } else { + dev_err(&urb->dev->dev, "stat urb: status %d\n", + urb->status); + } usb_free_urb(urb); return; } @@ -743,7 +771,9 @@ static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) uas_log_cmd_state(cmnd, __func__); spin_lock_irqsave(&devinfo->lock, flags); + WARN_ON(cmdinfo->state & COMMAND_ABORTED); cmdinfo->state |= COMMAND_ABORTED; + list_add_tail(&cmdinfo->dead, &devinfo->dead_list); if (cmdinfo->state & IS_IN_WORK_LIST) { list_del(&cmdinfo->work); cmdinfo->state &= ~IS_IN_WORK_LIST; @@ -776,11 +806,13 @@ static int uas_eh_bus_reset_handler(struct scsi_cmnd *cmnd) struct usb_device *udev = devinfo->udev; int err; + shost_printk(KERN_INFO, sdev->host, "%s start\n", __func__); devinfo->resetting = 1; uas_abort_work(devinfo); usb_kill_anchored_urbs(&devinfo->cmd_urbs); usb_kill_anchored_urbs(&devinfo->sense_urbs); usb_kill_anchored_urbs(&devinfo->data_urbs); + uas_zap_dead(devinfo); uas_free_streams(devinfo); err = usb_reset_device(udev); if (!err) @@ -988,6 +1020,7 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) spin_lock_init(&devinfo->lock); INIT_WORK(&devinfo->work, uas_do_work); INIT_LIST_HEAD(&devinfo->work_list); + INIT_LIST_HEAD(&devinfo->dead_list); uas_configure_endpoints(devinfo); result = scsi_init_shared_tag_map(shost, devinfo->qdepth - 3); @@ -1036,6 +1069,7 @@ static void uas_disconnect(struct usb_interface *intf) usb_kill_anchored_urbs(&devinfo->cmd_urbs); usb_kill_anchored_urbs(&devinfo->sense_urbs); usb_kill_anchored_urbs(&devinfo->data_urbs); + uas_zap_dead(devinfo); scsi_remove_host(shost); uas_free_streams(devinfo); kfree(devinfo); -- cgit v1.2.3-70-g09d2 From f491ecbb47d5a709d46401da3cd54ff8ee666ca0 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 13 Sep 2013 13:27:14 +0200 Subject: uas: replace BUG_ON() + WARN_ON() with WARN_ON_ONCE() Signed-off-by: Gerd Hoffmann Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index f0490382351..046eedfc382 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -154,7 +154,7 @@ static void uas_abort_work(struct uas_dev_info *devinfo) struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, SCp); uas_log_cmd_state(cmnd, __func__); - WARN_ON(cmdinfo->state & COMMAND_ABORTED); + WARN_ON_ONCE(cmdinfo->state & COMMAND_ABORTED); cmdinfo->state |= COMMAND_ABORTED; cmdinfo->state &= ~IS_IN_WORK_LIST; list_del(&cmdinfo->work); @@ -169,7 +169,7 @@ static void uas_add_work(struct uas_cmd_info *cmdinfo) struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, SCp); struct uas_dev_info *devinfo = cmnd->device->hostdata; - WARN_ON(!spin_is_locked(&devinfo->lock)); + WARN_ON_ONCE(!spin_is_locked(&devinfo->lock)); list_add_tail(&cmdinfo->work, &devinfo->work_list); cmdinfo->state |= IS_IN_WORK_LIST; schedule_work(&devinfo->work); @@ -187,7 +187,7 @@ static void uas_zap_dead(struct uas_dev_info *devinfo) struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, SCp); uas_log_cmd_state(cmnd, __func__); - WARN_ON(!(cmdinfo->state & COMMAND_ABORTED)); + WARN_ON_ONCE(!(cmdinfo->state & COMMAND_ABORTED)); /* all urbs are killed, clear inflight bits */ cmdinfo->state &= ~(COMMAND_INFLIGHT | DATA_IN_URB_INFLIGHT | @@ -271,13 +271,13 @@ static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller) struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; - WARN_ON(!spin_is_locked(&devinfo->lock)); + WARN_ON_ONCE(!spin_is_locked(&devinfo->lock)); if (cmdinfo->state & (COMMAND_INFLIGHT | DATA_IN_URB_INFLIGHT | DATA_OUT_URB_INFLIGHT | UNLINK_DATA_URBS)) return -EBUSY; - BUG_ON(cmdinfo->state & COMMAND_COMPLETED); + WARN_ON_ONCE(cmdinfo->state & COMMAND_COMPLETED); cmdinfo->state |= COMMAND_COMPLETED; usb_free_urb(cmdinfo->data_in_urb); usb_free_urb(cmdinfo->data_out_urb); @@ -398,8 +398,9 @@ static void uas_data_cmplt(struct urb *urb) sdb = scsi_out(cmnd); cmdinfo->state &= ~DATA_OUT_URB_INFLIGHT; } - BUG_ON(sdb == NULL); - if (urb->status) { + if (sdb == NULL) { + WARN_ON_ONCE(1); + } else if (urb->status) { /* error: no data transfered */ sdb->resid = sdb->length; } else { @@ -573,7 +574,7 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; int err; - WARN_ON(!spin_is_locked(&devinfo->lock)); + WARN_ON_ONCE(!spin_is_locked(&devinfo->lock)); if (cmdinfo->state & SUBMIT_STATUS_URB) { err = uas_submit_sense_urb(cmnd->device->host, gfp, cmdinfo->stream); @@ -771,7 +772,7 @@ static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) uas_log_cmd_state(cmnd, __func__); spin_lock_irqsave(&devinfo->lock, flags); - WARN_ON(cmdinfo->state & COMMAND_ABORTED); + WARN_ON_ONCE(cmdinfo->state & COMMAND_ABORTED); cmdinfo->state |= COMMAND_ABORTED; list_add_tail(&cmdinfo->dead, &devinfo->dead_list); if (cmdinfo->state & IS_IN_WORK_LIST) { -- cgit v1.2.3-70-g09d2 From d5f808d3f88e90b13a4839e07b3dc6e715e2ba88 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 17 Oct 2013 19:30:26 +0200 Subject: uas: Urbs must be anchored before submitting them Otherwise they may complete before they get anchored and thus never get unanchored (as the unanchoring is done by the usb core on completion). This commit also remove the usb_get_urb / usb_put_urb around cmd submission + anchoring, since if done in the proper order this is not necessary. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 046eedfc382..059ce62de4b 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -531,10 +531,12 @@ static int uas_submit_task_urb(struct scsi_cmnd *cmnd, gfp_t gfp, usb_free_urb, NULL); urb->transfer_flags |= URB_FREE_BUFFER; + usb_anchor_urb(urb, &devinfo->cmd_urbs); err = usb_submit_urb(urb, gfp); - if (err) + if (err) { + usb_unanchor_urb(urb); goto err; - usb_anchor_urb(urb, &devinfo->cmd_urbs); + } return 0; @@ -558,13 +560,14 @@ static int uas_submit_sense_urb(struct Scsi_Host *shost, urb = uas_alloc_sense_urb(devinfo, gfp, shost, stream); if (!urb) return SCSI_MLQUEUE_DEVICE_BUSY; + usb_anchor_urb(urb, &devinfo->sense_urbs); if (usb_submit_urb(urb, gfp)) { + usb_unanchor_urb(urb); shost_printk(KERN_INFO, shost, "sense urb submission failure\n"); usb_free_urb(urb); return SCSI_MLQUEUE_DEVICE_BUSY; } - usb_anchor_urb(urb, &devinfo->sense_urbs); return 0; } @@ -594,14 +597,15 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, } if (cmdinfo->state & SUBMIT_DATA_IN_URB) { + usb_anchor_urb(cmdinfo->data_in_urb, &devinfo->data_urbs); if (usb_submit_urb(cmdinfo->data_in_urb, gfp)) { + usb_unanchor_urb(cmdinfo->data_in_urb); scmd_printk(KERN_INFO, cmnd, "data in urb submission failure\n"); return SCSI_MLQUEUE_DEVICE_BUSY; } cmdinfo->state &= ~SUBMIT_DATA_IN_URB; cmdinfo->state |= DATA_IN_URB_INFLIGHT; - usb_anchor_urb(cmdinfo->data_in_urb, &devinfo->data_urbs); } if (cmdinfo->state & ALLOC_DATA_OUT_URB) { @@ -614,14 +618,15 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, } if (cmdinfo->state & SUBMIT_DATA_OUT_URB) { + usb_anchor_urb(cmdinfo->data_out_urb, &devinfo->data_urbs); if (usb_submit_urb(cmdinfo->data_out_urb, gfp)) { + usb_unanchor_urb(cmdinfo->data_out_urb); scmd_printk(KERN_INFO, cmnd, "data out urb submission failure\n"); return SCSI_MLQUEUE_DEVICE_BUSY; } cmdinfo->state &= ~SUBMIT_DATA_OUT_URB; cmdinfo->state |= DATA_OUT_URB_INFLIGHT; - usb_anchor_urb(cmdinfo->data_out_urb, &devinfo->data_urbs); } if (cmdinfo->state & ALLOC_CMD_URB) { @@ -633,14 +638,13 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, } if (cmdinfo->state & SUBMIT_CMD_URB) { - usb_get_urb(cmdinfo->cmd_urb); + usb_anchor_urb(cmdinfo->cmd_urb, &devinfo->cmd_urbs); if (usb_submit_urb(cmdinfo->cmd_urb, gfp)) { + usb_unanchor_urb(cmdinfo->cmd_urb); scmd_printk(KERN_INFO, cmnd, "cmd urb submission failure\n"); return SCSI_MLQUEUE_DEVICE_BUSY; } - usb_anchor_urb(cmdinfo->cmd_urb, &devinfo->cmd_urbs); - usb_put_urb(cmdinfo->cmd_urb); cmdinfo->cmd_urb = NULL; cmdinfo->state &= ~SUBMIT_CMD_URB; cmdinfo->state |= COMMAND_INFLIGHT; -- cgit v1.2.3-70-g09d2 From 6ce8213b3328ae4a1db34339c282144740ac1ec6 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 17 Oct 2013 19:00:45 +0200 Subject: uas: Properly set interface to altsetting 0 on probe failure - Rename labels to properly reflect this - Don't skip free-ing the streams when scsi_init_shared_tag_map fails Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 059ce62de4b..ec1b22d2950 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -993,8 +993,8 @@ static void uas_free_streams(struct uas_dev_info *devinfo) */ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) { - int result; - struct Scsi_Host *shost; + int result = -ENOMEM; + struct Scsi_Host *shost = NULL; struct uas_dev_info *devinfo; struct usb_device *udev = interface_to_usbdev(intf); @@ -1003,12 +1003,11 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) devinfo = kmalloc(sizeof(struct uas_dev_info), GFP_KERNEL); if (!devinfo) - return -ENOMEM; + goto set_alt0; - result = -ENOMEM; shost = scsi_host_alloc(&uas_host_template, sizeof(void *)); if (!shost) - goto free; + goto set_alt0; shost->max_cmd_len = 16 + 252; shost->max_id = 1; @@ -1030,11 +1029,11 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) result = scsi_init_shared_tag_map(shost, devinfo->qdepth - 3); if (result) - goto free; + goto free_streams; result = scsi_add_host(shost, &intf->dev); if (result) - goto deconfig_eps; + goto free_streams; shost->hostdata[0] = (unsigned long)devinfo; @@ -1042,9 +1041,10 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) usb_set_intfdata(intf, shost); return result; -deconfig_eps: +free_streams: uas_free_streams(devinfo); - free: +set_alt0: + usb_set_interface(udev, intf->altsetting[0].desc.bInterfaceNumber, 0); kfree(devinfo); if (shost) scsi_host_put(shost); -- cgit v1.2.3-70-g09d2 From 7e50e0bec45897caeb978e5aecc9184a2dc00df2 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 17 Oct 2013 19:19:04 +0200 Subject: uas: Avoid unnecessary unlock / lock calls around unlink_data_urbs All callers of unlink_data_urbs drop devinfo->lock before calling it, and then immediately take it again after the call. And the first thing unlink_data_urbs does is take the lock again, and the last thing it does is drop it. This commit removes all the unnecessary lock dropping and taking. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index ec1b22d2950..dcaf6119703 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -93,28 +93,26 @@ static void uas_configure_endpoints(struct uas_dev_info *devinfo); static void uas_free_streams(struct uas_dev_info *devinfo); static void uas_log_cmd_state(struct scsi_cmnd *cmnd, const char *caller); +/* Must be called with devinfo->lock held, will temporary unlock the lock */ static void uas_unlink_data_urbs(struct uas_dev_info *devinfo, - struct uas_cmd_info *cmdinfo) + struct uas_cmd_info *cmdinfo, + unsigned long *lock_flags) { - unsigned long flags; - /* * The UNLINK_DATA_URBS flag makes sure uas_try_complete * (called by urb completion) doesn't release cmdinfo * underneath us. */ - spin_lock_irqsave(&devinfo->lock, flags); cmdinfo->state |= UNLINK_DATA_URBS; - spin_unlock_irqrestore(&devinfo->lock, flags); + spin_unlock_irqrestore(&devinfo->lock, *lock_flags); if (cmdinfo->data_in_urb) usb_unlink_urb(cmdinfo->data_in_urb); if (cmdinfo->data_out_urb) usb_unlink_urb(cmdinfo->data_out_urb); - spin_lock_irqsave(&devinfo->lock, flags); + spin_lock_irqsave(&devinfo->lock, *lock_flags); cmdinfo->state &= ~UNLINK_DATA_URBS; - spin_unlock_irqrestore(&devinfo->lock, flags); } static void uas_do_work(struct work_struct *work) @@ -361,9 +359,7 @@ static void uas_stat_cmplt(struct urb *urb) uas_sense(urb, cmnd); if (cmnd->result != 0) { /* cancel data transfers on error */ - spin_unlock_irqrestore(&devinfo->lock, flags); - uas_unlink_data_urbs(devinfo, cmdinfo); - spin_lock_irqsave(&devinfo->lock, flags); + uas_unlink_data_urbs(devinfo, cmdinfo, &flags); } cmdinfo->state &= ~COMMAND_INFLIGHT; uas_try_complete(cmnd, __func__); @@ -787,9 +783,7 @@ static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) spin_unlock_irqrestore(&devinfo->lock, flags); ret = uas_eh_task_mgmt(cmnd, "ABORT TASK", TMF_ABORT_TASK); } else { - spin_unlock_irqrestore(&devinfo->lock, flags); - uas_unlink_data_urbs(devinfo, cmdinfo); - spin_lock_irqsave(&devinfo->lock, flags); + uas_unlink_data_urbs(devinfo, cmdinfo, &flags); uas_try_complete(cmnd, __func__); spin_unlock_irqrestore(&devinfo->lock, flags); ret = SUCCESS; -- cgit v1.2.3-70-g09d2 From a887cd366347c38607f0d9c28ca2baed40cac8fc Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 17 Oct 2013 19:47:28 +0200 Subject: uas: uas_alloc_cmd_urb: drop unused stream_id parameter The cmd endpoint never has streams, so the stream_id parameter is unused. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index dcaf6119703..5eacb805445 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -454,7 +454,7 @@ static struct urb *uas_alloc_sense_urb(struct uas_dev_info *devinfo, gfp_t gfp, } static struct urb *uas_alloc_cmd_urb(struct uas_dev_info *devinfo, gfp_t gfp, - struct scsi_cmnd *cmnd, u16 stream_id) + struct scsi_cmnd *cmnd) { struct usb_device *udev = devinfo->udev; struct scsi_device *sdev = cmnd->device; @@ -626,8 +626,7 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, } if (cmdinfo->state & ALLOC_CMD_URB) { - cmdinfo->cmd_urb = uas_alloc_cmd_urb(devinfo, gfp, cmnd, - cmdinfo->stream); + cmdinfo->cmd_urb = uas_alloc_cmd_urb(devinfo, gfp, cmnd); if (!cmdinfo->cmd_urb) return SCSI_MLQUEUE_DEVICE_BUSY; cmdinfo->state &= ~ALLOC_CMD_URB; -- cgit v1.2.3-70-g09d2 From 6c2334e9019039d7952190e239e6a8f0d10101fe Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 17 Oct 2013 22:20:54 +0200 Subject: uas: Fix uas not working when plugged into an ehci port I thought it would be a good idea to also test uas with usb-2, and it turns out it was, as it did not work. The problem is that the uas driver was passing the bEndpointAddress' direction bit to usb_rcvbulkpipe, the xhci code seems to not care about this, but with the ehci code this causes usb_submit_urb failure. With this fixed the uas code works nicely with an uas device plugged into an ehci port. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 5eacb805445..6ad5de9639d 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -948,13 +948,13 @@ static void uas_configure_endpoints(struct uas_dev_info *devinfo) eps[3] = usb_pipe_endpoint(udev, devinfo->data_out_pipe); } else { devinfo->cmd_pipe = usb_sndbulkpipe(udev, - eps[0]->desc.bEndpointAddress); + usb_endpoint_num(&eps[0]->desc)); devinfo->status_pipe = usb_rcvbulkpipe(udev, - eps[1]->desc.bEndpointAddress); + usb_endpoint_num(&eps[1]->desc)); devinfo->data_in_pipe = usb_rcvbulkpipe(udev, - eps[2]->desc.bEndpointAddress); + usb_endpoint_num(&eps[2]->desc)); devinfo->data_out_pipe = usb_sndbulkpipe(udev, - eps[3]->desc.bEndpointAddress); + usb_endpoint_num(&eps[3]->desc)); } devinfo->qdepth = usb_alloc_streams(devinfo->intf, eps + 1, 3, 256, -- cgit v1.2.3-70-g09d2 From be326f4c9bdfdff8a85145fb89b0a44c4d20ebc6 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 22 Sep 2013 16:27:02 +0200 Subject: uas: Fix reset locking Fix the uas_eh_bus_reset_handler not properly taking the usbdev lock before calling usb_device_reset, the usb-core expects this lock to be taken when usb_device_reset is called. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 6ad5de9639d..36ef82a3413 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -804,6 +804,13 @@ static int uas_eh_bus_reset_handler(struct scsi_cmnd *cmnd) struct usb_device *udev = devinfo->udev; int err; + err = usb_lock_device_for_reset(udev, devinfo->intf); + if (err) { + shost_printk(KERN_ERR, sdev->host, + "%s FAILED to get lock err %d\n", __func__, err); + return FAILED; + } + shost_printk(KERN_INFO, sdev->host, "%s start\n", __func__); devinfo->resetting = 1; uas_abort_work(devinfo); @@ -817,6 +824,8 @@ static int uas_eh_bus_reset_handler(struct scsi_cmnd *cmnd) uas_configure_endpoints(devinfo); devinfo->resetting = 0; + usb_unlock_device(udev); + if (err) { shost_printk(KERN_INFO, sdev->host, "%s FAILED\n", __func__); return FAILED; -- cgit v1.2.3-70-g09d2 From 4de7a3735bdc4219cf57a0d44f92c06d7127a211 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 22 Oct 2013 16:10:44 +0100 Subject: uas: Fix reset handling for externally triggered reset Handle usb-device resets not triggered from uas_eh_bus_reset_handler(), when this happens, disable cmd queuing during the reset, and wait for existing requests to finish in pre_reset. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 36ef82a3413..0ee5a05c0a7 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -818,10 +819,7 @@ static int uas_eh_bus_reset_handler(struct scsi_cmnd *cmnd) usb_kill_anchored_urbs(&devinfo->sense_urbs); usb_kill_anchored_urbs(&devinfo->data_urbs); uas_zap_dead(devinfo); - uas_free_streams(devinfo); err = usb_reset_device(udev); - if (!err) - uas_configure_endpoints(devinfo); devinfo->resetting = 0; usb_unlock_device(udev); @@ -1055,13 +1053,41 @@ set_alt0: static int uas_pre_reset(struct usb_interface *intf) { -/* XXX: Need to return 1 if it's not our device in error handling */ + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + unsigned long flags; + + /* Block new requests */ + spin_lock_irqsave(shost->host_lock, flags); + scsi_block_requests(shost); + spin_unlock_irqrestore(shost->host_lock, flags); + + /* Wait for any pending requests to complete */ + flush_work(&devinfo->work); + if (usb_wait_anchor_empty_timeout(&devinfo->sense_urbs, 5000) == 0) { + shost_printk(KERN_ERR, shost, "%s: timed out\n", __func__); + return 1; + } + + uas_free_streams(devinfo); + return 0; } static int uas_post_reset(struct usb_interface *intf) { -/* XXX: Need to return 1 if it's not our device in error handling */ + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + unsigned long flags; + + uas_configure_endpoints(devinfo); + + spin_lock_irqsave(shost->host_lock, flags); + scsi_report_bus_reset(shost, 0); + spin_unlock_irqrestore(shost->host_lock, flags); + + scsi_unblock_requests(shost); + return 0; } -- cgit v1.2.3-70-g09d2 From e52e031498cb51aff4f80a19a56700a127cf2a9a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 23 Oct 2013 14:27:09 +0100 Subject: uas: s/response_ui/response_iu/ Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 2 +- include/linux/usb/uas.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 0ee5a05c0a7..33f9dcd68e2 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -46,7 +46,7 @@ struct uas_dev_info { struct usb_anchor sense_urbs; struct usb_anchor data_urbs; int qdepth, resetting; - struct response_ui response; + struct response_iu response; unsigned cmd_pipe, status_pipe, data_in_pipe, data_out_pipe; unsigned use_streams:1; unsigned uas_sense_old:1; diff --git a/include/linux/usb/uas.h b/include/linux/usb/uas.h index 5499ab5c94b..14041780fb6 100644 --- a/include/linux/usb/uas.h +++ b/include/linux/usb/uas.h @@ -79,7 +79,7 @@ struct sense_iu { __u8 sense[SCSI_SENSE_BUFFERSIZE]; }; -struct response_ui { +struct response_iu { __u8 iu_id; __u8 rsvd1; __be16 tag; -- cgit v1.2.3-70-g09d2 From 00d202cc12127fe9a9fa477a78cb37e32d7f4360 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 31 Oct 2013 09:59:12 +0100 Subject: uas: Fix response iu struct definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The response iu struct before this patch has a size of 7 bytes (discounting padding), which is weird since all other iu-s are explictly padded to a multiple of 4 bytes. More over submitting a 7 byte bulk transfer to the status endpoint when expecting a response iu results in an USB babble error, as the device actually sends 8 bytes. Up on closer reading of the UAS spec: http://www.t10.org/cgi-bin/ac.pl?t=f&f=uas2r00.pdf The reason for this becomes clear, the 2 entries in "Table 17 — RESPONSE IU" are numbered 4 and 6, looking at other iu definitions in the spec, esp. multi-byte fields, this indicates that the ADDITIONAL RESPONSE INFORMATION field is not a 2 byte field as one might assume at a first look, but is a multi-byte field containing 3 bytes. This also aligns with the SCSI Architecture Model 4 spec, which UAS is based on which states in paragraph "7.1 Task management function procedure calls" that the "Additional Response Information" output argument for a Task management function procedure call is 3 bytes. Last but not least I've verified this by sending a logical unit reset task management call with an invalid lun to an actual uasp device, and received back a response-iu with byte 6 being 0, and byte 7 being 9, which is the responce code for an invalid iu, which confirms that the response code is being reported in byte 7 of the response iu rather then in byte 6. Things were working before despite this error in the response iu struct definition because the additional response info field is normally filled with zeros, and 0 is the response code value for success. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- include/linux/usb/uas.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/usb/uas.h b/include/linux/usb/uas.h index 14041780fb6..772b66bcdd7 100644 --- a/include/linux/usb/uas.h +++ b/include/linux/usb/uas.h @@ -83,7 +83,7 @@ struct response_iu { __u8 iu_id; __u8 rsvd1; __be16 tag; - __be16 add_response_info; + __u8 add_response_info[3]; __u8 response_code; }; -- cgit v1.2.3-70-g09d2 From d24354bbf78f9194ec21087130a69b84864e50df Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 21 Oct 2013 11:15:11 +0100 Subject: uas: Pack iu struct definitions The iu struct definitions are usb packet definitions, so no alignment should happen. Notice that assuming 32 bit alignment this does not make any difference at all. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- include/linux/usb/uas.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/linux/usb/uas.h b/include/linux/usb/uas.h index 772b66bcdd7..3fc8e8b9f04 100644 --- a/include/linux/usb/uas.h +++ b/include/linux/usb/uas.h @@ -9,7 +9,7 @@ struct iu { __u8 iu_id; __u8 rsvd1; __be16 tag; -}; +} __attribute__((__packed__)); enum { IU_ID_COMMAND = 0x01, @@ -52,7 +52,7 @@ struct command_iu { __u8 rsvd7; struct scsi_lun lun; __u8 cdb[16]; /* XXX: Overflow-checking tools may misunderstand */ -}; +} __attribute__((__packed__)); struct task_mgmt_iu { __u8 iu_id; @@ -62,7 +62,7 @@ struct task_mgmt_iu { __u8 rsvd2; __be16 task_tag; struct scsi_lun lun; -}; +} __attribute__((__packed__)); /* * Also used for the Read Ready and Write Ready IUs since they have the @@ -77,7 +77,7 @@ struct sense_iu { __u8 rsvd7[7]; __be16 len; __u8 sense[SCSI_SENSE_BUFFERSIZE]; -}; +} __attribute__((__packed__)); struct response_iu { __u8 iu_id; @@ -85,7 +85,7 @@ struct response_iu { __be16 tag; __u8 add_response_info[3]; __u8 response_code; -}; +} __attribute__((__packed__)); struct usb_pipe_usage_descriptor { __u8 bLength; -- cgit v1.2.3-70-g09d2 From d3f7c1560aee57d0ec293253e0c0e79a84ea3016 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 23 Oct 2013 17:46:17 +0100 Subject: uas: Use all available stream ids If we get ie 16 streams we can use stream-id 1-16, not 1-15. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 33f9dcd68e2..3f021f2fafd 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -722,7 +722,7 @@ static int uas_eh_task_mgmt(struct scsi_cmnd *cmnd, { struct Scsi_Host *shost = cmnd->device->host; struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; - u16 tag = devinfo->qdepth - 1; + u16 tag = devinfo->qdepth; unsigned long flags; spin_lock_irqsave(&devinfo->lock, flags); @@ -843,7 +843,7 @@ static int uas_slave_configure(struct scsi_device *sdev) { struct uas_dev_info *devinfo = sdev->hostdata; scsi_set_tag_type(sdev, MSG_ORDERED_TAG); - scsi_activate_tcq(sdev, devinfo->qdepth - 3); + scsi_activate_tcq(sdev, devinfo->qdepth - 2); return 0; } @@ -1027,7 +1027,7 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) INIT_LIST_HEAD(&devinfo->dead_list); uas_configure_endpoints(devinfo); - result = scsi_init_shared_tag_map(shost, devinfo->qdepth - 3); + result = scsi_init_shared_tag_map(shost, devinfo->qdepth - 2); if (result) goto free_streams; -- cgit v1.2.3-70-g09d2 From e1be067b681054e963dfd01346c0d7fc0f8a63aa Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 21 Oct 2013 08:00:58 +0100 Subject: uas: Add a uas_find_uas_alt_setting helper function This is a preparation patch for teaching usb-storage to not bind to uas devices. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 3f021f2fafd..54db36541b9 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -892,10 +892,10 @@ static int uas_isnt_supported(struct usb_device *udev) return -ENODEV; } -static int uas_switch_interface(struct usb_device *udev, - struct usb_interface *intf) +static int uas_find_uas_alt_setting(struct usb_interface *intf) { int i; + struct usb_device *udev = interface_to_usbdev(intf); int sg_supported = udev->bus->sg_tablesize != 0; for (i = 0; i < intf->num_altsetting; i++) { @@ -904,15 +904,26 @@ static int uas_switch_interface(struct usb_device *udev, if (uas_is_interface(alt)) { if (!sg_supported) return uas_isnt_supported(udev); - return usb_set_interface(udev, - alt->desc.bInterfaceNumber, - alt->desc.bAlternateSetting); + return alt->desc.bAlternateSetting; } } return -ENODEV; } +static int uas_switch_interface(struct usb_device *udev, + struct usb_interface *intf) +{ + int alt; + + alt = uas_find_uas_alt_setting(intf); + if (alt < 0) + return alt; + + return usb_set_interface(udev, + intf->altsetting[0].desc.bInterfaceNumber, alt); +} + static void uas_configure_endpoints(struct uas_dev_info *devinfo) { struct usb_host_endpoint *eps[4] = { }; -- cgit v1.2.3-70-g09d2 From 82aa0387d52dd0db2173b844ad52d114807c189b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 21 Oct 2013 08:53:31 +0100 Subject: uas: Move uas detect code to uas-detect.h This is a preparation patch for teaching usb-storage to not bind to uas devices. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas-detect.h | 40 ++++++++++++++++++++++++++++++++++++++++ drivers/usb/storage/uas.c | 40 ++-------------------------------------- 2 files changed, 42 insertions(+), 38 deletions(-) create mode 100644 drivers/usb/storage/uas-detect.h diff --git a/drivers/usb/storage/uas-detect.h b/drivers/usb/storage/uas-detect.h new file mode 100644 index 00000000000..28101c7e6a9 --- /dev/null +++ b/drivers/usb/storage/uas-detect.h @@ -0,0 +1,40 @@ +#include +#include + +static int uas_is_interface(struct usb_host_interface *intf) +{ + return (intf->desc.bInterfaceClass == USB_CLASS_MASS_STORAGE && + intf->desc.bInterfaceSubClass == USB_SC_SCSI && + intf->desc.bInterfaceProtocol == USB_PR_UAS); +} + +static int uas_isnt_supported(struct usb_device *udev) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + dev_warn(&udev->dev, "The driver for the USB controller %s does not " + "support scatter-gather which is\n", + hcd->driver->description); + dev_warn(&udev->dev, "required by the UAS driver. Please try an" + "alternative USB controller if you wish to use UAS.\n"); + return -ENODEV; +} + +static int uas_find_uas_alt_setting(struct usb_interface *intf) +{ + int i; + struct usb_device *udev = interface_to_usbdev(intf); + int sg_supported = udev->bus->sg_tablesize != 0; + + for (i = 0; i < intf->num_altsetting; i++) { + struct usb_host_interface *alt = &intf->altsetting[i]; + + if (uas_is_interface(alt)) { + if (!sg_supported) + return uas_isnt_supported(udev); + return alt->desc.bAlternateSetting; + } + } + + return -ENODEV; +} diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 54db36541b9..6ea892f32f7 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -25,6 +25,8 @@ #include #include +#include "uas-detect.h" + /* * The r00-r01c specs define this version of the SENSE IU data structure. * It's still in use by several different firmware releases. @@ -873,44 +875,6 @@ static struct usb_device_id uas_usb_ids[] = { }; MODULE_DEVICE_TABLE(usb, uas_usb_ids); -static int uas_is_interface(struct usb_host_interface *intf) -{ - return (intf->desc.bInterfaceClass == USB_CLASS_MASS_STORAGE && - intf->desc.bInterfaceSubClass == USB_SC_SCSI && - intf->desc.bInterfaceProtocol == USB_PR_UAS); -} - -static int uas_isnt_supported(struct usb_device *udev) -{ - struct usb_hcd *hcd = bus_to_hcd(udev->bus); - - dev_warn(&udev->dev, "The driver for the USB controller %s does not " - "support scatter-gather which is\n", - hcd->driver->description); - dev_warn(&udev->dev, "required by the UAS driver. Please try an" - "alternative USB controller if you wish to use UAS.\n"); - return -ENODEV; -} - -static int uas_find_uas_alt_setting(struct usb_interface *intf) -{ - int i; - struct usb_device *udev = interface_to_usbdev(intf); - int sg_supported = udev->bus->sg_tablesize != 0; - - for (i = 0; i < intf->num_altsetting; i++) { - struct usb_host_interface *alt = &intf->altsetting[i]; - - if (uas_is_interface(alt)) { - if (!sg_supported) - return uas_isnt_supported(udev); - return alt->desc.bAlternateSetting; - } - } - - return -ENODEV; -} - static int uas_switch_interface(struct usb_device *udev, struct usb_interface *intf) { -- cgit v1.2.3-70-g09d2 From 127329d76b8534fb58c207db1f172d8468b828ff Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 7 Nov 2013 08:19:45 +0100 Subject: xhci: xhci_mem_cleanup: make sure cmd_ring_reserved_trbs really is 0 cmd_ring_reserved_trbs gets decremented by xhci_free_stream_info(), so set it to 0 after freeing all rings, otherwise it wraps around to a very large value when rings with streams are free-ed. Before this patch the wrap-around could be triggered when xhci_resume calls xhci_mem_cleanup if the controller resume fails. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/host/xhci-mem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index edfb31ad594..60fc6720007 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1812,7 +1812,6 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) if (xhci->lpm_command) xhci_free_command(xhci, xhci->lpm_command); - xhci->cmd_ring_reserved_trbs = 0; if (xhci->cmd_ring) xhci_ring_free(xhci, xhci->cmd_ring); xhci->cmd_ring = NULL; @@ -1877,6 +1876,7 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) } no_bw: + xhci->cmd_ring_reserved_trbs = 0; xhci->num_usb2_ports = 0; xhci->num_usb3_ports = 0; xhci->num_active_eps = 0; -- cgit v1.2.3-70-g09d2 From 84c1e40fd794a883431fc7688f653d3faa0265f0 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 5 Nov 2013 15:50:03 +0100 Subject: xhci: The trb_address_map radix tree expects 1KB segment memory aligment If we align segment dma pool memory to 64 bytes, then a segment can be located at 0x10000040 - 0x1000043f, and a segment from another ring at 0x10000440 - 0x1000083f. The last trb in the first segment at 0x10000430 will then translate to the same radix tree key as the first trb of the second segment, while they are in different rings! This patches fixes this by changing the alignment of the dma pool to be 1KB rather then 64 bytes. An alternative fix would be to reduce the shift used to calculate the radix tree keys, but that would (slighlty) grow the radix trees so I believe this is the better fix. Note this patch is mostly theoretical since in practice I've not seen the dma_pool actually return not 1KB aligned memory. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/host/xhci-mem.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 60fc6720007..c089668308a 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -158,7 +158,7 @@ static void xhci_link_rings(struct xhci_hcd *xhci, struct xhci_ring *ring, * * The radix tree maps the upper portion of the TRB DMA address to a ring * segment that has the same upper portion of DMA addresses. For example, say I - * have segments of size 1KB, that are always 64-byte aligned. A segment may + * have segments of size 1KB, that are always 1KB aligned. A segment may * start at 0x10c91000 and end at 0x10c913f0. If I use the upper 10 bits, the * key to the stream ID is 0x43244. I can use the DMA address of the TRB to * pass the radix tree a key to get the right stream ID: @@ -2375,11 +2375,12 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) /* * Initialize the ring segment pool. The ring must be a contiguous * structure comprised of TRBs. The TRBs must be 16 byte aligned, - * however, the command ring segment needs 64-byte aligned segments, - * so we pick the greater alignment need. + * however, the command ring segment needs 64-byte aligned segments + * and our use of dma addresses in the trb_address_map radix tree needs + * TRB_SEGMENT_SIZE alignment, so we pick the greater alignment need. */ xhci->segment_pool = dma_pool_create("xHCI ring segments", dev, - TRB_SEGMENT_SIZE, 64, xhci->page_size); + TRB_SEGMENT_SIZE, TRB_SEGMENT_SIZE, xhci->page_size); /* See Table 46 and Note on Figure 55 */ xhci->device_pool = dma_pool_create("xHCI input/output contexts", dev, -- cgit v1.2.3-70-g09d2 From f7920884eb640bc642f3b4e56f5237d30a080eda Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 15 Nov 2013 12:14:38 +0100 Subject: xhci: Handle MaxPSASize == 0 Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/host/xhci.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 6cb9c2235a0..1db3c2794a4 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -3138,6 +3138,12 @@ int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, xhci_dbg(xhci, "Driver wants %u stream IDs (including stream 0).\n", num_streams); + /* MaxPSASize value 0 (2 streams) means streams are not supported */ + if (HCC_MAX_PSA(xhci->hcc_params) < 4) { + xhci_dbg(xhci, "xHCI controller does not support streams.\n"); + return -ENOSYS; + } + config_cmd = xhci_alloc_command(xhci, true, true, mem_flags); if (!config_cmd) { xhci_dbg(xhci, "Could not allocate xHCI command structure.\n"); -- cgit v1.2.3-70-g09d2 From 7a7b562d08ad6db98d6c8ec634620a11aaf8921a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 8 Nov 2013 16:37:26 +0100 Subject: usb: Clear host_endpoint->streams when implicitly freeing streams If streams are still allocated on device-reset or set-interface then the hcd code implictly frees the streams. Clear host_endpoint->streams in this case so that if a driver later tries to re-allocate them it won't run afoul of the device already having streams check in usb_alloc_streams(). Note normally streams still being allocated at reset / set-intf would be a driver bug, but this can happen without it being a driver bug on reset-resume. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/core/hub.c | 5 ++++- drivers/usb/core/message.c | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 763c3134dd7..4f7629d1ba6 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -5131,7 +5131,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev) struct usb_hcd *hcd = bus_to_hcd(udev->bus); struct usb_device_descriptor descriptor = udev->descriptor; struct usb_host_bos *bos; - int i, ret = 0; + int i, j, ret = 0; int port1 = udev->portnum; if (udev->state == USB_STATE_NOTATTACHED || @@ -5257,6 +5257,9 @@ static int usb_reset_and_verify_device(struct usb_device *udev) ret); goto re_enumerate; } + /* Resetting also frees any allocated streams */ + for (j = 0; j < intf->cur_altsetting->desc.bNumEndpoints; j++) + intf->cur_altsetting->endpoint[j].streams = 0; } done: diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index f829a1aad1c..96469574103 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1293,8 +1293,7 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate) struct usb_interface *iface; struct usb_host_interface *alt; struct usb_hcd *hcd = bus_to_hcd(dev->bus); - int ret; - int manual = 0; + int i, ret, manual = 0; unsigned int epaddr; unsigned int pipe; @@ -1329,6 +1328,10 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate) mutex_unlock(hcd->bandwidth_mutex); return -ENOMEM; } + /* Changing alt-setting also frees any allocated streams */ + for (i = 0; i < iface->cur_altsetting->desc.bNumEndpoints; i++) + iface->cur_altsetting->endpoint[i].streams = 0; + ret = usb_hcd_alloc_bandwidth(dev, NULL, iface->cur_altsetting, alt); if (ret < 0) { dev_info(&dev->dev, "Not enough bandwidth for altsetting %d\n", -- cgit v1.2.3-70-g09d2 From a82b76f7fa6154e8ab2d8071842a3e38b9c0d0ff Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 8 Nov 2013 13:41:05 +0100 Subject: usb: Reset USB-3 devices on USB-3 link bounce On disconnect USB3 protocol ports transit from U0 to SS.Inactive to Rx.Detect, on a recoverable error, the port stays in SS.Inactive and we recover from it by doing a warm-reset (through usb_device_reset if we have a udev for the port). If this really is a disconnect we may end up trying the warm-reset anyways, since khubd may run before the SS.Inactive to Rx.Detect transition, or it may get skipped if the transition to Rx.Detect happens before khubd gets run. With a loose connector, or in the case which actually led me to debugging this bad ACPI firmware toggling Vbus off and on in quick succession, the port may transition from Rx.Detect to U0 again before khubd gets run. In this case the device state is unknown really, but khubd happily goes into the resuscitate an existing device path, and the device driver never gets notified about the device state being messed up. If the above scenario happens with a streams using device, as soon as an urb is submitted to an endpoint with streams, the following appears in dmesg: ERROR Transfer event for disabled endpoint or incorrect stream ring @0000000036807420 00000000 00000000 04000000 04078000 Notice how the TRB address is all zeros. I've seen this both on Intel Pantherpoint and Nec xhci hosts. Luckily we can detect the U0 to SS.Inactive to Rx.Detect to U0 all having happened before khubd runs case since the C_LINK_STATE bit gets set in the portchange bits on the U0 -> SS.Inactive change. This bit will also be set on suspend / resume, but then it gets cleared by port_hub_init before khubd runs. So if the C_LINK_STATE bit is set and a warm-reset is not needed, iow the port is not still in SS.Inactive, and the port still has a connection, then the device needs to be reset to put it back in a known state. I've verified that doing the device reset also fixes the transfer event with all zeros address issue. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/core/hub.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 4f7629d1ba6..5da5394127e 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4758,6 +4758,8 @@ static void hub_events(void) /* deal with port status changes */ for (i = 1; i <= hdev->maxchild; i++) { + struct usb_device *udev = hub->ports[i - 1]->child; + if (test_bit(i, hub->busy_bits)) continue; connect_change = test_bit(i, hub->change_bits); @@ -4856,8 +4858,6 @@ static void hub_events(void) */ if (hub_port_warm_reset_required(hub, portstatus)) { int status; - struct usb_device *udev = - hub->ports[i - 1]->child; dev_dbg(hub_dev, "warm reset port %d\n", i); if (!udev || @@ -4874,6 +4874,24 @@ static void hub_events(void) usb_unlock_device(udev); connect_change = 0; } + /* + * On disconnect USB3 protocol ports transit from U0 to + * SS.Inactive to Rx.Detect. If this happens a warm- + * reset is not needed, but a (re)connect may happen + * before khubd runs and sees the disconnect, and the + * device may be an unknown state. + * + * If the port went through SS.Inactive without khubd + * seeing it the C_LINK_STATE change flag will be set, + * and we reset the dev to put it in a known state. + */ + } else if (udev && hub_is_superspeed(hub->hdev) && + (portchange & USB_PORT_STAT_C_LINK_STATE) && + (portstatus & USB_PORT_STAT_CONNECTION)) { + usb_lock_device(udev); + usb_reset_device(udev); + usb_unlock_device(udev); + connect_change = 0; } if (connect_change) -- cgit v1.2.3-70-g09d2 From 79b4c06112f12c31d03cf22b1ed5ce09423fd887 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 25 Oct 2013 17:04:33 +0100 Subject: uas: Add the posibilty to blacklist uas devices from using the uas driver Once we start supporting uas hardware, and as more and more uas devices become available, we will likely start seeing broken devices. This patch prepares for the inevitable need for blacklisting those devices from using the uas driver (they will use usb-storage instead). Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas-detect.h | 11 +++++++++ drivers/usb/storage/uas.c | 17 ++++++++++--- drivers/usb/storage/unusual_uas.h | 52 +++++++++++++++++++++++++++++++++++++++ include/linux/usb_usual.h | 6 +++-- 4 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 drivers/usb/storage/unusual_uas.h diff --git a/drivers/usb/storage/uas-detect.h b/drivers/usb/storage/uas-detect.h index 28101c7e6a9..02bf5ec957f 100644 --- a/drivers/usb/storage/uas-detect.h +++ b/drivers/usb/storage/uas-detect.h @@ -38,3 +38,14 @@ static int uas_find_uas_alt_setting(struct usb_interface *intf) return -ENODEV; } + +static int uas_use_uas_driver(struct usb_interface *intf, + const struct usb_device_id *id) +{ + unsigned long flags = id->driver_info; + + if (flags & US_FL_IGNORE_UAS) + return 0; + + return uas_find_uas_alt_setting(intf) >= 0; +} diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 6ea892f32f7..e817e72d867 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -866,7 +867,14 @@ static struct scsi_host_template uas_host_template = { .ordered_tag = 1, }; +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ + .driver_info = (flags) } + static struct usb_device_id uas_usb_ids[] = { +# include "unusual_uas.h" { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_SCSI, USB_PR_BULK) }, { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_SCSI, USB_PR_UAS) }, /* 0xaa is a prototype device I happen to have access to */ @@ -875,6 +883,8 @@ static struct usb_device_id uas_usb_ids[] = { }; MODULE_DEVICE_TABLE(usb, uas_usb_ids); +#undef UNUSUAL_DEV + static int uas_switch_interface(struct usb_device *udev, struct usb_interface *intf) { @@ -973,6 +983,9 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) struct uas_dev_info *devinfo; struct usb_device *udev = interface_to_usbdev(intf); + if (!uas_use_uas_driver(intf, id)) + return -ENODEV; + if (uas_switch_interface(udev, intf)) return -ENODEV; @@ -1083,10 +1096,6 @@ static void uas_disconnect(struct usb_interface *intf) kfree(devinfo); } -/* - * XXX: Should this plug into libusual so we can auto-upgrade devices from - * Bulk-Only to UAS? - */ static struct usb_driver uas_driver = { .name = "uas", .probe = uas_probe, diff --git a/drivers/usb/storage/unusual_uas.h b/drivers/usb/storage/unusual_uas.h new file mode 100644 index 00000000000..7244444df8e --- /dev/null +++ b/drivers/usb/storage/unusual_uas.h @@ -0,0 +1,52 @@ +/* Driver for USB Attached SCSI devices - Unusual Devices File + * + * (c) 2013 Hans de Goede + * + * Based on the same file for the usb-storage driver, which is: + * (c) 2000-2002 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * (c) 2000 Adam J. Richter (adam@yggdrasil.com), Yggdrasil Computing, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * IMPORTANT NOTE: This file must be included in another file which defines + * a UNUSUAL_DEV macro before this file is included. + */ + +/* + * If you edit this file, please try to keep it sorted first by VendorID, + * then by ProductID. + * + * If you want to add an entry for this file, be sure to include the + * following information: + * - a patch that adds the entry for your device, including your + * email address right above the entry (plus maybe a brief + * explanation of the reason for the entry), + * - lsusb -v output for the device + * Send your submission to Hans de Goede + * and don't forget to CC: the USB development list + */ + +/* + * This is an example entry for the US_FL_IGNORE_UAS flag. Once we have an + * actual entry using US_FL_IGNORE_UAS this entry should be removed. + * + * UNUSUAL_DEV( 0xabcd, 0x1234, 0x0100, 0x0100, + * "Example", + * "Storage with broken UAS", + * USB_SC_DEVICE, USB_PR_DEVICE, NULL, + * US_FL_IGNORE_UAS), + */ diff --git a/include/linux/usb_usual.h b/include/linux/usb_usual.h index 63035686603..1a64b26046e 100644 --- a/include/linux/usb_usual.h +++ b/include/linux/usb_usual.h @@ -67,8 +67,10 @@ /* Initial READ(10) (and others) must be retried */ \ US_FLAG(WRITE_CACHE, 0x00200000) \ /* Write Cache status is not available */ \ - US_FLAG(NEEDS_CAP16, 0x00400000) - /* cannot handle READ_CAPACITY_10 */ + US_FLAG(NEEDS_CAP16, 0x00400000) \ + /* cannot handle READ_CAPACITY_10 */ \ + US_FLAG(IGNORE_UAS, 0x00800000) \ + /* Device advertises UAS but it is broken */ #define US_FLAG(name, value) US_FL_##name = value , enum { US_DO_ALL_FLAGS }; -- cgit v1.2.3-70-g09d2 From 5bfd5b5d8b9cd9e8ba1709f2b9dc35bd4b26c8b1 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 21 Oct 2013 09:40:48 +0100 Subject: usb-storage: Don't bind to uas devices if the uas driver is enabled uas devices have 2 alternative settings on their usb-storage interface, one for usb-storage and one for uas. Using the uas driver is preferred, so if the uas driver is enabled, and the device has an uas alt setting, don't bind. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/unusual_devs.h | 5 +++++ drivers/usb/storage/usb.c | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index adbeb255616..f4a82291894 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -2086,6 +2086,11 @@ UNUSUAL_DEV( 0xed10, 0x7636, 0x0001, 0x0001, "Digital MP3 Audio Player", USB_SC_DEVICE, USB_PR_DEVICE, NULL, US_FL_NOT_LOCKABLE ), +/* Unusual uas devices */ +#if IS_ENABLED(CONFIG_USB_UAS) +#include "unusual_uas.h" +#endif + /* Control/Bulk transport for all SubClass values */ USUAL_DEV(USB_SC_RBC, USB_PR_CB), USUAL_DEV(USB_SC_8020, USB_PR_CB), diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c index 1c0b89f2a13..388f567524d 100644 --- a/drivers/usb/storage/usb.c +++ b/drivers/usb/storage/usb.c @@ -72,6 +72,10 @@ #include "sierra_ms.h" #include "option_ms.h" +#if IS_ENABLED(CONFIG_USB_UAS) +#include "uas-detect.h" +#endif + /* Some informational data */ MODULE_AUTHOR("Matthew Dharm "); MODULE_DESCRIPTION("USB Mass Storage driver for Linux"); @@ -1035,6 +1039,12 @@ static int storage_probe(struct usb_interface *intf, int result; int size; + /* If uas is enabled and this device can do uas then ignore it. */ +#if IS_ENABLED(CONFIG_USB_UAS) + if (uas_use_uas_driver(intf, id)) + return -ENXIO; +#endif + /* * If the device isn't standard (is handled by a subdriver * module) then don't accept it. -- cgit v1.2.3-70-g09d2 From d24d481b7d369b08cce734bf80be374eed5a6e58 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 16 Nov 2013 12:09:39 +0100 Subject: usb-storage: Modify and export adjust_quirks so that it can be used by uas Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/usb.c | 16 ++++++++++------ drivers/usb/storage/usb.h | 3 +++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c index 388f567524d..f1c96261a50 100644 --- a/drivers/usb/storage/usb.c +++ b/drivers/usb/storage/usb.c @@ -463,14 +463,14 @@ static int associate_dev(struct us_data *us, struct usb_interface *intf) #define TOLOWER(x) ((x) | 0x20) /* Adjust device flags based on the "quirks=" module parameter */ -static void adjust_quirks(struct us_data *us) +void usb_stor_adjust_quirks(struct usb_device *udev, unsigned long *fflags) { char *p; - u16 vid = le16_to_cpu(us->pusb_dev->descriptor.idVendor); - u16 pid = le16_to_cpu(us->pusb_dev->descriptor.idProduct); + u16 vid = le16_to_cpu(udev->descriptor.idVendor); + u16 pid = le16_to_cpu(udev->descriptor.idProduct); unsigned f = 0; unsigned int mask = (US_FL_SANE_SENSE | US_FL_BAD_SENSE | - US_FL_FIX_CAPACITY | + US_FL_FIX_CAPACITY | US_FL_IGNORE_UAS | US_FL_CAPACITY_HEURISTICS | US_FL_IGNORE_DEVICE | US_FL_NOT_LOCKABLE | US_FL_MAX_SECTORS_64 | US_FL_CAPACITY_OK | US_FL_IGNORE_RESIDUE | @@ -541,14 +541,18 @@ static void adjust_quirks(struct us_data *us) case 's': f |= US_FL_SINGLE_LUN; break; + case 'u': + f |= US_FL_IGNORE_UAS; + break; case 'w': f |= US_FL_NO_WP_DETECT; break; /* Ignore unrecognized flag characters */ } } - us->fflags = (us->fflags & ~mask) | f; + *fflags = (*fflags & ~mask) | f; } +EXPORT_SYMBOL_GPL(usb_stor_adjust_quirks); /* Get the unusual_devs entries and the string descriptors */ static int get_device_info(struct us_data *us, const struct usb_device_id *id, @@ -568,7 +572,7 @@ static int get_device_info(struct us_data *us, const struct usb_device_id *id, idesc->bInterfaceProtocol : unusual_dev->useTransport; us->fflags = id->driver_info; - adjust_quirks(us); + usb_stor_adjust_quirks(us->pusb_dev, &us->fflags); if (us->fflags & US_FL_IGNORE_DEVICE) { dev_info(pdev, "device ignored\n"); diff --git a/drivers/usb/storage/usb.h b/drivers/usb/storage/usb.h index 75f70f04f37..307e339a947 100644 --- a/drivers/usb/storage/usb.h +++ b/drivers/usb/storage/usb.h @@ -201,4 +201,7 @@ extern int usb_stor_probe1(struct us_data **pus, extern int usb_stor_probe2(struct us_data *us); extern void usb_stor_disconnect(struct usb_interface *intf); +extern void usb_stor_adjust_quirks(struct usb_device *dev, + unsigned long *fflags); + #endif -- cgit v1.2.3-70-g09d2 From 97172a660cfc744996112eb625a77282a4b627b7 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 16 Nov 2013 12:19:36 +0100 Subject: uas: Honor no-uas quirk set in usb-storage's quirks module parameter Falling back from uas to usb-storage requires coordination between uas and usb-storage, so use usb-storage's quirks module parameter, rather then requiring the user to pass a param to 2 different modules. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/Kconfig | 2 +- drivers/usb/storage/uas-detect.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/usb/storage/Kconfig b/drivers/usb/storage/Kconfig index 1dd0604d191..666dcb692e1 100644 --- a/drivers/usb/storage/Kconfig +++ b/drivers/usb/storage/Kconfig @@ -204,7 +204,7 @@ config USB_STORAGE_ENE_UB6250 config USB_UAS tristate "USB Attached SCSI" - depends on SCSI && BROKEN + depends on SCSI && USB_STORAGE && BROKEN help The USB Attached SCSI protocol is supported by some USB storage devices. It permits higher performance by supporting diff --git a/drivers/usb/storage/uas-detect.h b/drivers/usb/storage/uas-detect.h index 02bf5ec957f..082bde1fa74 100644 --- a/drivers/usb/storage/uas-detect.h +++ b/drivers/usb/storage/uas-detect.h @@ -1,5 +1,6 @@ #include #include +#include "usb.h" static int uas_is_interface(struct usb_host_interface *intf) { @@ -42,8 +43,11 @@ static int uas_find_uas_alt_setting(struct usb_interface *intf) static int uas_use_uas_driver(struct usb_interface *intf, const struct usb_device_id *id) { + struct usb_device *udev = interface_to_usbdev(intf); unsigned long flags = id->driver_info; + usb_stor_adjust_quirks(udev, &flags); + if (flags & US_FL_IGNORE_UAS) return 0; -- cgit v1.2.3-70-g09d2 From 34f11e59c3d5d025ad9162d8fd126e0b7ca54f40 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 29 Oct 2013 08:54:48 +0100 Subject: uas: Add uas_find_endpoints() helper function This is a preparation patch for adding better descriptor validation. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index e817e72d867..1ac66f290fb 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -898,16 +898,11 @@ static int uas_switch_interface(struct usb_device *udev, intf->altsetting[0].desc.bInterfaceNumber, alt); } -static void uas_configure_endpoints(struct uas_dev_info *devinfo) +static int uas_find_endpoints(struct usb_host_interface *alt, + struct usb_host_endpoint *eps[]) { - struct usb_host_endpoint *eps[4] = { }; - struct usb_interface *intf = devinfo->intf; - struct usb_device *udev = devinfo->udev; - struct usb_host_endpoint *endpoint = intf->cur_altsetting->endpoint; - unsigned i, n_endpoints = intf->cur_altsetting->desc.bNumEndpoints; - - devinfo->uas_sense_old = 0; - devinfo->cmnd = NULL; + struct usb_host_endpoint *endpoint = alt->endpoint; + unsigned i, n_endpoints = alt->desc.bNumEndpoints; for (i = 0; i < n_endpoints; i++) { unsigned char *extra = endpoint[i].extra; @@ -924,12 +919,29 @@ static void uas_configure_endpoints(struct uas_dev_info *devinfo) } } + if (!eps[0] || !eps[1] || !eps[2] || !eps[3]) + return -ENODEV; + + return 0; +} + +static void uas_configure_endpoints(struct uas_dev_info *devinfo) +{ + struct usb_host_endpoint *eps[4] = { }; + struct usb_device *udev = devinfo->udev; + int r; + + devinfo->uas_sense_old = 0; + devinfo->cmnd = NULL; + + r = uas_find_endpoints(devinfo->intf->cur_altsetting, eps); + /* - * Assume that if we didn't find a control pipe descriptor, we're + * Assume that if we didn't find a proper set of descriptors, we're * using a device with old firmware that happens to be set up like * this. */ - if (!eps[0]) { + if (r != 0) { devinfo->cmd_pipe = usb_sndbulkpipe(udev, 1); devinfo->status_pipe = usb_rcvbulkpipe(udev, 1); devinfo->data_in_pipe = usb_rcvbulkpipe(udev, 2); -- cgit v1.2.3-70-g09d2 From d495c1baa1b3ba277bb5ae24adeab0600151cba4 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 29 Oct 2013 09:06:54 +0100 Subject: uas: Fix bounds check in uas_find_endpoints The loop uses up to 3 bytes of the endpoint extra data. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 1ac66f290fb..7662b3e13c4 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -907,7 +907,7 @@ static int uas_find_endpoints(struct usb_host_interface *alt, for (i = 0; i < n_endpoints; i++) { unsigned char *extra = endpoint[i].extra; int len = endpoint[i].extralen; - while (len > 1) { + while (len >= 3) { if (extra[1] == USB_DT_PIPE_USAGE) { unsigned pipe_id = extra[2]; if (pipe_id > 0 && pipe_id < 5) -- cgit v1.2.3-70-g09d2 From d77adc0284beea5783c52b2af49217a37c2114cd Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 29 Oct 2013 10:03:34 +0100 Subject: uas: Move uas_find_endpoints to uas-detect.h No changes, just the move. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas-detect.h | 27 +++++++++++++++++++++++++++ drivers/usb/storage/uas.c | 27 --------------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/drivers/usb/storage/uas-detect.h b/drivers/usb/storage/uas-detect.h index 082bde1fa74..8de030a0a4a 100644 --- a/drivers/usb/storage/uas-detect.h +++ b/drivers/usb/storage/uas-detect.h @@ -40,6 +40,33 @@ static int uas_find_uas_alt_setting(struct usb_interface *intf) return -ENODEV; } +static int uas_find_endpoints(struct usb_host_interface *alt, + struct usb_host_endpoint *eps[]) +{ + struct usb_host_endpoint *endpoint = alt->endpoint; + unsigned i, n_endpoints = alt->desc.bNumEndpoints; + + for (i = 0; i < n_endpoints; i++) { + unsigned char *extra = endpoint[i].extra; + int len = endpoint[i].extralen; + while (len >= 3) { + if (extra[1] == USB_DT_PIPE_USAGE) { + unsigned pipe_id = extra[2]; + if (pipe_id > 0 && pipe_id < 5) + eps[pipe_id - 1] = &endpoint[i]; + break; + } + len -= extra[0]; + extra += extra[0]; + } + } + + if (!eps[0] || !eps[1] || !eps[2] || !eps[3]) + return -ENODEV; + + return 0; +} + static int uas_use_uas_driver(struct usb_interface *intf, const struct usb_device_id *id) { diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 7662b3e13c4..5cedc7f034b 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -898,33 +898,6 @@ static int uas_switch_interface(struct usb_device *udev, intf->altsetting[0].desc.bInterfaceNumber, alt); } -static int uas_find_endpoints(struct usb_host_interface *alt, - struct usb_host_endpoint *eps[]) -{ - struct usb_host_endpoint *endpoint = alt->endpoint; - unsigned i, n_endpoints = alt->desc.bNumEndpoints; - - for (i = 0; i < n_endpoints; i++) { - unsigned char *extra = endpoint[i].extra; - int len = endpoint[i].extralen; - while (len >= 3) { - if (extra[1] == USB_DT_PIPE_USAGE) { - unsigned pipe_id = extra[2]; - if (pipe_id > 0 && pipe_id < 5) - eps[pipe_id - 1] = &endpoint[i]; - break; - } - len -= extra[0]; - extra += extra[0]; - } - } - - if (!eps[0] || !eps[1] || !eps[2] || !eps[3]) - return -ENODEV; - - return 0; -} - static void uas_configure_endpoints(struct uas_dev_info *devinfo) { struct usb_host_endpoint *eps[4] = { }; -- cgit v1.2.3-70-g09d2 From 74d71aec619f33ec1ff5b2090792ab96d840bd3b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 29 Oct 2013 10:10:36 +0100 Subject: uas: Drop fixed endpoint config handling The fixed endpoint config code was only necessary to deal with an early uas prototype which has never been released, so lets drop it and enforce proper uas endpoint descriptors. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 5cedc7f034b..754468bbbfd 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -908,31 +908,17 @@ static void uas_configure_endpoints(struct uas_dev_info *devinfo) devinfo->cmnd = NULL; r = uas_find_endpoints(devinfo->intf->cur_altsetting, eps); - - /* - * Assume that if we didn't find a proper set of descriptors, we're - * using a device with old firmware that happens to be set up like - * this. - */ - if (r != 0) { - devinfo->cmd_pipe = usb_sndbulkpipe(udev, 1); - devinfo->status_pipe = usb_rcvbulkpipe(udev, 1); - devinfo->data_in_pipe = usb_rcvbulkpipe(udev, 2); - devinfo->data_out_pipe = usb_sndbulkpipe(udev, 2); - - eps[1] = usb_pipe_endpoint(udev, devinfo->status_pipe); - eps[2] = usb_pipe_endpoint(udev, devinfo->data_in_pipe); - eps[3] = usb_pipe_endpoint(udev, devinfo->data_out_pipe); - } else { - devinfo->cmd_pipe = usb_sndbulkpipe(udev, - usb_endpoint_num(&eps[0]->desc)); - devinfo->status_pipe = usb_rcvbulkpipe(udev, - usb_endpoint_num(&eps[1]->desc)); - devinfo->data_in_pipe = usb_rcvbulkpipe(udev, - usb_endpoint_num(&eps[2]->desc)); - devinfo->data_out_pipe = usb_sndbulkpipe(udev, - usb_endpoint_num(&eps[3]->desc)); - } + if (r) + return r; + + devinfo->cmd_pipe = usb_sndbulkpipe(udev, + usb_endpoint_num(&eps[0]->desc)); + devinfo->status_pipe = usb_rcvbulkpipe(udev, + usb_endpoint_num(&eps[1]->desc)); + devinfo->data_in_pipe = usb_rcvbulkpipe(udev, + usb_endpoint_num(&eps[2]->desc)); + devinfo->data_out_pipe = usb_sndbulkpipe(udev, + usb_endpoint_num(&eps[3]->desc)); devinfo->qdepth = usb_alloc_streams(devinfo->intf, eps + 1, 3, 256, GFP_KERNEL); -- cgit v1.2.3-70-g09d2 From 6134041bef0aeb9cb7c8a8daf045b44513cd8396 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 16 Nov 2013 11:37:41 +0100 Subject: uas: Verify endpoint descriptors from uas_use_uas_driver() Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas-detect.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/usb/storage/uas-detect.h b/drivers/usb/storage/uas-detect.h index 8de030a0a4a..b8a02e12a8a 100644 --- a/drivers/usb/storage/uas-detect.h +++ b/drivers/usb/storage/uas-detect.h @@ -70,13 +70,23 @@ static int uas_find_endpoints(struct usb_host_interface *alt, static int uas_use_uas_driver(struct usb_interface *intf, const struct usb_device_id *id) { + struct usb_host_endpoint *eps[4] = { }; struct usb_device *udev = interface_to_usbdev(intf); unsigned long flags = id->driver_info; + int r, alt; usb_stor_adjust_quirks(udev, &flags); if (flags & US_FL_IGNORE_UAS) return 0; - return uas_find_uas_alt_setting(intf) >= 0; + alt = uas_find_uas_alt_setting(intf); + if (alt < 0) + return 0; + + r = uas_find_endpoints(&intf->altsetting[alt], eps); + if (r < 0) + return 0; + + return 1; } -- cgit v1.2.3-70-g09d2 From 58d51444cdd066239e9b660d72319d941c758fc3 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 29 Oct 2013 10:23:26 +0100 Subject: uas: Not being able to alloc streams when connected through usb-3 is an error Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 754468bbbfd..d758faef866 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -93,7 +93,6 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, struct uas_dev_info *devinfo, gfp_t gfp); static void uas_do_work(struct work_struct *work); static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller); -static void uas_configure_endpoints(struct uas_dev_info *devinfo); static void uas_free_streams(struct uas_dev_info *devinfo); static void uas_log_cmd_state(struct scsi_cmnd *cmnd, const char *caller); @@ -898,7 +897,7 @@ static int uas_switch_interface(struct usb_device *udev, intf->altsetting[0].desc.bInterfaceNumber, alt); } -static void uas_configure_endpoints(struct uas_dev_info *devinfo) +static int uas_configure_endpoints(struct uas_dev_info *devinfo) { struct usb_host_endpoint *eps[4] = { }; struct usb_device *udev = devinfo->udev; @@ -920,14 +919,18 @@ static void uas_configure_endpoints(struct uas_dev_info *devinfo) devinfo->data_out_pipe = usb_sndbulkpipe(udev, usb_endpoint_num(&eps[3]->desc)); - devinfo->qdepth = usb_alloc_streams(devinfo->intf, eps + 1, 3, 256, - GFP_KERNEL); - if (devinfo->qdepth < 0) { + if (udev->speed != USB_SPEED_SUPER) { devinfo->qdepth = 256; devinfo->use_streams = 0; } else { + devinfo->qdepth = usb_alloc_streams(devinfo->intf, eps + 1, + 3, 256, GFP_KERNEL); + if (devinfo->qdepth < 0) + return devinfo->qdepth; devinfo->use_streams = 1; } + + return 0; } static void uas_free_streams(struct uas_dev_info *devinfo) @@ -984,7 +987,10 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) INIT_WORK(&devinfo->work, uas_do_work); INIT_LIST_HEAD(&devinfo->work_list); INIT_LIST_HEAD(&devinfo->dead_list); - uas_configure_endpoints(devinfo); + + result = uas_configure_endpoints(devinfo); + if (result) + goto set_alt0; result = scsi_init_shared_tag_map(shost, devinfo->qdepth - 2); if (result) @@ -1039,7 +1045,11 @@ static int uas_post_reset(struct usb_interface *intf) struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; unsigned long flags; - uas_configure_endpoints(devinfo); + if (uas_configure_endpoints(devinfo) != 0) { + shost_printk(KERN_ERR, shost, + "%s: alloc streams error after reset", __func__); + return 1; + } spin_lock_irqsave(shost->host_lock, flags); scsi_report_bus_reset(shost, 0); -- cgit v1.2.3-70-g09d2 From 70cf0fba7625987ef16085f458e3869c6e3043c1 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 29 Oct 2013 10:37:23 +0100 Subject: uas: task_mgmt: Kill the sense-urb if we fail to submit the cmd urb Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index d758faef866..9c6f9f9804f 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -550,39 +550,38 @@ err: * daft to me. */ -static int uas_submit_sense_urb(struct Scsi_Host *shost, - gfp_t gfp, unsigned int stream) +static struct urb *uas_submit_sense_urb(struct Scsi_Host *shost, + gfp_t gfp, unsigned int stream) { struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; struct urb *urb; urb = uas_alloc_sense_urb(devinfo, gfp, shost, stream); if (!urb) - return SCSI_MLQUEUE_DEVICE_BUSY; + return NULL; usb_anchor_urb(urb, &devinfo->sense_urbs); if (usb_submit_urb(urb, gfp)) { usb_unanchor_urb(urb); shost_printk(KERN_INFO, shost, "sense urb submission failure\n"); usb_free_urb(urb); - return SCSI_MLQUEUE_DEVICE_BUSY; + return NULL; } - return 0; + return urb; } static int uas_submit_urbs(struct scsi_cmnd *cmnd, struct uas_dev_info *devinfo, gfp_t gfp) { struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; - int err; + struct urb *urb; WARN_ON_ONCE(!spin_is_locked(&devinfo->lock)); if (cmdinfo->state & SUBMIT_STATUS_URB) { - err = uas_submit_sense_urb(cmnd->device->host, gfp, + urb = uas_submit_sense_urb(cmnd->device->host, gfp, cmdinfo->stream); - if (err) { - return err; - } + if (!urb) + return SCSI_MLQUEUE_DEVICE_BUSY; cmdinfo->state &= ~SUBMIT_STATUS_URB; } @@ -726,10 +725,12 @@ static int uas_eh_task_mgmt(struct scsi_cmnd *cmnd, struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; u16 tag = devinfo->qdepth; unsigned long flags; + struct urb *sense_urb; spin_lock_irqsave(&devinfo->lock, flags); memset(&devinfo->response, 0, sizeof(devinfo->response)); - if (uas_submit_sense_urb(shost, GFP_ATOMIC, tag)) { + sense_urb = uas_submit_sense_urb(shost, GFP_ATOMIC, tag); + if (!sense_urb) { shost_printk(KERN_INFO, shost, "%s: %s: submit sense urb failed\n", __func__, fname); @@ -741,6 +742,7 @@ static int uas_eh_task_mgmt(struct scsi_cmnd *cmnd, "%s: %s: submit task mgmt urb failed\n", __func__, fname); spin_unlock_irqrestore(&devinfo->lock, flags); + usb_kill_urb(sense_urb); return FAILED; } spin_unlock_irqrestore(&devinfo->lock, flags); -- cgit v1.2.3-70-g09d2 From b83b86a352280cc8cbbf3760096c703986143b81 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 29 Oct 2013 10:51:00 +0100 Subject: uas: Don't allow more then one task to run at the same time Since we use a fixed tag / stream for tasks we cannot allow more then one to run at the same time. This could happen before this time if a task timed out. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 9c6f9f9804f..7fc4ad20775 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -53,6 +53,7 @@ struct uas_dev_info { unsigned cmd_pipe, status_pipe, data_in_pipe, data_out_pipe; unsigned use_streams:1; unsigned uas_sense_old:1; + unsigned running_task:1; struct scsi_cmnd *cmnd; spinlock_t lock; struct work_struct work; @@ -195,6 +196,7 @@ static void uas_zap_dead(struct uas_dev_info *devinfo) DATA_OUT_URB_INFLIGHT); uas_try_complete(cmnd, __func__); } + devinfo->running_task = 0; spin_unlock_irqrestore(&devinfo->lock, flags); } @@ -340,6 +342,9 @@ static void uas_stat_cmplt(struct urb *urb) if (!cmnd) { if (iu->iu_id == IU_ID_RESPONSE) { + if (!devinfo->running_task) + dev_warn(&urb->dev->dev, + "stat urb: recv unexpected response iu\n"); /* store results for uas_eh_task_mgmt() */ memcpy(&devinfo->response, iu, sizeof(devinfo->response)); } @@ -726,14 +731,26 @@ static int uas_eh_task_mgmt(struct scsi_cmnd *cmnd, u16 tag = devinfo->qdepth; unsigned long flags; struct urb *sense_urb; + int result = SUCCESS; spin_lock_irqsave(&devinfo->lock, flags); + + if (devinfo->running_task) { + shost_printk(KERN_INFO, shost, + "%s: %s: error already running a task\n", + __func__, fname); + spin_unlock_irqrestore(&devinfo->lock, flags); + return FAILED; + } + + devinfo->running_task = 1; memset(&devinfo->response, 0, sizeof(devinfo->response)); sense_urb = uas_submit_sense_urb(shost, GFP_ATOMIC, tag); if (!sense_urb) { shost_printk(KERN_INFO, shost, "%s: %s: submit sense urb failed\n", __func__, fname); + devinfo->running_task = 0; spin_unlock_irqrestore(&devinfo->lock, flags); return FAILED; } @@ -741,6 +758,7 @@ static int uas_eh_task_mgmt(struct scsi_cmnd *cmnd, shost_printk(KERN_INFO, shost, "%s: %s: submit task mgmt urb failed\n", __func__, fname); + devinfo->running_task = 0; spin_unlock_irqrestore(&devinfo->lock, flags); usb_kill_urb(sense_urb); return FAILED; @@ -748,23 +766,33 @@ static int uas_eh_task_mgmt(struct scsi_cmnd *cmnd, spin_unlock_irqrestore(&devinfo->lock, flags); if (usb_wait_anchor_empty_timeout(&devinfo->sense_urbs, 3000) == 0) { + /* + * Note we deliberately do not clear running_task here. If we + * allow new tasks to be submitted, there is no way to figure + * out if a received response_iu is for the failed task or for + * the new one. A bus-reset will eventually clear running_task. + */ shost_printk(KERN_INFO, shost, "%s: %s timed out\n", __func__, fname); return FAILED; } + + spin_lock_irqsave(&devinfo->lock, flags); + devinfo->running_task = 0; if (be16_to_cpu(devinfo->response.tag) != tag) { shost_printk(KERN_INFO, shost, "%s: %s failed (wrong tag %d/%d)\n", __func__, fname, be16_to_cpu(devinfo->response.tag), tag); - return FAILED; - } - if (devinfo->response.response_code != RC_TMF_COMPLETE) { + result = FAILED; + } else if (devinfo->response.response_code != RC_TMF_COMPLETE) { shost_printk(KERN_INFO, shost, "%s: %s failed (rc 0x%x)\n", __func__, fname, devinfo->response.response_code); - return FAILED; + result = FAILED; } - return SUCCESS; + spin_unlock_irqrestore(&devinfo->lock, flags); + + return result; } static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) @@ -982,6 +1010,7 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) devinfo->intf = intf; devinfo->udev = udev; devinfo->resetting = 0; + devinfo->running_task = 0; init_usb_anchor(&devinfo->cmd_urbs); init_usb_anchor(&devinfo->sense_urbs); init_usb_anchor(&devinfo->data_urbs); -- cgit v1.2.3-70-g09d2 From e36e64930cffd94e1c37fdb82f35989384aa946b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 7 Nov 2013 08:35:55 +0100 Subject: uas: Use GFP_NOIO rather then GFP_ATOMIC where possible We can sleep in our own workqueue (which is the whole reason for having it), and scsi error handlers are also always called from a context which may sleep. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 7fc4ad20775..8023944f250 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -133,7 +133,7 @@ static void uas_do_work(struct work_struct *work) struct scsi_pointer *scp = (void *)cmdinfo; struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, SCp); - err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_ATOMIC); + err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_NOIO); if (!err) { cmdinfo->state &= ~IS_IN_WORK_LIST; list_del(&cmdinfo->work); @@ -745,7 +745,7 @@ static int uas_eh_task_mgmt(struct scsi_cmnd *cmnd, devinfo->running_task = 1; memset(&devinfo->response, 0, sizeof(devinfo->response)); - sense_urb = uas_submit_sense_urb(shost, GFP_ATOMIC, tag); + sense_urb = uas_submit_sense_urb(shost, GFP_NOIO, tag); if (!sense_urb) { shost_printk(KERN_INFO, shost, "%s: %s: submit sense urb failed\n", @@ -754,7 +754,7 @@ static int uas_eh_task_mgmt(struct scsi_cmnd *cmnd, spin_unlock_irqrestore(&devinfo->lock, flags); return FAILED; } - if (uas_submit_task_urb(cmnd, GFP_ATOMIC, function, tag)) { + if (uas_submit_task_urb(cmnd, GFP_NOIO, function, tag)) { shost_printk(KERN_INFO, shost, "%s: %s: submit task mgmt urb failed\n", __func__, fname); -- cgit v1.2.3-70-g09d2 From 0df1f663f32e5dc28cba68375b09bba5eaad103f Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 7 Nov 2013 08:47:05 +0100 Subject: uas: Add suspend/resume support Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 8023944f250..7a16ed8e8aa 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -1091,6 +1091,45 @@ static int uas_post_reset(struct usb_interface *intf) return 0; } +static int uas_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + + /* Wait for any pending requests to complete */ + flush_work(&devinfo->work); + if (usb_wait_anchor_empty_timeout(&devinfo->sense_urbs, 5000) == 0) { + shost_printk(KERN_ERR, shost, "%s: timed out\n", __func__); + return -ETIME; + } + + return 0; +} + +static int uas_resume(struct usb_interface *intf) +{ + return 0; +} + +static int uas_reset_resume(struct usb_interface *intf) +{ + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + unsigned long flags; + + if (uas_configure_endpoints(devinfo) != 0) { + shost_printk(KERN_ERR, shost, + "%s: alloc streams error after reset", __func__); + return -EIO; + } + + spin_lock_irqsave(shost->host_lock, flags); + scsi_report_bus_reset(shost, 0); + spin_unlock_irqrestore(shost->host_lock, flags); + + return 0; +} + static void uas_disconnect(struct usb_interface *intf) { struct Scsi_Host *shost = usb_get_intfdata(intf); @@ -1114,6 +1153,9 @@ static struct usb_driver uas_driver = { .disconnect = uas_disconnect, .pre_reset = uas_pre_reset, .post_reset = uas_post_reset, + .suspend = uas_suspend, + .resume = uas_resume, + .reset_resume = uas_reset_resume, .id_table = uas_usb_ids, }; -- cgit v1.2.3-70-g09d2 From da65c2bb99542d05f2d8f67efe6627915f4c5ea4 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 11 Nov 2013 11:51:42 +0100 Subject: uas: Reset device on reboot Some BIOS-es will hang on reboot when an uas device is attached and left in uas mode on reboot. This commit adds a shutdown handler which on reboot puts the device back into usb-storage mode, fixing the hang on reboot on these systems. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 7a16ed8e8aa..019f2030ea0 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -54,6 +54,7 @@ struct uas_dev_info { unsigned use_streams:1; unsigned uas_sense_old:1; unsigned running_task:1; + unsigned shutdown:1; struct scsi_cmnd *cmnd; spinlock_t lock; struct work_struct work; @@ -1011,6 +1012,7 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) devinfo->udev = udev; devinfo->resetting = 0; devinfo->running_task = 0; + devinfo->shutdown = 0; init_usb_anchor(&devinfo->cmd_urbs); init_usb_anchor(&devinfo->sense_urbs); init_usb_anchor(&devinfo->data_urbs); @@ -1053,6 +1055,9 @@ static int uas_pre_reset(struct usb_interface *intf) struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; unsigned long flags; + if (devinfo->shutdown) + return 0; + /* Block new requests */ spin_lock_irqsave(shost->host_lock, flags); scsi_block_requests(shost); @@ -1076,6 +1081,9 @@ static int uas_post_reset(struct usb_interface *intf) struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; unsigned long flags; + if (devinfo->shutdown) + return 0; + if (uas_configure_endpoints(devinfo) != 0) { shost_printk(KERN_ERR, shost, "%s: alloc streams error after reset", __func__); @@ -1147,6 +1155,27 @@ static void uas_disconnect(struct usb_interface *intf) kfree(devinfo); } +/* + * Put the device back in usb-storage mode on shutdown, as some BIOS-es + * hang on reboot when the device is still in uas mode. Note the reset is + * necessary as some devices won't revert to usb-storage mode without it. + */ +static void uas_shutdown(struct device *dev) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *udev = interface_to_usbdev(intf); + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + + if (system_state != SYSTEM_RESTART) + return; + + devinfo->shutdown = 1; + uas_free_streams(devinfo); + usb_set_interface(udev, intf->altsetting[0].desc.bInterfaceNumber, 0); + usb_reset_device(udev); +} + static struct usb_driver uas_driver = { .name = "uas", .probe = uas_probe, @@ -1156,6 +1185,7 @@ static struct usb_driver uas_driver = { .suspend = uas_suspend, .resume = uas_resume, .reset_resume = uas_reset_resume, + .drvwrap.driver.shutdown = uas_shutdown, .id_table = uas_usb_ids, }; -- cgit v1.2.3-70-g09d2 From f323abcda35ea4bae851c9be8f115ee45cc9cf22 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 12 Nov 2013 10:51:33 +0100 Subject: uas: Fix task-management not working when connected over USB-2 For USB-2 connections the stream-id must always be 0. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 019f2030ea0..10e580e56d4 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -746,7 +746,8 @@ static int uas_eh_task_mgmt(struct scsi_cmnd *cmnd, devinfo->running_task = 1; memset(&devinfo->response, 0, sizeof(devinfo->response)); - sense_urb = uas_submit_sense_urb(shost, GFP_NOIO, tag); + sense_urb = uas_submit_sense_urb(shost, GFP_NOIO, + devinfo->use_streams ? tag : 0); if (!sense_urb) { shost_printk(KERN_INFO, shost, "%s: %s: submit sense urb failed\n", -- cgit v1.2.3-70-g09d2 From c6d4579d4ba24c494d03daf656cd2ff2a9e683c6 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 12 Nov 2013 10:53:57 +0100 Subject: uas: uas_alloc_data_urb: Remove unnecessary use_streams check uas_alloc_data_urb always gets called with a stream_id value of 0 when not using streams. Removing the check makes it consistent with uas_alloc_sense_urb. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 10e580e56d4..e06505c8f6f 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -429,8 +429,7 @@ static struct urb *uas_alloc_data_urb(struct uas_dev_info *devinfo, gfp_t gfp, goto out; usb_fill_bulk_urb(urb, udev, pipe, NULL, sdb->length, uas_data_cmplt, cmnd); - if (devinfo->use_streams) - urb->stream_id = stream_id; + urb->stream_id = stream_id; urb->num_sgs = udev->bus->sg_tablesize ? sdb->table.nents : 0; urb->sg = sdb->table.sgl; out: -- cgit v1.2.3-70-g09d2 From 61c09ce510a1eba8595beda6aac194f42571d768 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 12 Nov 2013 13:44:20 +0100 Subject: uas: Properly complete inflight commands on bus-reset or disconnect Before this commit the uas driver would keep track of scsi commands which still need to have some urbs submitted to the device, and complete this with an ABORT result code on bus-reset or disconnect, but in flight scsi commands which have all their urbs submitted, and thus are not part of the work list, would never get their done callback called. The problem is killed sense urbs don't have any tag info, so it is impossible to tell which scsi cmd they belong to, so merely making sure all the urbs have completed one way or the other is not enough. This commit fixes this by changing the work list to an inflight list, which keeps tracks of all inflight scsi cmnds, using the IS_IN_WORK_LIST flag to determine if actual work needs to be done in uas_do_work(), and by moving marking all inflight scsi commands as aborted and moving them to the dead list on bus-reset or disconnect. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index e06505c8f6f..1a188399e09 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -58,7 +58,7 @@ struct uas_dev_info { struct scsi_cmnd *cmnd; spinlock_t lock; struct work_struct work; - struct list_head work_list; + struct list_head inflight_list; struct list_head dead_list; }; @@ -86,7 +86,7 @@ struct uas_cmd_info { struct urb *cmd_urb; struct urb *data_in_urb; struct urb *data_out_urb; - struct list_head work; + struct list_head inflight; struct list_head dead; }; @@ -125,34 +125,36 @@ static void uas_do_work(struct work_struct *work) struct uas_dev_info *devinfo = container_of(work, struct uas_dev_info, work); struct uas_cmd_info *cmdinfo; - struct uas_cmd_info *temp; unsigned long flags; int err; spin_lock_irqsave(&devinfo->lock, flags); - list_for_each_entry_safe(cmdinfo, temp, &devinfo->work_list, work) { + list_for_each_entry(cmdinfo, &devinfo->inflight_list, inflight) { struct scsi_pointer *scp = (void *)cmdinfo; struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, SCp); + + if (!(cmdinfo->state & IS_IN_WORK_LIST)) + continue; + err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_NOIO); - if (!err) { + if (!err) cmdinfo->state &= ~IS_IN_WORK_LIST; - list_del(&cmdinfo->work); - } else { + else schedule_work(&devinfo->work); - } } spin_unlock_irqrestore(&devinfo->lock, flags); } -static void uas_abort_work(struct uas_dev_info *devinfo) +static void uas_abort_inflight(struct uas_dev_info *devinfo) { struct uas_cmd_info *cmdinfo; struct uas_cmd_info *temp; unsigned long flags; spin_lock_irqsave(&devinfo->lock, flags); - list_for_each_entry_safe(cmdinfo, temp, &devinfo->work_list, work) { + list_for_each_entry_safe(cmdinfo, temp, &devinfo->inflight_list, + inflight) { struct scsi_pointer *scp = (void *)cmdinfo; struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, SCp); @@ -160,7 +162,7 @@ static void uas_abort_work(struct uas_dev_info *devinfo) WARN_ON_ONCE(cmdinfo->state & COMMAND_ABORTED); cmdinfo->state |= COMMAND_ABORTED; cmdinfo->state &= ~IS_IN_WORK_LIST; - list_del(&cmdinfo->work); + list_del(&cmdinfo->inflight); list_add_tail(&cmdinfo->dead, &devinfo->dead_list); } spin_unlock_irqrestore(&devinfo->lock, flags); @@ -173,7 +175,6 @@ static void uas_add_work(struct uas_cmd_info *cmdinfo) struct uas_dev_info *devinfo = cmnd->device->hostdata; WARN_ON_ONCE(!spin_is_locked(&devinfo->lock)); - list_add_tail(&cmdinfo->work, &devinfo->work_list); cmdinfo->state |= IS_IN_WORK_LIST; schedule_work(&devinfo->work); } @@ -289,7 +290,8 @@ static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller) scmd_printk(KERN_INFO, cmnd, "abort completed\n"); cmnd->result = DID_ABORT << 16; list_del(&cmdinfo->dead); - } + } else + list_del(&cmdinfo->inflight); cmnd->scsi_done(cmnd); return 0; } @@ -717,6 +719,7 @@ static int uas_queuecommand_lck(struct scsi_cmnd *cmnd, uas_add_work(cmdinfo); } + list_add_tail(&cmdinfo->inflight, &devinfo->inflight_list); spin_unlock_irqrestore(&devinfo->lock, flags); return 0; } @@ -807,11 +810,9 @@ static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) spin_lock_irqsave(&devinfo->lock, flags); WARN_ON_ONCE(cmdinfo->state & COMMAND_ABORTED); cmdinfo->state |= COMMAND_ABORTED; + cmdinfo->state &= ~IS_IN_WORK_LIST; + list_del(&cmdinfo->inflight); list_add_tail(&cmdinfo->dead, &devinfo->dead_list); - if (cmdinfo->state & IS_IN_WORK_LIST) { - list_del(&cmdinfo->work); - cmdinfo->state &= ~IS_IN_WORK_LIST; - } if (cmdinfo->state & COMMAND_INFLIGHT) { spin_unlock_irqrestore(&devinfo->lock, flags); ret = uas_eh_task_mgmt(cmnd, "ABORT TASK", TMF_ABORT_TASK); @@ -847,7 +848,7 @@ static int uas_eh_bus_reset_handler(struct scsi_cmnd *cmnd) shost_printk(KERN_INFO, sdev->host, "%s start\n", __func__); devinfo->resetting = 1; - uas_abort_work(devinfo); + uas_abort_inflight(devinfo); usb_kill_anchored_urbs(&devinfo->cmd_urbs); usb_kill_anchored_urbs(&devinfo->sense_urbs); usb_kill_anchored_urbs(&devinfo->data_urbs); @@ -1018,7 +1019,7 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) init_usb_anchor(&devinfo->data_urbs); spin_lock_init(&devinfo->lock); INIT_WORK(&devinfo->work, uas_do_work); - INIT_LIST_HEAD(&devinfo->work_list); + INIT_LIST_HEAD(&devinfo->inflight_list); INIT_LIST_HEAD(&devinfo->dead_list); result = uas_configure_endpoints(devinfo); @@ -1145,7 +1146,7 @@ static void uas_disconnect(struct usb_interface *intf) devinfo->resetting = 1; cancel_work_sync(&devinfo->work); - uas_abort_work(devinfo); + uas_abort_inflight(devinfo); usb_kill_anchored_urbs(&devinfo->cmd_urbs); usb_kill_anchored_urbs(&devinfo->sense_urbs); usb_kill_anchored_urbs(&devinfo->data_urbs); -- cgit v1.2.3-70-g09d2 From da3033ea08397fb70279f22789002e6001432f3d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 12 Nov 2013 13:57:24 +0100 Subject: uas: add uas_mark_cmd_dead helper function Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 1a188399e09..7810c135a69 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -146,6 +146,21 @@ static void uas_do_work(struct work_struct *work) spin_unlock_irqrestore(&devinfo->lock, flags); } +static void uas_mark_cmd_dead(struct uas_dev_info *devinfo, + struct uas_cmd_info *cmdinfo, const char *caller) +{ + struct scsi_pointer *scp = (void *)cmdinfo; + struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, SCp); + + uas_log_cmd_state(cmnd, caller); + WARN_ON_ONCE(!spin_is_locked(&devinfo->lock)); + WARN_ON_ONCE(cmdinfo->state & COMMAND_ABORTED); + cmdinfo->state |= COMMAND_ABORTED; + cmdinfo->state &= ~IS_IN_WORK_LIST; + list_del(&cmdinfo->inflight); + list_add_tail(&cmdinfo->dead, &devinfo->dead_list); +} + static void uas_abort_inflight(struct uas_dev_info *devinfo) { struct uas_cmd_info *cmdinfo; @@ -154,17 +169,8 @@ static void uas_abort_inflight(struct uas_dev_info *devinfo) spin_lock_irqsave(&devinfo->lock, flags); list_for_each_entry_safe(cmdinfo, temp, &devinfo->inflight_list, - inflight) { - struct scsi_pointer *scp = (void *)cmdinfo; - struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, - SCp); - uas_log_cmd_state(cmnd, __func__); - WARN_ON_ONCE(cmdinfo->state & COMMAND_ABORTED); - cmdinfo->state |= COMMAND_ABORTED; - cmdinfo->state &= ~IS_IN_WORK_LIST; - list_del(&cmdinfo->inflight); - list_add_tail(&cmdinfo->dead, &devinfo->dead_list); - } + inflight) + uas_mark_cmd_dead(devinfo, cmdinfo, __func__); spin_unlock_irqrestore(&devinfo->lock, flags); } @@ -806,13 +812,8 @@ static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) unsigned long flags; int ret; - uas_log_cmd_state(cmnd, __func__); spin_lock_irqsave(&devinfo->lock, flags); - WARN_ON_ONCE(cmdinfo->state & COMMAND_ABORTED); - cmdinfo->state |= COMMAND_ABORTED; - cmdinfo->state &= ~IS_IN_WORK_LIST; - list_del(&cmdinfo->inflight); - list_add_tail(&cmdinfo->dead, &devinfo->dead_list); + uas_mark_cmd_dead(devinfo, cmdinfo, __func__); if (cmdinfo->state & COMMAND_INFLIGHT) { spin_unlock_irqrestore(&devinfo->lock, flags); ret = uas_eh_task_mgmt(cmnd, "ABORT TASK", TMF_ABORT_TASK); -- cgit v1.2.3-70-g09d2 From 040d1a8f11f390f36a8cd7fc04c0c836639b0b6a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 12 Nov 2013 14:02:12 +0100 Subject: uas: cmdinfo: use only one list head cmds are either on the inflight list or on the dead list, never both, so we only need one list head. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 7810c135a69..cfe0102fcba 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -86,8 +86,7 @@ struct uas_cmd_info { struct urb *cmd_urb; struct urb *data_in_urb; struct urb *data_out_urb; - struct list_head inflight; - struct list_head dead; + struct list_head list; }; /* I hate forward declarations, but I actually have a loop */ @@ -129,7 +128,7 @@ static void uas_do_work(struct work_struct *work) int err; spin_lock_irqsave(&devinfo->lock, flags); - list_for_each_entry(cmdinfo, &devinfo->inflight_list, inflight) { + list_for_each_entry(cmdinfo, &devinfo->inflight_list, list) { struct scsi_pointer *scp = (void *)cmdinfo; struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, SCp); @@ -157,8 +156,7 @@ static void uas_mark_cmd_dead(struct uas_dev_info *devinfo, WARN_ON_ONCE(cmdinfo->state & COMMAND_ABORTED); cmdinfo->state |= COMMAND_ABORTED; cmdinfo->state &= ~IS_IN_WORK_LIST; - list_del(&cmdinfo->inflight); - list_add_tail(&cmdinfo->dead, &devinfo->dead_list); + list_move_tail(&cmdinfo->list, &devinfo->dead_list); } static void uas_abort_inflight(struct uas_dev_info *devinfo) @@ -168,8 +166,7 @@ static void uas_abort_inflight(struct uas_dev_info *devinfo) unsigned long flags; spin_lock_irqsave(&devinfo->lock, flags); - list_for_each_entry_safe(cmdinfo, temp, &devinfo->inflight_list, - inflight) + list_for_each_entry_safe(cmdinfo, temp, &devinfo->inflight_list, list) uas_mark_cmd_dead(devinfo, cmdinfo, __func__); spin_unlock_irqrestore(&devinfo->lock, flags); } @@ -192,7 +189,7 @@ static void uas_zap_dead(struct uas_dev_info *devinfo) unsigned long flags; spin_lock_irqsave(&devinfo->lock, flags); - list_for_each_entry_safe(cmdinfo, temp, &devinfo->dead_list, dead) { + list_for_each_entry_safe(cmdinfo, temp, &devinfo->dead_list, list) { struct scsi_pointer *scp = (void *)cmdinfo; struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, SCp); @@ -295,9 +292,8 @@ static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller) if (cmdinfo->state & COMMAND_ABORTED) { scmd_printk(KERN_INFO, cmnd, "abort completed\n"); cmnd->result = DID_ABORT << 16; - list_del(&cmdinfo->dead); - } else - list_del(&cmdinfo->inflight); + } + list_del(&cmdinfo->list); cmnd->scsi_done(cmnd); return 0; } @@ -725,7 +721,7 @@ static int uas_queuecommand_lck(struct scsi_cmnd *cmnd, uas_add_work(cmdinfo); } - list_add_tail(&cmdinfo->inflight, &devinfo->inflight_list); + list_add_tail(&cmdinfo->list, &devinfo->inflight_list); spin_unlock_irqrestore(&devinfo->lock, flags); return 0; } -- cgit v1.2.3-70-g09d2 From c6f63207a3ba689025b2120792ea831cf72f9a81 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 13 Nov 2013 09:24:15 +0100 Subject: uas: Fix command / task mgmt submission racing with disconnect Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index cfe0102fcba..8c685801e26 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -670,13 +670,15 @@ static int uas_queuecommand_lck(struct scsi_cmnd *cmnd, BUILD_BUG_ON(sizeof(struct uas_cmd_info) > sizeof(struct scsi_pointer)); + spin_lock_irqsave(&devinfo->lock, flags); + if (devinfo->resetting) { cmnd->result = DID_ERROR << 16; cmnd->scsi_done(cmnd); + spin_unlock_irqrestore(&devinfo->lock, flags); return 0; } - spin_lock_irqsave(&devinfo->lock, flags); if (devinfo->cmnd) { spin_unlock_irqrestore(&devinfo->lock, flags); return SCSI_MLQUEUE_DEVICE_BUSY; @@ -740,6 +742,11 @@ static int uas_eh_task_mgmt(struct scsi_cmnd *cmnd, spin_lock_irqsave(&devinfo->lock, flags); + if (devinfo->resetting) { + spin_unlock_irqrestore(&devinfo->lock, flags); + return FAILED; + } + if (devinfo->running_task) { shost_printk(KERN_INFO, shost, "%s: %s: error already running a task\n", @@ -809,6 +816,12 @@ static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) int ret; spin_lock_irqsave(&devinfo->lock, flags); + + if (devinfo->resetting) { + spin_unlock_irqrestore(&devinfo->lock, flags); + return FAILED; + } + uas_mark_cmd_dead(devinfo, cmdinfo, __func__); if (cmdinfo->state & COMMAND_INFLIGHT) { spin_unlock_irqrestore(&devinfo->lock, flags); -- cgit v1.2.3-70-g09d2 From 21fc05b680f6fba868b41e2713ade3fdea4049f9 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 13 Nov 2013 09:32:22 +0100 Subject: uas: Fix memory management The scsi-host structure is refcounted, scsi_remove_host tears down the scsi-host but does not decrement the refcount, so we need to call scsi_put_host on disconnect to get the underlying memory to be freed. After calling scsi_remove_host, the scsi-core may still hold a reference to the scsi-host, iow we may still get called after uas_disconnect, but we do our own life cycle management of uas_devinfo, freeing it on disconnect, and thus may end up using devinfo after it has been freed. Switch to letting scsi_host_alloc allocate and manage the memory for us. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 8c685801e26..d81d041842f 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -315,7 +315,7 @@ static void uas_stat_cmplt(struct urb *urb) { struct iu *iu = urb->transfer_buffer; struct Scsi_Host *shost = urb->context; - struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; struct scsi_cmnd *cmnd; struct uas_cmd_info *cmdinfo; unsigned long flags; @@ -562,7 +562,7 @@ err: static struct urb *uas_submit_sense_urb(struct Scsi_Host *shost, gfp_t gfp, unsigned int stream) { - struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; struct urb *urb; urb = uas_alloc_sense_urb(devinfo, gfp, shost, stream); @@ -734,7 +734,7 @@ static int uas_eh_task_mgmt(struct scsi_cmnd *cmnd, const char *fname, u8 function) { struct Scsi_Host *shost = cmnd->device->host; - struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; u16 tag = devinfo->qdepth; unsigned long flags; struct urb *sense_urb; @@ -879,7 +879,7 @@ static int uas_eh_bus_reset_handler(struct scsi_cmnd *cmnd) static int uas_slave_alloc(struct scsi_device *sdev) { - sdev->hostdata = (void *)sdev->host->hostdata[0]; + sdev->hostdata = (void *)sdev->host->hostdata; return 0; } @@ -1005,11 +1005,8 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) if (uas_switch_interface(udev, intf)) return -ENODEV; - devinfo = kmalloc(sizeof(struct uas_dev_info), GFP_KERNEL); - if (!devinfo) - goto set_alt0; - - shost = scsi_host_alloc(&uas_host_template, sizeof(void *)); + shost = scsi_host_alloc(&uas_host_template, + sizeof(struct uas_dev_info)); if (!shost) goto set_alt0; @@ -1019,6 +1016,7 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) shost->max_channel = 0; shost->sg_tablesize = udev->bus->sg_tablesize; + devinfo = (struct uas_dev_info *)shost->hostdata; devinfo->intf = intf; devinfo->udev = udev; devinfo->resetting = 0; @@ -1044,8 +1042,6 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) if (result) goto free_streams; - shost->hostdata[0] = (unsigned long)devinfo; - scsi_scan_host(shost); usb_set_intfdata(intf, shost); return result; @@ -1054,7 +1050,6 @@ free_streams: uas_free_streams(devinfo); set_alt0: usb_set_interface(udev, intf->altsetting[0].desc.bInterfaceNumber, 0); - kfree(devinfo); if (shost) scsi_host_put(shost); return result; @@ -1063,7 +1058,7 @@ set_alt0: static int uas_pre_reset(struct usb_interface *intf) { struct Scsi_Host *shost = usb_get_intfdata(intf); - struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; unsigned long flags; if (devinfo->shutdown) @@ -1089,7 +1084,7 @@ static int uas_pre_reset(struct usb_interface *intf) static int uas_post_reset(struct usb_interface *intf) { struct Scsi_Host *shost = usb_get_intfdata(intf); - struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; unsigned long flags; if (devinfo->shutdown) @@ -1113,7 +1108,7 @@ static int uas_post_reset(struct usb_interface *intf) static int uas_suspend(struct usb_interface *intf, pm_message_t message) { struct Scsi_Host *shost = usb_get_intfdata(intf); - struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; /* Wait for any pending requests to complete */ flush_work(&devinfo->work); @@ -1133,7 +1128,7 @@ static int uas_resume(struct usb_interface *intf) static int uas_reset_resume(struct usb_interface *intf) { struct Scsi_Host *shost = usb_get_intfdata(intf); - struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; unsigned long flags; if (uas_configure_endpoints(devinfo) != 0) { @@ -1152,7 +1147,7 @@ static int uas_reset_resume(struct usb_interface *intf) static void uas_disconnect(struct usb_interface *intf) { struct Scsi_Host *shost = usb_get_intfdata(intf); - struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; devinfo->resetting = 1; cancel_work_sync(&devinfo->work); @@ -1163,7 +1158,7 @@ static void uas_disconnect(struct usb_interface *intf) uas_zap_dead(devinfo); scsi_remove_host(shost); uas_free_streams(devinfo); - kfree(devinfo); + scsi_host_put(shost); } /* @@ -1176,7 +1171,7 @@ static void uas_shutdown(struct device *dev) struct usb_interface *intf = to_usb_interface(dev); struct usb_device *udev = interface_to_usbdev(intf); struct Scsi_Host *shost = usb_get_intfdata(intf); - struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; if (system_state != SYSTEM_RESTART) return; -- cgit v1.2.3-70-g09d2 From 3a4462e0e2fe8f715f54147d36b5433a7ff5a85a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 14 Nov 2013 11:06:13 +0100 Subject: uas: Clear cmdinfo on command queue-ing The scsi error handling path re-uses previously queued up (and errored-out) cmds. If such a re-used cmd had a data-phase then cmdinfo will have data_in_urb / data_out_urb still set to the free-ed urbs from the errored-out cmd, and they will get free-ed a second time when the error handling cmd completes, corrupting the kernel heap. Clearing cmdinfo on command queue-ing fixes this, and seems like a good idea in general. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index d81d041842f..fceffccc1be 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -684,6 +684,8 @@ static int uas_queuecommand_lck(struct scsi_cmnd *cmnd, return SCSI_MLQUEUE_DEVICE_BUSY; } + memset(cmdinfo, 0, sizeof(*cmdinfo)); + if (blk_rq_tagged(cmnd->request)) { cmdinfo->stream = cmnd->request->tag + 2; } else { -- cgit v1.2.3-70-g09d2 From 673331c87c492898a9152f3754f3174128e1514a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 14 Nov 2013 14:27:27 +0100 Subject: uas: Use the right error codes for different kinds of errors Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index fceffccc1be..6ec48c2daf4 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -146,7 +146,8 @@ static void uas_do_work(struct work_struct *work) } static void uas_mark_cmd_dead(struct uas_dev_info *devinfo, - struct uas_cmd_info *cmdinfo, const char *caller) + struct uas_cmd_info *cmdinfo, + int result, const char *caller) { struct scsi_pointer *scp = (void *)cmdinfo; struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, SCp); @@ -156,10 +157,12 @@ static void uas_mark_cmd_dead(struct uas_dev_info *devinfo, WARN_ON_ONCE(cmdinfo->state & COMMAND_ABORTED); cmdinfo->state |= COMMAND_ABORTED; cmdinfo->state &= ~IS_IN_WORK_LIST; + cmnd->result = result << 16; list_move_tail(&cmdinfo->list, &devinfo->dead_list); } -static void uas_abort_inflight(struct uas_dev_info *devinfo) +static void uas_abort_inflight(struct uas_dev_info *devinfo, int result, + const char *caller) { struct uas_cmd_info *cmdinfo; struct uas_cmd_info *temp; @@ -167,7 +170,7 @@ static void uas_abort_inflight(struct uas_dev_info *devinfo) spin_lock_irqsave(&devinfo->lock, flags); list_for_each_entry_safe(cmdinfo, temp, &devinfo->inflight_list, list) - uas_mark_cmd_dead(devinfo, cmdinfo, __func__); + uas_mark_cmd_dead(devinfo, cmdinfo, result, caller); spin_unlock_irqrestore(&devinfo->lock, flags); } @@ -289,10 +292,8 @@ static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller) cmdinfo->state |= COMMAND_COMPLETED; usb_free_urb(cmdinfo->data_in_urb); usb_free_urb(cmdinfo->data_out_urb); - if (cmdinfo->state & COMMAND_ABORTED) { + if (cmdinfo->state & COMMAND_ABORTED) scmd_printk(KERN_INFO, cmnd, "abort completed\n"); - cmnd->result = DID_ABORT << 16; - } list_del(&cmdinfo->list); cmnd->scsi_done(cmnd); return 0; @@ -824,7 +825,7 @@ static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) return FAILED; } - uas_mark_cmd_dead(devinfo, cmdinfo, __func__); + uas_mark_cmd_dead(devinfo, cmdinfo, DID_ABORT, __func__); if (cmdinfo->state & COMMAND_INFLIGHT) { spin_unlock_irqrestore(&devinfo->lock, flags); ret = uas_eh_task_mgmt(cmnd, "ABORT TASK", TMF_ABORT_TASK); @@ -860,7 +861,7 @@ static int uas_eh_bus_reset_handler(struct scsi_cmnd *cmnd) shost_printk(KERN_INFO, sdev->host, "%s start\n", __func__); devinfo->resetting = 1; - uas_abort_inflight(devinfo); + uas_abort_inflight(devinfo, DID_RESET, __func__); usb_kill_anchored_urbs(&devinfo->cmd_urbs); usb_kill_anchored_urbs(&devinfo->sense_urbs); usb_kill_anchored_urbs(&devinfo->data_urbs); @@ -1153,7 +1154,7 @@ static void uas_disconnect(struct usb_interface *intf) devinfo->resetting = 1; cancel_work_sync(&devinfo->work); - uas_abort_inflight(devinfo); + uas_abort_inflight(devinfo, DID_NO_CONNECT, __func__); usb_kill_anchored_urbs(&devinfo->cmd_urbs); usb_kill_anchored_urbs(&devinfo->sense_urbs); usb_kill_anchored_urbs(&devinfo->data_urbs); -- cgit v1.2.3-70-g09d2 From 876285cc9cf418f626375f28bb0fc5d88012f12d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 7 Nov 2013 08:52:42 +0100 Subject: uas: Improve error reporting Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 60 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 6ec48c2daf4..f09205b162e 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -411,6 +411,12 @@ static void uas_data_cmplt(struct urb *urb) if (sdb == NULL) { WARN_ON_ONCE(1); } else if (urb->status) { + if (urb->status != -ECONNRESET) { + uas_log_cmd_state(cmnd, __func__); + scmd_printk(KERN_ERR, cmnd, + "data cmplt err %d stream %d\n", + urb->status, urb->stream_id); + } /* error: no data transfered */ sdb->resid = sdb->length; } else { @@ -420,6 +426,17 @@ static void uas_data_cmplt(struct urb *urb) spin_unlock_irqrestore(&devinfo->lock, flags); } +static void uas_cmd_cmplt(struct urb *urb) +{ + struct scsi_cmnd *cmnd = urb->context; + + if (urb->status) { + uas_log_cmd_state(cmnd, __func__); + scmd_printk(KERN_ERR, cmnd, "cmd cmplt err %d\n", urb->status); + } + usb_free_urb(urb); +} + static struct urb *uas_alloc_data_urb(struct uas_dev_info *devinfo, gfp_t gfp, unsigned int pipe, u16 stream_id, struct scsi_cmnd *cmnd, @@ -497,7 +514,7 @@ static struct urb *uas_alloc_cmd_urb(struct uas_dev_info *devinfo, gfp_t gfp, memcpy(iu->cdb, cmnd->cmnd, cmnd->cmd_len); usb_fill_bulk_urb(urb, udev, devinfo->cmd_pipe, iu, sizeof(*iu) + len, - usb_free_urb, NULL); + uas_cmd_cmplt, cmnd); urb->transfer_flags |= URB_FREE_BUFFER; out: return urb; @@ -537,13 +554,15 @@ static int uas_submit_task_urb(struct scsi_cmnd *cmnd, gfp_t gfp, } usb_fill_bulk_urb(urb, udev, devinfo->cmd_pipe, iu, sizeof(*iu), - usb_free_urb, NULL); + uas_cmd_cmplt, cmnd); urb->transfer_flags |= URB_FREE_BUFFER; usb_anchor_urb(urb, &devinfo->cmd_urbs); err = usb_submit_urb(urb, gfp); if (err) { usb_unanchor_urb(urb); + uas_log_cmd_state(cmnd, __func__); + scmd_printk(KERN_ERR, cmnd, "task submission err %d\n", err); goto err; } @@ -560,20 +579,25 @@ err: * daft to me. */ -static struct urb *uas_submit_sense_urb(struct Scsi_Host *shost, +static struct urb *uas_submit_sense_urb(struct scsi_cmnd *cmnd, gfp_t gfp, unsigned int stream) { + struct Scsi_Host *shost = cmnd->device->host; struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; struct urb *urb; + int err; urb = uas_alloc_sense_urb(devinfo, gfp, shost, stream); if (!urb) return NULL; usb_anchor_urb(urb, &devinfo->sense_urbs); - if (usb_submit_urb(urb, gfp)) { + err = usb_submit_urb(urb, gfp); + if (err) { usb_unanchor_urb(urb); + uas_log_cmd_state(cmnd, __func__); shost_printk(KERN_INFO, shost, - "sense urb submission failure\n"); + "sense urb submission error %d stream %d\n", + err, stream); usb_free_urb(urb); return NULL; } @@ -585,11 +609,11 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, { struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; struct urb *urb; + int err; WARN_ON_ONCE(!spin_is_locked(&devinfo->lock)); if (cmdinfo->state & SUBMIT_STATUS_URB) { - urb = uas_submit_sense_urb(cmnd->device->host, gfp, - cmdinfo->stream); + urb = uas_submit_sense_urb(cmnd, gfp, cmdinfo->stream); if (!urb) return SCSI_MLQUEUE_DEVICE_BUSY; cmdinfo->state &= ~SUBMIT_STATUS_URB; @@ -606,10 +630,13 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, if (cmdinfo->state & SUBMIT_DATA_IN_URB) { usb_anchor_urb(cmdinfo->data_in_urb, &devinfo->data_urbs); - if (usb_submit_urb(cmdinfo->data_in_urb, gfp)) { + err = usb_submit_urb(cmdinfo->data_in_urb, gfp); + if (err) { usb_unanchor_urb(cmdinfo->data_in_urb); + uas_log_cmd_state(cmnd, __func__); scmd_printk(KERN_INFO, cmnd, - "data in urb submission failure\n"); + "data in urb submission error %d stream %d\n", + err, cmdinfo->data_in_urb->stream_id); return SCSI_MLQUEUE_DEVICE_BUSY; } cmdinfo->state &= ~SUBMIT_DATA_IN_URB; @@ -627,10 +654,13 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, if (cmdinfo->state & SUBMIT_DATA_OUT_URB) { usb_anchor_urb(cmdinfo->data_out_urb, &devinfo->data_urbs); - if (usb_submit_urb(cmdinfo->data_out_urb, gfp)) { + err = usb_submit_urb(cmdinfo->data_out_urb, gfp); + if (err) { usb_unanchor_urb(cmdinfo->data_out_urb); + uas_log_cmd_state(cmnd, __func__); scmd_printk(KERN_INFO, cmnd, - "data out urb submission failure\n"); + "data out urb submission error %d stream %d\n", + err, cmdinfo->data_out_urb->stream_id); return SCSI_MLQUEUE_DEVICE_BUSY; } cmdinfo->state &= ~SUBMIT_DATA_OUT_URB; @@ -646,10 +676,12 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, if (cmdinfo->state & SUBMIT_CMD_URB) { usb_anchor_urb(cmdinfo->cmd_urb, &devinfo->cmd_urbs); - if (usb_submit_urb(cmdinfo->cmd_urb, gfp)) { + err = usb_submit_urb(cmdinfo->cmd_urb, gfp); + if (err) { usb_unanchor_urb(cmdinfo->cmd_urb); + uas_log_cmd_state(cmnd, __func__); scmd_printk(KERN_INFO, cmnd, - "cmd urb submission failure\n"); + "cmd urb submission error %d\n", err); return SCSI_MLQUEUE_DEVICE_BUSY; } cmdinfo->cmd_urb = NULL; @@ -760,7 +792,7 @@ static int uas_eh_task_mgmt(struct scsi_cmnd *cmnd, devinfo->running_task = 1; memset(&devinfo->response, 0, sizeof(devinfo->response)); - sense_urb = uas_submit_sense_urb(shost, GFP_NOIO, + sense_urb = uas_submit_sense_urb(cmnd, GFP_NOIO, devinfo->use_streams ? tag : 0); if (!sense_urb) { shost_printk(KERN_INFO, shost, -- cgit v1.2.3-70-g09d2 From 8e453155d7f8dfa53863ba6f8da6c68f7c17ece4 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 15 Nov 2013 10:04:11 +0100 Subject: uas: Add some data in/out ready iu sanity checks Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index f09205b162e..62086829af1 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -379,9 +379,19 @@ static void uas_stat_cmplt(struct urb *urb) uas_try_complete(cmnd, __func__); break; case IU_ID_READ_READY: + if (!cmdinfo->data_in_urb || + (cmdinfo->state & DATA_IN_URB_INFLIGHT)) { + scmd_printk(KERN_ERR, cmnd, "unexpected read rdy\n"); + break; + } uas_xfer_data(urb, cmnd, SUBMIT_DATA_IN_URB); break; case IU_ID_WRITE_READY: + if (!cmdinfo->data_out_urb || + (cmdinfo->state & DATA_OUT_URB_INFLIGHT)) { + scmd_printk(KERN_ERR, cmnd, "unexpected write rdy\n"); + break; + } uas_xfer_data(urb, cmnd, SUBMIT_DATA_OUT_URB); break; default: -- cgit v1.2.3-70-g09d2 From 37599f9603bed3d72becdc1a59c164576df9fbfc Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 15 Nov 2013 10:04:31 +0100 Subject: uas: Make sure sg elements are properly aligned Copy the sg alignment trick from the usb-storage driver, without this I'm seeing intermittent errors when using uas devices with an ehci controller. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 62086829af1..ad97615b75b 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -925,6 +925,24 @@ static int uas_eh_bus_reset_handler(struct scsi_cmnd *cmnd) static int uas_slave_alloc(struct scsi_device *sdev) { sdev->hostdata = (void *)sdev->host->hostdata; + + /* USB has unusual DMA-alignment requirements: Although the + * starting address of each scatter-gather element doesn't matter, + * the length of each element except the last must be divisible + * by the Bulk maxpacket value. There's currently no way to + * express this by block-layer constraints, so we'll cop out + * and simply require addresses to be aligned at 512-byte + * boundaries. This is okay since most block I/O involves + * hardware sectors that are multiples of 512 bytes in length, + * and since host controllers up through USB 2.0 have maxpacket + * values no larger than 512. + * + * But it doesn't suffice for Wireless USB, where Bulk maxpacket + * values can be as large as 2048. To make that work properly + * will require changes to the block layer. + */ + blk_queue_update_dma_alignment(sdev->request_queue, (512 - 1)); + return 0; } -- cgit v1.2.3-70-g09d2 From dc88608dba784f902b3127fd68d0c4f92a532cd0 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 13 Sep 2013 13:27:15 +0200 Subject: uas: remove BROKEN xhci streams support is fixed, unblock usb attached scsi. Signed-off-by: Gerd Hoffmann Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/storage/Kconfig b/drivers/usb/storage/Kconfig index 666dcb692e1..13b5bfbaf95 100644 --- a/drivers/usb/storage/Kconfig +++ b/drivers/usb/storage/Kconfig @@ -204,7 +204,7 @@ config USB_STORAGE_ENE_UB6250 config USB_UAS tristate "USB Attached SCSI" - depends on SCSI && USB_STORAGE && BROKEN + depends on SCSI && USB_STORAGE help The USB Attached SCSI protocol is supported by some USB storage devices. It permits higher performance by supporting -- cgit v1.2.3-70-g09d2 From f50a4968deb7bf38c46f5baf62db9431a099531a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 28 Oct 2013 10:48:04 +0000 Subject: uas: Add Hans de Goede as uas maintainer At the kernel-summit Sarah Sharp asked me if I was willing to become the uas maintainer. I said yes, and here is a patch to make this official. Also remove Matthew Wilcox and Sarah Sharp as maintainers at their request. I've also added myself to the module's author tag, so that if people look there rather then in maintainers they will know they should bug me about uas too. Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- MAINTAINERS | 3 +-- drivers/usb/storage/uas.c | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index c6d0e93eff6..fc536bc7430 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9063,8 +9063,7 @@ S: Maintained F: drivers/net/wireless/ath/ar5523/ USB ATTACHED SCSI -M: Matthew Wilcox -M: Sarah Sharp +M: Hans de Goede M: Gerd Hoffmann L: linux-usb@vger.kernel.org L: linux-scsi@vger.kernel.org diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index ad97615b75b..08e9710f193 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -2,6 +2,7 @@ * USB Attached SCSI * Note that this is not the same as the USB Mass Storage driver * + * Copyright Hans de Goede for Red Hat, Inc. 2013 * Copyright Matthew Wilcox for Intel Corp, 2010 * Copyright Sarah Sharp for Intel Corp, 2010 * @@ -1261,4 +1262,5 @@ static struct usb_driver uas_driver = { module_usb_driver(uas_driver); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Matthew Wilcox and Sarah Sharp"); +MODULE_AUTHOR( + "Hans de Goede , Matthew Wilcox and Sarah Sharp"); -- cgit v1.2.3-70-g09d2 From 7cace978fba5d0ec6eed50509cda40eea85f8e98 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 29 Oct 2013 11:36:43 +0100 Subject: uas: Remove comment about registering a uas scsi controller for each usb bus Although an interesting concept, I don't think that this is a good idea: -This will result in lots of "virtual" scsi controllers confusing users -If we get a scsi-bus-reset we will now need to do a usb-device-reset of all uas devices on the same usb bus, which is something to avoid if possible Signed-off-by: Hans de Goede Signed-off-by: Sarah Sharp --- drivers/usb/storage/uas.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 08e9710f193..a7ac97cc594 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -1050,12 +1050,6 @@ static void uas_free_streams(struct uas_dev_info *devinfo) usb_free_streams(devinfo->intf, eps, 3, GFP_KERNEL); } -/* - * XXX: What I'd like to do here is register a SCSI host for each USB host in - * the system. Follow usb-storage's design of registering a SCSI host for - * each USB device for the moment. Can implement this by walking up the - * USB hierarchy until we find a USB host. - */ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) { int result = -ENOMEM; -- cgit v1.2.3-70-g09d2 From 50e8725e7c429701e530439013f9681e1fa36b5d Mon Sep 17 00:00:00 2001 From: Sarah Sharp Date: Fri, 21 Feb 2014 09:27:30 -0800 Subject: xhci: Refactor command watchdog and fix split string. In preparation for fixing this function for streams endpoints, refactor code in the command watchdog timeout function into two new functions. One kills all URBs on a ring (either stream or endpoint), the other kills all URBs associated with an endpoint. Fix a split string while we're at it. Signed-off-by: Sarah Sharp --- drivers/usb/host/xhci-ring.c | 70 ++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index b3d27e6467e..58cbc06ecdf 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -900,6 +900,43 @@ remove_finished_td: /* Return to the event handler with xhci->lock re-acquired */ } +static void xhci_kill_ring_urbs(struct xhci_hcd *xhci, struct xhci_ring *ring) +{ + struct xhci_td *cur_td; + + while (!list_empty(&ring->td_list)) { + cur_td = list_first_entry(&ring->td_list, + struct xhci_td, td_list); + list_del_init(&cur_td->td_list); + if (!list_empty(&cur_td->cancelled_td_list)) + list_del_init(&cur_td->cancelled_td_list); + xhci_giveback_urb_in_irq(xhci, cur_td, -ESHUTDOWN); + } +} + +static void xhci_kill_endpoint_urbs(struct xhci_hcd *xhci, + int slot_id, int ep_index) +{ + struct xhci_td *cur_td; + struct xhci_virt_ep *ep; + struct xhci_ring *ring; + + ep = &xhci->devs[slot_id]->eps[ep_index]; + ring = ep->ring; + if (!ring) + return; + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Killing URBs for slot ID %u, ep index %u", + slot_id, ep_index); + xhci_kill_ring_urbs(xhci, ring); + while (!list_empty(&ep->cancelled_td_list)) { + cur_td = list_first_entry(&ep->cancelled_td_list, + struct xhci_td, cancelled_td_list); + list_del_init(&cur_td->cancelled_td_list); + xhci_giveback_urb_in_irq(xhci, cur_td, -ESHUTDOWN); + } +} + /* Watchdog timer function for when a stop endpoint command fails to complete. * In this case, we assume the host controller is broken or dying or dead. The * host may still be completing some other events, so we have to be careful to @@ -923,9 +960,6 @@ void xhci_stop_endpoint_command_watchdog(unsigned long arg) { struct xhci_hcd *xhci; struct xhci_virt_ep *ep; - struct xhci_virt_ep *temp_ep; - struct xhci_ring *ring; - struct xhci_td *cur_td; int ret, i, j; unsigned long flags; @@ -982,34 +1016,8 @@ void xhci_stop_endpoint_command_watchdog(unsigned long arg) for (i = 0; i < MAX_HC_SLOTS; i++) { if (!xhci->devs[i]) continue; - for (j = 0; j < 31; j++) { - temp_ep = &xhci->devs[i]->eps[j]; - ring = temp_ep->ring; - if (!ring) - continue; - xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, - "Killing URBs for slot ID %u, " - "ep index %u", i, j); - while (!list_empty(&ring->td_list)) { - cur_td = list_first_entry(&ring->td_list, - struct xhci_td, - td_list); - list_del_init(&cur_td->td_list); - if (!list_empty(&cur_td->cancelled_td_list)) - list_del_init(&cur_td->cancelled_td_list); - xhci_giveback_urb_in_irq(xhci, cur_td, - -ESHUTDOWN); - } - while (!list_empty(&temp_ep->cancelled_td_list)) { - cur_td = list_first_entry( - &temp_ep->cancelled_td_list, - struct xhci_td, - cancelled_td_list); - list_del_init(&cur_td->cancelled_td_list); - xhci_giveback_urb_in_irq(xhci, cur_td, - -ESHUTDOWN); - } - } + for (j = 0; j < 31; j++) + xhci_kill_endpoint_urbs(xhci, i, j); } spin_unlock_irqrestore(&xhci->lock, flags); xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, -- cgit v1.2.3-70-g09d2 From 21d0e51bfb290349adc02fa8fec716d77f53df51 Mon Sep 17 00:00:00 2001 From: Sarah Sharp Date: Fri, 21 Feb 2014 14:29:02 -0800 Subject: xhci: Kill streams URBs when the host dies. If the host controller stops responding to commands, we need to kill all the URBs that were queued to all endpoints. The current code would only kill URBs that had been queued to the endpoint rings. ep->ring is set to NULL if streams has been enabled for the endpoint, which means URBs submitted with a non-zero stream_id would never get killed. Fix this. Signed-off-by: Sarah Sharp --- drivers/usb/host/xhci-ring.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 58cbc06ecdf..5f926bea5ab 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -922,13 +922,27 @@ static void xhci_kill_endpoint_urbs(struct xhci_hcd *xhci, struct xhci_ring *ring; ep = &xhci->devs[slot_id]->eps[ep_index]; - ring = ep->ring; - if (!ring) - return; - xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, - "Killing URBs for slot ID %u, ep index %u", - slot_id, ep_index); - xhci_kill_ring_urbs(xhci, ring); + if ((ep->ep_state & EP_HAS_STREAMS) || + (ep->ep_state & EP_GETTING_NO_STREAMS)) { + int stream_id; + + for (stream_id = 0; stream_id < ep->stream_info->num_streams; + stream_id++) { + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Killing URBs for slot ID %u, ep index %u, stream %u", + slot_id, ep_index, stream_id + 1); + xhci_kill_ring_urbs(xhci, + ep->stream_info->stream_rings[stream_id]); + } + } else { + ring = ep->ring; + if (!ring) + return; + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Killing URBs for slot ID %u, ep index %u", + slot_id, ep_index); + xhci_kill_ring_urbs(xhci, ring); + } while (!list_empty(&ep->cancelled_td_list)) { cur_td = list_first_entry(&ep->cancelled_td_list, struct xhci_td, cancelled_td_list); -- cgit v1.2.3-70-g09d2 From 14aec589327a6fc4035f5327d90ac5548f501c4c Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Tue, 11 Feb 2014 20:36:04 +0100 Subject: storage: accept some UAS devices if streams are unavailable On some older XHCIs streams are not supported and the UAS driver will fail at probe time. For those devices storage should try to bind to UAS devices. This patch adds a flag for stream support to HCDs and evaluates it. [Note: Sarah fixed a bug where the USB 2.0 root hub, not USB 3.0 root hub would get marked as being able to support streams.] Signed-off-by: Oliver Neukum Signed-off-by: Sarah Sharp Acked-by: Hans de Goede --- drivers/usb/host/xhci-pci.c | 3 +++ drivers/usb/host/xhci-plat.c | 3 +++ drivers/usb/storage/uas-detect.h | 4 ++++ include/linux/usb/hcd.h | 1 + 4 files changed, 11 insertions(+) diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 6c03584ac15..cbf0c5cffc9 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -222,6 +222,9 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) goto put_usb3_hcd; /* Roothub already marked as USB 3.0 speed */ + if (HCC_MAX_PSA(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + return 0; put_usb3_hcd: diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 8affef91078..151901ce1ba 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -158,6 +158,9 @@ static int xhci_plat_probe(struct platform_device *pdev) */ *((struct xhci_hcd **) xhci->shared_hcd->hcd_priv) = xhci; + if (HCC_MAX_PSA(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); if (ret) goto put_usb3_hcd; diff --git a/drivers/usb/storage/uas-detect.h b/drivers/usb/storage/uas-detect.h index b8a02e12a8a..bb05b984d5f 100644 --- a/drivers/usb/storage/uas-detect.h +++ b/drivers/usb/storage/uas-detect.h @@ -72,6 +72,7 @@ static int uas_use_uas_driver(struct usb_interface *intf, { struct usb_host_endpoint *eps[4] = { }; struct usb_device *udev = interface_to_usbdev(intf); + struct usb_hcd *hcd = bus_to_hcd(udev->bus); unsigned long flags = id->driver_info; int r, alt; @@ -80,6 +81,9 @@ static int uas_use_uas_driver(struct usb_interface *intf, if (flags & US_FL_IGNORE_UAS) return 0; + if (udev->speed >= USB_SPEED_SUPER && !hcd->can_do_streams) + return 0; + alt = uas_find_uas_alt_setting(intf); if (alt < 0) return 0; diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index efe8d8a7c7a..485cd5e2100 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -143,6 +143,7 @@ struct usb_hcd { unsigned authorized_default:1; unsigned has_tt:1; /* Integrated TT in root hub */ unsigned amd_resume_bug:1; /* AMD remote wakeup quirk */ + unsigned can_do_streams:1; /* HC supports streams */ unsigned int irq; /* irq allocated */ void __iomem *regs; /* device memory/io */ -- cgit v1.2.3-70-g09d2 From eee3f15d5f1f4f0c283dd4db67dc1b874a2852d1 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 25 Feb 2014 10:58:43 -0600 Subject: usb: musb: avoid NULL pointer dereference instead of relying on the otg pointer, which can be NULL in certain cases, we can use the gadget and host pointers we already hold inside struct musb. Cc: Tested-by: Tony Lindgren Signed-off-by: Felipe Balbi --- drivers/usb/musb/musb_core.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index fc192ad9cc6..6c1dd42e589 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -438,7 +438,6 @@ void musb_hnp_stop(struct musb *musb) static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, u8 devctl) { - struct usb_otg *otg = musb->xceiv->otg; irqreturn_t handled = IRQ_NONE; dev_dbg(musb->controller, "<== DevCtl=%02x, int_usb=0x%x\n", devctl, @@ -653,7 +652,7 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, break; case OTG_STATE_B_PERIPHERAL: musb_g_suspend(musb); - musb->is_active = otg->gadget->b_hnp_enable; + musb->is_active = musb->g.b_hnp_enable; if (musb->is_active) { musb->xceiv->state = OTG_STATE_B_WAIT_ACON; dev_dbg(musb->controller, "HNP: Setting timer for b_ase0_brst\n"); @@ -669,7 +668,7 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, break; case OTG_STATE_A_HOST: musb->xceiv->state = OTG_STATE_A_SUSPEND; - musb->is_active = otg->host->b_hnp_enable; + musb->is_active = musb->hcd->self.b_hnp_enable; break; case OTG_STATE_B_HOST: /* Transition to B_PERIPHERAL, see 6.8.2.6 p 44 */ -- cgit v1.2.3-70-g09d2 From 7241a21a7178eb327ca2c8f2ffc325b10e1e05bd Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Sat, 22 Feb 2014 04:29:15 +0300 Subject: usb: phy: rcar-gen2-usb: always use 'dev' variable in probe() method The probe() method has the 'dev' local variable declared and used but strangely not in all cases where it should be... Signed-off-by: Sergei Shtylyov Signed-off-by: Felipe Balbi --- drivers/usb/phy/phy-rcar-gen2-usb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/phy/phy-rcar-gen2-usb.c b/drivers/usb/phy/phy-rcar-gen2-usb.c index 551e0a6c0e2..388d89f6b14 100644 --- a/drivers/usb/phy/phy-rcar-gen2-usb.c +++ b/drivers/usb/phy/phy-rcar-gen2-usb.c @@ -177,15 +177,15 @@ static int rcar_gen2_usb_phy_probe(struct platform_device *pdev) struct clk *clk; int retval; - pdata = dev_get_platdata(&pdev->dev); + pdata = dev_get_platdata(dev); if (!pdata) { dev_err(dev, "No platform data\n"); return -EINVAL; } - clk = devm_clk_get(&pdev->dev, "usbhs"); + clk = devm_clk_get(dev, "usbhs"); if (IS_ERR(clk)) { - dev_err(&pdev->dev, "Can't get the clock\n"); + dev_err(dev, "Can't get the clock\n"); return PTR_ERR(clk); } -- cgit v1.2.3-70-g09d2 From 3f83e53878b9cb3314d76f957a497562ce508984 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Feb 2014 20:49:53 +0100 Subject: usb: musb: USB_MUSB_DUAL_ROLE/USB_MUSB_GADGET should depend on HAS_DMA If NO_DMA=y: drivers/built-in.o: In function `txstate': musb_gadget.c:(.text+0x35955a): undefined reference to `dma_unmap_single' musb_gadget.c:(.text+0x35957e): undefined reference to `dma_sync_single_for_cpu' drivers/built-in.o: In function `musb_g_giveback': (.text+0x359672): undefined reference to `dma_mapping_error' drivers/built-in.o: In function `musb_g_giveback': (.text+0x3596ba): undefined reference to `dma_unmap_single' drivers/built-in.o: In function `musb_g_giveback': (.text+0x3596e0): undefined reference to `dma_sync_single_for_cpu' drivers/built-in.o: In function `rxstate': musb_gadget.c:(.text+0x3599d0): undefined reference to `dma_unmap_single' musb_gadget.c:(.text+0x3599f6): undefined reference to `dma_sync_single_for_cpu' drivers/built-in.o: In function `musb_gadget_queue': musb_gadget.c:(.text+0x35a8c0): undefined reference to `dma_map_single' musb_gadget.c:(.text+0x35a8d0): undefined reference to `dma_mapping_error' musb_gadget.c:(.text+0x35a906): undefined reference to `dma_sync_single_for_cpu' musb_gadget.c:(.text+0x35a9a0): undefined reference to `dma_unmap_single' musb_gadget.c:(.text+0x35a9c8): undefined reference to `dma_sync_single_for_cpu' Signed-off-by: Geert Uytterhoeven Signed-off-by: Felipe Balbi --- drivers/usb/musb/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index 688dc8bb192..8b789792f6f 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -43,6 +43,7 @@ config USB_MUSB_HOST config USB_MUSB_GADGET bool "Gadget only mode" depends on USB_GADGET=y || USB_GADGET=USB_MUSB_HDRC + depends on HAS_DMA help Select this when you want to use MUSB in gadget mode only, thereby the host feature will be regressed. @@ -50,6 +51,7 @@ config USB_MUSB_GADGET config USB_MUSB_DUAL_ROLE bool "Dual Role mode" depends on ((USB=y || USB=USB_MUSB_HDRC) && (USB_GADGET=y || USB_GADGET=USB_MUSB_HDRC)) + depends on HAS_DMA help This is the default mode of working of MUSB controller where both host and gadget features are enabled. -- cgit v1.2.3-70-g09d2 From 0e06bcac79ce6bb49adee22a991206933b7ea52d Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Thu, 20 Feb 2014 09:26:25 +0100 Subject: usb: gadget: at91_udc: fix ep maxpacket initialisation This patch fixes problem with unnecessary usb_ep_set_maxpacket_limit() usage. It should not be used in at91udc_probe() function, where maxpacket values are set for field "maxpacket" of struct at91_ep, which is representation of endpoint in driver internals. Function usb_ep_set_maxpacket_limit() is called in udc_reinit() function, where struct usb_ep instances are initialised with values set previously in struct at91_ep instances. So it's very important to initialise it properly. Signed-off-by: Robert Baldyga Signed-off-by: Felipe Balbi --- drivers/usb/gadget/at91_udc.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/usb/gadget/at91_udc.c b/drivers/usb/gadget/at91_udc.c index cea8c20a142..f605ad8c190 100644 --- a/drivers/usb/gadget/at91_udc.c +++ b/drivers/usb/gadget/at91_udc.c @@ -1758,15 +1758,15 @@ static int at91udc_probe(struct platform_device *pdev) /* newer chips have more FIFO memory than rm9200 */ if (cpu_is_at91sam9260() || cpu_is_at91sam9g20()) { - usb_ep_set_maxpacket_limit(&udc->ep[0].ep, 64); - usb_ep_set_maxpacket_limit(&udc->ep[3].ep, 64); - usb_ep_set_maxpacket_limit(&udc->ep[4].ep, 512); - usb_ep_set_maxpacket_limit(&udc->ep[5].ep, 512); + udc->ep[0].maxpacket = 64; + udc->ep[3].maxpacket = 64; + udc->ep[4].maxpacket = 512; + udc->ep[5].maxpacket = 512; } else if (cpu_is_at91sam9261() || cpu_is_at91sam9g10()) { - usb_ep_set_maxpacket_limit(&udc->ep[3].ep, 64); + udc->ep[3].maxpacket = 64; } else if (cpu_is_at91sam9263()) { - usb_ep_set_maxpacket_limit(&udc->ep[0].ep, 64); - usb_ep_set_maxpacket_limit(&udc->ep[3].ep, 64); + udc->ep[0].maxpacket = 64; + udc->ep[3].maxpacket = 64; } udc->udp_baseaddr = ioremap(res->start, resource_size(res)); -- cgit v1.2.3-70-g09d2 From f3af36511e60669a2b5644d17378c7ea4e42d8b1 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Fri, 13 Dec 2013 14:19:33 -0600 Subject: usb: dwc3: gadget: always enable IOC on bulk/interrupt transfers by setting IOC always, we can recycle TRBs a lot sooner at the expense of some increased CPU load. The extra load seems to be quite minimal on OMAP5 devices (instead of 1 IRQ for one MSC transfer, we get CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS). Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 2da0a5a2803..9e878d9bc90 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -771,9 +771,6 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, trb->ctrl = DWC3_TRBCTL_ISOCHRONOUS_FIRST; else trb->ctrl = DWC3_TRBCTL_ISOCHRONOUS; - - if (!req->request.no_interrupt && !chain) - trb->ctrl |= DWC3_TRB_CTRL_IOC; break; case USB_ENDPOINT_XFER_BULK: @@ -788,6 +785,9 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, BUG(); } + if (!req->request.no_interrupt && !chain) + trb->ctrl |= DWC3_TRB_CTRL_IOC; + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { trb->ctrl |= DWC3_TRB_CTRL_ISP_IMI; trb->ctrl |= DWC3_TRB_CTRL_CSP; @@ -1855,9 +1855,6 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep, return 1; } - if ((event->status & DEPEVT_STATUS_IOC) && - (trb->ctrl & DWC3_TRB_CTRL_IOC)) - return 0; return 1; } -- cgit v1.2.3-70-g09d2 From 183ca11179f6d3b99e0431bae6acb84350b82dea Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 25 Feb 2014 14:08:51 -0600 Subject: usb: dwc3: core: define bit 10 of GCTL register This bit is necessary for implemeting workaround for known issue with some revisions of this core. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.h | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 69c4583933d..f2693b63b71 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -157,6 +157,7 @@ #define DWC3_GCTL_PRTCAP_OTG 3 #define DWC3_GCTL_CORESOFTRESET (1 << 11) +#define DWC3_GCTL_SOFITPSYNC (1 << 10) #define DWC3_GCTL_SCALEDOWN(n) ((n) << 4) #define DWC3_GCTL_SCALEDOWN_MASK DWC3_GCTL_SCALEDOWN(3) #define DWC3_GCTL_DISSCRAMBLE (1 << 3) -- cgit v1.2.3-70-g09d2 From 32a4a135847b1e600c64756b7c7c7a91eb2f0aa9 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 25 Feb 2014 14:00:13 -0600 Subject: usb: dwc3: workaround: clock gating issues Revisions between 2.10a and 2.50a (included) have a known issue which may cause xHCI compliance tests to fail and/or quality issues with Isochronous transactions. Note that this issue only impacts certain configurations of those revisions, namely the ones which have clock gating enabled. The suggested workaround is to disable clock gating in known broken revisions, make sure HW LPM is disabled and set GCTL.SOFITPSYNC to 1. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.c | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index a49217ae353..785feeeac3c 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -314,7 +314,25 @@ static int dwc3_core_init(struct dwc3 *dwc) switch (DWC3_GHWPARAMS1_EN_PWROPT(dwc->hwparams.hwparams1)) { case DWC3_GHWPARAMS1_EN_PWROPT_CLK: - reg &= ~DWC3_GCTL_DSBLCLKGTNG; + /** + * WORKAROUND: DWC3 revisions between 2.10a and 2.50a have an + * issue which would cause xHCI compliance tests to fail. + * + * Because of that we cannot enable clock gating on such + * configurations. + * + * Refers to: + * + * STAR#9000588375: Clock Gating, SOF Issues when ref_clk-Based + * SOF/ITP Mode Used + */ + if ((dwc->dr_mode == USB_DR_MODE_HOST || + dwc->dr_mode == USB_DR_MODE_OTG) && + (dwc->revision >= DWC3_REVISION_210A && + dwc->revision <= DWC3_REVISION_250A)) + reg |= DWC3_GCTL_DSBLCLKGTNG | DWC3_GCTL_SOFITPSYNC; + else + reg &= ~DWC3_GCTL_DSBLCLKGTNG; break; default: dev_dbg(dwc->dev, "No power optimization available\n"); @@ -479,6 +497,14 @@ static int dwc3_probe(struct platform_device *pdev) goto err0; } + if (IS_ENABLED(CONFIG_USB_DWC3_HOST)) + dwc->dr_mode = USB_DR_MODE_HOST; + else if (IS_ENABLED(CONFIG_USB_DWC3_GADGET)) + dwc->dr_mode = USB_DR_MODE_PERIPHERAL; + + if (dwc->dr_mode == USB_DR_MODE_UNKNOWN) + dwc->dr_mode = USB_DR_MODE_OTG; + ret = dwc3_core_init(dwc); if (ret) { dev_err(dev, "failed to initialize core\n"); @@ -494,14 +520,6 @@ static int dwc3_probe(struct platform_device *pdev) goto err1; } - if (IS_ENABLED(CONFIG_USB_DWC3_HOST)) - dwc->dr_mode = USB_DR_MODE_HOST; - else if (IS_ENABLED(CONFIG_USB_DWC3_GADGET)) - dwc->dr_mode = USB_DR_MODE_PERIPHERAL; - - if (dwc->dr_mode == USB_DR_MODE_UNKNOWN) - dwc->dr_mode = USB_DR_MODE_OTG; - switch (dwc->dr_mode) { case USB_DR_MODE_PERIPHERAL: dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); -- cgit v1.2.3-70-g09d2 From e1dadd3b0f277e59847d6b7de86ff67306bee4b1 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 25 Feb 2014 14:47:54 -0600 Subject: usb: dwc3: workaround: bogus hibernation events Revision 2.20a of the core has a known issue which would generate bogus hibernation events _and_ random failures on USB CV TD.9.23 test case. The suggested workaround is to ignore hibernation events which don't match currently connected speed. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 9e878d9bc90..7f4e6dd63c0 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2378,6 +2378,30 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc, dev_vdbg(dwc->dev, "%s link %d\n", __func__, dwc->link_state); } +static void dwc3_gadget_hibernation_interrupt(struct dwc3 *dwc, + unsigned int evtinfo) +{ + unsigned int is_ss = evtinfo & BIT(4); + + /** + * WORKAROUND: DWC3 revison 2.20a with hibernation support + * have a known issue which can cause USB CV TD.9.23 to fail + * randomly. + * + * Because of this issue, core could generate bogus hibernation + * events which SW needs to ignore. + * + * Refers to: + * + * STAR#9000546576: Device Mode Hibernation: Issue in USB 2.0 + * Device Fallback from SuperSpeed + */ + if (is_ss ^ (dwc->speed == USB_SPEED_SUPER)) + return; + + /* enter hibernation here */ +} + static void dwc3_gadget_interrupt(struct dwc3 *dwc, const struct dwc3_event_devt *event) { @@ -2394,6 +2418,13 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc, case DWC3_DEVICE_EVENT_WAKEUP: dwc3_gadget_wakeup_interrupt(dwc); break; + case DWC3_DEVICE_EVENT_HIBER_REQ: + if (dev_WARN_ONCE(dwc->dev, !dwc->has_hibernation, + "unexpected hibernation event\n")) + break; + + dwc3_gadget_hibernation_interrupt(dwc, event->event_info); + break; case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE: dwc3_gadget_linksts_change_interrupt(dwc, event->event_info); break; -- cgit v1.2.3-70-g09d2 From f2b685d5aad81facb85542227f5f8215eda8f4ce Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 19 Dec 2013 12:12:37 -0600 Subject: usb: dwc3: cleanup struct dwc3 move 1-bit flags to the bottom of the structure, sort all bit flags alphabetically, add documentation which was missing. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.h | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index f2693b63b71..eab166a02cd 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -616,14 +616,6 @@ struct dwc3_scratchpad_array { * @usb3_phy: pointer to USB3 PHY * @dcfg: saved contents of DCFG register * @gctl: saved contents of GCTL register - * @is_selfpowered: true when we are selfpowered - * @three_stage_setup: set if we perform a three phase setup - * @ep0_bounced: true when we used bounce buffer - * @ep0_expect_in: true when we expect a DATA IN transfer - * @start_config_issued: true when StartConfig command has been issued - * @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround - * @needs_fifo_resize: not all users might want fifo resizing, flag it - * @resize_fifos: tells us it's ok to reconfigure our TxFIFO sizes. * @isoch_delay: wValue from Set Isochronous Delay request; * @u2sel: parameter from Set SEL request. * @u2pel: parameter from Set SEL request. @@ -638,6 +630,19 @@ struct dwc3_scratchpad_array { * @mem: points to start of memory which is used for this struct. * @hwparams: copy of hwparams registers * @root: debugfs root folder pointer + * @regset: debugfs pointer to regdump file + * @test_mode: true when we're entering a USB test mode + * @test_mode_nr: test feature selector + * @delayed_status: true when gadget driver asks for delayed status + * @ep0_bounced: true when we used bounce buffer + * @ep0_expect_in: true when we expect a DATA IN transfer + * @is_selfpowered: true when we are selfpowered + * @needs_fifo_resize: not all users might want fifo resizing, flag it + * @pullups_connected: true when Run/Stop bit is set + * @resize_fifos: tells us it's ok to reconfigure our TxFIFO sizes. + * @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround + * @start_config_issued: true when StartConfig command has been issued + * @three_stage_setup: set if we perform a three phase setup */ struct dwc3 { struct usb_ctrlrequest *ctrl_req; @@ -697,17 +702,6 @@ struct dwc3 { #define DWC3_REVISION_240A 0x5533240a #define DWC3_REVISION_250A 0x5533250a - unsigned is_selfpowered:1; - unsigned three_stage_setup:1; - unsigned ep0_bounced:1; - unsigned ep0_expect_in:1; - unsigned start_config_issued:1; - unsigned setup_packet_pending:1; - unsigned delayed_status:1; - unsigned needs_fifo_resize:1; - unsigned resize_fifos:1; - unsigned pullups_connected:1; - enum dwc3_ep0_next ep0_next_event; enum dwc3_ep0_state ep0state; enum dwc3_link_state link_state; @@ -731,6 +725,17 @@ struct dwc3 { u8 test_mode; u8 test_mode_nr; + + unsigned delayed_status:1; + unsigned ep0_bounced:1; + unsigned ep0_expect_in:1; + unsigned is_selfpowered:1; + unsigned needs_fifo_resize:1; + unsigned pullups_connected:1; + unsigned resize_fifos:1; + unsigned setup_packet_pending:1; + unsigned start_config_issued:1; + unsigned three_stage_setup:1; }; /* -------------------------------------------------------------------------- */ -- cgit v1.2.3-70-g09d2 From 81bc5599d66d741552fd8e519bcb91ed8d683786 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 19 Dec 2013 12:14:29 -0600 Subject: usb: dwc3: add has_hibernation flag this will tell driver that this version of the core was configured with hibernation feature enabled. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index eab166a02cd..a2b01785bb3 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -636,6 +636,7 @@ struct dwc3_scratchpad_array { * @delayed_status: true when gadget driver asks for delayed status * @ep0_bounced: true when we used bounce buffer * @ep0_expect_in: true when we expect a DATA IN transfer + * @has_hibernation: true when dwc3 was configured with Hibernation * @is_selfpowered: true when we are selfpowered * @needs_fifo_resize: not all users might want fifo resizing, flag it * @pullups_connected: true when Run/Stop bit is set @@ -729,6 +730,7 @@ struct dwc3 { unsigned delayed_status:1; unsigned ep0_bounced:1; unsigned ep0_expect_in:1; + unsigned has_hibernation:1; unsigned is_selfpowered:1; unsigned needs_fifo_resize:1; unsigned pullups_connected:1; -- cgit v1.2.3-70-g09d2 From 4cfcf876767ab845e1db0c0b2671db27b1a3586c Mon Sep 17 00:00:00 2001 From: Paul Zimmerman Date: Fri, 27 Apr 2012 13:56:23 +0300 Subject: usb: dwc3: add 'saved_state' field to dwc3_ep structure This extra field will save endpoint state when we're about to enter hibernation. It will be used later to restore the endpoint state when resuming. Signed-off-by: Paul Zimmerman Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index a2b01785bb3..4c14796a5ff 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -394,6 +394,7 @@ struct dwc3_event_buffer { * @busy_slot: first slot which is owned by HW * @desc: usb_endpoint_descriptor pointer * @dwc: pointer to DWC controller + * @saved_state: ep state saved during hibernation * @flags: endpoint flags (wedged, stalled, ...) * @current_trb: index of current used trb * @number: endpoint number (1 - 15) @@ -416,6 +417,7 @@ struct dwc3_ep { const struct usb_ss_ep_comp_descriptor *comp_desc; struct dwc3 *dwc; + u32 saved_state; unsigned flags; #define DWC3_EP_ENABLED (1 << 0) #define DWC3_EP_STALL (1 << 1) -- cgit v1.2.3-70-g09d2 From 911f1f88cadf4b64bad5aa4c257d72494a62f928 Mon Sep 17 00:00:00 2001 From: Paul Zimmerman Date: Fri, 27 Apr 2012 13:35:15 +0300 Subject: usb: dwc3: gadget: implement dwc3_gadget_get_link_state This function will be used during hibernation to get the current link state. It will be needed at least for Hibernation support. Signed-off-by: Paul Zimmerman Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 16 ++++++++++++++++ drivers/usb/dwc3/gadget.h | 1 + 2 files changed, 17 insertions(+) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 7f4e6dd63c0..7d00738b4b6 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -67,6 +67,22 @@ int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode) return 0; } +/** + * dwc3_gadget_get_link_state - Gets current state of USB Link + * @dwc: pointer to our context structure + * + * Caller should take care of locking. This function will + * return the link state on success (>= 0) or -ETIMEDOUT. + */ +int dwc3_gadget_get_link_state(struct dwc3 *dwc) +{ + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + + return DWC3_DSTS_USBLNKST(reg); +} + /** * dwc3_gadget_set_link_state - Sets USB Link to a particular State * @dwc: pointer to our context structure diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h index febe1aa7b71..d1012447787 100644 --- a/drivers/usb/dwc3/gadget.h +++ b/drivers/usb/dwc3/gadget.h @@ -86,6 +86,7 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, int status); 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); void dwc3_ep0_interrupt(struct dwc3 *dwc, -- cgit v1.2.3-70-g09d2 From 265b70a73aff2a21e74f1fbe7ffaaeb4608adc98 Mon Sep 17 00:00:00 2001 From: Paul Zimmerman Date: Thu, 19 Dec 2013 12:38:49 -0600 Subject: usb: dwc3: gadget: add a 'restore' argument to set_ep_config That argument will be used in later patches when we have working hibernation support. For now, always pass it as false. The idea of this patch is to decrease to size of following patches and slowly add hibernation building blocks to the gadget side of dwc3 so that it becomes very easy to review the actual hibernation code. [ balbi@ti.com : rewrote patch on top of current tree. Added commit log. ] Signed-off-by: Paul Zimmerman Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 7d00738b4b6..87b09bd6266 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -433,7 +433,7 @@ static int dwc3_gadget_start_config(struct dwc3 *dwc, struct dwc3_ep *dep) static int dwc3_gadget_set_ep_config(struct dwc3 *dwc, struct dwc3_ep *dep, const struct usb_endpoint_descriptor *desc, const struct usb_ss_ep_comp_descriptor *comp_desc, - bool ignore) + bool ignore, bool restore) { struct dwc3_gadget_ep_cmd_params params; @@ -452,6 +452,11 @@ static int dwc3_gadget_set_ep_config(struct dwc3 *dwc, struct dwc3_ep *dep, if (ignore) params.param0 |= DWC3_DEPCFG_IGN_SEQ_NUM; + if (restore) { + params.param0 |= DWC3_DEPCFG_ACTION_RESTORE; + params.param2 |= dep->saved_state; + } + params.param1 = DWC3_DEPCFG_XFER_COMPLETE_EN | DWC3_DEPCFG_XFER_NOT_READY_EN; @@ -510,7 +515,7 @@ static int dwc3_gadget_set_xfer_resource(struct dwc3 *dwc, struct dwc3_ep *dep) static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, const struct usb_endpoint_descriptor *desc, const struct usb_ss_ep_comp_descriptor *comp_desc, - bool ignore) + bool ignore, bool restore) { struct dwc3 *dwc = dep->dwc; u32 reg; @@ -524,7 +529,8 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, return ret; } - ret = dwc3_gadget_set_ep_config(dwc, dep, desc, comp_desc, ignore); + ret = dwc3_gadget_set_ep_config(dwc, dep, desc, comp_desc, ignore, + restore); if (ret) return ret; @@ -675,7 +681,7 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, } spin_lock_irqsave(&dwc->lock, flags); - ret = __dwc3_gadget_ep_enable(dep, desc, ep->comp_desc, false); + ret = __dwc3_gadget_ep_enable(dep, desc, ep->comp_desc, false, false); spin_unlock_irqrestore(&dwc->lock, flags); return ret; @@ -1565,14 +1571,16 @@ static int dwc3_gadget_start(struct usb_gadget *g, dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); dep = dwc->eps[0]; - ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false); + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false, + false); if (ret) { dev_err(dwc->dev, "failed to enable %s\n", dep->name); goto err2; } dep = dwc->eps[1]; - ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false); + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false, + false); if (ret) { dev_err(dwc->dev, "failed to enable %s\n", dep->name); goto err3; @@ -2276,14 +2284,16 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) } dep = dwc->eps[0]; - ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, true); + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, true, + false); if (ret) { dev_err(dwc->dev, "failed to enable %s\n", dep->name); return; } dep = dwc->eps[1]; - ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, true); + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, true, + false); if (ret) { dev_err(dwc->dev, "failed to enable %s\n", dep->name); return; @@ -2738,12 +2748,14 @@ int dwc3_gadget_resume(struct dwc3 *dwc) dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); dep = dwc->eps[0]; - ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false); + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false, + false); if (ret) goto err0; dep = dwc->eps[1]; - ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false); + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false, + false); if (ret) goto err1; -- cgit v1.2.3-70-g09d2 From 0ffcaf3798bfd83aed0a650cddb9d1ee825ff2bb Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 19 Dec 2013 13:04:28 -0600 Subject: usb: dwc3: core: allocate scratch buffers We must read HWPARAMS4 register to figure out how many scratch buffers we should allocate. Later patch will use "Set Scratchpad Buffer Array" command to pass the pointer to the IP so it can be used during hibernation. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++++ drivers/usb/dwc3/core.h | 6 +++ 2 files changed, 111 insertions(+) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 785feeeac3c..8c627c9a513 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -242,6 +242,90 @@ static void dwc3_event_buffers_cleanup(struct dwc3 *dwc) } } +static int dwc3_alloc_scratch_buffers(struct dwc3 *dwc) +{ + if (!dwc->has_hibernation) + return 0; + + if (!dwc->nr_scratch) + return 0; + + dwc->scratchbuf = kmalloc_array(dwc->nr_scratch, + DWC3_SCRATCHBUF_SIZE, GFP_KERNEL); + if (!dwc->scratchbuf) + return -ENOMEM; + + return 0; +} + +static int dwc3_setup_scratch_buffers(struct dwc3 *dwc) +{ + dma_addr_t scratch_addr; + u32 param; + int ret; + + if (!dwc->has_hibernation) + return 0; + + if (!dwc->nr_scratch) + return 0; + + /* should never fall here */ + if (!WARN_ON(dwc->scratchbuf)) + return 0; + + scratch_addr = dma_map_single(dwc->dev, dwc->scratchbuf, + dwc->nr_scratch * DWC3_SCRATCHBUF_SIZE, + DMA_BIDIRECTIONAL); + if (dma_mapping_error(dwc->dev, scratch_addr)) { + dev_err(dwc->dev, "failed to map scratch buffer\n"); + ret = -EFAULT; + goto err0; + } + + dwc->scratch_addr = scratch_addr; + + param = lower_32_bits(scratch_addr); + + ret = dwc3_send_gadget_generic_command(dwc, + DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO, param); + if (ret < 0) + goto err1; + + param = upper_32_bits(scratch_addr); + + ret = dwc3_send_gadget_generic_command(dwc, + DWC3_DGCMD_SET_SCRATCHPAD_ADDR_HI, param); + if (ret < 0) + goto err1; + + return 0; + +err1: + dma_unmap_single(dwc->dev, dwc->scratch_addr, dwc->nr_scratch * + DWC3_SCRATCHBUF_SIZE, DMA_BIDIRECTIONAL); + +err0: + return ret; +} + +static void dwc3_free_scratch_buffers(struct dwc3 *dwc) +{ + if (!dwc->has_hibernation) + return; + + if (!dwc->nr_scratch) + return; + + /* should never fall here */ + if (!WARN_ON(dwc->scratchbuf)) + return; + + dma_unmap_single(dwc->dev, dwc->scratch_addr, dwc->nr_scratch * + DWC3_SCRATCHBUF_SIZE, DMA_BIDIRECTIONAL); + kfree(dwc->scratchbuf); +} + static void dwc3_core_num_eps(struct dwc3 *dwc) { struct dwc3_hwparams *parms = &dwc->hwparams; @@ -277,6 +361,7 @@ static void dwc3_cache_hwparams(struct dwc3 *dwc) static int dwc3_core_init(struct dwc3 *dwc) { unsigned long timeout; + u32 hwparams4 = dwc->hwparams.hwparams4; u32 reg; int ret; @@ -334,6 +419,10 @@ static int dwc3_core_init(struct dwc3 *dwc) else reg &= ~DWC3_GCTL_DSBLCLKGTNG; break; + case DWC3_GHWPARAMS1_EN_PWROPT_HIB: + /* enable hibernation here */ + dwc->nr_scratch = DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS(hwparams4); + break; default: dev_dbg(dwc->dev, "No power optimization available\n"); } @@ -351,14 +440,30 @@ static int dwc3_core_init(struct dwc3 *dwc) dwc3_writel(dwc->regs, DWC3_GCTL, reg); + ret = dwc3_alloc_scratch_buffers(dwc); + if (ret) + goto err1; + + ret = dwc3_setup_scratch_buffers(dwc); + if (ret) + goto err2; + return 0; +err2: + dwc3_free_scratch_buffers(dwc); + +err1: + usb_phy_shutdown(dwc->usb2_phy); + usb_phy_shutdown(dwc->usb3_phy); + err0: return ret; } static void dwc3_core_exit(struct dwc3 *dwc) { + dwc3_free_scratch_buffers(dwc); usb_phy_shutdown(dwc->usb2_phy); usb_phy_shutdown(dwc->usb3_phy); } diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 4c14796a5ff..bbb5b78eaf0 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -36,6 +36,7 @@ #define DWC3_ENDPOINTS_NUM 32 #define DWC3_XHCI_RESOURCES_NUM 2 +#define DWC3_SCRATCHBUF_SIZE 4096 /* each buffer is assumed to be 4KiB */ #define DWC3_EVENT_SIZE 4 /* bytes */ #define DWC3_EVENT_MAX_NUM 64 /* 2 events/endpoint */ #define DWC3_EVENT_BUFFERS_SIZE (DWC3_EVENT_SIZE * DWC3_EVENT_MAX_NUM) @@ -601,6 +602,7 @@ struct dwc3_scratchpad_array { * @ep0_trb: dma address of ep0_trb * @ep0_usb_req: dummy req used while handling STD USB requests * @ep0_bounce_addr: dma address of ep0_bounce + * @scratch_addr: dma address of scratchbuf * @lock: for synchronizing * @dev: pointer to our struct device * @xhci: pointer to our xHCI child @@ -609,6 +611,7 @@ struct dwc3_scratchpad_array { * @gadget_driver: pointer to the gadget driver * @regs: base address for our registers * @regs_size: address space size + * @nr_scratch: number of scratch buffers * @num_event_buffers: calculated number of event buffers * @u1u2: only used on revisions <1.83a for workaround * @maximum_speed: maximum speed requested (mainly for testing purposes) @@ -651,10 +654,12 @@ struct dwc3 { struct usb_ctrlrequest *ctrl_req; struct dwc3_trb *ep0_trb; void *ep0_bounce; + void *scratchbuf; u8 *setup_buf; dma_addr_t ctrl_req_addr; dma_addr_t ep0_trb_addr; dma_addr_t ep0_bounce_addr; + dma_addr_t scratch_addr; struct dwc3_request ep0_usb_req; /* device lock */ @@ -683,6 +688,7 @@ struct dwc3 { u32 dcfg; u32 gctl; + u32 nr_scratch; u32 num_event_buffers; u32 u1u2; u32 maximum_speed; -- cgit v1.2.3-70-g09d2 From 835fadb40c952562e4586b719ce15022ef7ced87 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 19 Dec 2013 14:02:53 -0600 Subject: usb: dwc3: core: fix indentation no functional changes, just converting spaces into tab. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index bbb5b78eaf0..00b057860d7 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -320,7 +320,7 @@ /* Device Endpoint Command Register */ #define DWC3_DEPCMD_PARAM_SHIFT 16 #define DWC3_DEPCMD_PARAM(x) ((x) << DWC3_DEPCMD_PARAM_SHIFT) -#define DWC3_DEPCMD_GET_RSC_IDX(x) (((x) >> DWC3_DEPCMD_PARAM_SHIFT) & 0x7f) +#define DWC3_DEPCMD_GET_RSC_IDX(x) (((x) >> DWC3_DEPCMD_PARAM_SHIFT) & 0x7f) #define DWC3_DEPCMD_STATUS(x) (((x) >> 15) & 1) #define DWC3_DEPCMD_HIPRI_FORCERM (1 << 11) #define DWC3_DEPCMD_CMDACT (1 << 10) -- cgit v1.2.3-70-g09d2 From 7b2a0368bbc9bc4a7936d8587eaff4b8c35b2247 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 19 Dec 2013 13:43:19 -0600 Subject: usb: dwc3: gadget: set KEEP_CONNECT in case of hibernation if we have hibernation configured, Databook instructs us to set KEEP_CONNECT bit together with RUN_STOP bit, in step 9 of section 12.3.6.1 Initialization for Hibernation Support. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 87b09bd6266..3934d0afc18 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1409,7 +1409,7 @@ static int dwc3_gadget_set_selfpowered(struct usb_gadget *g, return 0; } -static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on) +static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend) { u32 reg; u32 timeout = 500; @@ -1424,9 +1424,17 @@ static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on) if (dwc->revision >= DWC3_REVISION_194A) reg &= ~DWC3_DCTL_KEEP_CONNECT; reg |= DWC3_DCTL_RUN_STOP; + + if (dwc->has_hibernation) + reg |= DWC3_DCTL_KEEP_CONNECT; + dwc->pullups_connected = true; } else { reg &= ~DWC3_DCTL_RUN_STOP; + + if (dwc->has_hibernation && !suspend) + reg &= ~DWC3_DCTL_KEEP_CONNECT; + dwc->pullups_connected = false; } @@ -1464,7 +1472,7 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) is_on = !!is_on; spin_lock_irqsave(&dwc->lock, flags); - ret = dwc3_gadget_run_stop(dwc, is_on); + ret = dwc3_gadget_run_stop(dwc, is_on, false); spin_unlock_irqrestore(&dwc->lock, flags); return ret; @@ -2715,8 +2723,10 @@ void dwc3_gadget_exit(struct dwc3 *dwc) int dwc3_gadget_prepare(struct dwc3 *dwc) { - if (dwc->pullups_connected) + if (dwc->pullups_connected) { dwc3_gadget_disable_irq(dwc); + dwc3_gadget_run_stop(dwc, true, true); + } return 0; } @@ -2725,7 +2735,7 @@ void dwc3_gadget_complete(struct dwc3 *dwc) { if (dwc->pullups_connected) { dwc3_gadget_enable_irq(dwc); - dwc3_gadget_run_stop(dwc, true); + dwc3_gadget_run_stop(dwc, true, false); } } -- cgit v1.2.3-70-g09d2 From 356363bf6b2fc4785d874ef87ed2fad4b8543490 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 19 Dec 2013 16:37:05 -0600 Subject: usb: dwc3: gadget: make sure HIRD threshold is 0 in superspeed During superspeed, HIRD threshold should always be zero. Curent driver wasn't making sure that was the case. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 3934d0afc18..df9749eac6a 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2288,6 +2288,10 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) */ reg |= DWC3_DCTL_HIRD_THRES(12); + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + } else { + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~DWC3_DCTL_HIRD_THRES_MASK; dwc3_writel(dwc->regs, DWC3_DCTL, reg); } -- cgit v1.2.3-70-g09d2 From b992e6813e257269e9599ca07be8bd52df784bf5 Mon Sep 17 00:00:00 2001 From: Paul Zimmerman Date: Fri, 27 Apr 2012 14:17:35 +0300 Subject: usb: dwc3: gadget: add 'force' argument to stop_active_transfer It's not always we need to force a transfer to be removed from the core's internal cache. This extra argument will help differentiating those two cases. Signed-off-by: Paul Zimmerman Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index df9749eac6a..f1cb0984046 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -570,13 +570,13 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, return 0; } -static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum); +static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force); static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep) { struct dwc3_request *req; if (!list_empty(&dep->req_queued)) { - dwc3_stop_active_transfer(dwc, dep->number); + dwc3_stop_active_transfer(dwc, dep->number, true); /* - giveback all requests to gadget driver */ while (!list_empty(&dep->req_queued)) { @@ -1099,7 +1099,7 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) */ if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { if (list_empty(&dep->req_queued)) { - dwc3_stop_active_transfer(dwc, dep->number); + dwc3_stop_active_transfer(dwc, dep->number, true); dep->flags = DWC3_EP_ENABLED; } return 0; @@ -1185,7 +1185,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, } if (r == req) { /* wait until it is processed */ - dwc3_stop_active_transfer(dwc, dep->number); + dwc3_stop_active_transfer(dwc, dep->number, true); goto out1; } dev_err(dwc->dev, "request %p was not queued to %s\n", @@ -1881,7 +1881,7 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep, */ dep->flags = DWC3_EP_PENDING_REQUEST; } else { - dwc3_stop_active_transfer(dwc, dep->number); + dwc3_stop_active_transfer(dwc, dep->number, true); dep->flags = DWC3_EP_ENABLED; } return 1; @@ -2028,7 +2028,7 @@ static void dwc3_disconnect_gadget(struct dwc3 *dwc) } } -static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum) +static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force) { struct dwc3_ep *dep; struct dwc3_gadget_ep_cmd_params params; @@ -2060,7 +2060,8 @@ static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum) */ cmd = DWC3_DEPCMD_ENDTRANSFER; - cmd |= DWC3_DEPCMD_HIPRI_FORCERM | DWC3_DEPCMD_CMDIOC; + cmd |= force ? DWC3_DEPCMD_HIPRI_FORCERM : 0; + cmd |= DWC3_DEPCMD_CMDIOC; cmd |= DWC3_DEPCMD_PARAM(dep->resource_index); memset(¶ms, 0, sizeof(params)); ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, ¶ms); -- cgit v1.2.3-70-g09d2 From bc5ba2e0b829c9397f96df1191c7d2319ebc36d9 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 26 Feb 2014 10:17:07 -0600 Subject: usb: dwc3: gadget: call gadget driver's ->suspend/->resume When going into bus suspend/resume we _must_ call gadget driver's ->suspend/->resume callbacks accordingly. This patch implements that very feature which has been missing forever. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index f1cb0984046..9f82436b4b1 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2028,6 +2028,24 @@ static void dwc3_disconnect_gadget(struct dwc3 *dwc) } } +static void dwc3_suspend_gadget(struct dwc3 *dwc) +{ + if (dwc->gadget_driver && dwc->gadget_driver->disconnect) { + spin_unlock(&dwc->lock); + dwc->gadget_driver->suspend(&dwc->gadget); + spin_lock(&dwc->lock); + } +} + +static void dwc3_resume_gadget(struct dwc3 *dwc) +{ + if (dwc->gadget_driver && dwc->gadget_driver->disconnect) { + spin_unlock(&dwc->lock); + dwc->gadget_driver->resume(&dwc->gadget); + spin_lock(&dwc->lock); + } +} + static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force) { struct dwc3_ep *dep; @@ -2414,6 +2432,23 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc, dwc->link_state = next; + switch (next) { + case DWC3_LINK_STATE_U1: + if (dwc->speed == USB_SPEED_SUPER) + dwc3_suspend_gadget(dwc); + break; + case DWC3_LINK_STATE_U2: + case DWC3_LINK_STATE_U3: + dwc3_suspend_gadget(dwc); + break; + case DWC3_LINK_STATE_RESUME: + dwc3_resume_gadget(dwc); + break; + default: + /* do nothing */ + break; + } + dev_vdbg(dwc->dev, "%s link %d\n", __func__, dwc->link_state); } -- cgit v1.2.3-70-g09d2 From b997ada5db905e24494bcc689c7b2c07d278ca27 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 26 Jul 2012 13:26:50 +0300 Subject: usb: dwc3: gadget: pre-start Stream transfers when they're queued we need to pre-start stream transfers otherwise we will never know when to start them. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 9f82436b4b1..1730cd0928c 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1129,6 +1129,23 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) return ret; } + /* + * 4. Stream Capable Bulk Endpoints. We need to start the transfer + * right away, otherwise host will not know we have streams to be + * handled. + */ + if (dep->stream_capable) { + int ret; + + ret = __dwc3_gadget_kick_transfer(dep, 0, true); + if (ret && ret != -EBUSY) { + struct dwc3 *dwc = dep->dwc; + + dev_dbg(dwc->dev, "%s: failed to kick transfers\n", + dep->name); + } + } + return 0; } -- cgit v1.2.3-70-g09d2 From 610183051d8f9421f138c4203ca894387f9f8839 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 4 Mar 2014 09:23:50 -0600 Subject: usb: dwc3: fix randconfig build errors commit 388e5c5 (usb: dwc3: remove dwc3 dependency on host AND gadget.) created the possibility for host-only and peripheral-only dwc3 builds but left a possible randconfig build error when host-only builds are selected. Cc: # v3.8+ Reported-by: Jim Davis Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.h | 33 +++++++++++++++++++++++++++++++++ drivers/usb/dwc3/gadget.h | 13 ------------- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 00b057860d7..5b92c9ed89b 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -872,6 +872,19 @@ union dwc3_event { struct dwc3_event_gevt gevt; }; +/** + * struct dwc3_gadget_ep_cmd_params - representation of endpoint command + * parameters + * @param2: third parameter + * @param1: second parameter + * @param0: first parameter + */ +struct dwc3_gadget_ep_cmd_params { + u32 param2; + u32 param1; + u32 param0; +}; + /* * DWC3 Features to be used as Driver Data */ @@ -897,11 +910,31 @@ static inline void dwc3_host_exit(struct dwc3 *dwc) #if IS_ENABLED(CONFIG_USB_DWC3_GADGET) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) int dwc3_gadget_init(struct dwc3 *dwc); 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 *dwc, unsigned ep, + unsigned cmd, struct dwc3_gadget_ep_cmd_params *params); +int dwc3_send_gadget_generic_command(struct dwc3 *dwc, int cmd, u32 param); #else static inline int dwc3_gadget_init(struct dwc3 *dwc) { return 0; } static inline void dwc3_gadget_exit(struct dwc3 *dwc) { } +static inline int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode) +{ return 0; } +static inline int dwc3_gadget_get_link_state(struct dwc3 *dwc) +{ return 0; } +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 *dwc, unsigned ep, + unsigned cmd, struct dwc3_gadget_ep_cmd_params *params) +{ return 0; } +static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc, + int cmd, u32 param) +{ return 0; } #endif /* power management interface */ diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h index d1012447787..a0ee75b68a8 100644 --- a/drivers/usb/dwc3/gadget.h +++ b/drivers/usb/dwc3/gadget.h @@ -56,12 +56,6 @@ struct dwc3; /* DEPXFERCFG parameter 0 */ #define DWC3_DEPXFERCFG_NUM_XFER_RES(n) ((n) & 0xffff) -struct dwc3_gadget_ep_cmd_params { - u32 param2; - u32 param1; - u32 param0; -}; - /* -------------------------------------------------------------------------- */ #define to_dwc3_request(r) (container_of(r, struct dwc3_request, request)) @@ -85,10 +79,6 @@ static inline void dwc3_gadget_move_request_queued(struct dwc3_request *req) void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, int status); -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); - void dwc3_ep0_interrupt(struct dwc3 *dwc, const struct dwc3_event_depevt *event); void dwc3_ep0_out_start(struct dwc3 *dwc); @@ -96,9 +86,6 @@ 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 dwc3_send_gadget_ep_cmd(struct dwc3 *dwc, unsigned ep, - unsigned cmd, struct dwc3_gadget_ep_cmd_params *params); -int dwc3_send_gadget_generic_command(struct dwc3 *dwc, int cmd, u32 param); /** * dwc3_gadget_ep_get_transfer_index - Gets transfer index from HW -- cgit v1.2.3-70-g09d2 From dbf5aaf7ce29fb623d4e74818bc4fb3e9c84632e Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 4 Mar 2014 09:35:02 -0600 Subject: usb: dwc3: define more revisions few new revisions of the core have been released, add them to our list of revisions so we can apply workarounds if necessary. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 5b92c9ed89b..535bb6e1f2b 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -710,6 +710,9 @@ struct dwc3 { #define DWC3_REVISION_230A 0x5533230a #define DWC3_REVISION_240A 0x5533240a #define DWC3_REVISION_250A 0x5533250a +#define DWC3_REVISION_260A 0x5533260a +#define DWC3_REVISION_270A 0x5533270a +#define DWC3_REVISION_280A 0x5533280a enum dwc3_ep0_next ep0_next_event; enum dwc3_ep0_state ep0state; -- cgit v1.2.3-70-g09d2 From 8d7212bc4107b343e93c13aa30c70b845f7f94ad Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Mon, 25 Nov 2013 15:31:26 +0530 Subject: Documentation: dt bindings: move ..usb/usb-phy.txt to ..phy/ti-phy.txt Since now we have a separate folder for phy, move the PHY dt binding documentation of TI to that folder. Signed-off-by: Kishon Vijay Abraham I Signed-off-by: Felipe Balbi --- Documentation/devicetree/bindings/phy/ti-phy.txt | 48 +++++++++++++++++++++++ Documentation/devicetree/bindings/usb/usb-phy.txt | 48 ----------------------- 2 files changed, 48 insertions(+), 48 deletions(-) create mode 100644 Documentation/devicetree/bindings/phy/ti-phy.txt delete mode 100644 Documentation/devicetree/bindings/usb/usb-phy.txt diff --git a/Documentation/devicetree/bindings/phy/ti-phy.txt b/Documentation/devicetree/bindings/phy/ti-phy.txt new file mode 100644 index 00000000000..207e14cc650 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/ti-phy.txt @@ -0,0 +1,48 @@ +TI PHY: DT DOCUMENTATION FOR PHYs in TI PLATFORMs + +OMAP USB2 PHY + +Required properties: + - compatible: Should be "ti,omap-usb2" + - reg : Address and length of the register set for the device. + - #phy-cells: determine the number of cells that should be given in the + phandle while referencing this phy. + +Optional properties: + - ctrl-module : phandle of the control module used by PHY driver to power on + the PHY. + +This is usually a subnode of ocp2scp to which it is connected. + +usb2phy@4a0ad080 { + compatible = "ti,omap-usb2"; + reg = <0x4a0ad080 0x58>; + ctrl-module = <&omap_control_usb>; + #phy-cells = <0>; +}; + +TI PIPE3 PHY + +Required properties: + - compatible: Should be "ti,phy-usb3". "ti,omap-usb3" is deprecated. + - reg : Address and length of the register set for the device. + - reg-names: The names of the register addresses corresponding to the registers + filled in "reg". + - #phy-cells: determine the number of cells that should be given in the + phandle while referencing this phy. + +Optional properties: + - ctrl-module : phandle of the control module used by PHY driver to power on + the PHY. + +This is usually a subnode of ocp2scp to which it is connected. + +usb3phy@4a084400 { + compatible = "ti,phy-usb3"; + reg = <0x4a084400 0x80>, + <0x4a084800 0x64>, + <0x4a084c00 0x40>; + reg-names = "phy_rx", "phy_tx", "pll_ctrl"; + ctrl-module = <&omap_control_usb>; + #phy-cells = <0>; +}; diff --git a/Documentation/devicetree/bindings/usb/usb-phy.txt b/Documentation/devicetree/bindings/usb/usb-phy.txt deleted file mode 100644 index c0245c88898..00000000000 --- a/Documentation/devicetree/bindings/usb/usb-phy.txt +++ /dev/null @@ -1,48 +0,0 @@ -USB PHY - -OMAP USB2 PHY - -Required properties: - - compatible: Should be "ti,omap-usb2" - - reg : Address and length of the register set for the device. - - #phy-cells: determine the number of cells that should be given in the - phandle while referencing this phy. - -Optional properties: - - ctrl-module : phandle of the control module used by PHY driver to power on - the PHY. - -This is usually a subnode of ocp2scp to which it is connected. - -usb2phy@4a0ad080 { - compatible = "ti,omap-usb2"; - reg = <0x4a0ad080 0x58>; - ctrl-module = <&omap_control_usb>; - #phy-cells = <0>; -}; - -OMAP USB3 PHY - -Required properties: - - compatible: Should be "ti,omap-usb3" - - reg : Address and length of the register set for the device. - - reg-names: The names of the register addresses corresponding to the registers - filled in "reg". - - #phy-cells: determine the number of cells that should be given in the - phandle while referencing this phy. - -Optional properties: - - ctrl-module : phandle of the control module used by PHY driver to power on - the PHY. - -This is usually a subnode of ocp2scp to which it is connected. - -usb3phy@4a084400 { - compatible = "ti,omap-usb3"; - reg = <0x4a084400 0x80>, - <0x4a084800 0x64>, - <0x4a084c00 0x40>; - reg-names = "phy_rx", "phy_tx", "pll_ctrl"; - ctrl-module = <&omap_control_usb>; - #phy-cells = <0>; -}; -- cgit v1.2.3-70-g09d2 From 122f06e60f90a43d9b2fb30662af688dfb759379 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Mon, 3 Mar 2014 17:08:10 +0530 Subject: usb: dwc3: core: support optional PHYs Since PHYs for dwc3 is optional (not all SoCs having PHYs for DWC3 should be programmed), do not return from probe if the USB PHY library returns -ENODEV as that indicates the platform does not have a programmable PHY. While this can be considered as a temporary fix, a long term solution would be to add 'nop' PHY for platforms that does not have programmable PHY. Signed-off-by: Kishon Vijay Abraham I Reviewed-by: Roger Quadros Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.c | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 8c627c9a513..c89570cc068 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -534,32 +534,26 @@ static int dwc3_probe(struct platform_device *pdev) if (IS_ERR(dwc->usb2_phy)) { ret = PTR_ERR(dwc->usb2_phy); - - /* - * if -ENXIO is returned, it means PHY layer wasn't - * enabled, so it makes no sense to return -EPROBE_DEFER - * in that case, since no PHY driver will ever probe. - */ - if (ret == -ENXIO) + if (ret == -ENXIO || ret == -ENODEV) { + dwc->usb2_phy = NULL; + } else if (ret == -EPROBE_DEFER) { return ret; - - dev_err(dev, "no usb2 phy configured\n"); - return -EPROBE_DEFER; + } else { + dev_err(dev, "no usb2 phy configured\n"); + return ret; + } } if (IS_ERR(dwc->usb3_phy)) { ret = PTR_ERR(dwc->usb3_phy); - - /* - * if -ENXIO is returned, it means PHY layer wasn't - * enabled, so it makes no sense to return -EPROBE_DEFER - * in that case, since no PHY driver will ever probe. - */ - if (ret == -ENXIO) + if (ret == -ENXIO || ret == -ENODEV) { + dwc->usb3_phy = NULL; + } else if (ret == -EPROBE_DEFER) { return ret; - - dev_err(dev, "no usb3 phy configured\n"); - return -EPROBE_DEFER; + } else { + dev_err(dev, "no usb3 phy configured\n"); + return ret; + } } dwc->xhci_resources[0].start = res->start; -- cgit v1.2.3-70-g09d2 From 57303488cd37da58263e842de134dc65f7c626d5 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Mon, 3 Mar 2014 17:08:11 +0530 Subject: usb: dwc3: adapt dwc3 core to use Generic PHY Framework Adapted dwc3 core to use the Generic PHY Framework. So for init, exit, power_on and power_off the following APIs are used phy_init(), phy_exit(), phy_power_on() and phy_power_off(). However using the old USB phy library wont be removed till the PHYs of all other SoC's using dwc3 core is adapted to the Generic PHY Framework. Signed-off-by: Kishon Vijay Abraham I Signed-off-by: Felipe Balbi --- Documentation/devicetree/bindings/usb/dwc3.txt | 6 +- drivers/usb/dwc3/core.c | 80 +++++++++++++++++++++++++- drivers/usb/dwc3/core.h | 7 +++ 3 files changed, 88 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/dwc3.txt b/Documentation/devicetree/bindings/usb/dwc3.txt index e807635f9e1..471366d6a12 100644 --- a/Documentation/devicetree/bindings/usb/dwc3.txt +++ b/Documentation/devicetree/bindings/usb/dwc3.txt @@ -6,11 +6,13 @@ Required properties: - compatible: must be "snps,dwc3" - reg : Address and length of the register set for the device - interrupts: Interrupts used by the dwc3 controller. + +Optional properties: - usb-phy : array of phandle for the PHY device. The first element in the array is expected to be a handle to the USB2/HS PHY and the second element is expected to be a handle to the USB3/SS PHY - -Optional properties: + - phys: from the *Generic PHY* bindings + - phy-names: from the *Generic PHY* bindings - tx-fifo-resize: determines if the FIFO *has* to be reallocated. This is usually a subnode to DWC3 glue to which it is connected. diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index c89570cc068..d001417e8e3 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -61,9 +61,10 @@ void dwc3_set_mode(struct dwc3 *dwc, u32 mode) * dwc3_core_soft_reset - Issues core soft reset and PHY reset * @dwc: pointer to our context structure */ -static void dwc3_core_soft_reset(struct dwc3 *dwc) +static int dwc3_core_soft_reset(struct dwc3 *dwc) { u32 reg; + int ret; /* Before Resetting PHY, put Core in Reset */ reg = dwc3_readl(dwc->regs, DWC3_GCTL); @@ -82,6 +83,15 @@ static void dwc3_core_soft_reset(struct dwc3 *dwc) usb_phy_init(dwc->usb2_phy); usb_phy_init(dwc->usb3_phy); + ret = phy_init(dwc->usb2_generic_phy); + if (ret < 0) + return ret; + + ret = phy_init(dwc->usb3_generic_phy); + if (ret < 0) { + phy_exit(dwc->usb2_generic_phy); + return ret; + } mdelay(100); /* Clear USB3 PHY reset */ @@ -100,6 +110,8 @@ static void dwc3_core_soft_reset(struct dwc3 *dwc) reg = dwc3_readl(dwc->regs, DWC3_GCTL); reg &= ~DWC3_GCTL_CORESOFTRESET; dwc3_writel(dwc->regs, DWC3_GCTL, reg); + + return 0; } /** @@ -391,7 +403,9 @@ static int dwc3_core_init(struct dwc3 *dwc) cpu_relax(); } while (true); - dwc3_core_soft_reset(dwc); + ret = dwc3_core_soft_reset(dwc); + if (ret) + goto err0; reg = dwc3_readl(dwc->regs, DWC3_GCTL); reg &= ~DWC3_GCTL_SCALEDOWN_MASK; @@ -456,6 +470,8 @@ err2: err1: usb_phy_shutdown(dwc->usb2_phy); usb_phy_shutdown(dwc->usb3_phy); + phy_exit(dwc->usb2_generic_phy); + phy_exit(dwc->usb3_generic_phy); err0: return ret; @@ -466,6 +482,8 @@ static void dwc3_core_exit(struct dwc3 *dwc) dwc3_free_scratch_buffers(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); } #define DWC3_ALIGN_MASK (16 - 1) @@ -556,6 +574,32 @@ static int dwc3_probe(struct platform_device *pdev) } } + dwc->usb2_generic_phy = devm_phy_get(dev, "usb2-phy"); + if (IS_ERR(dwc->usb2_generic_phy)) { + ret = PTR_ERR(dwc->usb2_generic_phy); + if (ret == -ENOSYS || ret == -ENODEV) { + dwc->usb2_generic_phy = NULL; + } else if (ret == -EPROBE_DEFER) { + return ret; + } else { + dev_err(dev, "no usb2 phy configured\n"); + return ret; + } + } + + dwc->usb3_generic_phy = devm_phy_get(dev, "usb3-phy"); + if (IS_ERR(dwc->usb3_generic_phy)) { + ret = PTR_ERR(dwc->usb3_generic_phy); + if (ret == -ENOSYS || ret == -ENODEV) { + dwc->usb3_generic_phy = NULL; + } else if (ret == -EPROBE_DEFER) { + return ret; + } else { + dev_err(dev, "no usb3 phy configured\n"); + return ret; + } + } + dwc->xhci_resources[0].start = res->start; dwc->xhci_resources[0].end = dwc->xhci_resources[0].start + DWC3_XHCI_REGS_END; @@ -612,11 +656,18 @@ static int dwc3_probe(struct platform_device *pdev) usb_phy_set_suspend(dwc->usb2_phy, 0); usb_phy_set_suspend(dwc->usb3_phy, 0); + ret = phy_power_on(dwc->usb2_generic_phy); + if (ret < 0) + goto err1; + + ret = phy_power_on(dwc->usb3_generic_phy); + if (ret < 0) + goto err_usb2phy_power; ret = dwc3_event_buffers_setup(dwc); if (ret) { dev_err(dwc->dev, "failed to setup event buffers\n"); - goto err1; + goto err_usb3phy_power; } switch (dwc->dr_mode) { @@ -685,6 +736,12 @@ err3: err2: dwc3_event_buffers_cleanup(dwc); +err_usb3phy_power: + phy_power_off(dwc->usb3_generic_phy); + +err_usb2phy_power: + phy_power_off(dwc->usb2_generic_phy); + err1: usb_phy_set_suspend(dwc->usb2_phy, 1); usb_phy_set_suspend(dwc->usb3_phy, 1); @@ -702,6 +759,8 @@ static int dwc3_remove(struct platform_device *pdev) 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); pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); @@ -799,6 +858,8 @@ static int dwc3_suspend(struct device *dev) usb_phy_shutdown(dwc->usb3_phy); usb_phy_shutdown(dwc->usb2_phy); + phy_exit(dwc->usb2_generic_phy); + phy_exit(dwc->usb3_generic_phy); return 0; } @@ -807,9 +868,17 @@ static int dwc3_resume(struct device *dev) { struct dwc3 *dwc = dev_get_drvdata(dev); unsigned long flags; + int ret; usb_phy_init(dwc->usb3_phy); usb_phy_init(dwc->usb2_phy); + ret = phy_init(dwc->usb2_generic_phy); + if (ret < 0) + return ret; + + ret = phy_init(dwc->usb3_generic_phy); + if (ret < 0) + goto err_usb2phy_init; spin_lock_irqsave(&dwc->lock, flags); @@ -833,6 +902,11 @@ static int dwc3_resume(struct device *dev) pm_runtime_enable(dev); return 0; + +err_usb2phy_init: + phy_exit(dwc->usb2_generic_phy); + + return ret; } static const struct dev_pm_ops dwc3_dev_pm_ops = { diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 535bb6e1f2b..57332e3768e 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -31,6 +31,8 @@ #include #include +#include + /* Global constants */ #define DWC3_EP0_BOUNCE_SIZE 512 #define DWC3_ENDPOINTS_NUM 32 @@ -619,6 +621,8 @@ struct dwc3_scratchpad_array { * @dr_mode: requested mode of operation * @usb2_phy: pointer to USB2 PHY * @usb3_phy: pointer to USB3 PHY + * @usb2_generic_phy: pointer to USB2 PHY + * @usb3_generic_phy: pointer to USB3 PHY * @dcfg: saved contents of DCFG register * @gctl: saved contents of GCTL register * @isoch_delay: wValue from Set Isochronous Delay request; @@ -679,6 +683,9 @@ struct dwc3 { struct usb_phy *usb2_phy; struct usb_phy *usb3_phy; + struct phy *usb2_generic_phy; + struct phy *usb3_generic_phy; + void __iomem *regs; size_t regs_size; -- cgit v1.2.3-70-g09d2 From aecbc31d767cb549e93a44e50218e20d1bc66b59 Mon Sep 17 00:00:00 2001 From: George Cherian Date: Thu, 27 Feb 2014 10:44:41 +0530 Subject: usb: musb: musb_cppi41: Dont reprogram DMA if tear down is initiated Reprogramming the DMA after tear down is initiated leads to warning. This is mainly seen with ISOCH since we do a delayed completion for ISOCH transfers. In ISOCH transfers dma_completion should not reprogram if the channel tear down is initiated. Signed-off-by: George Cherian Signed-off-by: Felipe Balbi --- drivers/usb/musb/musb_cppi41.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/musb/musb_cppi41.c b/drivers/usb/musb/musb_cppi41.c index 5b0f2a58f8c..7b8bbf53127 100644 --- a/drivers/usb/musb/musb_cppi41.c +++ b/drivers/usb/musb/musb_cppi41.c @@ -132,7 +132,8 @@ static void cppi41_trans_done(struct cppi41_dma_channel *cppi41_channel) struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep; struct musb *musb = hw_ep->musb; - if (!cppi41_channel->prog_len) { + if (!cppi41_channel->prog_len || + (cppi41_channel->channel.status == MUSB_DMA_STATUS_FREE)) { /* done, complete */ cppi41_channel->channel.actual_len = -- cgit v1.2.3-70-g09d2 From b83e333a4d03b73eb5ea7fc1cb86683c2575b237 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Fri, 28 Feb 2014 13:06:11 +0100 Subject: usb: gadget: s3c-hsotg: add proper suspend/resume support This patch adds suspend/resume support to s3c-hsotg driver. It makes UDC driver more power efficient. Signed-off-by: Marek Szyprowski Tested-by: Robert Baldyga Signed-off-by: Felipe Balbi --- drivers/usb/gadget/s3c-hsotg.c | 53 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/s3c-hsotg.c b/drivers/usb/gadget/s3c-hsotg.c index 99c8e3ca6a3..2a9cb674926 100644 --- a/drivers/usb/gadget/s3c-hsotg.c +++ b/drivers/usb/gadget/s3c-hsotg.c @@ -3774,10 +3774,55 @@ static int s3c_hsotg_remove(struct platform_device *pdev) return 0; } -#if 1 -#define s3c_hsotg_suspend NULL -#define s3c_hsotg_resume NULL -#endif +static int s3c_hsotg_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct s3c_hsotg *hsotg = platform_get_drvdata(pdev); + unsigned long flags; + int ret = 0; + + if (hsotg->driver) + dev_info(hsotg->dev, "suspending usb gadget %s\n", + hsotg->driver->driver.name); + + spin_lock_irqsave(&hsotg->lock, flags); + s3c_hsotg_disconnect(hsotg); + s3c_hsotg_phy_disable(hsotg); + hsotg->gadget.speed = USB_SPEED_UNKNOWN; + spin_unlock_irqrestore(&hsotg->lock, flags); + + if (hsotg->driver) { + int ep; + for (ep = 0; ep < hsotg->num_of_eps; ep++) + s3c_hsotg_ep_disable(&hsotg->eps[ep].ep); + + ret = regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), + hsotg->supplies); + } + + return ret; +} + +static int s3c_hsotg_resume(struct platform_device *pdev) +{ + struct s3c_hsotg *hsotg = platform_get_drvdata(pdev); + unsigned long flags; + int ret = 0; + + if (hsotg->driver) { + dev_info(hsotg->dev, "resuming usb gadget %s\n", + hsotg->driver->driver.name); + ret = regulator_bulk_enable(ARRAY_SIZE(hsotg->supplies), + hsotg->supplies); + } + + spin_lock_irqsave(&hsotg->lock, flags); + hsotg->last_rst = jiffies; + s3c_hsotg_phy_enable(hsotg); + s3c_hsotg_core_init(hsotg); + spin_unlock_irqrestore(&hsotg->lock, flags); + + return ret; +} #ifdef CONFIG_OF static const struct of_device_id s3c_hsotg_of_ids[] = { -- cgit v1.2.3-70-g09d2 From 14de8c3a62b945e5fc8da02fe859d4b75e7bdebc Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 24 Feb 2014 10:20:53 +0800 Subject: usb: doc: phy-mxs: Add more compatible strings Add "fsl,imx6q-usbphy" for imx6dq and imx6dl, add "fsl,imx6sl-usbphy" for imx6sl, and "fsl,imx23-usbphy" is still a fallback for other strings. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi --- Documentation/devicetree/bindings/usb/mxs-phy.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/mxs-phy.txt b/Documentation/devicetree/bindings/usb/mxs-phy.txt index 5835b27146e..ea5134a92ad 100644 --- a/Documentation/devicetree/bindings/usb/mxs-phy.txt +++ b/Documentation/devicetree/bindings/usb/mxs-phy.txt @@ -1,7 +1,11 @@ * Freescale MXS USB Phy Device Required properties: -- compatible: Should be "fsl,imx23-usbphy" +- compatible: should contain: + * "fsl,imx23-usbphy" for imx23 and imx28 + * "fsl,imx6q-usbphy" for imx6dq and imx6dl + * "fsl,imx6sl-usbphy" for imx6sl + "fsl,imx23-usbphy" is still a fallback for other strings - reg: Should contain registers location and length - interrupts: Should contain phy interrupt -- cgit v1.2.3-70-g09d2 From 2400780ea18acd8df5895c6946c5eec3bf2f8859 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 24 Feb 2014 10:20:54 +0800 Subject: usb: phy: mxs: Add platform judgement code The mxs-phy has several bugs and features at different versions, the driver code can get it through of_device_id.data. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi --- drivers/usb/phy/phy-mxs-usb.c | 58 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index b42897b6474..cf58d8ec990 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -1,5 +1,5 @@ /* - * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012-2013 Freescale Semiconductor, Inc. * Copyright (C) 2012 Marek Vasut * on behalf of DENX Software Engineering GmbH * @@ -20,6 +20,7 @@ #include #include #include +#include #define DRIVER_NAME "mxs_phy" @@ -34,13 +35,55 @@ #define BM_USBPHY_CTRL_ENUTMILEVEL2 BIT(14) #define BM_USBPHY_CTRL_ENHOSTDISCONDETECT BIT(1) +#define to_mxs_phy(p) container_of((p), struct mxs_phy, phy) + +/* Do disconnection between PHY and controller without vbus */ +#define MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS BIT(0) + +/* + * The PHY will be in messy if there is a wakeup after putting + * bus to suspend (set portsc.suspendM) but before setting PHY to low + * power mode (set portsc.phcd). + */ +#define MXS_PHY_ABNORMAL_IN_SUSPEND BIT(1) + +/* + * The SOF sends too fast after resuming, it will cause disconnection + * between host and high speed device. + */ +#define MXS_PHY_SENDING_SOF_TOO_FAST BIT(2) + +struct mxs_phy_data { + unsigned int flags; +}; + +static const struct mxs_phy_data imx23_phy_data = { + .flags = MXS_PHY_ABNORMAL_IN_SUSPEND | MXS_PHY_SENDING_SOF_TOO_FAST, +}; + +static const struct mxs_phy_data imx6q_phy_data = { + .flags = MXS_PHY_SENDING_SOF_TOO_FAST | + MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS, +}; + +static const struct mxs_phy_data imx6sl_phy_data = { + .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS, +}; + +static const struct of_device_id mxs_phy_dt_ids[] = { + { .compatible = "fsl,imx6sl-usbphy", .data = &imx6sl_phy_data, }, + { .compatible = "fsl,imx6q-usbphy", .data = &imx6q_phy_data, }, + { .compatible = "fsl,imx23-usbphy", .data = &imx23_phy_data, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_phy_dt_ids); + struct mxs_phy { struct usb_phy phy; struct clk *clk; + const struct mxs_phy_data *data; }; -#define to_mxs_phy(p) container_of((p), struct mxs_phy, phy) - static int mxs_phy_hw_init(struct mxs_phy *mxs_phy) { int ret; @@ -138,6 +181,8 @@ static int mxs_phy_probe(struct platform_device *pdev) struct clk *clk; struct mxs_phy *mxs_phy; int ret; + const struct of_device_id *of_id = + of_match_device(mxs_phy_dt_ids, &pdev->dev); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(&pdev->dev, res); @@ -168,6 +213,7 @@ static int mxs_phy_probe(struct platform_device *pdev) mxs_phy->phy.type = USB_PHY_TYPE_USB2; mxs_phy->clk = clk; + mxs_phy->data = of_id->data; platform_set_drvdata(pdev, mxs_phy); @@ -187,12 +233,6 @@ static int mxs_phy_remove(struct platform_device *pdev) return 0; } -static const struct of_device_id mxs_phy_dt_ids[] = { - { .compatible = "fsl,imx23-usbphy", }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, mxs_phy_dt_ids); - static struct platform_driver mxs_phy_driver = { .probe = mxs_phy_probe, .remove = mxs_phy_remove, -- cgit v1.2.3-70-g09d2 From 1364414411acf0fe691ae7e045a64ee366692ba0 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 24 Feb 2014 10:20:55 +0800 Subject: usb: phy: mxs: Add auto clock and power setting The auto setting is used to open related power and clocks automatically after receiving wakeup signal. With this feature, the PHY's clock and power can be recovered correctly from low power mode, it is guaranteed by IC logic. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi --- drivers/usb/phy/phy-mxs-usb.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index cf58d8ec990..d7adca3738f 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -31,6 +31,11 @@ #define BM_USBPHY_CTRL_SFTRST BIT(31) #define BM_USBPHY_CTRL_CLKGATE BIT(30) +#define BM_USBPHY_CTRL_ENAUTOSET_USBCLKS BIT(26) +#define BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE BIT(25) +#define BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD BIT(20) +#define BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE BIT(19) +#define BM_USBPHY_CTRL_ENAUTO_PWRON_PLL BIT(18) #define BM_USBPHY_CTRL_ENUTMILEVEL3 BIT(15) #define BM_USBPHY_CTRL_ENUTMILEVEL2 BIT(14) #define BM_USBPHY_CTRL_ENHOSTDISCONDETECT BIT(1) @@ -96,9 +101,18 @@ static int mxs_phy_hw_init(struct mxs_phy *mxs_phy) /* Power up the PHY */ writel(0, base + HW_USBPHY_PWD); - /* enable FS/LS device */ - writel(BM_USBPHY_CTRL_ENUTMILEVEL2 | - BM_USBPHY_CTRL_ENUTMILEVEL3, + /* + * USB PHY Ctrl Setting + * - Auto clock/power on + * - Enable full/low speed support + */ + writel(BM_USBPHY_CTRL_ENAUTOSET_USBCLKS | + BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE | + BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD | + BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE | + BM_USBPHY_CTRL_ENAUTO_PWRON_PLL | + BM_USBPHY_CTRL_ENUTMILEVEL2 | + BM_USBPHY_CTRL_ENUTMILEVEL3, base + HW_USBPHY_CTRL_SET); return 0; -- cgit v1.2.3-70-g09d2 From d9c130328d2e4b09c0c689e5d3144c0d955ce966 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 24 Feb 2014 10:20:56 +0800 Subject: usb: doc: phy-mxs: update binding for adding anatop phandle Add anatop phandle which is used to access anatop registers to control PHY's power and other USB operations. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi --- Documentation/devicetree/bindings/usb/mxs-phy.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/mxs-phy.txt b/Documentation/devicetree/bindings/usb/mxs-phy.txt index ea5134a92ad..cef181a9d8b 100644 --- a/Documentation/devicetree/bindings/usb/mxs-phy.txt +++ b/Documentation/devicetree/bindings/usb/mxs-phy.txt @@ -8,10 +8,12 @@ Required properties: "fsl,imx23-usbphy" is still a fallback for other strings - reg: Should contain registers location and length - interrupts: Should contain phy interrupt +- fsl,anatop: phandle for anatop register, it is only for imx6 SoC series Example: usbphy1: usbphy@020c9000 { compatible = "fsl,imx6q-usbphy", "fsl,imx23-usbphy"; reg = <0x020c9000 0x1000>; interrupts = <0 44 0x04>; + fsl,anatop = <&anatop>; }; -- cgit v1.2.3-70-g09d2 From 0d896538d83642c329658f04d11b407a3cf71f62 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 24 Feb 2014 10:20:57 +0800 Subject: usb: phy: mxs: Add anatop regmap It is needed by imx6 SoC series, but not for imx23 and imx28. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi --- drivers/usb/phy/phy-mxs-usb.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index d7adca3738f..2411e050351 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -21,6 +21,8 @@ #include #include #include +#include +#include #define DRIVER_NAME "mxs_phy" @@ -87,6 +89,7 @@ struct mxs_phy { struct usb_phy phy; struct clk *clk; const struct mxs_phy_data *data; + struct regmap *regmap_anatop; }; static int mxs_phy_hw_init(struct mxs_phy *mxs_phy) @@ -197,6 +200,7 @@ static int mxs_phy_probe(struct platform_device *pdev) int ret; const struct of_device_id *of_id = of_match_device(mxs_phy_dt_ids, &pdev->dev); + struct device_node *np = pdev->dev.of_node; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(&pdev->dev, res); @@ -216,6 +220,17 @@ static int mxs_phy_probe(struct platform_device *pdev) return -ENOMEM; } + /* Some SoCs don't have anatop registers */ + if (of_get_property(np, "fsl,anatop", NULL)) { + mxs_phy->regmap_anatop = syscon_regmap_lookup_by_phandle + (np, "fsl,anatop"); + if (IS_ERR(mxs_phy->regmap_anatop)) { + dev_dbg(&pdev->dev, + "failed to find regmap for anatop\n"); + return PTR_ERR(mxs_phy->regmap_anatop); + } + } + mxs_phy->phy.io_priv = base; mxs_phy->phy.dev = &pdev->dev; mxs_phy->phy.label = DRIVER_NAME; -- cgit v1.2.3-70-g09d2 From f6a158243e569785a995e2218d9521716cdc9d79 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 24 Feb 2014 10:20:58 +0800 Subject: usb: phy: mxs: change description of usb device speed Change "high speed" to "HS" Change "non-high speed" to "FS/LS" Implementation of notify_suspend and notify_resume will be different according to mxs_phy_data->flags. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi --- drivers/usb/phy/phy-mxs-usb.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index 2411e050351..1663a6683ec 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -168,8 +168,8 @@ static int mxs_phy_suspend(struct usb_phy *x, int suspend) static int mxs_phy_on_connect(struct usb_phy *phy, enum usb_device_speed speed) { - dev_dbg(phy->dev, "%s speed device has connected\n", - (speed == USB_SPEED_HIGH) ? "high" : "non-high"); + dev_dbg(phy->dev, "%s device has connected\n", + (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS"); if (speed == USB_SPEED_HIGH) writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, @@ -181,8 +181,8 @@ static int mxs_phy_on_connect(struct usb_phy *phy, static int mxs_phy_on_disconnect(struct usb_phy *phy, enum usb_device_speed speed) { - dev_dbg(phy->dev, "%s speed device has disconnected\n", - (speed == USB_SPEED_HIGH) ? "high" : "non-high"); + dev_dbg(phy->dev, "%s device has disconnected\n", + (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS"); if (speed == USB_SPEED_HIGH) writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, -- cgit v1.2.3-70-g09d2 From 22db05ecf2bad318c87f5a721e93694b2bb68a75 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 24 Feb 2014 10:20:59 +0800 Subject: usb: phy: mxs: Enable IC fixes for related SoCs Two PHY bugs are fixed by IC logic, but these bits are not enabled by default, so we enable them at driver. The two bugs are: MXS_PHY_ABNORMAL_IN_SUSPEND and MXS_PHY_SENDING_SOF_TOO_FAST which are described at code. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi --- drivers/usb/phy/phy-mxs-usb.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index 1663a6683ec..cb711354315 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -31,6 +31,10 @@ #define HW_USBPHY_CTRL_SET 0x34 #define HW_USBPHY_CTRL_CLR 0x38 +#define HW_USBPHY_IP 0x90 +#define HW_USBPHY_IP_SET 0x94 +#define HW_USBPHY_IP_CLR 0x98 + #define BM_USBPHY_CTRL_SFTRST BIT(31) #define BM_USBPHY_CTRL_CLKGATE BIT(30) #define BM_USBPHY_CTRL_ENAUTOSET_USBCLKS BIT(26) @@ -42,6 +46,8 @@ #define BM_USBPHY_CTRL_ENUTMILEVEL2 BIT(14) #define BM_USBPHY_CTRL_ENHOSTDISCONDETECT BIT(1) +#define BM_USBPHY_IP_FIX (BIT(17) | BIT(18)) + #define to_mxs_phy(p) container_of((p), struct mxs_phy, phy) /* Do disconnection between PHY and controller without vbus */ @@ -60,6 +66,14 @@ */ #define MXS_PHY_SENDING_SOF_TOO_FAST BIT(2) +/* + * IC has bug fixes logic, they include + * MXS_PHY_ABNORMAL_IN_SUSPEND and MXS_PHY_SENDING_SOF_TOO_FAST + * which are described at above flags, the RTL will handle it + * according to different versions. + */ +#define MXS_PHY_NEED_IP_FIX BIT(3) + struct mxs_phy_data { unsigned int flags; }; @@ -70,11 +84,13 @@ static const struct mxs_phy_data imx23_phy_data = { static const struct mxs_phy_data imx6q_phy_data = { .flags = MXS_PHY_SENDING_SOF_TOO_FAST | - MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS, + MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | + MXS_PHY_NEED_IP_FIX, }; static const struct mxs_phy_data imx6sl_phy_data = { - .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS, + .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | + MXS_PHY_NEED_IP_FIX, }; static const struct of_device_id mxs_phy_dt_ids[] = { @@ -118,6 +134,9 @@ static int mxs_phy_hw_init(struct mxs_phy *mxs_phy) BM_USBPHY_CTRL_ENUTMILEVEL3, base + HW_USBPHY_CTRL_SET); + if (mxs_phy->data->flags & MXS_PHY_NEED_IP_FIX) + writel(BM_USBPHY_IP_FIX, base + HW_USBPHY_IP_SET); + return 0; } -- cgit v1.2.3-70-g09d2 From 83be181b64222630957b9f59fd2d8c8864cb8972 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 24 Feb 2014 10:21:00 +0800 Subject: usb: phy: mxs: add controller id It is used to access un-regulator registers according to different controllers. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi --- drivers/usb/phy/phy-mxs-usb.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index cb711354315..3009ab57614 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -106,6 +106,7 @@ struct mxs_phy { struct clk *clk; const struct mxs_phy_data *data; struct regmap *regmap_anatop; + int port_id; }; static int mxs_phy_hw_init(struct mxs_phy *mxs_phy) @@ -250,6 +251,11 @@ static int mxs_phy_probe(struct platform_device *pdev) } } + ret = of_alias_get_id(np, "usbphy"); + if (ret < 0) + dev_dbg(&pdev->dev, "failed to get alias id, errno %d\n", ret); + mxs_phy->port_id = ret; + mxs_phy->phy.io_priv = base; mxs_phy->phy.dev = &pdev->dev; mxs_phy->phy.label = DRIVER_NAME; -- cgit v1.2.3-70-g09d2 From 57bf9b09a6ad517b0c790b786845660ec5604774 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 24 Feb 2014 10:21:01 +0800 Subject: usb: phy: Add set_wakeup API This API is used to set wakeup enable at PHY registers, in that case, the PHY can be waken up from suspend due to external events, like vbus change, dp/dm change and id change. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi --- include/linux/usb/phy.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h index 6c0b1c513db..353053a33f2 100644 --- a/include/linux/usb/phy.h +++ b/include/linux/usb/phy.h @@ -111,6 +111,13 @@ struct usb_phy { int (*set_suspend)(struct usb_phy *x, int suspend); + /* + * Set wakeup enable for PHY, in that case, the PHY can be + * woken up from suspend status due to external events, + * like vbus change, dp/dm change and id. + */ + int (*set_wakeup)(struct usb_phy *x, bool enabled); + /* notify phy connect status change */ int (*notify_connect)(struct usb_phy *x, enum usb_device_speed speed); @@ -264,6 +271,15 @@ usb_phy_set_suspend(struct usb_phy *x, int suspend) return 0; } +static inline int +usb_phy_set_wakeup(struct usb_phy *x, bool enabled) +{ + if (x && x->set_wakeup) + return x->set_wakeup(x, enabled); + else + return 0; +} + static inline int usb_phy_notify_connect(struct usb_phy *x, enum usb_device_speed speed) { -- cgit v1.2.3-70-g09d2 From 3f1265056be0a3a569a0409410d4b11048688216 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 24 Feb 2014 10:21:02 +0800 Subject: usb: phy: mxs: Add implementation of set_wakeup When we need the PHY can be waken up by external signals, we can call this API. Besides, we call mxs_phy_disconnect_line at this API to close the connection between USB PHY and controller, after that, the line state from controller is SE0. Once the PHY is out of power, without calling mxs_phy_disconnect_line, there are unknown wakeups due to dp/dm floating at device mode. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi --- drivers/usb/phy/phy-mxs-usb.c | 116 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index 3009ab57614..da2eb6c968c 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -31,6 +31,9 @@ #define HW_USBPHY_CTRL_SET 0x34 #define HW_USBPHY_CTRL_CLR 0x38 +#define HW_USBPHY_DEBUG_SET 0x54 +#define HW_USBPHY_DEBUG_CLR 0x58 + #define HW_USBPHY_IP 0x90 #define HW_USBPHY_IP_SET 0x94 #define HW_USBPHY_IP_CLR 0x98 @@ -39,6 +42,9 @@ #define BM_USBPHY_CTRL_CLKGATE BIT(30) #define BM_USBPHY_CTRL_ENAUTOSET_USBCLKS BIT(26) #define BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE BIT(25) +#define BM_USBPHY_CTRL_ENVBUSCHG_WKUP BIT(23) +#define BM_USBPHY_CTRL_ENIDCHG_WKUP BIT(22) +#define BM_USBPHY_CTRL_ENDPDMCHG_WKUP BIT(21) #define BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD BIT(20) #define BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE BIT(19) #define BM_USBPHY_CTRL_ENAUTO_PWRON_PLL BIT(18) @@ -48,6 +54,25 @@ #define BM_USBPHY_IP_FIX (BIT(17) | BIT(18)) +#define BM_USBPHY_DEBUG_CLKGATE BIT(30) + +/* Anatop Registers */ +#define ANADIG_USB1_VBUS_DET_STAT 0x1c0 +#define ANADIG_USB2_VBUS_DET_STAT 0x220 + +#define ANADIG_USB1_LOOPBACK_SET 0x1e4 +#define ANADIG_USB1_LOOPBACK_CLR 0x1e8 +#define ANADIG_USB2_LOOPBACK_SET 0x244 +#define ANADIG_USB2_LOOPBACK_CLR 0x248 + +#define BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID BIT(3) +#define BM_ANADIG_USB2_VBUS_DET_STAT_VBUS_VALID BIT(3) + +#define BM_ANADIG_USB1_LOOPBACK_UTMI_DIG_TST1 BIT(2) +#define BM_ANADIG_USB1_LOOPBACK_TSTI_TX_EN BIT(5) +#define BM_ANADIG_USB2_LOOPBACK_UTMI_DIG_TST1 BIT(2) +#define BM_ANADIG_USB2_LOOPBACK_TSTI_TX_EN BIT(5) + #define to_mxs_phy(p) container_of((p), struct mxs_phy, phy) /* Do disconnection between PHY and controller without vbus */ @@ -141,6 +166,79 @@ static int mxs_phy_hw_init(struct mxs_phy *mxs_phy) return 0; } +/* Return true if the vbus is there */ +static bool mxs_phy_get_vbus_status(struct mxs_phy *mxs_phy) +{ + unsigned int vbus_value; + + if (mxs_phy->port_id == 0) + regmap_read(mxs_phy->regmap_anatop, + ANADIG_USB1_VBUS_DET_STAT, + &vbus_value); + else if (mxs_phy->port_id == 1) + regmap_read(mxs_phy->regmap_anatop, + ANADIG_USB2_VBUS_DET_STAT, + &vbus_value); + + if (vbus_value & BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID) + return true; + else + return false; +} + +static void __mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool disconnect) +{ + void __iomem *base = mxs_phy->phy.io_priv; + u32 reg; + + if (disconnect) + writel_relaxed(BM_USBPHY_DEBUG_CLKGATE, + base + HW_USBPHY_DEBUG_CLR); + + if (mxs_phy->port_id == 0) { + reg = disconnect ? ANADIG_USB1_LOOPBACK_SET + : ANADIG_USB1_LOOPBACK_CLR; + regmap_write(mxs_phy->regmap_anatop, reg, + BM_ANADIG_USB1_LOOPBACK_UTMI_DIG_TST1 | + BM_ANADIG_USB1_LOOPBACK_TSTI_TX_EN); + } else if (mxs_phy->port_id == 1) { + reg = disconnect ? ANADIG_USB2_LOOPBACK_SET + : ANADIG_USB2_LOOPBACK_CLR; + regmap_write(mxs_phy->regmap_anatop, reg, + BM_ANADIG_USB2_LOOPBACK_UTMI_DIG_TST1 | + BM_ANADIG_USB2_LOOPBACK_TSTI_TX_EN); + } + + if (!disconnect) + writel_relaxed(BM_USBPHY_DEBUG_CLKGATE, + base + HW_USBPHY_DEBUG_SET); + + /* Delay some time, and let Linestate be SE0 for controller */ + if (disconnect) + usleep_range(500, 1000); +} + +static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on) +{ + bool vbus_is_on = false; + + /* If the SoCs don't need to disconnect line without vbus, quit */ + if (!(mxs_phy->data->flags & MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS)) + return; + + /* If the SoCs don't have anatop, quit */ + if (!mxs_phy->regmap_anatop) + return; + + vbus_is_on = mxs_phy_get_vbus_status(mxs_phy); + + if (on && !vbus_is_on) + __mxs_phy_disconnect_line(mxs_phy, true); + else + __mxs_phy_disconnect_line(mxs_phy, false); + +} + static int mxs_phy_init(struct usb_phy *phy) { int ret; @@ -185,6 +283,23 @@ static int mxs_phy_suspend(struct usb_phy *x, int suspend) return 0; } +static int mxs_phy_set_wakeup(struct usb_phy *x, bool enabled) +{ + struct mxs_phy *mxs_phy = to_mxs_phy(x); + u32 value = BM_USBPHY_CTRL_ENVBUSCHG_WKUP | + BM_USBPHY_CTRL_ENDPDMCHG_WKUP | + BM_USBPHY_CTRL_ENIDCHG_WKUP; + if (enabled) { + mxs_phy_disconnect_line(mxs_phy, true); + writel_relaxed(value, x->io_priv + HW_USBPHY_CTRL_SET); + } else { + writel_relaxed(value, x->io_priv + HW_USBPHY_CTRL_CLR); + mxs_phy_disconnect_line(mxs_phy, false); + } + + return 0; +} + static int mxs_phy_on_connect(struct usb_phy *phy, enum usb_device_speed speed) { @@ -265,6 +380,7 @@ static int mxs_phy_probe(struct platform_device *pdev) mxs_phy->phy.notify_connect = mxs_phy_on_connect; mxs_phy->phy.notify_disconnect = mxs_phy_on_disconnect; mxs_phy->phy.type = USB_PHY_TYPE_USB2; + mxs_phy->phy.set_wakeup = mxs_phy_set_wakeup; mxs_phy->clk = clk; mxs_phy->data = of_id->data; -- cgit v1.2.3-70-g09d2 From bf78343800861c65d4004e7f60a03cc9bc2410b3 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 24 Feb 2014 10:21:03 +0800 Subject: usb: phy: mxs: Add system suspend/resume API We need this to keep PHY's power on or off during the system suspend mode. If we need to enable USB wakeup, then we must keep PHY's power being on during the system suspend mode. Otherwise, we need to keep PHY's power being off to save power. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi --- drivers/usb/phy/phy-mxs-usb.c | 61 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index da2eb6c968c..31ef59f8890 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -57,6 +57,10 @@ #define BM_USBPHY_DEBUG_CLKGATE BIT(30) /* Anatop Registers */ +#define ANADIG_ANA_MISC0 0x150 +#define ANADIG_ANA_MISC0_SET 0x154 +#define ANADIG_ANA_MISC0_CLR 0x158 + #define ANADIG_USB1_VBUS_DET_STAT 0x1c0 #define ANADIG_USB2_VBUS_DET_STAT 0x220 @@ -65,6 +69,9 @@ #define ANADIG_USB2_LOOPBACK_SET 0x244 #define ANADIG_USB2_LOOPBACK_CLR 0x248 +#define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG BIT(12) +#define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL BIT(11) + #define BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID BIT(3) #define BM_ANADIG_USB2_VBUS_DET_STAT_VBUS_VALID BIT(3) @@ -134,6 +141,16 @@ struct mxs_phy { int port_id; }; +static inline bool is_imx6q_phy(struct mxs_phy *mxs_phy) +{ + return mxs_phy->data == &imx6q_phy_data; +} + +static inline bool is_imx6sl_phy(struct mxs_phy *mxs_phy) +{ + return mxs_phy->data == &imx6sl_phy_data; +} + static int mxs_phy_hw_init(struct mxs_phy *mxs_phy) { int ret; @@ -387,6 +404,8 @@ static int mxs_phy_probe(struct platform_device *pdev) platform_set_drvdata(pdev, mxs_phy); + device_set_wakeup_capable(&pdev->dev, true); + ret = usb_add_phy_dev(&mxs_phy->phy); if (ret) return ret; @@ -403,6 +422,47 @@ static int mxs_phy_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_SLEEP +static void mxs_phy_enable_ldo_in_suspend(struct mxs_phy *mxs_phy, bool on) +{ + unsigned int reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR; + + /* If the SoCs don't have anatop, quit */ + if (!mxs_phy->regmap_anatop) + return; + + if (is_imx6q_phy(mxs_phy)) + regmap_write(mxs_phy->regmap_anatop, reg, + BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG); + else if (is_imx6sl_phy(mxs_phy)) + regmap_write(mxs_phy->regmap_anatop, + reg, BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL); +} + +static int mxs_phy_system_suspend(struct device *dev) +{ + struct mxs_phy *mxs_phy = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + mxs_phy_enable_ldo_in_suspend(mxs_phy, true); + + return 0; +} + +static int mxs_phy_system_resume(struct device *dev) +{ + struct mxs_phy *mxs_phy = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + mxs_phy_enable_ldo_in_suspend(mxs_phy, false); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(mxs_phy_pm, mxs_phy_system_suspend, + mxs_phy_system_resume); + static struct platform_driver mxs_phy_driver = { .probe = mxs_phy_probe, .remove = mxs_phy_remove, @@ -410,6 +470,7 @@ static struct platform_driver mxs_phy_driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = mxs_phy_dt_ids, + .pm = &mxs_phy_pm, }, }; -- cgit v1.2.3-70-g09d2 From 47d1845ffac0af9e4b439277fee50b62aed650a7 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 24 Feb 2014 10:21:04 +0800 Subject: usb: phy: mxs: Add sync time after controller clear phcd After clear portsc.phcd, PHY needs 200us stable time for switch 32K clock to AHB clock. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi --- drivers/usb/phy/phy-mxs-usb.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index 31ef59f8890..c42bdf0c4a1 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -151,6 +151,15 @@ static inline bool is_imx6sl_phy(struct mxs_phy *mxs_phy) return mxs_phy->data == &imx6sl_phy_data; } +/* + * PHY needs some 32K cycles to switch from 32K clock to + * bus (such as AHB/AXI, etc) clock. + */ +static void mxs_phy_clock_switch_delay(void) +{ + usleep_range(300, 400); +} + static int mxs_phy_hw_init(struct mxs_phy *mxs_phy) { int ret; @@ -261,6 +270,7 @@ static int mxs_phy_init(struct usb_phy *phy) int ret; struct mxs_phy *mxs_phy = to_mxs_phy(phy); + mxs_phy_clock_switch_delay(); ret = clk_prepare_enable(mxs_phy->clk); if (ret) return ret; @@ -289,6 +299,7 @@ static int mxs_phy_suspend(struct usb_phy *x, int suspend) x->io_priv + HW_USBPHY_CTRL_SET); clk_disable_unprepare(mxs_phy->clk); } else { + mxs_phy_clock_switch_delay(); ret = clk_prepare_enable(mxs_phy->clk); if (ret) return ret; -- cgit v1.2.3-70-g09d2 From fb0e139d93f50f0cfa3ff4377d34c70dbc542797 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 27 Feb 2014 16:42:13 +0100 Subject: usb: gadget: atmel_usba: fix crash when no endpoint are specified If no endpoints are present in the device tree, the kernel will crash with the following error: Unable to handle kernel paging request at virtual address 00101008 [...] [] (composite_dev_prepare) from [] (composite_bind+0x5c/0x190) [] (composite_bind) from [] (udc_bind_to_driver+0x48/0xf0) [] (udc_bind_to_driver) from [] (usb_gadget_probe_driver+0x7c/0xa0) [] (usb_gadget_probe_driver) from [] (do_one_initcall+0x94/0x140) [] (do_one_initcall) from [] (kernel_init_freeable+0xec/0x1b4) [] (kernel_init_freeable) from [] (kernel_init+0x8/0xe4) [] (kernel_init) from [] (ret_from_fork+0x14/0x24) Code: e5950014 e1a04001 e5902008 e3a010d0 (e5922008) ---[ end trace 35c74bdd89b373d0 ]--- Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b This checks for that case and returns an error, not allowing the driver to be loaded with no endpoints. Signed-off-by: Alexandre Belloni Signed-off-by: Felipe Balbi --- drivers/usb/gadget/atmel_usba_udc.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/gadget/atmel_usba_udc.c b/drivers/usb/gadget/atmel_usba_udc.c index 18a44919ae9..e57991edcf6 100644 --- a/drivers/usb/gadget/atmel_usba_udc.c +++ b/drivers/usb/gadget/atmel_usba_udc.c @@ -1914,6 +1914,12 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, i++; } + if (i == 0) { + dev_err(&pdev->dev, "of_probe: no endpoint specified\n"); + ret = -EINVAL; + goto err; + } + return eps; err: return ERR_PTR(ret); -- cgit v1.2.3-70-g09d2 From d8eb6c653ef6b323d630de3c5685478469e248bc Mon Sep 17 00:00:00 2001 From: Gregory CLEMENT Date: Mon, 3 Mar 2014 17:48:34 +0100 Subject: usb: gadget: atmel_usba: fix crashed during stopping when DEBUG is enabled commit 511f3c5 (usb: gadget: udc-core: fix a regression during gadget driver unbinding) introduced a crash when DEBUG is enabled. The debug trace in the atmel_usba_stop function made the assumption that the driver pointer passed in parameter was not NULL, but since the commit above, such assumption was no longer always true. This commit now uses the driver pointer stored in udc which fixes this issue. [ balbi@ti.com : improved commit log a bit ] Cc: # v3.2+ Acked-by: Alexandre Belloni Signed-off-by: Gregory CLEMENT Signed-off-by: Felipe Balbi --- drivers/usb/gadget/atmel_usba_udc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/atmel_usba_udc.c b/drivers/usb/gadget/atmel_usba_udc.c index e57991edcf6..9f65324f9ae 100644 --- a/drivers/usb/gadget/atmel_usba_udc.c +++ b/drivers/usb/gadget/atmel_usba_udc.c @@ -1827,12 +1827,12 @@ static int atmel_usba_stop(struct usb_gadget *gadget, toggle_bias(0); usba_writel(udc, CTRL, USBA_DISABLE_MASK); - udc->driver = NULL; - clk_disable_unprepare(udc->hclk); clk_disable_unprepare(udc->pclk); - DBG(DBG_GADGET, "unregistered driver `%s'\n", driver->driver.name); + DBG(DBG_GADGET, "unregistered driver `%s'\n", udc->driver->driver.name); + + udc->driver = NULL; return 0; } -- cgit v1.2.3-70-g09d2 From 8d4e897bd0150fab594a871484e554472ee01452 Mon Sep 17 00:00:00 2001 From: Manu Gautam Date: Fri, 28 Feb 2014 16:50:22 +0530 Subject: usb: gadget: f_fs: Add support for SuperSpeed Mode Allow userspace to pass SuperSpeed descriptors and handle them in the driver accordingly. This change doesn't modify existing desc_header and thereby keeps the ABI changes backward compatible i.e. existing userspace drivers compiled with old header (functionfs.h) would continue to work with the updated kernel. Signed-off-by: Manu Gautam Acked-by: Michal Nazarewicz Signed-off-by: Felipe Balbi --- drivers/usb/gadget/f_fs.c | 182 +++++++++++++++++++++++++++--------- drivers/usb/gadget/u_fs.h | 10 +- include/uapi/linux/usb/functionfs.h | 5 + 3 files changed, 147 insertions(+), 50 deletions(-) diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index 1ae741fdace..66f60b9a34a 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -134,8 +134,8 @@ struct ffs_ep { struct usb_ep *ep; /* P: ffs->eps_lock */ struct usb_request *req; /* P: epfile->mutex */ - /* [0]: full speed, [1]: high speed */ - struct usb_endpoint_descriptor *descs[2]; + /* [0]: full speed, [1]: high speed, [2]: super speed */ + struct usb_endpoint_descriptor *descs[3]; u8 num; @@ -1450,10 +1450,11 @@ static void ffs_data_reset(struct ffs_data *ffs) ffs->raw_strings = NULL; ffs->stringtabs = NULL; - ffs->raw_descs_length = 0; - ffs->raw_fs_descs_length = 0; + ffs->raw_fs_hs_descs_length = 0; + ffs->raw_ss_descs_length = 0; ffs->fs_descs_count = 0; ffs->hs_descs_count = 0; + ffs->ss_descs_count = 0; ffs->strings_count = 0; ffs->interfaces_count = 0; @@ -1596,7 +1597,24 @@ static int ffs_func_eps_enable(struct ffs_function *func) spin_lock_irqsave(&func->ffs->eps_lock, flags); do { struct usb_endpoint_descriptor *ds; - ds = ep->descs[ep->descs[1] ? 1 : 0]; + int desc_idx; + + if (ffs->gadget->speed == USB_SPEED_SUPER) + desc_idx = 2; + else if (ffs->gadget->speed == USB_SPEED_HIGH) + desc_idx = 1; + else + desc_idx = 0; + + /* fall-back to lower speed if desc missing for current speed */ + do { + ds = ep->descs[desc_idx]; + } while (!ds && --desc_idx >= 0); + + if (!ds) { + ret = -EINVAL; + break; + } ep->ep->driver_data = ep; ep->ep->desc = ds; @@ -1731,6 +1749,12 @@ static int __must_check ffs_do_desc(char *data, unsigned len, } break; + case USB_DT_SS_ENDPOINT_COMP: + pr_vdebug("EP SS companion descriptor\n"); + if (length != sizeof(struct usb_ss_ep_comp_descriptor)) + goto inv_length; + break; + case USB_DT_OTHER_SPEED_CONFIG: case USB_DT_INTERFACE_POWER: case USB_DT_DEBUG: @@ -1841,8 +1865,8 @@ static int __ffs_data_do_entity(enum ffs_entity_type type, static int __ffs_data_got_descs(struct ffs_data *ffs, char *const _data, size_t len) { - unsigned fs_count, hs_count; - int fs_len, ret = -EINVAL; + unsigned fs_count, hs_count, ss_count = 0; + int fs_len, hs_len, ss_len, ret = -EINVAL; char *data = _data; ENTER(); @@ -1853,9 +1877,6 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, fs_count = get_unaligned_le32(data + 8); hs_count = get_unaligned_le32(data + 12); - if (!fs_count && !hs_count) - goto einval; - data += 16; len -= 16; @@ -1874,22 +1895,54 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, } if (likely(hs_count)) { - ret = ffs_do_descs(hs_count, data, len, + hs_len = ffs_do_descs(hs_count, data, len, __ffs_data_do_entity, ffs); - if (unlikely(ret < 0)) + if (unlikely(hs_len < 0)) { + ret = hs_len; + goto error; + } + + data += hs_len; + len -= hs_len; + } else { + hs_len = 0; + } + + if (len >= 8) { + /* Check SS_MAGIC for presence of ss_descs and get SS_COUNT */ + if (get_unaligned_le32(data) != FUNCTIONFS_SS_DESC_MAGIC) + goto einval; + + ss_count = get_unaligned_le32(data + 4); + data += 8; + len -= 8; + } + + if (!fs_count && !hs_count && !ss_count) + goto einval; + + if (ss_count) { + ss_len = ffs_do_descs(ss_count, data, len, + __ffs_data_do_entity, ffs); + if (unlikely(ss_len < 0)) { + ret = ss_len; goto error; + } + ret = ss_len; } else { + ss_len = 0; ret = 0; } if (unlikely(len != ret)) goto einval; - ffs->raw_fs_descs_length = fs_len; - ffs->raw_descs_length = fs_len + ret; - ffs->raw_descs = _data; - ffs->fs_descs_count = fs_count; - ffs->hs_descs_count = hs_count; + ffs->raw_fs_hs_descs_length = fs_len + hs_len; + ffs->raw_ss_descs_length = ss_len; + ffs->raw_descs = _data; + ffs->fs_descs_count = fs_count; + ffs->hs_descs_count = hs_count; + ffs->ss_descs_count = ss_count; return 0; @@ -2112,21 +2165,28 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep, struct usb_endpoint_descriptor *ds = (void *)desc; struct ffs_function *func = priv; struct ffs_ep *ffs_ep; - - /* - * If hs_descriptors is not NULL then we are reading hs - * descriptors now - */ - const int isHS = func->function.hs_descriptors != NULL; - unsigned idx; + unsigned ep_desc_id, idx; + static const char *speed_names[] = { "full", "high", "super" }; if (type != FFS_DESCRIPTOR) return 0; - if (isHS) + /* + * If ss_descriptors is not NULL, we are reading super speed + * descriptors; if hs_descriptors is not NULL, we are reading high + * speed descriptors; otherwise, we are reading full speed + * descriptors. + */ + if (func->function.ss_descriptors) { + ep_desc_id = 2; + func->function.ss_descriptors[(long)valuep] = desc; + } else if (func->function.hs_descriptors) { + ep_desc_id = 1; func->function.hs_descriptors[(long)valuep] = desc; - else + } else { + ep_desc_id = 0; func->function.fs_descriptors[(long)valuep] = desc; + } if (!desc || desc->bDescriptorType != USB_DT_ENDPOINT) return 0; @@ -2134,13 +2194,13 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep, idx = (ds->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK) - 1; ffs_ep = func->eps + idx; - if (unlikely(ffs_ep->descs[isHS])) { - pr_vdebug("two %sspeed descriptors for EP %d\n", - isHS ? "high" : "full", + if (unlikely(ffs_ep->descs[ep_desc_id])) { + pr_err("two %sspeed descriptors for EP %d\n", + speed_names[ep_desc_id], ds->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); return -EINVAL; } - ffs_ep->descs[isHS] = ds; + ffs_ep->descs[ep_desc_id] = ds; ffs_dump_mem(": Original ep desc", ds, ds->bLength); if (ffs_ep->ep) { @@ -2284,8 +2344,10 @@ static int _ffs_func_bind(struct usb_configuration *c, const int full = !!func->ffs->fs_descs_count; const int high = gadget_is_dualspeed(func->gadget) && func->ffs->hs_descs_count; + const int super = gadget_is_superspeed(func->gadget) && + func->ffs->ss_descs_count; - int ret; + int fs_len, hs_len, ret; /* Make it a single chunk, less management later on */ vla_group(d); @@ -2294,15 +2356,17 @@ static int _ffs_func_bind(struct usb_configuration *c, full ? ffs->fs_descs_count + 1 : 0); vla_item_with_sz(d, struct usb_descriptor_header *, hs_descs, high ? ffs->hs_descs_count + 1 : 0); + vla_item_with_sz(d, struct usb_descriptor_header *, ss_descs, + super ? ffs->ss_descs_count + 1 : 0); vla_item_with_sz(d, short, inums, ffs->interfaces_count); vla_item_with_sz(d, char, raw_descs, - high ? ffs->raw_descs_length : ffs->raw_fs_descs_length); + ffs->raw_fs_hs_descs_length + ffs->raw_ss_descs_length); char *vlabuf; ENTER(); - /* Only high speed but not supported by gadget? */ - if (unlikely(!(full | high))) + /* Has descriptors only for speeds gadget does not support */ + if (unlikely(!(full | high | super))) return -ENOTSUPP; /* Allocate a single chunk, less management later on */ @@ -2312,8 +2376,16 @@ static int _ffs_func_bind(struct usb_configuration *c, /* Zero */ memset(vla_ptr(vlabuf, d, eps), 0, d_eps__sz); + /* Copy only raw (hs,fs) descriptors (until ss_magic and ss_count) */ memcpy(vla_ptr(vlabuf, d, raw_descs), ffs->raw_descs + 16, - d_raw_descs__sz); + ffs->raw_fs_hs_descs_length); + /* Copy SS descs present @ header + hs_fs_descs + ss_magic + ss_count */ + if (func->ffs->ss_descs_count) + memcpy(vla_ptr(vlabuf, d, raw_descs) + + ffs->raw_fs_hs_descs_length, + ffs->raw_descs + 16 + ffs->raw_fs_hs_descs_length + 8, + ffs->raw_ss_descs_length); + memset(vla_ptr(vlabuf, d, inums), 0xff, d_inums__sz); for (ret = ffs->eps_count; ret; --ret) { struct ffs_ep *ptr; @@ -2335,22 +2407,38 @@ static int _ffs_func_bind(struct usb_configuration *c, */ if (likely(full)) { func->function.fs_descriptors = vla_ptr(vlabuf, d, fs_descs); - ret = ffs_do_descs(ffs->fs_descs_count, - vla_ptr(vlabuf, d, raw_descs), - d_raw_descs__sz, - __ffs_func_bind_do_descs, func); - if (unlikely(ret < 0)) + fs_len = ffs_do_descs(ffs->fs_descs_count, + vla_ptr(vlabuf, d, raw_descs), + d_raw_descs__sz, + __ffs_func_bind_do_descs, func); + if (unlikely(fs_len < 0)) { + ret = fs_len; goto error; + } } else { - ret = 0; + fs_len = 0; } if (likely(high)) { func->function.hs_descriptors = vla_ptr(vlabuf, d, hs_descs); - ret = ffs_do_descs(ffs->hs_descs_count, - vla_ptr(vlabuf, d, raw_descs) + ret, - d_raw_descs__sz - ret, - __ffs_func_bind_do_descs, func); + hs_len = ffs_do_descs(ffs->hs_descs_count, + vla_ptr(vlabuf, d, raw_descs) + fs_len, + d_raw_descs__sz - fs_len, + __ffs_func_bind_do_descs, func); + if (unlikely(hs_len < 0)) { + ret = hs_len; + goto error; + } + } else { + hs_len = 0; + } + + if (likely(super)) { + func->function.ss_descriptors = vla_ptr(vlabuf, d, ss_descs); + ret = ffs_do_descs(ffs->ss_descs_count, + vla_ptr(vlabuf, d, raw_descs) + fs_len + hs_len, + d_raw_descs__sz - fs_len - hs_len, + __ffs_func_bind_do_descs, func); if (unlikely(ret < 0)) goto error; } @@ -2361,7 +2449,8 @@ static int _ffs_func_bind(struct usb_configuration *c, * now. */ ret = ffs_do_descs(ffs->fs_descs_count + - (high ? ffs->hs_descs_count : 0), + (high ? ffs->hs_descs_count : 0) + + (super ? ffs->ss_descs_count : 0), vla_ptr(vlabuf, d, raw_descs), d_raw_descs__sz, __ffs_func_bind_do_nums, func); if (unlikely(ret < 0)) @@ -2708,6 +2797,7 @@ static void ffs_func_unbind(struct usb_configuration *c, */ func->function.fs_descriptors = NULL; func->function.hs_descriptors = NULL; + func->function.ss_descriptors = NULL; func->interfaces_nums = NULL; ffs_event_add(ffs, FUNCTIONFS_UNBIND); diff --git a/drivers/usb/gadget/u_fs.h b/drivers/usb/gadget/u_fs.h index c39e805025b..0deb6d5f7c3 100644 --- a/drivers/usb/gadget/u_fs.h +++ b/drivers/usb/gadget/u_fs.h @@ -208,14 +208,16 @@ struct ffs_data { /* * Real descriptors are 16 bytes after raw_descs (so you need * to skip 16 bytes (ie. ffs->raw_descs + 16) to get to the - * first full speed descriptor). raw_descs_length and - * raw_fs_descs_length do not have those 16 bytes added. + * first full speed descriptor). + * raw_fs_hs_descs_length does not have those 16 bytes added. + * ss_descs are 8 bytes (ss_magic + count) pass the hs_descs */ const void *raw_descs; - unsigned raw_descs_length; - unsigned raw_fs_descs_length; + unsigned raw_fs_hs_descs_length; + unsigned raw_ss_descs_length; unsigned fs_descs_count; unsigned hs_descs_count; + unsigned ss_descs_count; unsigned short strings_count; unsigned short interfaces_count; diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h index d6b01283f85..0f8f7be5b0d 100644 --- a/include/uapi/linux/usb/functionfs.h +++ b/include/uapi/linux/usb/functionfs.h @@ -13,6 +13,7 @@ enum { FUNCTIONFS_STRINGS_MAGIC = 2 }; +#define FUNCTIONFS_SS_DESC_MAGIC 0x0055DE5C #ifndef __KERNEL__ @@ -50,7 +51,11 @@ struct usb_functionfs_descs_head { * | 12 | hs_count | LE32 | number of high-speed descriptors | * | 16 | fs_descrs | Descriptor[] | list of full-speed descriptors | * | | hs_descrs | Descriptor[] | list of high-speed descriptors | + * | | ss_magic | LE32 | FUNCTIONFS_SS_DESC_MAGIC | + * | | ss_count | LE32 | number of super-speed descriptors | + * | | ss_descrs | Descriptor[] | list of super-speed descriptors | * + * ss_magic: if present then it implies that SS_DESCs are also present * descs are just valid USB descriptors and have the following format: * * | off | name | type | description | -- cgit v1.2.3-70-g09d2 From ac8dde11f2b397fe2282f585d5eb427a13675ea2 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Fri, 28 Feb 2014 16:50:23 +0530 Subject: usb: gadget: f_fs: Add flags to descriptors block This reworks the way SuperSpeed descriptors are added and instead of having a magic after full and high speed descriptors, it reworks the whole descriptors block to include a flags field which lists which descriptors are present and makes future extensions possible. Signed-off-by: Michal Nazarewicz Signed-off-by: Felipe Balbi --- drivers/usb/gadget/f_fs.c | 132 +++++++++++++++--------------------- drivers/usb/gadget/u_fs.h | 12 ++-- include/uapi/linux/usb/functionfs.h | 49 +++++++------ 3 files changed, 91 insertions(+), 102 deletions(-) diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index 66f60b9a34a..42f7a0e4be5 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -1434,7 +1434,7 @@ static void ffs_data_clear(struct ffs_data *ffs) if (ffs->epfiles) ffs_epfiles_destroy(ffs->epfiles, ffs->eps_count); - kfree(ffs->raw_descs); + kfree(ffs->raw_descs_data); kfree(ffs->raw_strings); kfree(ffs->stringtabs); } @@ -1446,12 +1446,12 @@ static void ffs_data_reset(struct ffs_data *ffs) ffs_data_clear(ffs); ffs->epfiles = NULL; + ffs->raw_descs_data = NULL; ffs->raw_descs = NULL; ffs->raw_strings = NULL; ffs->stringtabs = NULL; - ffs->raw_fs_hs_descs_length = 0; - ffs->raw_ss_descs_length = 0; + ffs->raw_descs_length = 0; ffs->fs_descs_count = 0; ffs->hs_descs_count = 0; ffs->ss_descs_count = 0; @@ -1865,89 +1865,76 @@ static int __ffs_data_do_entity(enum ffs_entity_type type, static int __ffs_data_got_descs(struct ffs_data *ffs, char *const _data, size_t len) { - unsigned fs_count, hs_count, ss_count = 0; - int fs_len, hs_len, ss_len, ret = -EINVAL; - char *data = _data; + char *data = _data, *raw_descs; + unsigned counts[3], flags; + int ret = -EINVAL, i; ENTER(); - if (unlikely(get_unaligned_le32(data) != FUNCTIONFS_DESCRIPTORS_MAGIC || - get_unaligned_le32(data + 4) != len)) + if (get_unaligned_le32(data + 4) != len) goto error; - fs_count = get_unaligned_le32(data + 8); - hs_count = get_unaligned_le32(data + 12); - - data += 16; - len -= 16; - if (likely(fs_count)) { - fs_len = ffs_do_descs(fs_count, data, len, - __ffs_data_do_entity, ffs); - if (unlikely(fs_len < 0)) { - ret = fs_len; + switch (get_unaligned_le32(data)) { + case FUNCTIONFS_DESCRIPTORS_MAGIC: + flags = FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC; + data += 8; + len -= 8; + break; + case FUNCTIONFS_DESCRIPTORS_MAGIC_V2: + flags = get_unaligned_le32(data + 8); + if (flags & ~(FUNCTIONFS_HAS_FS_DESC | + FUNCTIONFS_HAS_HS_DESC | + FUNCTIONFS_HAS_SS_DESC)) { + ret = -ENOSYS; goto error; } - - data += fs_len; - len -= fs_len; - } else { - fs_len = 0; + data += 12; + len -= 12; + break; + default: + goto error; } - if (likely(hs_count)) { - hs_len = ffs_do_descs(hs_count, data, len, - __ffs_data_do_entity, ffs); - if (unlikely(hs_len < 0)) { - ret = hs_len; + /* Read fs_count, hs_count and ss_count (if present) */ + for (i = 0; i < 3; ++i) { + if (!(flags & (1 << i))) { + counts[i] = 0; + } else if (len < 4) { goto error; + } else { + counts[i] = get_unaligned_le32(data); + data += 4; + len -= 4; } - - data += hs_len; - len -= hs_len; - } else { - hs_len = 0; - } - - if (len >= 8) { - /* Check SS_MAGIC for presence of ss_descs and get SS_COUNT */ - if (get_unaligned_le32(data) != FUNCTIONFS_SS_DESC_MAGIC) - goto einval; - - ss_count = get_unaligned_le32(data + 4); - data += 8; - len -= 8; } - if (!fs_count && !hs_count && !ss_count) - goto einval; - - if (ss_count) { - ss_len = ffs_do_descs(ss_count, data, len, + /* Read descriptors */ + raw_descs = data; + for (i = 0; i < 3; ++i) { + if (!counts[i]) + continue; + ret = ffs_do_descs(counts[i], data, len, __ffs_data_do_entity, ffs); - if (unlikely(ss_len < 0)) { - ret = ss_len; + if (ret < 0) goto error; - } - ret = ss_len; - } else { - ss_len = 0; - ret = 0; + data += ret; + len -= ret; } - if (unlikely(len != ret)) - goto einval; + if (raw_descs == data || len) { + ret = -EINVAL; + goto error; + } - ffs->raw_fs_hs_descs_length = fs_len + hs_len; - ffs->raw_ss_descs_length = ss_len; - ffs->raw_descs = _data; - ffs->fs_descs_count = fs_count; - ffs->hs_descs_count = hs_count; - ffs->ss_descs_count = ss_count; + ffs->raw_descs_data = _data; + ffs->raw_descs = raw_descs; + ffs->raw_descs_length = data - raw_descs; + ffs->fs_descs_count = counts[0]; + ffs->hs_descs_count = counts[1]; + ffs->ss_descs_count = counts[2]; return 0; -einval: - ret = -EINVAL; error: kfree(_data); return ret; @@ -2359,8 +2346,7 @@ static int _ffs_func_bind(struct usb_configuration *c, vla_item_with_sz(d, struct usb_descriptor_header *, ss_descs, super ? ffs->ss_descs_count + 1 : 0); vla_item_with_sz(d, short, inums, ffs->interfaces_count); - vla_item_with_sz(d, char, raw_descs, - ffs->raw_fs_hs_descs_length + ffs->raw_ss_descs_length); + vla_item_with_sz(d, char, raw_descs, ffs->raw_descs_length); char *vlabuf; ENTER(); @@ -2376,15 +2362,9 @@ static int _ffs_func_bind(struct usb_configuration *c, /* Zero */ memset(vla_ptr(vlabuf, d, eps), 0, d_eps__sz); - /* Copy only raw (hs,fs) descriptors (until ss_magic and ss_count) */ - memcpy(vla_ptr(vlabuf, d, raw_descs), ffs->raw_descs + 16, - ffs->raw_fs_hs_descs_length); - /* Copy SS descs present @ header + hs_fs_descs + ss_magic + ss_count */ - if (func->ffs->ss_descs_count) - memcpy(vla_ptr(vlabuf, d, raw_descs) + - ffs->raw_fs_hs_descs_length, - ffs->raw_descs + 16 + ffs->raw_fs_hs_descs_length + 8, - ffs->raw_ss_descs_length); + /* Copy descriptors */ + memcpy(vla_ptr(vlabuf, d, raw_descs), ffs->raw_descs, + ffs->raw_descs_length); memset(vla_ptr(vlabuf, d, inums), 0xff, d_inums__sz); for (ret = ffs->eps_count; ret; --ret) { diff --git a/drivers/usb/gadget/u_fs.h b/drivers/usb/gadget/u_fs.h index 0deb6d5f7c3..bf0ba375d45 100644 --- a/drivers/usb/gadget/u_fs.h +++ b/drivers/usb/gadget/u_fs.h @@ -206,15 +206,13 @@ struct ffs_data { /* filled by __ffs_data_got_descs() */ /* - * Real descriptors are 16 bytes after raw_descs (so you need - * to skip 16 bytes (ie. ffs->raw_descs + 16) to get to the - * first full speed descriptor). - * raw_fs_hs_descs_length does not have those 16 bytes added. - * ss_descs are 8 bytes (ss_magic + count) pass the hs_descs + * raw_descs is what you kfree, real_descs points inside of raw_descs, + * where full speed, high speed and super speed descriptors start. + * real_descs_length is the length of all those descriptors. */ + const void *raw_descs_data; const void *raw_descs; - unsigned raw_fs_hs_descs_length; - unsigned raw_ss_descs_length; + unsigned raw_descs_length; unsigned fs_descs_count; unsigned hs_descs_count; unsigned ss_descs_count; diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h index 0f8f7be5b0d..2a4b4a72a4f 100644 --- a/include/uapi/linux/usb/functionfs.h +++ b/include/uapi/linux/usb/functionfs.h @@ -10,10 +10,15 @@ enum { FUNCTIONFS_DESCRIPTORS_MAGIC = 1, - FUNCTIONFS_STRINGS_MAGIC = 2 + FUNCTIONFS_STRINGS_MAGIC = 2, + FUNCTIONFS_DESCRIPTORS_MAGIC_V2 = 3, }; -#define FUNCTIONFS_SS_DESC_MAGIC 0x0055DE5C +enum functionfs_flags { + FUNCTIONFS_HAS_FS_DESC = 1, + FUNCTIONFS_HAS_HS_DESC = 2, + FUNCTIONFS_HAS_SS_DESC = 4, +}; #ifndef __KERNEL__ @@ -29,34 +34,40 @@ struct usb_endpoint_descriptor_no_audio { } __attribute__((packed)); -/* - * All numbers must be in little endian order. - */ - -struct usb_functionfs_descs_head { - __le32 magic; - __le32 length; - __le32 fs_count; - __le32 hs_count; -} __attribute__((packed)); - /* * Descriptors format: * * | off | name | type | description | * |-----+-----------+--------------+--------------------------------------| - * | 0 | magic | LE32 | FUNCTIONFS_{FS,HS}_DESCRIPTORS_MAGIC | + * | 0 | magic | LE32 | FUNCTIONFS_DESCRIPTORS_MAGIC_V2 | + * | 4 | length | LE32 | length of the whole data chunk | + * | 8 | flags | LE32 | combination of functionfs_flags | + * | | fs_count | LE32 | number of full-speed descriptors | + * | | hs_count | LE32 | number of high-speed descriptors | + * | | ss_count | LE32 | number of super-speed descriptors | + * | | fs_descrs | Descriptor[] | list of full-speed descriptors | + * | | hs_descrs | Descriptor[] | list of high-speed descriptors | + * | | ss_descrs | Descriptor[] | list of super-speed descriptors | + * + * Depending on which flags are set, various fields may be missing in the + * structure. Any flags that are not recognised cause the whole block to be + * rejected with -ENOSYS. + * + * Legacy descriptors format: + * + * | off | name | type | description | + * |-----+-----------+--------------+--------------------------------------| + * | 0 | magic | LE32 | FUNCTIONFS_DESCRIPTORS_MAGIC | * | 4 | length | LE32 | length of the whole data chunk | * | 8 | fs_count | LE32 | number of full-speed descriptors | * | 12 | hs_count | LE32 | number of high-speed descriptors | * | 16 | fs_descrs | Descriptor[] | list of full-speed descriptors | * | | hs_descrs | Descriptor[] | list of high-speed descriptors | - * | | ss_magic | LE32 | FUNCTIONFS_SS_DESC_MAGIC | - * | | ss_count | LE32 | number of super-speed descriptors | - * | | ss_descrs | Descriptor[] | list of super-speed descriptors | * - * ss_magic: if present then it implies that SS_DESCs are also present - * descs are just valid USB descriptors and have the following format: + * All numbers must be in little endian order. + * + * Descriptor[] is an array of valid USB descriptors which have the following + * format: * * | off | name | type | description | * |-----+-----------------+------+--------------------------| -- cgit v1.2.3-70-g09d2 From a70143bbef6bf06050c32a26d99e917b3e82deb7 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Mon, 3 Mar 2014 17:08:12 +0530 Subject: drivers: phy: usb3/pipe3: Adapt pipe3 driver to Generic PHY Framework Adapted omap-usb3 PHY driver to Generic PHY Framework and moved phy-omap-usb3 driver in drivers/usb/phy to drivers/phy and also renamed the file to phy-ti-pipe3 since this same driver will be used for SATA PHY and PCIE PHY. Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/Kconfig | 11 ++ drivers/phy/Makefile | 1 + drivers/phy/phy-ti-pipe3.c | 413 ++++++++++++++++++++++++++++++++++++++++ drivers/usb/phy/Kconfig | 11 -- drivers/usb/phy/Makefile | 1 - drivers/usb/phy/phy-omap-usb3.c | 361 ----------------------------------- 6 files changed, 425 insertions(+), 373 deletions(-) create mode 100644 drivers/phy/phy-ti-pipe3.c delete mode 100644 drivers/usb/phy/phy-omap-usb3.c diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index dc1756cf5d7..fde4192cc32 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -43,6 +43,17 @@ config OMAP_USB2 The USB OTG controller communicates with the comparator using this driver. +config TI_PIPE3 + tristate "TI PIPE3 PHY Driver" + depends on ARCH_OMAP2PLUS || COMPILE_TEST + select GENERIC_PHY + select OMAP_CONTROL_USB + help + Enable this to support the PIPE3 PHY that is part of TI SOCs. This + driver takes care of all the PHY functionality apart from comparator. + This driver interacts with the "OMAP Control PHY Driver" to power + on/off the PHY. + config TWL4030_USB tristate "TWL4030 USB Transceiver Driver" depends on TWL4030_CORE && REGULATOR_TWL4030 && USB_MUSB_OMAP2PLUS diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 5d0b59edaf8..977a50ead5a 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_PHY_EXYNOS_DP_VIDEO) += phy-exynos-dp-video.o obj-$(CONFIG_PHY_EXYNOS_MIPI_VIDEO) += phy-exynos-mipi-video.o obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.o obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o +obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c new file mode 100644 index 00000000000..c8d16746a5e --- /dev/null +++ b/drivers/phy/phy-ti-pipe3.c @@ -0,0 +1,413 @@ +/* + * phy-ti-pipe3 - PIPE3 PHY driver. + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Author: Kishon Vijay Abraham I + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PLL_STATUS 0x00000004 +#define PLL_GO 0x00000008 +#define PLL_CONFIGURATION1 0x0000000C +#define PLL_CONFIGURATION2 0x00000010 +#define PLL_CONFIGURATION3 0x00000014 +#define PLL_CONFIGURATION4 0x00000020 + +#define PLL_REGM_MASK 0x001FFE00 +#define PLL_REGM_SHIFT 0x9 +#define PLL_REGM_F_MASK 0x0003FFFF +#define PLL_REGM_F_SHIFT 0x0 +#define PLL_REGN_MASK 0x000001FE +#define PLL_REGN_SHIFT 0x1 +#define PLL_SELFREQDCO_MASK 0x0000000E +#define PLL_SELFREQDCO_SHIFT 0x1 +#define PLL_SD_MASK 0x0003FC00 +#define PLL_SD_SHIFT 0x9 +#define SET_PLL_GO 0x1 +#define PLL_TICOPWDN 0x10000 +#define PLL_LOCK 0x2 +#define PLL_IDLE 0x1 + +/* + * This is an Empirical value that works, need to confirm the actual + * value required for the PIPE3PHY_PLL_CONFIGURATION2.PLL_IDLE status + * to be correctly reflected in the PIPE3PHY_PLL_STATUS register. + */ +# define PLL_IDLE_TIME 100; + +struct pipe3_dpll_params { + u16 m; + u8 n; + u8 freq:3; + u8 sd; + u32 mf; +}; + +struct ti_pipe3 { + void __iomem *pll_ctrl_base; + struct device *dev; + struct device *control_dev; + struct clk *wkupclk; + struct clk *sys_clk; + struct clk *optclk; +}; + +struct pipe3_dpll_map { + unsigned long rate; + struct pipe3_dpll_params params; +}; + +static struct pipe3_dpll_map dpll_map[] = { + {12000000, {1250, 5, 4, 20, 0} }, /* 12 MHz */ + {16800000, {3125, 20, 4, 20, 0} }, /* 16.8 MHz */ + {19200000, {1172, 8, 4, 20, 65537} }, /* 19.2 MHz */ + {20000000, {1000, 7, 4, 10, 0} }, /* 20 MHz */ + {26000000, {1250, 12, 4, 20, 0} }, /* 26 MHz */ + {38400000, {3125, 47, 4, 20, 92843} }, /* 38.4 MHz */ +}; + +static inline u32 ti_pipe3_readl(void __iomem *addr, unsigned offset) +{ + return __raw_readl(addr + offset); +} + +static inline void ti_pipe3_writel(void __iomem *addr, unsigned offset, + u32 data) +{ + __raw_writel(data, addr + offset); +} + +static struct pipe3_dpll_params *ti_pipe3_get_dpll_params(unsigned long rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dpll_map); i++) { + if (rate == dpll_map[i].rate) + return &dpll_map[i].params; + } + + return NULL; +} + +static int ti_pipe3_power_off(struct phy *x) +{ + struct ti_pipe3 *phy = phy_get_drvdata(x); + int val; + int timeout = PLL_IDLE_TIME; + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); + val |= PLL_IDLE; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); + + do { + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); + if (val & PLL_TICOPWDN) + break; + udelay(5); + } while (--timeout); + + if (!timeout) { + dev_err(phy->dev, "power off failed\n"); + return -EBUSY; + } + + omap_control_usb_phy_power(phy->control_dev, 0); + + return 0; +} + +static int ti_pipe3_power_on(struct phy *x) +{ + struct ti_pipe3 *phy = phy_get_drvdata(x); + int val; + int timeout = PLL_IDLE_TIME; + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); + val &= ~PLL_IDLE; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); + + do { + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); + if (!(val & PLL_TICOPWDN)) + break; + udelay(5); + } while (--timeout); + + if (!timeout) { + dev_err(phy->dev, "power on failed\n"); + return -EBUSY; + } + + return 0; +} + +static void ti_pipe3_dpll_relock(struct ti_pipe3 *phy) +{ + u32 val; + unsigned long timeout; + + ti_pipe3_writel(phy->pll_ctrl_base, PLL_GO, SET_PLL_GO); + + timeout = jiffies + msecs_to_jiffies(20); + do { + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); + if (val & PLL_LOCK) + break; + } while (!WARN_ON(time_after(jiffies, timeout))); +} + +static int ti_pipe3_dpll_lock(struct ti_pipe3 *phy) +{ + u32 val; + unsigned long rate; + struct pipe3_dpll_params *dpll_params; + + rate = clk_get_rate(phy->sys_clk); + dpll_params = ti_pipe3_get_dpll_params(rate); + if (!dpll_params) { + dev_err(phy->dev, + "No DPLL configuration for %lu Hz SYS CLK\n", rate); + return -EINVAL; + } + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1); + val &= ~PLL_REGN_MASK; + val |= dpll_params->n << PLL_REGN_SHIFT; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION1, val); + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); + val &= ~PLL_SELFREQDCO_MASK; + val |= dpll_params->freq << PLL_SELFREQDCO_SHIFT; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1); + val &= ~PLL_REGM_MASK; + val |= dpll_params->m << PLL_REGM_SHIFT; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION1, val); + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION4); + val &= ~PLL_REGM_F_MASK; + val |= dpll_params->mf << PLL_REGM_F_SHIFT; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION4, val); + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION3); + val &= ~PLL_SD_MASK; + val |= dpll_params->sd << PLL_SD_SHIFT; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION3, val); + + ti_pipe3_dpll_relock(phy); + + return 0; +} + +static int ti_pipe3_init(struct phy *x) +{ + struct ti_pipe3 *phy = phy_get_drvdata(x); + int ret; + + ret = ti_pipe3_dpll_lock(phy); + if (ret) + return ret; + + omap_control_usb_phy_power(phy->control_dev, 1); + + return 0; +} + +static struct phy_ops ops = { + .init = ti_pipe3_init, + .power_on = ti_pipe3_power_on, + .power_off = ti_pipe3_power_off, + .owner = THIS_MODULE, +}; + +static int ti_pipe3_probe(struct platform_device *pdev) +{ + struct ti_pipe3 *phy; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct resource *res; + struct device_node *node = pdev->dev.of_node; + struct device_node *control_node; + struct platform_device *control_pdev; + + if (!node) + return -EINVAL; + + phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); + if (!phy) { + dev_err(&pdev->dev, "unable to alloc mem for TI PIPE3 PHY\n"); + return -ENOMEM; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pll_ctrl"); + phy->pll_ctrl_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(phy->pll_ctrl_base)) + return PTR_ERR(phy->pll_ctrl_base); + + phy->dev = &pdev->dev; + + phy->wkupclk = devm_clk_get(phy->dev, "usb_phy_cm_clk32k"); + if (IS_ERR(phy->wkupclk)) { + dev_err(&pdev->dev, "unable to get usb_phy_cm_clk32k\n"); + return PTR_ERR(phy->wkupclk); + } + clk_prepare(phy->wkupclk); + + phy->optclk = devm_clk_get(phy->dev, "usb_otg_ss_refclk960m"); + if (IS_ERR(phy->optclk)) { + dev_err(&pdev->dev, "unable to get usb_otg_ss_refclk960m\n"); + return PTR_ERR(phy->optclk); + } + clk_prepare(phy->optclk); + + phy->sys_clk = devm_clk_get(phy->dev, "sys_clkin"); + if (IS_ERR(phy->sys_clk)) { + pr_err("%s: unable to get sys_clkin\n", __func__); + return -EINVAL; + } + + control_node = of_parse_phandle(node, "ctrl-module", 0); + if (!control_node) { + dev_err(&pdev->dev, "Failed to get control device phandle\n"); + return -EINVAL; + } + + control_pdev = of_find_device_by_node(control_node); + if (!control_pdev) { + dev_err(&pdev->dev, "Failed to get control device\n"); + return -EINVAL; + } + + phy->control_dev = &control_pdev->dev; + + omap_control_usb_phy_power(phy->control_dev, 0); + + platform_set_drvdata(pdev, phy); + pm_runtime_enable(phy->dev); + + generic_phy = devm_phy_create(phy->dev, &ops, NULL); + if (IS_ERR(generic_phy)) + return PTR_ERR(generic_phy); + + phy_set_drvdata(generic_phy, phy); + phy_provider = devm_of_phy_provider_register(phy->dev, + of_phy_simple_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + pm_runtime_get(&pdev->dev); + + return 0; +} + +static int ti_pipe3_remove(struct platform_device *pdev) +{ + struct ti_pipe3 *phy = platform_get_drvdata(pdev); + + clk_unprepare(phy->wkupclk); + clk_unprepare(phy->optclk); + if (!pm_runtime_suspended(&pdev->dev)) + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_RUNTIME + +static int ti_pipe3_runtime_suspend(struct device *dev) +{ + struct ti_pipe3 *phy = dev_get_drvdata(dev); + + clk_disable(phy->wkupclk); + clk_disable(phy->optclk); + + return 0; +} + +static int ti_pipe3_runtime_resume(struct device *dev) +{ + u32 ret = 0; + struct ti_pipe3 *phy = dev_get_drvdata(dev); + + ret = clk_enable(phy->optclk); + if (ret) { + dev_err(phy->dev, "Failed to enable optclk %d\n", ret); + goto err1; + } + + ret = clk_enable(phy->wkupclk); + if (ret) { + dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret); + goto err2; + } + + return 0; + +err2: + clk_disable(phy->optclk); + +err1: + return ret; +} + +static const struct dev_pm_ops ti_pipe3_pm_ops = { + SET_RUNTIME_PM_OPS(ti_pipe3_runtime_suspend, + ti_pipe3_runtime_resume, NULL) +}; + +#define DEV_PM_OPS (&ti_pipe3_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif + +#ifdef CONFIG_OF +static const struct of_device_id ti_pipe3_id_table[] = { + { .compatible = "ti,phy-usb3" }, + { .compatible = "ti,omap-usb3" }, + {} +}; +MODULE_DEVICE_TABLE(of, ti_pipe3_id_table); +#endif + +static struct platform_driver ti_pipe3_driver = { + .probe = ti_pipe3_probe, + .remove = ti_pipe3_remove, + .driver = { + .name = "ti-pipe3", + .owner = THIS_MODULE, + .pm = DEV_PM_OPS, + .of_match_table = of_match_ptr(ti_pipe3_id_table), + }, +}; + +module_platform_driver(ti_pipe3_driver); + +MODULE_ALIAS("platform: ti_pipe3"); +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("TI PIPE3 phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 7d1451d5bbe..c337ba2d066 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -85,17 +85,6 @@ config OMAP_CONTROL_USB power on the USB2 PHY is present in OMAP4 and OMAP5. OMAP5 has an additional register to power on USB3 PHY. -config OMAP_USB3 - tristate "OMAP USB3 PHY Driver" - depends on ARCH_OMAP2PLUS || COMPILE_TEST - select OMAP_CONTROL_USB - select USB_PHY - help - Enable this to support the USB3 PHY that is part of SOC. This - driver takes care of all the PHY functionality apart from comparator. - This driver interacts with the "OMAP Control USB Driver" to power - on/off the PHY. - config AM335X_CONTROL_USB tristate diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index be58adae349..15f1878388f 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -17,7 +17,6 @@ obj-$(CONFIG_OMAP_CONTROL_USB) += phy-omap-control.o obj-$(CONFIG_AM335X_CONTROL_USB) += phy-am335x-control.o obj-$(CONFIG_AM335X_PHY_USB) += phy-am335x.o obj-$(CONFIG_OMAP_OTG) += phy-omap-otg.o -obj-$(CONFIG_OMAP_USB3) += phy-omap-usb3.o obj-$(CONFIG_SAMSUNG_USBPHY) += phy-samsung-usb.o obj-$(CONFIG_SAMSUNG_USB2PHY) += phy-samsung-usb2.o obj-$(CONFIG_SAMSUNG_USB3PHY) += phy-samsung-usb3.o diff --git a/drivers/usb/phy/phy-omap-usb3.c b/drivers/usb/phy/phy-omap-usb3.c deleted file mode 100644 index 0c6ba29bddd..00000000000 --- a/drivers/usb/phy/phy-omap-usb3.c +++ /dev/null @@ -1,361 +0,0 @@ -/* - * omap-usb3 - USB PHY, talking to dwc3 controller in OMAP. - * - * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Author: Kishon Vijay Abraham I - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define PLL_STATUS 0x00000004 -#define PLL_GO 0x00000008 -#define PLL_CONFIGURATION1 0x0000000C -#define PLL_CONFIGURATION2 0x00000010 -#define PLL_CONFIGURATION3 0x00000014 -#define PLL_CONFIGURATION4 0x00000020 - -#define PLL_REGM_MASK 0x001FFE00 -#define PLL_REGM_SHIFT 0x9 -#define PLL_REGM_F_MASK 0x0003FFFF -#define PLL_REGM_F_SHIFT 0x0 -#define PLL_REGN_MASK 0x000001FE -#define PLL_REGN_SHIFT 0x1 -#define PLL_SELFREQDCO_MASK 0x0000000E -#define PLL_SELFREQDCO_SHIFT 0x1 -#define PLL_SD_MASK 0x0003FC00 -#define PLL_SD_SHIFT 0x9 -#define SET_PLL_GO 0x1 -#define PLL_TICOPWDN 0x10000 -#define PLL_LOCK 0x2 -#define PLL_IDLE 0x1 - -/* - * This is an Empirical value that works, need to confirm the actual - * value required for the USB3PHY_PLL_CONFIGURATION2.PLL_IDLE status - * to be correctly reflected in the USB3PHY_PLL_STATUS register. - */ -# define PLL_IDLE_TIME 100; - -struct usb_dpll_map { - unsigned long rate; - struct usb_dpll_params params; -}; - -static struct usb_dpll_map dpll_map[] = { - {12000000, {1250, 5, 4, 20, 0} }, /* 12 MHz */ - {16800000, {3125, 20, 4, 20, 0} }, /* 16.8 MHz */ - {19200000, {1172, 8, 4, 20, 65537} }, /* 19.2 MHz */ - {20000000, {1000, 7, 4, 10, 0} }, /* 20 MHz */ - {26000000, {1250, 12, 4, 20, 0} }, /* 26 MHz */ - {38400000, {3125, 47, 4, 20, 92843} }, /* 38.4 MHz */ -}; - -static struct usb_dpll_params *omap_usb3_get_dpll_params(unsigned long rate) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(dpll_map); i++) { - if (rate == dpll_map[i].rate) - return &dpll_map[i].params; - } - - return NULL; -} - -static int omap_usb3_suspend(struct usb_phy *x, int suspend) -{ - struct omap_usb *phy = phy_to_omapusb(x); - int val; - int timeout = PLL_IDLE_TIME; - - if (suspend && !phy->is_suspended) { - val = omap_usb_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); - val |= PLL_IDLE; - omap_usb_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); - - do { - val = omap_usb_readl(phy->pll_ctrl_base, PLL_STATUS); - if (val & PLL_TICOPWDN) - break; - udelay(1); - } while (--timeout); - - omap_control_usb_phy_power(phy->control_dev, 0); - - phy->is_suspended = 1; - } else if (!suspend && phy->is_suspended) { - phy->is_suspended = 0; - - val = omap_usb_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); - val &= ~PLL_IDLE; - omap_usb_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); - - do { - val = omap_usb_readl(phy->pll_ctrl_base, PLL_STATUS); - if (!(val & PLL_TICOPWDN)) - break; - udelay(1); - } while (--timeout); - } - - return 0; -} - -static void omap_usb_dpll_relock(struct omap_usb *phy) -{ - u32 val; - unsigned long timeout; - - omap_usb_writel(phy->pll_ctrl_base, PLL_GO, SET_PLL_GO); - - timeout = jiffies + msecs_to_jiffies(20); - do { - val = omap_usb_readl(phy->pll_ctrl_base, PLL_STATUS); - if (val & PLL_LOCK) - break; - } while (!WARN_ON(time_after(jiffies, timeout))); -} - -static int omap_usb_dpll_lock(struct omap_usb *phy) -{ - u32 val; - unsigned long rate; - struct usb_dpll_params *dpll_params; - - rate = clk_get_rate(phy->sys_clk); - dpll_params = omap_usb3_get_dpll_params(rate); - if (!dpll_params) { - dev_err(phy->dev, - "No DPLL configuration for %lu Hz SYS CLK\n", rate); - return -EINVAL; - } - - val = omap_usb_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1); - val &= ~PLL_REGN_MASK; - val |= dpll_params->n << PLL_REGN_SHIFT; - omap_usb_writel(phy->pll_ctrl_base, PLL_CONFIGURATION1, val); - - val = omap_usb_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); - val &= ~PLL_SELFREQDCO_MASK; - val |= dpll_params->freq << PLL_SELFREQDCO_SHIFT; - omap_usb_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); - - val = omap_usb_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1); - val &= ~PLL_REGM_MASK; - val |= dpll_params->m << PLL_REGM_SHIFT; - omap_usb_writel(phy->pll_ctrl_base, PLL_CONFIGURATION1, val); - - val = omap_usb_readl(phy->pll_ctrl_base, PLL_CONFIGURATION4); - val &= ~PLL_REGM_F_MASK; - val |= dpll_params->mf << PLL_REGM_F_SHIFT; - omap_usb_writel(phy->pll_ctrl_base, PLL_CONFIGURATION4, val); - - val = omap_usb_readl(phy->pll_ctrl_base, PLL_CONFIGURATION3); - val &= ~PLL_SD_MASK; - val |= dpll_params->sd << PLL_SD_SHIFT; - omap_usb_writel(phy->pll_ctrl_base, PLL_CONFIGURATION3, val); - - omap_usb_dpll_relock(phy); - - return 0; -} - -static int omap_usb3_init(struct usb_phy *x) -{ - struct omap_usb *phy = phy_to_omapusb(x); - int ret; - - ret = omap_usb_dpll_lock(phy); - if (ret) - return ret; - - omap_control_usb_phy_power(phy->control_dev, 1); - - return 0; -} - -static int omap_usb3_probe(struct platform_device *pdev) -{ - struct omap_usb *phy; - struct resource *res; - struct device_node *node = pdev->dev.of_node; - struct device_node *control_node; - struct platform_device *control_pdev; - - if (!node) - return -EINVAL; - - phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); - if (!phy) { - dev_err(&pdev->dev, "unable to alloc mem for OMAP USB3 PHY\n"); - return -ENOMEM; - } - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pll_ctrl"); - phy->pll_ctrl_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(phy->pll_ctrl_base)) - return PTR_ERR(phy->pll_ctrl_base); - - phy->dev = &pdev->dev; - - phy->phy.dev = phy->dev; - phy->phy.label = "omap-usb3"; - phy->phy.init = omap_usb3_init; - phy->phy.set_suspend = omap_usb3_suspend; - phy->phy.type = USB_PHY_TYPE_USB3; - - phy->is_suspended = 1; - phy->wkupclk = devm_clk_get(phy->dev, "usb_phy_cm_clk32k"); - if (IS_ERR(phy->wkupclk)) { - dev_err(&pdev->dev, "unable to get usb_phy_cm_clk32k\n"); - return PTR_ERR(phy->wkupclk); - } - clk_prepare(phy->wkupclk); - - phy->optclk = devm_clk_get(phy->dev, "usb_otg_ss_refclk960m"); - if (IS_ERR(phy->optclk)) { - dev_err(&pdev->dev, "unable to get usb_otg_ss_refclk960m\n"); - return PTR_ERR(phy->optclk); - } - clk_prepare(phy->optclk); - - phy->sys_clk = devm_clk_get(phy->dev, "sys_clkin"); - if (IS_ERR(phy->sys_clk)) { - pr_err("%s: unable to get sys_clkin\n", __func__); - return -EINVAL; - } - - control_node = of_parse_phandle(node, "ctrl-module", 0); - if (!control_node) { - dev_err(&pdev->dev, "Failed to get control device phandle\n"); - return -EINVAL; - } - control_pdev = of_find_device_by_node(control_node); - if (!control_pdev) { - dev_err(&pdev->dev, "Failed to get control device\n"); - return -EINVAL; - } - - phy->control_dev = &control_pdev->dev; - - omap_control_usb_phy_power(phy->control_dev, 0); - usb_add_phy_dev(&phy->phy); - - platform_set_drvdata(pdev, phy); - - pm_runtime_enable(phy->dev); - pm_runtime_get(&pdev->dev); - - return 0; -} - -static int omap_usb3_remove(struct platform_device *pdev) -{ - struct omap_usb *phy = platform_get_drvdata(pdev); - - clk_unprepare(phy->wkupclk); - clk_unprepare(phy->optclk); - usb_remove_phy(&phy->phy); - if (!pm_runtime_suspended(&pdev->dev)) - pm_runtime_put(&pdev->dev); - pm_runtime_disable(&pdev->dev); - - return 0; -} - -#ifdef CONFIG_PM_RUNTIME - -static int omap_usb3_runtime_suspend(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - struct omap_usb *phy = platform_get_drvdata(pdev); - - clk_disable(phy->wkupclk); - clk_disable(phy->optclk); - - return 0; -} - -static int omap_usb3_runtime_resume(struct device *dev) -{ - u32 ret = 0; - struct platform_device *pdev = to_platform_device(dev); - struct omap_usb *phy = platform_get_drvdata(pdev); - - ret = clk_enable(phy->optclk); - if (ret) { - dev_err(phy->dev, "Failed to enable optclk %d\n", ret); - goto err1; - } - - ret = clk_enable(phy->wkupclk); - if (ret) { - dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret); - goto err2; - } - - return 0; - -err2: - clk_disable(phy->optclk); - -err1: - return ret; -} - -static const struct dev_pm_ops omap_usb3_pm_ops = { - SET_RUNTIME_PM_OPS(omap_usb3_runtime_suspend, omap_usb3_runtime_resume, - NULL) -}; - -#define DEV_PM_OPS (&omap_usb3_pm_ops) -#else -#define DEV_PM_OPS NULL -#endif - -#ifdef CONFIG_OF -static const struct of_device_id omap_usb3_id_table[] = { - { .compatible = "ti,omap-usb3" }, - {} -}; -MODULE_DEVICE_TABLE(of, omap_usb3_id_table); -#endif - -static struct platform_driver omap_usb3_driver = { - .probe = omap_usb3_probe, - .remove = omap_usb3_remove, - .driver = { - .name = "omap-usb3", - .owner = THIS_MODULE, - .pm = DEV_PM_OPS, - .of_match_table = of_match_ptr(omap_usb3_id_table), - }, -}; - -module_platform_driver(omap_usb3_driver); - -MODULE_ALIAS("platform: omap_usb3"); -MODULE_AUTHOR("Texas Instruments Inc."); -MODULE_DESCRIPTION("OMAP USB3 phy driver"); -MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-70-g09d2 From 8cf7651f7046aa4e20bb98c36bec3ddba48351f9 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Mon, 3 Mar 2014 17:08:13 +0530 Subject: usb: phy: omap-usb2: remove *set_suspend* callback from omap-usb2 Now that omap-usb2 is adapted to the new generic PHY framework, *set_suspend* ops can be removed from omap-usb2 driver. Signed-off-by: Kishon Vijay Abraham I Acked-by: Felipe Balbi Reviewed-by: Sylwester Nawrocki --- drivers/phy/phy-omap-usb2.c | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/drivers/phy/phy-omap-usb2.c b/drivers/phy/phy-omap-usb2.c index 7699752fba1..705af5a11fb 100644 --- a/drivers/phy/phy-omap-usb2.c +++ b/drivers/phy/phy-omap-usb2.c @@ -98,28 +98,6 @@ static int omap_usb_set_peripheral(struct usb_otg *otg, return 0; } -static int omap_usb2_suspend(struct usb_phy *x, int suspend) -{ - struct omap_usb *phy = phy_to_omapusb(x); - int ret; - - if (suspend && !phy->is_suspended) { - omap_control_usb_phy_power(phy->control_dev, 0); - pm_runtime_put_sync(phy->dev); - phy->is_suspended = 1; - } else if (!suspend && phy->is_suspended) { - ret = pm_runtime_get_sync(phy->dev); - if (ret < 0) { - dev_err(phy->dev, "get_sync failed with err %d\n", ret); - return ret; - } - omap_control_usb_phy_power(phy->control_dev, 1); - phy->is_suspended = 0; - } - - return 0; -} - static int omap_usb_power_off(struct phy *x) { struct omap_usb *phy = phy_get_drvdata(x); @@ -173,7 +151,6 @@ static int omap_usb2_probe(struct platform_device *pdev) phy->phy.dev = phy->dev; phy->phy.label = "omap-usb2"; - phy->phy.set_suspend = omap_usb2_suspend; phy->phy.otg = otg; phy->phy.type = USB_PHY_TYPE_USB2; @@ -190,8 +167,6 @@ static int omap_usb2_probe(struct platform_device *pdev) } phy->control_dev = &control_pdev->dev; - - phy->is_suspended = 1; omap_control_usb_phy_power(phy->control_dev, 0); otg->set_host = omap_usb_set_host; -- cgit v1.2.3-70-g09d2 From d95faaec4763b94c9eedac9a1f933753015651ed Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Thu, 6 Mar 2014 16:38:38 +0200 Subject: phy: omap-control: Update DT binding information Move omap-control binding information to the right location. Acked-by: Kishon Vijay Abraham I Signed-off-by: Roger Quadros Signed-off-by: Felipe Balbi --- Documentation/devicetree/bindings/phy/ti-phy.txt | 25 ++++++++++++++++++++++ Documentation/devicetree/bindings/usb/omap-usb.txt | 24 --------------------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/Documentation/devicetree/bindings/phy/ti-phy.txt b/Documentation/devicetree/bindings/phy/ti-phy.txt index 207e14cc650..41dc132e782 100644 --- a/Documentation/devicetree/bindings/phy/ti-phy.txt +++ b/Documentation/devicetree/bindings/phy/ti-phy.txt @@ -1,5 +1,30 @@ TI PHY: DT DOCUMENTATION FOR PHYs in TI PLATFORMs +OMAP CONTROL PHY + +Required properties: + - compatible: Should be one of + "ti,control-phy-otghs" - if it has otghs_control mailbox register as on OMAP4. + "ti,control-phy-usb2" - if it has Power down bit in control_dev_conf register + e.g. USB2_PHY on OMAP5. + "ti,control-phy-pipe3" - if it has DPLL and individual Rx & Tx power control + e.g. USB3 PHY and SATA PHY on OMAP5. + "ti,control-phy-dra7usb2" - if it has power down register like USB2 PHY on + DRA7 platform. + "ti,control-phy-am437usb2" - if it has power down register like USB2 PHY on + AM437 platform. + - reg : Address and length of the register set for the device. It contains + the address of "otghs_control" for control-phy-otghs or "power" register + for other types. + - reg-names: should be "otghs_control" control-phy-otghs and "power" for + other types. + +omap_control_usb: omap-control-usb@4a002300 { + compatible = "ti,control-phy-otghs"; + reg = <0x4a00233c 0x4>; + reg-names = "otghs_control"; +}; + OMAP USB2 PHY Required properties: diff --git a/Documentation/devicetree/bindings/usb/omap-usb.txt b/Documentation/devicetree/bindings/usb/omap-usb.txt index c495135115c..38b2faec419 100644 --- a/Documentation/devicetree/bindings/usb/omap-usb.txt +++ b/Documentation/devicetree/bindings/usb/omap-usb.txt @@ -76,27 +76,3 @@ omap_dwc3 { ranges; }; -OMAP CONTROL USB - -Required properties: - - compatible: Should be one of - "ti,control-phy-otghs" - if it has otghs_control mailbox register as on OMAP4. - "ti,control-phy-usb2" - if it has Power down bit in control_dev_conf register - e.g. USB2_PHY on OMAP5. - "ti,control-phy-pipe3" - if it has DPLL and individual Rx & Tx power control - e.g. USB3 PHY and SATA PHY on OMAP5. - "ti,control-phy-dra7usb2" - if it has power down register like USB2 PHY on - DRA7 platform. - "ti,control-phy-am437usb2" - if it has power down register like USB2 PHY on - AM437 platform. - - reg : Address and length of the register set for the device. It contains - the address of "otghs_control" for control-phy-otghs or "power" register - for other types. - - reg-names: should be "otghs_control" control-phy-otghs and "power" for - other types. - -omap_control_usb: omap-control-usb@4a002300 { - compatible = "ti,control-phy-otghs"; - reg = <0x4a00233c 0x4>; - reg-names = "otghs_control"; -}; -- cgit v1.2.3-70-g09d2 From bcffae7708eb8352f44dc510b326541fe43a02a4 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Mon, 3 Mar 2014 19:30:17 +0200 Subject: xhci: Prevent runtime pm from autosuspending during initialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xHCI driver has its own pci probe function that will call usb_hcd_pci_probe to register its usb-2 bus, and then continue to manually register the usb-3 bus. usb_hcd_pci_probe does a pm_runtime_put_noidle at the end and might thus trigger a runtime suspend before the usb-3 bus is ready. Prevent the runtime suspend by increasing the usage count in the beginning of xhci_pci_probe, and decrease it once the usb-3 bus is ready. xhci-platform driver is not using usb_hcd_pci_probe to set up busses and should not need to have it's usage count increased during probe. Signed-off-by: Mathias Nyman Acked-by: Dan Williams Acked-by: Alan Stern Signed-off-by: Sarah Sharp Cc: stable@vger.kernel.org --- drivers/usb/host/xhci-pci.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index cbf0c5cffc9..47390e369cd 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -190,6 +190,10 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) struct usb_hcd *hcd; driver = (struct hc_driver *)id->driver_data; + + /* Prevent runtime suspending between USB-2 and USB-3 initialization */ + pm_runtime_get_noresume(&dev->dev); + /* Register the USB 2.0 roothub. * FIXME: USB core must know to register the USB 2.0 roothub first. * This is sort of silly, because we could just set the HCD driver flags @@ -199,7 +203,7 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) retval = usb_hcd_pci_probe(dev, id); if (retval) - return retval; + goto put_runtime_pm; /* USB 2.0 roothub is stored in the PCI device now. */ hcd = dev_get_drvdata(&dev->dev); @@ -225,12 +229,17 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) if (HCC_MAX_PSA(xhci->hcc_params) >= 4) xhci->shared_hcd->can_do_streams = 1; + /* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */ + pm_runtime_put_noidle(&dev->dev); + return 0; put_usb3_hcd: usb_put_hcd(xhci->shared_hcd); dealloc_usb2_hcd: usb_hcd_pci_remove(dev); +put_runtime_pm: + pm_runtime_put_noidle(&dev->dev); return retval; } -- cgit v1.2.3-70-g09d2 From 7969943789df1196faa9ba67518d83fd93e4f9f6 Mon Sep 17 00:00:00 2001 From: Adrian Huang Date: Thu, 27 Feb 2014 11:26:03 +0000 Subject: xhci: add the meaningful IRQ description if it is empty When some xHCI host controllers fall back to use the legacy IRQ, the member irq_descr of the usb_hcd structure will be empty. This leads to the empty string of the xHCI host controller in /proc/interrupts. Here is the example (The irq 19 is the xHCI host controller): CPU0 0: 91 IO-APIC-edge timer 8: 1 IO-APIC-edge rtc0 9: 7191 IO-APIC-fasteoi acpi 18: 104 IR-IO-APIC-fasteoi ehci_hcd:usb1, ehci_hcd:usb2 19: 473 IR-IO-APIC-fasteoi After applying the patch, the name of the registered xHCI host controller can be displayed correctly. Here is the example: CPU0 0: 91 IO-APIC-edge timer 8: 1 IO-APIC-edge rtc0 9: 7191 IO-APIC-fasteoi acpi 18: 104 IR-IO-APIC-fasteoi ehci_hcd:usb1, ehci_hcd:usb2 19: 473 IR-IO-APIC-fasteoi xhci_hcd:usb3 Tested on v3.14-rc4. Signed-off-by: Adrian Huang Reviewed-by: Nagananda Chumbalkar Signed-off-by: Sarah Sharp --- drivers/usb/host/xhci.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 1db3c2794a4..652be2138b4 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -390,6 +390,10 @@ static int xhci_try_enable_msi(struct usb_hcd *hcd) } legacy_irq: + if (!strlen(hcd->irq_descr)) + snprintf(hcd->irq_descr, sizeof(hcd->irq_descr), "%s:usb%d", + hcd->driver->description, hcd->self.busnum); + /* fall back to legacy interrupt*/ ret = request_irq(pdev->irq, &usb_hcd_irq, IRQF_SHARED, hcd->irq_descr, hcd); -- cgit v1.2.3-70-g09d2 From 73a30bfc0d526db899033165db6f95c427e70505 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 7 Mar 2014 14:19:57 +0300 Subject: usb: dwc3: gadget: cut and paste fixups in suspend/resume These were cut and paste from the ->disconnect function. Fixes commit 30d577b9bcc4 ('usb: dwc3: gadget: call gadget driver's ->suspend/->resume') Signed-off-by: Dan Carpenter Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 1730cd0928c..4af75d0e53d 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2047,7 +2047,7 @@ static void dwc3_disconnect_gadget(struct dwc3 *dwc) static void dwc3_suspend_gadget(struct dwc3 *dwc) { - if (dwc->gadget_driver && dwc->gadget_driver->disconnect) { + if (dwc->gadget_driver && dwc->gadget_driver->suspend) { spin_unlock(&dwc->lock); dwc->gadget_driver->suspend(&dwc->gadget); spin_lock(&dwc->lock); @@ -2056,7 +2056,7 @@ static void dwc3_suspend_gadget(struct dwc3 *dwc) static void dwc3_resume_gadget(struct dwc3 *dwc) { - if (dwc->gadget_driver && dwc->gadget_driver->disconnect) { + if (dwc->gadget_driver && dwc->gadget_driver->resume) { spin_unlock(&dwc->lock); dwc->gadget_driver->resume(&dwc->gadget); spin_lock(&dwc->lock); -- cgit v1.2.3-70-g09d2 From 3f89204bae896e1d44468886a646b84acadc3c8f Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 7 Mar 2014 14:20:22 +0300 Subject: usb: dwc3: gadget: remove known conditions We know what "value" is and it upsets static checkers that we appear to have doubts about it. Signed-off-by: Dan Carpenter Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 4af75d0e53d..a740eac74d5 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1233,8 +1233,7 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value) ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, DWC3_DEPCMD_SETSTALL, ¶ms); if (ret) - dev_err(dwc->dev, "failed to %s STALL on %s\n", - value ? "set" : "clear", + dev_err(dwc->dev, "failed to set STALL on %s\n", dep->name); else dep->flags |= DWC3_EP_STALL; @@ -1242,8 +1241,7 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value) ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, DWC3_DEPCMD_CLEARSTALL, ¶ms); if (ret) - dev_err(dwc->dev, "failed to %s STALL on %s\n", - value ? "set" : "clear", + dev_err(dwc->dev, "failed to clear STALL on %s\n", dep->name); else dep->flags &= ~(DWC3_EP_STALL | DWC3_EP_WEDGE); -- cgit v1.2.3-70-g09d2 From 716fb91dfe1777bd6d5e598f3d3572214b3ed296 Mon Sep 17 00:00:00 2001 From: Weinn Jheng Date: Thu, 27 Feb 2014 17:49:00 +0800 Subject: usb: gadget: u_ether: move hardware transmit to RX NAPI In order to reduce the interrupt times in the embedded system, a receiving workqueue is introduced. This modification also enhanced the overall throughput as the benefits of reducing interrupt occurrence. This work was derived from previous work: u_ether: move hardware transmit to RX workqueue. Which should be base on codeaurora's work. However, the benchmark on my platform shows the throughput with workqueue is slightly better than NAPI. Signed-off-by: Weinn Jheng Cc: David Brownell Cc: David S. Miller Cc: Stephen Hemminger Cc: Greg Kroah-Hartman Cc: Manu Gautam Signed-off-by: Felipe Balbi --- drivers/usb/gadget/u_ether.c | 101 ++++++++++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 35 deletions(-) diff --git a/drivers/usb/gadget/u_ether.c b/drivers/usb/gadget/u_ether.c index b7d4f82872b..50d09c28913 100644 --- a/drivers/usb/gadget/u_ether.c +++ b/drivers/usb/gadget/u_ether.c @@ -48,6 +48,8 @@ #define UETH__VERSION "29-May-2008" +#define GETHER_NAPI_WEIGHT 32 + struct eth_dev { /* lock is held while accessing port_usb */ @@ -72,6 +74,7 @@ struct eth_dev { struct sk_buff_head *list); struct work_struct work; + struct napi_struct rx_napi; unsigned long todo; #define WORK_RX_MEMORY 0 @@ -253,18 +256,16 @@ enomem: DBG(dev, "rx submit --> %d\n", retval); if (skb) dev_kfree_skb_any(skb); - spin_lock_irqsave(&dev->req_lock, flags); - list_add(&req->list, &dev->rx_reqs); - spin_unlock_irqrestore(&dev->req_lock, flags); } return retval; } static void rx_complete(struct usb_ep *ep, struct usb_request *req) { - struct sk_buff *skb = req->context, *skb2; + struct sk_buff *skb = req->context; struct eth_dev *dev = ep->driver_data; int status = req->status; + bool rx_queue = 0; switch (status) { @@ -288,30 +289,8 @@ static void rx_complete(struct usb_ep *ep, struct usb_request *req) } else { skb_queue_tail(&dev->rx_frames, skb); } - skb = NULL; - - skb2 = skb_dequeue(&dev->rx_frames); - while (skb2) { - if (status < 0 - || ETH_HLEN > skb2->len - || skb2->len > VLAN_ETH_FRAME_LEN) { - dev->net->stats.rx_errors++; - dev->net->stats.rx_length_errors++; - DBG(dev, "rx length %d\n", skb2->len); - dev_kfree_skb_any(skb2); - goto next_frame; - } - skb2->protocol = eth_type_trans(skb2, dev->net); - dev->net->stats.rx_packets++; - dev->net->stats.rx_bytes += skb2->len; - - /* no buffer copies needed, unless hardware can't - * use skb buffers. - */ - status = netif_rx(skb2); -next_frame: - skb2 = skb_dequeue(&dev->rx_frames); - } + if (!status) + rx_queue = 1; break; /* software-driven interface shutdown */ @@ -334,22 +313,20 @@ quiesce: /* FALLTHROUGH */ default: + rx_queue = 1; + dev_kfree_skb_any(skb); dev->net->stats.rx_errors++; DBG(dev, "rx status %d\n", status); break; } - if (skb) - dev_kfree_skb_any(skb); - if (!netif_running(dev->net)) { clean: spin_lock(&dev->req_lock); list_add(&req->list, &dev->rx_reqs); spin_unlock(&dev->req_lock); - req = NULL; - } - if (req) - rx_submit(dev, req, GFP_ATOMIC); + + if (rx_queue && likely(napi_schedule_prep(&dev->rx_napi))) + __napi_schedule(&dev->rx_napi); } static int prealloc(struct list_head *list, struct usb_ep *ep, unsigned n) @@ -414,16 +391,24 @@ static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags) { struct usb_request *req; unsigned long flags; + int rx_counts = 0; /* fill unused rxq slots with some skb */ spin_lock_irqsave(&dev->req_lock, flags); while (!list_empty(&dev->rx_reqs)) { + + if (++rx_counts > qlen(dev->gadget, dev->qmult)) + break; + req = container_of(dev->rx_reqs.next, struct usb_request, list); list_del_init(&req->list); spin_unlock_irqrestore(&dev->req_lock, flags); if (rx_submit(dev, req, gfp_flags) < 0) { + spin_lock_irqsave(&dev->req_lock, flags); + list_add(&req->list, &dev->rx_reqs); + spin_unlock_irqrestore(&dev->req_lock, flags); defer_kevent(dev, WORK_RX_MEMORY); return; } @@ -433,6 +418,41 @@ static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags) spin_unlock_irqrestore(&dev->req_lock, flags); } +static int gether_poll(struct napi_struct *napi, int budget) +{ + struct eth_dev *dev = container_of(napi, struct eth_dev, rx_napi); + struct sk_buff *skb; + unsigned int work_done = 0; + int status = 0; + + while ((skb = skb_dequeue(&dev->rx_frames))) { + if (status < 0 + || ETH_HLEN > skb->len + || skb->len > VLAN_ETH_FRAME_LEN) { + dev->net->stats.rx_errors++; + dev->net->stats.rx_length_errors++; + DBG(dev, "rx length %d\n", skb->len); + dev_kfree_skb_any(skb); + continue; + } + skb->protocol = eth_type_trans(skb, dev->net); + dev->net->stats.rx_packets++; + dev->net->stats.rx_bytes += skb->len; + + status = netif_rx_ni(skb); + } + + if (netif_running(dev->net)) { + rx_fill(dev, GFP_KERNEL); + work_done++; + } + + if (work_done < budget) + napi_complete(&dev->rx_napi); + + return work_done; +} + static void eth_work(struct work_struct *work) { struct eth_dev *dev = container_of(work, struct eth_dev, work); @@ -625,6 +645,7 @@ static void eth_start(struct eth_dev *dev, gfp_t gfp_flags) /* and open the tx floodgates */ atomic_set(&dev->tx_qlen, 0); netif_wake_queue(dev->net); + napi_enable(&dev->rx_napi); } static int eth_open(struct net_device *net) @@ -651,6 +672,7 @@ static int eth_stop(struct net_device *net) unsigned long flags; VDBG(dev, "%s\n", __func__); + napi_disable(&dev->rx_napi); netif_stop_queue(net); DBG(dev, "stop stats: rx/tx %ld/%ld, errs %ld/%ld\n", @@ -768,6 +790,7 @@ struct eth_dev *gether_setup_name(struct usb_gadget *g, return ERR_PTR(-ENOMEM); dev = netdev_priv(net); + netif_napi_add(net, &dev->rx_napi, gether_poll, GETHER_NAPI_WEIGHT); spin_lock_init(&dev->lock); spin_lock_init(&dev->req_lock); INIT_WORK(&dev->work, eth_work); @@ -830,6 +853,7 @@ struct net_device *gether_setup_name_default(const char *netname) return ERR_PTR(-ENOMEM); dev = netdev_priv(net); + netif_napi_add(net, &dev->rx_napi, gether_poll, GETHER_NAPI_WEIGHT); spin_lock_init(&dev->lock); spin_lock_init(&dev->req_lock); INIT_WORK(&dev->work, eth_work); @@ -1113,6 +1137,7 @@ void gether_disconnect(struct gether *link) { struct eth_dev *dev = link->ioport; struct usb_request *req; + struct sk_buff *skb; WARN_ON(!dev); if (!dev) @@ -1139,6 +1164,12 @@ void gether_disconnect(struct gether *link) spin_lock(&dev->req_lock); } spin_unlock(&dev->req_lock); + + spin_lock(&dev->rx_frames.lock); + while ((skb = __skb_dequeue(&dev->rx_frames))) + dev_kfree_skb_any(skb); + spin_unlock(&dev->rx_frames.lock); + link->in_ep->driver_data = NULL; link->in_ep->desc = NULL; -- cgit v1.2.3-70-g09d2 From 8bebbe8dc6145303db05964fb09657aac2a7e909 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Thu, 27 Feb 2014 07:38:19 +0800 Subject: usb: phy: fsm: update OTG HNP state transition conditions according to OTG and EH 2.0 spec. According to:"On-The-Go and Embedded Host Supplement to the USB Revision 2.0 Specification July 27, 2012 Revision 2.0 version 1.1a" - From a_host to a_wait_bcon if !b_conn - Add transition from a_host to a_wait_vfall if id state is high or a_bus_drop - From a_wait_vfall to a_idle if a_wait_vfall_tmout Signed-off-by: Li Jun Signed-off-by: Felipe Balbi --- drivers/usb/phy/phy-fsm-usb.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/usb/phy/phy-fsm-usb.c b/drivers/usb/phy/phy-fsm-usb.c index 7aa314ef4a8..c47e5a6edde 100644 --- a/drivers/usb/phy/phy-fsm-usb.c +++ b/drivers/usb/phy/phy-fsm-usb.c @@ -317,10 +317,12 @@ int otg_statemachine(struct otg_fsm *fsm) otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); break; case OTG_STATE_A_HOST: - if ((!fsm->a_bus_req || fsm->a_suspend_req_inf) && + if (fsm->id || fsm->a_bus_drop) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + else if ((!fsm->a_bus_req || fsm->a_suspend_req_inf) && fsm->otg->host->b_hnp_enable) otg_set_state(fsm, OTG_STATE_A_SUSPEND); - else if (fsm->id || !fsm->b_conn || fsm->a_bus_drop) + else if (!fsm->b_conn) otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); else if (!fsm->a_vbus_vld) otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); @@ -346,8 +348,7 @@ int otg_statemachine(struct otg_fsm *fsm) otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); break; case OTG_STATE_A_WAIT_VFALL: - if (fsm->a_wait_vfall_tmout || fsm->id || fsm->a_bus_req || - (!fsm->a_sess_vld && !fsm->b_conn)) + if (fsm->a_wait_vfall_tmout) otg_set_state(fsm, OTG_STATE_A_IDLE); break; case OTG_STATE_A_VBUS_ERR: -- cgit v1.2.3-70-g09d2 From cfe919b53b807ab32e89e1c662c6d242948449bd Mon Sep 17 00:00:00 2001 From: Chuansheng Liu Date: Tue, 4 Mar 2014 15:34:57 +0800 Subject: usb: gadget: return the right length in ffs_epfile_io() When the request length is aligned to maxpacketsize, sometimes the return length ret > the user space requested len. At that time, we will use min_t(size_t, ret, len) to limit the size in case of user data buffer overflow. But we need return the min_t(size_t, ret, len) to tell the user space rightly also. [ balbi@ti.com: also fix comment's indentation ] Acked-by: Michal Nazarewicz Reviewed-by: David Cohen Signed-off-by: Chuansheng Liu Signed-off-by: Felipe Balbi --- drivers/usb/gadget/f_fs.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index 42f7a0e4be5..b2e922dcb40 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -838,19 +838,21 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) ret = -EINTR; usb_ep_dequeue(ep->ep, req); } else { - /* - * XXX We may end up silently droping data here. - * Since data_len (i.e. req->length) may be bigger - * than len (after being rounded up to maxpacketsize), - * we may end up with more data then user space has - * space for. - */ - ret = ep->status; - if (io_data->read && ret > 0 && - unlikely(copy_to_user(io_data->buf, data, - min_t(size_t, ret, - io_data->len)))) - ret = -EFAULT; + /* + * XXX We may end up silently droping data + * here. Since data_len (i.e. req->length) may + * be bigger than len (after being rounded up + * to maxpacketsize), we may end up with more + * data then user space has space for. + */ + ret = ep->status; + if (io_data->read && ret > 0) { + ret = min_t(size_t, ret, io_data->len); + + if (unlikely(copy_to_user(io_data->buf, + data, ret))) + ret = -EFAULT; + } } kfree(data); } -- cgit v1.2.3-70-g09d2 From e99952093169b6d6fce8d612d6540c18c8759fff Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Fri, 7 Mar 2014 12:15:01 +0200 Subject: phy: omap-control: update dra7 and am437 usb2 Documentation bindings The dra7-usb2 and am437-usb2 bindings have not yet been used. Change them to be more elegant. Acked-by: Kishon Vijay Abraham I Signed-off-by: Roger Quadros Signed-off-by: Felipe Balbi --- Documentation/devicetree/bindings/phy/ti-phy.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/phy/ti-phy.txt b/Documentation/devicetree/bindings/phy/ti-phy.txt index 41dc132e782..5dd8544e6a3 100644 --- a/Documentation/devicetree/bindings/phy/ti-phy.txt +++ b/Documentation/devicetree/bindings/phy/ti-phy.txt @@ -9,9 +9,9 @@ Required properties: e.g. USB2_PHY on OMAP5. "ti,control-phy-pipe3" - if it has DPLL and individual Rx & Tx power control e.g. USB3 PHY and SATA PHY on OMAP5. - "ti,control-phy-dra7usb2" - if it has power down register like USB2 PHY on + "ti,control-phy-usb2-dra7" - if it has power down register like USB2 PHY on DRA7 platform. - "ti,control-phy-am437usb2" - if it has power down register like USB2 PHY on + "ti,control-phy-usb2-am437" - if it has power down register like USB2 PHY on AM437 platform. - reg : Address and length of the register set for the device. It contains the address of "otghs_control" for control-phy-otghs or "power" register -- cgit v1.2.3-70-g09d2 From 57554008699a640c7af114c3f67707a9b88d69a6 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Fri, 7 Mar 2014 12:15:02 +0200 Subject: phy: ti-pipe3: Add clocks to PIPE3 PHY Documentation binding Add wakeup, system and reference clocks to DT binding documentation. Acked-by: Kishon Vijay Abraham I Signed-off-by: Roger Quadros Signed-off-by: Felipe Balbi --- Documentation/devicetree/bindings/phy/ti-phy.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/ti-phy.txt b/Documentation/devicetree/bindings/phy/ti-phy.txt index 5dd8544e6a3..3fbdd2995b9 100644 --- a/Documentation/devicetree/bindings/phy/ti-phy.txt +++ b/Documentation/devicetree/bindings/phy/ti-phy.txt @@ -55,6 +55,12 @@ Required properties: filled in "reg". - #phy-cells: determine the number of cells that should be given in the phandle while referencing this phy. + - clocks: a list of phandles and clock-specifier pairs, one for each entry in + clock-names. + - clock-names: should include: + * "wkupclk" - wakeup clock. + * "sysclk" - system clock. + * "refclk" - reference clock. Optional properties: - ctrl-module : phandle of the control module used by PHY driver to power on @@ -70,4 +76,10 @@ usb3phy@4a084400 { reg-names = "phy_rx", "phy_tx", "pll_ctrl"; ctrl-module = <&omap_control_usb>; #phy-cells = <0>; + clocks = <&usb_phy_cm_clk32k>, + <&sys_clkin>, + <&usb_otg_ss_refclk960m>; + clock-names = "wkupclk", + "sysclk", + "refclk"; }; -- cgit v1.2.3-70-g09d2 From 4b76e14d95f7b69e71eabc002dcb0dcb9ebb5340 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Fri, 7 Mar 2014 12:15:03 +0200 Subject: phy: ti-pipe3: Add SATA compatible to Documentation binding SATA PHY needs a new compatible ID. Add it to the DT binding documentation. Acked-by: Kishon Vijay Abraham I Signed-off-by: Roger Quadros Signed-off-by: Felipe Balbi --- Documentation/devicetree/bindings/phy/ti-phy.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/phy/ti-phy.txt b/Documentation/devicetree/bindings/phy/ti-phy.txt index 3fbdd2995b9..788fb0fa376 100644 --- a/Documentation/devicetree/bindings/phy/ti-phy.txt +++ b/Documentation/devicetree/bindings/phy/ti-phy.txt @@ -49,7 +49,8 @@ usb2phy@4a0ad080 { TI PIPE3 PHY Required properties: - - compatible: Should be "ti,phy-usb3". "ti,omap-usb3" is deprecated. + - compatible: Should be "ti,phy-usb3" or "ti,phy-pipe3-sata". + "ti,omap-usb3" is deprecated. - reg : Address and length of the register set for the device. - reg-names: The names of the register addresses corresponding to the registers filled in "reg". -- cgit v1.2.3-70-g09d2 From 6284be23db6b32e17ef34d082386967350d10d1a Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Mon, 3 Mar 2014 17:08:14 +0530 Subject: phy: omap-usb2: move omap_usb.h from linux/usb/ to linux/phy/ No functional change. Moved omap_usb.h from linux/usb/ to linux/phy/. Also removed the unused members of struct omap_usb (after phy-omap-pipe3 started using it's own header file) Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-omap-usb2.c | 2 +- drivers/usb/phy/phy-twl6030-usb.c | 2 +- include/linux/phy/omap_usb.h | 64 +++++++++++++++++++++++++++++++++++++ include/linux/usb/omap_usb.h | 67 --------------------------------------- 4 files changed, 66 insertions(+), 69 deletions(-) create mode 100644 include/linux/phy/omap_usb.h delete mode 100644 include/linux/usb/omap_usb.h diff --git a/drivers/phy/phy-omap-usb2.c b/drivers/phy/phy-omap-usb2.c index 705af5a11fb..9c3f056b3dd 100644 --- a/drivers/phy/phy-omap-usb2.c +++ b/drivers/phy/phy-omap-usb2.c @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/usb/phy/phy-twl6030-usb.c b/drivers/usb/phy/phy-twl6030-usb.c index 214172b68d5..04778cf80d6 100644 --- a/drivers/usb/phy/phy-twl6030-usb.c +++ b/drivers/usb/phy/phy-twl6030-usb.c @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/include/linux/phy/omap_usb.h b/include/linux/phy/omap_usb.h new file mode 100644 index 00000000000..19d343c37fb --- /dev/null +++ b/include/linux/phy/omap_usb.h @@ -0,0 +1,64 @@ +/* + * omap_usb.h -- omap usb2 phy header file + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Author: Kishon Vijay Abraham I + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __DRIVERS_OMAP_USB2_H +#define __DRIVERS_OMAP_USB2_H + +#include +#include + +struct usb_dpll_params { + u16 m; + u8 n; + u8 freq:3; + u8 sd; + u32 mf; +}; + +struct omap_usb { + struct usb_phy phy; + struct phy_companion *comparator; + struct device *dev; + struct device *control_dev; + struct clk *wkupclk; + struct clk *optclk; +}; + +#define phy_to_omapusb(x) container_of((x), struct omap_usb, phy) + +#if defined(CONFIG_OMAP_USB2) || defined(CONFIG_OMAP_USB2_MODULE) +extern int omap_usb2_set_comparator(struct phy_companion *comparator); +#else +static inline int omap_usb2_set_comparator(struct phy_companion *comparator) +{ + return -ENODEV; +} +#endif + +static inline u32 omap_usb_readl(void __iomem *addr, unsigned offset) +{ + return __raw_readl(addr + offset); +} + +static inline void omap_usb_writel(void __iomem *addr, unsigned offset, + u32 data) +{ + __raw_writel(data, addr + offset); +} + +#endif /* __DRIVERS_OMAP_USB_H */ diff --git a/include/linux/usb/omap_usb.h b/include/linux/usb/omap_usb.h deleted file mode 100644 index 6ae29360e1d..00000000000 --- a/include/linux/usb/omap_usb.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * omap_usb.h -- omap usb2 phy header file - * - * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Author: Kishon Vijay Abraham I - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef __DRIVERS_OMAP_USB2_H -#define __DRIVERS_OMAP_USB2_H - -#include -#include - -struct usb_dpll_params { - u16 m; - u8 n; - u8 freq:3; - u8 sd; - u32 mf; -}; - -struct omap_usb { - struct usb_phy phy; - struct phy_companion *comparator; - void __iomem *pll_ctrl_base; - struct device *dev; - struct device *control_dev; - struct clk *wkupclk; - struct clk *sys_clk; - struct clk *optclk; - u8 is_suspended:1; -}; - -#define phy_to_omapusb(x) container_of((x), struct omap_usb, phy) - -#if defined(CONFIG_OMAP_USB2) || defined(CONFIG_OMAP_USB2_MODULE) -extern int omap_usb2_set_comparator(struct phy_companion *comparator); -#else -static inline int omap_usb2_set_comparator(struct phy_companion *comparator) -{ - return -ENODEV; -} -#endif - -static inline u32 omap_usb_readl(void __iomem *addr, unsigned offset) -{ - return __raw_readl(addr + offset); -} - -static inline void omap_usb_writel(void __iomem *addr, unsigned offset, - u32 data) -{ - __raw_writel(data, addr + offset); -} - -#endif /* __DRIVERS_OMAP_USB_H */ -- cgit v1.2.3-70-g09d2 From 2a6da97ff530650d26570a6a1ec0ac1deac927bd Mon Sep 17 00:00:00 2001 From: Thomas Pugliese Date: Tue, 4 Mar 2014 11:24:55 -0600 Subject: usb: wusbcore: fix potential double list_del on urb dequeue This patch locks rpipe->seg_lock around the entire transfer segment cleanup loop in wa_urb_dequeue instead of just one case of the switch statement. This fixes a race between __wa_xfer_delayed_run and wa_urb_dequeue where a transfer segment in the WA_SEG_DELAYED state could be removed from the rpipe seg_list twice leading to memory corruption. It also switches the spin_lock call to use the non-irqsave version since the xfer->lock is already held and irqs already disabled. Signed-off-by: Thomas Pugliese Signed-off-by: Greg Kroah-Hartman --- drivers/usb/wusbcore/wa-xfer.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c index 404ce81b286..6e0d377d437 100644 --- a/drivers/usb/wusbcore/wa-xfer.c +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -1974,6 +1974,11 @@ int wa_urb_dequeue(struct wahc *wa, struct urb *urb, int status) goto out_unlock; /* setup(), enqueue_b() completes */ /* Ok, the xfer is in flight already, it's been setup and submitted.*/ xfer_abort_pending = __wa_xfer_abort(xfer) >= 0; + /* + * grab the rpipe->seg_lock here to prevent racing with + * __wa_xfer_delayed_run. + */ + spin_lock(&rpipe->seg_lock); for (cnt = 0; cnt < xfer->segs; cnt++) { seg = xfer->seg[cnt]; pr_debug("%s: xfer id 0x%08X#%d status = %d\n", @@ -1994,10 +1999,8 @@ int wa_urb_dequeue(struct wahc *wa, struct urb *urb, int status) */ seg->status = WA_SEG_ABORTED; seg->result = -ENOENT; - spin_lock_irqsave(&rpipe->seg_lock, flags2); list_del(&seg->list_node); xfer->segs_done++; - spin_unlock_irqrestore(&rpipe->seg_lock, flags2); break; case WA_SEG_DONE: case WA_SEG_ERROR: @@ -2026,6 +2029,7 @@ int wa_urb_dequeue(struct wahc *wa, struct urb *urb, int status) break; } } + spin_unlock(&rpipe->seg_lock); xfer->result = urb->status; /* -ENOENT or -ECONNRESET */ done = __wa_xfer_is_done(xfer); spin_unlock_irqrestore(&xfer->lock, flags); -- cgit v1.2.3-70-g09d2 From 5090ecea133325f762704f00963bca1b024ee691 Mon Sep 17 00:00:00 2001 From: Thomas Pugliese Date: Tue, 4 Mar 2014 11:24:56 -0600 Subject: usb: wusbcore: don't mark WA_SEG_DTI_PENDING segs as done in urb_dequeue Data for transfer segments in the WA_SEG_DTI_PENDING state is actively being read by the driver. Let the buffer read callback handle the transfer cleanup since cleaning it up in wa_urb_dequeue will cause the read callback to access invalid memory if the transfer is completed. Signed-off-by: Thomas Pugliese Signed-off-by: Greg Kroah-Hartman --- drivers/usb/wusbcore/wa-xfer.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c index 6e0d377d437..cf7c95ceebe 100644 --- a/drivers/usb/wusbcore/wa-xfer.c +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -2005,6 +2005,16 @@ int wa_urb_dequeue(struct wahc *wa, struct urb *urb, int status) case WA_SEG_DONE: case WA_SEG_ERROR: case WA_SEG_ABORTED: + break; + /* + * The buf_in data for a segment in the + * WA_SEG_DTI_PENDING state is actively being read. + * Let wa_buf_in_cb handle it since it will be called + * and will increment xfer->segs_done. Cleaning up + * here could cause wa_buf_in_cb to access the xfer + * after it has been completed/freed. + */ + case WA_SEG_DTI_PENDING: break; /* * In the states below, the HWA device already knows @@ -2015,7 +2025,6 @@ int wa_urb_dequeue(struct wahc *wa, struct urb *urb, int status) */ case WA_SEG_SUBMITTED: case WA_SEG_PENDING: - case WA_SEG_DTI_PENDING: /* * Check if the abort was successfully sent. This could * be false if the HWA has been removed but we haven't -- cgit v1.2.3-70-g09d2 From 86e2864d7e9d429b94624a28ba3f05fc2db89051 Mon Sep 17 00:00:00 2001 From: Thomas Pugliese Date: Thu, 6 Mar 2014 12:53:37 -0600 Subject: usb: wusbcore: disable transfer notifications for Alereon HWAs The HWA driver does not do anything with transfer notifications after receiving the first one and the Alereon HWA allows them to be disabled as a performance optimization. This patch sends a vendor specific command to the Alereon HWA on startup to disable transfer notifications. If the command is successful, the DTI system is started immediately since that would normally be started upon the first reception of a transfer notification which will no longer be sent. Signed-off-by: Thomas Pugliese Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/hwa-hc.c | 42 ++++++++++++++++++++- drivers/usb/wusbcore/wa-hc.h | 11 ++++++ drivers/usb/wusbcore/wa-xfer.c | 83 +++++++++++++++++++++++++----------------- 3 files changed, 100 insertions(+), 36 deletions(-) diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c index e07669993f5..d0d8fadf706 100644 --- a/drivers/usb/host/hwa-hc.c +++ b/drivers/usb/host/hwa-hc.c @@ -261,8 +261,44 @@ static int __hwahc_op_wusbhc_start(struct wusbhc *wusbhc) dev_err(dev, "cannot listen to notifications: %d\n", result); goto error_stop; } + /* + * If WUSB_QUIRK_ALEREON_HWA_DISABLE_XFER_NOTIFICATIONS is set, + * disable transfer notifications. + */ + if (hwahc->wa.quirks & + WUSB_QUIRK_ALEREON_HWA_DISABLE_XFER_NOTIFICATIONS) { + struct usb_host_interface *cur_altsetting = + hwahc->wa.usb_iface->cur_altsetting; + + result = usb_control_msg(hwahc->wa.usb_dev, + usb_sndctrlpipe(hwahc->wa.usb_dev, 0), + WA_REQ_ALEREON_DISABLE_XFER_NOTIFICATIONS, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_INTERFACE, + WA_REQ_ALEREON_FEATURE_SET, + cur_altsetting->desc.bInterfaceNumber, + NULL, 0, + USB_CTRL_SET_TIMEOUT); + /* + * If we successfully sent the control message, start DTI here + * because no transfer notifications will be received which is + * where DTI is normally started. + */ + if (result == 0) + result = wa_dti_start(&hwahc->wa); + else + result = 0; /* OK. Continue normally. */ + + if (result < 0) { + dev_err(dev, "cannot start DTI: %d\n", result); + goto error_dti_start; + } + } + return result; +error_dti_start: + wa_nep_disarm(&hwahc->wa); error_stop: __wa_clear_feature(&hwahc->wa, WA_ENABLE); return result; @@ -827,10 +863,12 @@ static void hwahc_disconnect(struct usb_interface *usb_iface) static struct usb_device_id hwahc_id_table[] = { /* Alereon 5310 */ { USB_DEVICE_AND_INTERFACE_INFO(0x13dc, 0x5310, 0xe0, 0x02, 0x01), - .driver_info = WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC }, + .driver_info = WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC | + WUSB_QUIRK_ALEREON_HWA_DISABLE_XFER_NOTIFICATIONS }, /* Alereon 5611 */ { USB_DEVICE_AND_INTERFACE_INFO(0x13dc, 0x5611, 0xe0, 0x02, 0x01), - .driver_info = WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC }, + .driver_info = WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC | + WUSB_QUIRK_ALEREON_HWA_DISABLE_XFER_NOTIFICATIONS }, /* FIXME: use class labels for this */ { USB_INTERFACE_INFO(0xe0, 0x02, 0x01), }, {}, diff --git a/drivers/usb/wusbcore/wa-hc.h b/drivers/usb/wusbcore/wa-hc.h index a2ef84b8397..7510960588d 100644 --- a/drivers/usb/wusbcore/wa-hc.h +++ b/drivers/usb/wusbcore/wa-hc.h @@ -134,8 +134,18 @@ enum wa_quirks { * requests to be concatenated and not sent as separate packets. */ WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC = 0x01, + /* + * The Alereon HWA can be instructed to not send transfer notifications + * as an optimization. + */ + WUSB_QUIRK_ALEREON_HWA_DISABLE_XFER_NOTIFICATIONS = 0x02, }; +enum wa_vendor_specific_requests { + WA_REQ_ALEREON_DISABLE_XFER_NOTIFICATIONS = 0x4C, + WA_REQ_ALEREON_FEATURE_SET = 0x01, + WA_REQ_ALEREON_FEATURE_CLEAR = 0x00, +}; /** * Instance of a HWA Host Controller * @@ -234,6 +244,7 @@ struct wahc { extern int wa_create(struct wahc *wa, struct usb_interface *iface, kernel_ulong_t); extern void __wa_destroy(struct wahc *wa); +extern int wa_dti_start(struct wahc *wa); void wa_reset_all(struct wahc *wa); diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c index cf7c95ceebe..52c7259705e 100644 --- a/drivers/usb/wusbcore/wa-xfer.c +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -2741,41 +2741,15 @@ out: } /* - * Transfer complete notification - * - * Called from the notif.c code. We get a notification on EP2 saying - * that some endpoint has some transfer result data available. We are - * about to read it. - * - * To speed up things, we always have a URB reading the DTI URB; we - * don't really set it up and start it until the first xfer complete - * notification arrives, which is what we do here. - * - * Follow up in wa_dti_cb(), as that's where the whole state - * machine starts. - * - * So here we just initialize the DTI URB for reading transfer result - * notifications and also the buffer-in URB, for reading buffers. Then - * we just submit the DTI URB. - * - * @wa shall be referenced + * Initialize the DTI URB for reading transfer result notifications and also + * the buffer-in URB, for reading buffers. Then we just submit the DTI URB. */ -void wa_handle_notif_xfer(struct wahc *wa, struct wa_notif_hdr *notif_hdr) +int wa_dti_start(struct wahc *wa) { - int result; - struct device *dev = &wa->usb_iface->dev; - struct wa_notif_xfer *notif_xfer; const struct usb_endpoint_descriptor *dti_epd = wa->dti_epd; + struct device *dev = &wa->usb_iface->dev; + int result = -ENOMEM; - notif_xfer = container_of(notif_hdr, struct wa_notif_xfer, hdr); - BUG_ON(notif_hdr->bNotifyType != WA_NOTIF_TRANSFER); - - if ((0x80 | notif_xfer->bEndpoint) != dti_epd->bEndpointAddress) { - /* FIXME: hardcoded limitation, adapt */ - dev_err(dev, "BUG: DTI ep is %u, not %u (hack me)\n", - notif_xfer->bEndpoint, dti_epd->bEndpointAddress); - goto error; - } if (wa->dti_urb != NULL) /* DTI URB already started */ goto out; @@ -2786,7 +2760,7 @@ void wa_handle_notif_xfer(struct wahc *wa, struct wa_notif_hdr *notif_hdr) } usb_fill_bulk_urb( wa->dti_urb, wa->usb_dev, - usb_rcvbulkpipe(wa->usb_dev, 0x80 | notif_xfer->bEndpoint), + usb_rcvbulkpipe(wa->usb_dev, 0x80 | dti_epd->bEndpointAddress), wa->dti_buf, wa->dti_buf_size, wa_dti_cb, wa); @@ -2797,7 +2771,7 @@ void wa_handle_notif_xfer(struct wahc *wa, struct wa_notif_hdr *notif_hdr) } usb_fill_bulk_urb( wa->buf_in_urb, wa->usb_dev, - usb_rcvbulkpipe(wa->usb_dev, 0x80 | notif_xfer->bEndpoint), + usb_rcvbulkpipe(wa->usb_dev, 0x80 | dti_epd->bEndpointAddress), NULL, 0, wa_buf_in_cb, wa); result = usb_submit_urb(wa->dti_urb, GFP_KERNEL); if (result < 0) { @@ -2806,7 +2780,7 @@ void wa_handle_notif_xfer(struct wahc *wa, struct wa_notif_hdr *notif_hdr) goto error_dti_urb_submit; } out: - return; + return 0; error_dti_urb_submit: usb_put_urb(wa->buf_in_urb); @@ -2815,6 +2789,47 @@ error_buf_in_urb_alloc: usb_put_urb(wa->dti_urb); wa->dti_urb = NULL; error_dti_urb_alloc: + return result; +} +EXPORT_SYMBOL_GPL(wa_dti_start); +/* + * Transfer complete notification + * + * Called from the notif.c code. We get a notification on EP2 saying + * that some endpoint has some transfer result data available. We are + * about to read it. + * + * To speed up things, we always have a URB reading the DTI URB; we + * don't really set it up and start it until the first xfer complete + * notification arrives, which is what we do here. + * + * Follow up in wa_dti_cb(), as that's where the whole state + * machine starts. + * + * @wa shall be referenced + */ +void wa_handle_notif_xfer(struct wahc *wa, struct wa_notif_hdr *notif_hdr) +{ + struct device *dev = &wa->usb_iface->dev; + struct wa_notif_xfer *notif_xfer; + const struct usb_endpoint_descriptor *dti_epd = wa->dti_epd; + + notif_xfer = container_of(notif_hdr, struct wa_notif_xfer, hdr); + BUG_ON(notif_hdr->bNotifyType != WA_NOTIF_TRANSFER); + + if ((0x80 | notif_xfer->bEndpoint) != dti_epd->bEndpointAddress) { + /* FIXME: hardcoded limitation, adapt */ + dev_err(dev, "BUG: DTI ep is %u, not %u (hack me)\n", + notif_xfer->bEndpoint, dti_epd->bEndpointAddress); + goto error; + } + + /* attempt to start the DTI ep processing. */ + if (wa_dti_start(wa) < 0) + goto error; + + return; + error: wa_reset_all(wa); } -- cgit v1.2.3-70-g09d2 From 0b3f3b2c777a2f7d20c9826a190ffd5bbd288f8f Mon Sep 17 00:00:00 2001 From: Kamil Debski Date: Thu, 6 Mar 2014 12:16:46 +0100 Subject: phy: core: Add an exported of_phy_get function Previously the of_phy_get function took a struct device * and was declared static. It was impossible to call it from another driver and thus it was impossible to get phy defined for a given node. The old function was renamed to _of_phy_get and was left for internal use. of_phy_get function was added and it was exported. The function enables to get a phy for a given device tree node. Signed-off-by: Kamil Debski Reviewed-by: Tomasz Figa Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-core.c | 45 ++++++++++++++++++++++++++++++++++++--------- include/linux/phy/phy.h | 6 ++++++ 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index 6c738376daf..7c1b0e14a23 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -274,8 +274,8 @@ int phy_power_off(struct phy *phy) EXPORT_SYMBOL_GPL(phy_power_off); /** - * of_phy_get() - lookup and obtain a reference to a phy by phandle - * @dev: device that requests this phy + * _of_phy_get() - lookup and obtain a reference to a phy by phandle + * @np: device_node for which to get the phy * @index: the index of the phy * * Returns the phy associated with the given phandle value, @@ -284,20 +284,17 @@ EXPORT_SYMBOL_GPL(phy_power_off); * not yet loaded. This function uses of_xlate call back function provided * while registering the phy_provider to find the phy instance. */ -static struct phy *of_phy_get(struct device *dev, int index) +static struct phy *_of_phy_get(struct device_node *np, int index) { int ret; struct phy_provider *phy_provider; struct phy *phy = NULL; struct of_phandle_args args; - ret = of_parse_phandle_with_args(dev->of_node, "phys", "#phy-cells", + ret = of_parse_phandle_with_args(np, "phys", "#phy-cells", index, &args); - if (ret) { - dev_dbg(dev, "failed to get phy in %s node\n", - dev->of_node->full_name); + if (ret) return ERR_PTR(-ENODEV); - } mutex_lock(&phy_provider_mutex); phy_provider = of_phy_provider_lookup(args.np); @@ -316,6 +313,36 @@ err0: return phy; } +/** + * of_phy_get() - lookup and obtain a reference to a phy using a device_node. + * @np: device_node for which to get the phy + * @con_id: name of the phy from device's point of view + * + * Returns the phy driver, after getting a refcount to it; or + * -ENODEV if there is no such phy. The caller is responsible for + * calling phy_put() to release that count. + */ +struct phy *of_phy_get(struct device_node *np, const char *con_id) +{ + struct phy *phy = NULL; + int index = 0; + + if (con_id) + index = of_property_match_string(np, "phy-names", con_id); + + phy = _of_phy_get(np, index); + if (IS_ERR(phy)) + return phy; + + if (!try_module_get(phy->ops->owner)) + return ERR_PTR(-EPROBE_DEFER); + + get_device(&phy->dev); + + return phy; +} +EXPORT_SYMBOL_GPL(of_phy_get); + /** * phy_put() - release the PHY * @phy: the phy returned by phy_get() @@ -407,7 +434,7 @@ struct phy *phy_get(struct device *dev, const char *string) if (dev->of_node) { index = of_property_match_string(dev->of_node, "phy-names", string); - phy = of_phy_get(dev, index); + phy = _of_phy_get(dev->of_node, index); } else { phy = phy_lookup(dev, string); } diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h index 3f83459dbb2..50c7629b586 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -151,6 +151,7 @@ struct phy *devm_phy_get(struct device *dev, const char *string); struct phy *devm_phy_optional_get(struct device *dev, const char *string); void phy_put(struct phy *phy); void devm_phy_put(struct device *dev, struct phy *phy); +struct phy *of_phy_get(struct device_node *np, const char *con_id); struct phy *of_phy_simple_xlate(struct device *dev, struct of_phandle_args *args); struct phy *phy_create(struct device *dev, const struct phy_ops *ops, @@ -259,6 +260,11 @@ static inline void devm_phy_put(struct device *dev, struct phy *phy) { } +static inline struct phy *of_phy_get(struct device_node *np, const char *con_id) +{ + return ERR_PTR(-ENOSYS); +} + static inline struct phy *of_phy_simple_xlate(struct device *dev, struct of_phandle_args *args) { -- cgit v1.2.3-70-g09d2 From b5d682f4eb76c98f2ca5658926df43dd05cf2c37 Mon Sep 17 00:00:00 2001 From: Kamil Debski Date: Thu, 6 Mar 2014 12:16:47 +0100 Subject: phy: core: Add devm_of_phy_get to phy-core Adding devm_of_phy_get will allow to get phys by supplying a pointer to the struct device_node instead of struct device. Signed-off-by: Kamil Debski Reviewed-by: Tomasz Figa Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-core.c | 31 +++++++++++++++++++++++++++++++ include/linux/phy/phy.h | 9 +++++++++ 2 files changed, 40 insertions(+) diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index 7c1b0e14a23..623b71c54b3 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -525,6 +525,37 @@ struct phy *devm_phy_optional_get(struct device *dev, const char *string) } EXPORT_SYMBOL_GPL(devm_phy_optional_get); +/** + * devm_of_phy_get() - lookup and obtain a reference to a phy. + * @dev: device that requests this phy + * @np: node containing the phy + * @con_id: name of the phy from device's point of view + * + * Gets the phy using of_phy_get(), and associates a device with it using + * devres. On driver detach, release function is invoked on the devres data, + * then, devres data is freed. + */ +struct phy *devm_of_phy_get(struct device *dev, struct device_node *np, + const char *con_id) +{ + struct phy **ptr, *phy; + + ptr = devres_alloc(devm_phy_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + phy = of_phy_get(np, con_id); + if (!IS_ERR(phy)) { + *ptr = phy; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return phy; +} +EXPORT_SYMBOL_GPL(devm_of_phy_get); + /** * phy_create() - create a new phy * @dev: device that is creating the new phy diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h index 50c7629b586..e2f5ca96cdd 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -149,6 +149,8 @@ struct phy *phy_get(struct device *dev, const char *string); struct phy *phy_optional_get(struct device *dev, const char *string); struct phy *devm_phy_get(struct device *dev, const char *string); struct phy *devm_phy_optional_get(struct device *dev, const char *string); +struct phy *devm_of_phy_get(struct device *dev, struct device_node *np, + const char *con_id); void phy_put(struct phy *phy); void devm_phy_put(struct device *dev, struct phy *phy); struct phy *of_phy_get(struct device_node *np, const char *con_id); @@ -252,6 +254,13 @@ static inline struct phy *devm_phy_optional_get(struct device *dev, return ERR_PTR(-ENOSYS); } +static inline struct phy *devm_of_phy_get(struct device *dev, + struct device_node *np, + const char *con_id) +{ + return ERR_PTR(-ENOSYS); +} + static inline void phy_put(struct phy *phy) { } -- cgit v1.2.3-70-g09d2 From 06fb01373cae0bbf9af3a2b49e29c4341a5ba311 Mon Sep 17 00:00:00 2001 From: Kamil Debski Date: Thu, 6 Mar 2014 12:16:48 +0100 Subject: phy: Add new Exynos USB 2.0 PHY driver Add a new driver for the Exynos USB 2.0 PHY. The new driver uses the generic PHY framework. The driver includes support for the Exynos 4x10 and 4x12 SoC families. Signed-off-by: Kamil Debski Reviewed-by: Tomasz Figa Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/samsung-phy.txt | 53 ++++ Documentation/phy/samsung-usb2.txt | 135 +++++++++ drivers/phy/Kconfig | 29 ++ drivers/phy/Makefile | 3 + drivers/phy/phy-exynos4210-usb2.c | 261 ++++++++++++++++ drivers/phy/phy-exynos4x12-usb2.c | 328 +++++++++++++++++++++ drivers/phy/phy-samsung-usb2.c | 222 ++++++++++++++ drivers/phy/phy-samsung-usb2.h | 66 +++++ 8 files changed, 1097 insertions(+) create mode 100644 Documentation/phy/samsung-usb2.txt create mode 100644 drivers/phy/phy-exynos4210-usb2.c create mode 100644 drivers/phy/phy-exynos4x12-usb2.c create mode 100644 drivers/phy/phy-samsung-usb2.c create mode 100644 drivers/phy/phy-samsung-usb2.h diff --git a/Documentation/devicetree/bindings/phy/samsung-phy.txt b/Documentation/devicetree/bindings/phy/samsung-phy.txt index c0fccaa1671..bf955abba5d 100644 --- a/Documentation/devicetree/bindings/phy/samsung-phy.txt +++ b/Documentation/devicetree/bindings/phy/samsung-phy.txt @@ -20,3 +20,56 @@ Required properties: - compatible : should be "samsung,exynos5250-dp-video-phy"; - reg : offset and length of the Display Port PHY register set; - #phy-cells : from the generic PHY bindings, must be 0; + +Samsung S5P/EXYNOS SoC series USB PHY +------------------------------------------------- + +Required properties: +- compatible : should be one of the listed compatibles: + - "samsung,exynos4210-usb2-phy" + - "samsung,exynos4x12-usb2-phy" +- reg : a list of registers used by phy driver + - first and obligatory is the location of phy modules registers +- samsung,sysreg-phandle - handle to syscon used to control the system registers +- samsung,pmureg-phandle - handle to syscon used to control PMU registers +- #phy-cells : from the generic phy bindings, must be 1; +- clocks and clock-names: + - the "phy" clock is required by the phy module, used as a gate + - the "ref" clock is used to get the rate of the clock provided to the + PHY module + +The first phandle argument in the PHY specifier identifies the PHY, its +meaning is compatible dependent. For the currently supported SoCs (Exynos 4210 +and Exynos 4212) it is as follows: + 0 - USB device ("device"), + 1 - USB host ("host"), + 2 - HSIC0 ("hsic0"), + 3 - HSIC1 ("hsic1"), + +Exynos 4210 and Exynos 4212 use mode switching and require that mode switch +register is supplied. + +Example: + +For Exynos 4412 (compatible with Exynos 4212): + +usbphy: phy@125b0000 { + compatible = "samsung,exynos4x12-usb2-phy"; + reg = <0x125b0000 0x100>; + clocks = <&clock 305>, <&clock 2>; + clock-names = "phy", "ref"; + status = "okay"; + #phy-cells = <1>; + samsung,sysreg-phandle = <&sys_reg>; + samsung,pmureg-phandle = <&pmu_reg>; +}; + +Then the PHY can be used in other nodes such as: + +phy-consumer@12340000 { + phys = <&usbphy 2>; + phy-names = "phy"; +}; + +Refer to DT bindings documentation of particular PHY consumer devices for more +information about required PHYs and the way of specification. diff --git a/Documentation/phy/samsung-usb2.txt b/Documentation/phy/samsung-usb2.txt new file mode 100644 index 00000000000..ed12d437189 --- /dev/null +++ b/Documentation/phy/samsung-usb2.txt @@ -0,0 +1,135 @@ +.------------------------------------------------------------------------------+ +| Samsung USB 2.0 PHY adaptation layer | ++-----------------------------------------------------------------------------+' + +| 1. Description ++---------------- + +The architecture of the USB 2.0 PHY module in Samsung SoCs is similar +among many SoCs. In spite of the similarities it proved difficult to +create a one driver that would fit all these PHY controllers. Often +the differences were minor and were found in particular bits of the +registers of the PHY. In some rare cases the order of register writes or +the PHY powering up process had to be altered. This adaptation layer is +a compromise between having separate drivers and having a single driver +with added support for many special cases. + +| 2. Files description ++---------------------- + +- phy-samsung-usb2.c + This is the main file of the adaptation layer. This file contains + the probe function and provides two callbacks to the Generic PHY + Framework. This two callbacks are used to power on and power off the + phy. They carry out the common work that has to be done on all version + of the PHY module. Depending on which SoC was chosen they execute SoC + specific callbacks. The specific SoC version is selected by choosing + the appropriate compatible string. In addition, this file contains + struct of_device_id definitions for particular SoCs. + +- phy-samsung-usb2.h + This is the include file. It declares the structures used by this + driver. In addition it should contain extern declarations for + structures that describe particular SoCs. + +| 3. Supporting SoCs ++-------------------- + +To support a new SoC a new file should be added to the drivers/phy +directory. Each SoC's configuration is stored in an instance of the +struct samsung_usb2_phy_config. + +struct samsung_usb2_phy_config { + const struct samsung_usb2_common_phy *phys; + int (*rate_to_clk)(unsigned long, u32 *); + unsigned int num_phys; + bool has_mode_switch; +}; + +The num_phys is the number of phys handled by the driver. *phys is an +array that contains the configuration for each phy. The has_mode_switch +property is a boolean flag that determines whether the SoC has USB host +and device on a single pair of pins. If so, a special register has to +be modified to change the internal routing of these pins between a USB +device or host module. + +For example the configuration for Exynos 4210 is following: + +const struct samsung_usb2_phy_config exynos4210_usb2_phy_config = { + .has_mode_switch = 0, + .num_phys = EXYNOS4210_NUM_PHYS, + .phys = exynos4210_phys, + .rate_to_clk = exynos4210_rate_to_clk, +} + +- int (*rate_to_clk)(unsigned long, u32 *) + The rate_to_clk callback is to convert the rate of the clock + used as the reference clock for the PHY module to the value + that should be written in the hardware register. + +The exynos4210_phys configuration array is as follows: + +static const struct samsung_usb2_common_phy exynos4210_phys[] = { + { + .label = "device", + .id = EXYNOS4210_DEVICE, + .power_on = exynos4210_power_on, + .power_off = exynos4210_power_off, + }, + { + .label = "host", + .id = EXYNOS4210_HOST, + .power_on = exynos4210_power_on, + .power_off = exynos4210_power_off, + }, + { + .label = "hsic0", + .id = EXYNOS4210_HSIC0, + .power_on = exynos4210_power_on, + .power_off = exynos4210_power_off, + }, + { + .label = "hsic1", + .id = EXYNOS4210_HSIC1, + .power_on = exynos4210_power_on, + .power_off = exynos4210_power_off, + }, + {}, +}; + +- int (*power_on)(struct samsung_usb2_phy_instance *); +- int (*power_off)(struct samsung_usb2_phy_instance *); + These two callbacks are used to power on and power off the phy + by modifying appropriate registers. + +Final change to the driver is adding appropriate compatible value to the +phy-samsung-usb2.c file. In case of Exynos 4210 the following lines were +added to the struct of_device_id samsung_usb2_phy_of_match[] array: + +#ifdef CONFIG_PHY_EXYNOS4210_USB2 + { + .compatible = "samsung,exynos4210-usb2-phy", + .data = &exynos4210_usb2_phy_config, + }, +#endif + +To add further flexibility to the driver the Kconfig file enables to +include support for selected SoCs in the compiled driver. The Kconfig +entry for Exynos 4210 is following: + +config PHY_EXYNOS4210_USB2 + bool "Support for Exynos 4210" + depends on PHY_SAMSUNG_USB2 + depends on CPU_EXYNOS4210 + help + Enable USB PHY support for Exynos 4210. This option requires that + Samsung USB 2.0 PHY driver is enabled and means that support for this + particular SoC is compiled in the driver. In case of Exynos 4210 four + phys are available - device, host, HSCI0 and HSCI1. + +The newly created file that supports the new SoC has to be also added to the +Makefile. In case of Exynos 4210 the added line is following: + +obj-$(CONFIG_PHY_EXYNOS4210_USB2) += phy-exynos4210-usb2.o + +After completing these steps the support for the new SoC should be ready. diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index fde4192cc32..33748365375 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -107,4 +107,33 @@ config PHY_SUN4I_USB This driver controls the entire USB PHY block, both the USB OTG parts, as well as the 2 regular USB 2 host PHYs. +config PHY_SAMSUNG_USB2 + tristate "Samsung USB 2.0 PHY driver" + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support the Samsung USB 2.0 PHY driver for Samsung + SoCs. This driver provides the interface for USB 2.0 PHY. Support for + particular SoCs has to be enabled in addition to this driver. Number + and type of supported phys depends on the SoC. + +config PHY_EXYNOS4210_USB2 + bool "Support for Exynos 4210" + depends on PHY_SAMSUNG_USB2 + depends on CPU_EXYNOS4210 + help + Enable USB PHY support for Exynos 4210. This option requires that + Samsung USB 2.0 PHY driver is enabled and means that support for this + particular SoC is compiled in the driver. In case of Exynos 4210 four + phys are available - device, host, HSIC0 and HSIC1. + +config PHY_EXYNOS4X12_USB2 + bool "Support for Exynos 4x12" + depends on PHY_SAMSUNG_USB2 + depends on (SOC_EXYNOS4212 || SOC_EXYNOS4412) + help + Enable USB PHY support for Exynos 4x12. This option requires that + Samsung USB 2.0 PHY driver is enabled and means that support for this + particular SoC is compiled in the driver. In case of Exynos 4x12 four + phys are available - device, host, HSIC0 and HSIC1. endmenu diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 977a50ead5a..c4ddd23d6d2 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -12,3 +12,6 @@ obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o +obj-$(CONFIG_PHY_SAMSUNG_USB2) += phy-samsung-usb2.o +obj-$(CONFIG_PHY_EXYNOS4210_USB2) += phy-exynos4210-usb2.o +obj-$(CONFIG_PHY_EXYNOS4X12_USB2) += phy-exynos4x12-usb2.o diff --git a/drivers/phy/phy-exynos4210-usb2.c b/drivers/phy/phy-exynos4210-usb2.c new file mode 100644 index 00000000000..236a52ad94e --- /dev/null +++ b/drivers/phy/phy-exynos4210-usb2.c @@ -0,0 +1,261 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver - Exynos 4210 support + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Kamil Debski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include "phy-samsung-usb2.h" + +/* Exynos USB PHY registers */ + +/* PHY power control */ +#define EXYNOS_4210_UPHYPWR 0x0 + +#define EXYNOS_4210_UPHYPWR_PHY0_SUSPEND BIT(0) +#define EXYNOS_4210_UPHYPWR_PHY0_PWR BIT(3) +#define EXYNOS_4210_UPHYPWR_PHY0_OTG_PWR BIT(4) +#define EXYNOS_4210_UPHYPWR_PHY0_SLEEP BIT(5) +#define EXYNOS_4210_UPHYPWR_PHY0 ( \ + EXYNOS_4210_UPHYPWR_PHY0_SUSPEND | \ + EXYNOS_4210_UPHYPWR_PHY0_PWR | \ + EXYNOS_4210_UPHYPWR_PHY0_OTG_PWR | \ + EXYNOS_4210_UPHYPWR_PHY0_SLEEP) + +#define EXYNOS_4210_UPHYPWR_PHY1_SUSPEND BIT(6) +#define EXYNOS_4210_UPHYPWR_PHY1_PWR BIT(7) +#define EXYNOS_4210_UPHYPWR_PHY1_SLEEP BIT(8) +#define EXYNOS_4210_UPHYPWR_PHY1 ( \ + EXYNOS_4210_UPHYPWR_PHY1_SUSPEND | \ + EXYNOS_4210_UPHYPWR_PHY1_PWR | \ + EXYNOS_4210_UPHYPWR_PHY1_SLEEP) + +#define EXYNOS_4210_UPHYPWR_HSIC0_SUSPEND BIT(9) +#define EXYNOS_4210_UPHYPWR_HSIC0_SLEEP BIT(10) +#define EXYNOS_4210_UPHYPWR_HSIC0 ( \ + EXYNOS_4210_UPHYPWR_HSIC0_SUSPEND | \ + EXYNOS_4210_UPHYPWR_HSIC0_SLEEP) + +#define EXYNOS_4210_UPHYPWR_HSIC1_SUSPEND BIT(11) +#define EXYNOS_4210_UPHYPWR_HSIC1_SLEEP BIT(12) +#define EXYNOS_4210_UPHYPWR_HSIC1 ( \ + EXYNOS_4210_UPHYPWR_HSIC1_SUSPEND | \ + EXYNOS_4210_UPHYPWR_HSIC1_SLEEP) + +/* PHY clock control */ +#define EXYNOS_4210_UPHYCLK 0x4 + +#define EXYNOS_4210_UPHYCLK_PHYFSEL_MASK (0x3 << 0) +#define EXYNOS_4210_UPHYCLK_PHYFSEL_OFFSET 0 +#define EXYNOS_4210_UPHYCLK_PHYFSEL_48MHZ (0x0 << 0) +#define EXYNOS_4210_UPHYCLK_PHYFSEL_24MHZ (0x3 << 0) +#define EXYNOS_4210_UPHYCLK_PHYFSEL_12MHZ (0x2 << 0) + +#define EXYNOS_4210_UPHYCLK_PHY0_ID_PULLUP BIT(2) +#define EXYNOS_4210_UPHYCLK_PHY0_COMMON_ON BIT(4) +#define EXYNOS_4210_UPHYCLK_PHY1_COMMON_ON BIT(7) + +/* PHY reset control */ +#define EXYNOS_4210_UPHYRST 0x8 + +#define EXYNOS_4210_URSTCON_PHY0 BIT(0) +#define EXYNOS_4210_URSTCON_OTG_HLINK BIT(1) +#define EXYNOS_4210_URSTCON_OTG_PHYLINK BIT(2) +#define EXYNOS_4210_URSTCON_PHY1_ALL BIT(3) +#define EXYNOS_4210_URSTCON_PHY1_P0 BIT(4) +#define EXYNOS_4210_URSTCON_PHY1_P1P2 BIT(5) +#define EXYNOS_4210_URSTCON_HOST_LINK_ALL BIT(6) +#define EXYNOS_4210_URSTCON_HOST_LINK_P0 BIT(7) +#define EXYNOS_4210_URSTCON_HOST_LINK_P1 BIT(8) +#define EXYNOS_4210_URSTCON_HOST_LINK_P2 BIT(9) + +/* Isolation, configured in the power management unit */ +#define EXYNOS_4210_USB_ISOL_DEVICE_OFFSET 0x704 +#define EXYNOS_4210_USB_ISOL_DEVICE BIT(0) +#define EXYNOS_4210_USB_ISOL_HOST_OFFSET 0x708 +#define EXYNOS_4210_USB_ISOL_HOST BIT(0) + +/* USBYPHY1 Floating prevention */ +#define EXYNOS_4210_UPHY1CON 0x34 +#define EXYNOS_4210_UPHY1CON_FLOAT_PREVENTION 0x1 + +/* Mode switching SUB Device <-> Host */ +#define EXYNOS_4210_MODE_SWITCH_OFFSET 0x21c +#define EXYNOS_4210_MODE_SWITCH_MASK 1 +#define EXYNOS_4210_MODE_SWITCH_DEVICE 0 +#define EXYNOS_4210_MODE_SWITCH_HOST 1 + +enum exynos4210_phy_id { + EXYNOS4210_DEVICE, + EXYNOS4210_HOST, + EXYNOS4210_HSIC0, + EXYNOS4210_HSIC1, + EXYNOS4210_NUM_PHYS, +}; + +/* + * exynos4210_rate_to_clk() converts the supplied clock rate to the value that + * can be written to the phy register. + */ +static int exynos4210_rate_to_clk(unsigned long rate, u32 *reg) +{ + switch (rate) { + case 12 * MHZ: + *reg = EXYNOS_4210_UPHYCLK_PHYFSEL_12MHZ; + break; + case 24 * MHZ: + *reg = EXYNOS_4210_UPHYCLK_PHYFSEL_24MHZ; + break; + case 48 * MHZ: + *reg = EXYNOS_4210_UPHYCLK_PHYFSEL_48MHZ; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void exynos4210_isol(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 offset; + u32 mask; + + switch (inst->cfg->id) { + case EXYNOS4210_DEVICE: + offset = EXYNOS_4210_USB_ISOL_DEVICE_OFFSET; + mask = EXYNOS_4210_USB_ISOL_DEVICE; + break; + case EXYNOS4210_HOST: + offset = EXYNOS_4210_USB_ISOL_HOST_OFFSET; + mask = EXYNOS_4210_USB_ISOL_HOST; + break; + default: + return; + }; + + regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask); +} + +static void exynos4210_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 rstbits = 0; + u32 phypwr = 0; + u32 rst; + u32 pwr; + u32 clk; + + switch (inst->cfg->id) { + case EXYNOS4210_DEVICE: + phypwr = EXYNOS_4210_UPHYPWR_PHY0; + rstbits = EXYNOS_4210_URSTCON_PHY0; + break; + case EXYNOS4210_HOST: + phypwr = EXYNOS_4210_UPHYPWR_PHY1; + rstbits = EXYNOS_4210_URSTCON_PHY1_ALL | + EXYNOS_4210_URSTCON_PHY1_P0 | + EXYNOS_4210_URSTCON_PHY1_P1P2 | + EXYNOS_4210_URSTCON_HOST_LINK_ALL | + EXYNOS_4210_URSTCON_HOST_LINK_P0; + writel(on, drv->reg_phy + EXYNOS_4210_UPHY1CON); + break; + case EXYNOS4210_HSIC0: + phypwr = EXYNOS_4210_UPHYPWR_HSIC0; + rstbits = EXYNOS_4210_URSTCON_PHY1_P1P2 | + EXYNOS_4210_URSTCON_HOST_LINK_P1; + break; + case EXYNOS4210_HSIC1: + phypwr = EXYNOS_4210_UPHYPWR_HSIC1; + rstbits = EXYNOS_4210_URSTCON_PHY1_P1P2 | + EXYNOS_4210_URSTCON_HOST_LINK_P2; + break; + }; + + if (on) { + clk = readl(drv->reg_phy + EXYNOS_4210_UPHYCLK); + clk &= ~EXYNOS_4210_UPHYCLK_PHYFSEL_MASK; + clk |= drv->ref_reg_val << EXYNOS_4210_UPHYCLK_PHYFSEL_OFFSET; + writel(clk, drv->reg_phy + EXYNOS_4210_UPHYCLK); + + pwr = readl(drv->reg_phy + EXYNOS_4210_UPHYPWR); + pwr &= ~phypwr; + writel(pwr, drv->reg_phy + EXYNOS_4210_UPHYPWR); + + rst = readl(drv->reg_phy + EXYNOS_4210_UPHYRST); + rst |= rstbits; + writel(rst, drv->reg_phy + EXYNOS_4210_UPHYRST); + udelay(10); + rst &= ~rstbits; + writel(rst, drv->reg_phy + EXYNOS_4210_UPHYRST); + /* The following delay is necessary for the reset sequence to be + * completed */ + udelay(80); + } else { + pwr = readl(drv->reg_phy + EXYNOS_4210_UPHYPWR); + pwr |= phypwr; + writel(pwr, drv->reg_phy + EXYNOS_4210_UPHYPWR); + } +} + +static int exynos4210_power_on(struct samsung_usb2_phy_instance *inst) +{ + /* Order of initialisation is important - first power then isolation */ + exynos4210_phy_pwr(inst, 1); + exynos4210_isol(inst, 0); + + return 0; +} + +static int exynos4210_power_off(struct samsung_usb2_phy_instance *inst) +{ + exynos4210_isol(inst, 1); + exynos4210_phy_pwr(inst, 0); + + return 0; +} + + +static const struct samsung_usb2_common_phy exynos4210_phys[] = { + { + .label = "device", + .id = EXYNOS4210_DEVICE, + .power_on = exynos4210_power_on, + .power_off = exynos4210_power_off, + }, + { + .label = "host", + .id = EXYNOS4210_HOST, + .power_on = exynos4210_power_on, + .power_off = exynos4210_power_off, + }, + { + .label = "hsic0", + .id = EXYNOS4210_HSIC0, + .power_on = exynos4210_power_on, + .power_off = exynos4210_power_off, + }, + { + .label = "hsic1", + .id = EXYNOS4210_HSIC1, + .power_on = exynos4210_power_on, + .power_off = exynos4210_power_off, + }, + {}, +}; + +const struct samsung_usb2_phy_config exynos4210_usb2_phy_config = { + .has_mode_switch = 0, + .num_phys = EXYNOS4210_NUM_PHYS, + .phys = exynos4210_phys, + .rate_to_clk = exynos4210_rate_to_clk, +}; diff --git a/drivers/phy/phy-exynos4x12-usb2.c b/drivers/phy/phy-exynos4x12-usb2.c new file mode 100644 index 00000000000..d92a7cc5698 --- /dev/null +++ b/drivers/phy/phy-exynos4x12-usb2.c @@ -0,0 +1,328 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver - Exynos 4x12 support + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Kamil Debski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include "phy-samsung-usb2.h" + +/* Exynos USB PHY registers */ + +/* PHY power control */ +#define EXYNOS_4x12_UPHYPWR 0x0 + +#define EXYNOS_4x12_UPHYPWR_PHY0_SUSPEND BIT(0) +#define EXYNOS_4x12_UPHYPWR_PHY0_PWR BIT(3) +#define EXYNOS_4x12_UPHYPWR_PHY0_OTG_PWR BIT(4) +#define EXYNOS_4x12_UPHYPWR_PHY0_SLEEP BIT(5) +#define EXYNOS_4x12_UPHYPWR_PHY0 ( \ + EXYNOS_4x12_UPHYPWR_PHY0_SUSPEND | \ + EXYNOS_4x12_UPHYPWR_PHY0_PWR | \ + EXYNOS_4x12_UPHYPWR_PHY0_OTG_PWR | \ + EXYNOS_4x12_UPHYPWR_PHY0_SLEEP) + +#define EXYNOS_4x12_UPHYPWR_PHY1_SUSPEND BIT(6) +#define EXYNOS_4x12_UPHYPWR_PHY1_PWR BIT(7) +#define EXYNOS_4x12_UPHYPWR_PHY1_SLEEP BIT(8) +#define EXYNOS_4x12_UPHYPWR_PHY1 ( \ + EXYNOS_4x12_UPHYPWR_PHY1_SUSPEND | \ + EXYNOS_4x12_UPHYPWR_PHY1_PWR | \ + EXYNOS_4x12_UPHYPWR_PHY1_SLEEP) + +#define EXYNOS_4x12_UPHYPWR_HSIC0_SUSPEND BIT(9) +#define EXYNOS_4x12_UPHYPWR_HSIC0_PWR BIT(10) +#define EXYNOS_4x12_UPHYPWR_HSIC0_SLEEP BIT(11) +#define EXYNOS_4x12_UPHYPWR_HSIC0 ( \ + EXYNOS_4x12_UPHYPWR_HSIC0_SUSPEND | \ + EXYNOS_4x12_UPHYPWR_HSIC0_PWR | \ + EXYNOS_4x12_UPHYPWR_HSIC0_SLEEP) + +#define EXYNOS_4x12_UPHYPWR_HSIC1_SUSPEND BIT(12) +#define EXYNOS_4x12_UPHYPWR_HSIC1_PWR BIT(13) +#define EXYNOS_4x12_UPHYPWR_HSIC1_SLEEP BIT(14) +#define EXYNOS_4x12_UPHYPWR_HSIC1 ( \ + EXYNOS_4x12_UPHYPWR_HSIC1_SUSPEND | \ + EXYNOS_4x12_UPHYPWR_HSIC1_PWR | \ + EXYNOS_4x12_UPHYPWR_HSIC1_SLEEP) + +/* PHY clock control */ +#define EXYNOS_4x12_UPHYCLK 0x4 + +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_MASK (0x7 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_OFFSET 0 +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_9MHZ6 (0x0 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_10MHZ (0x1 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_12MHZ (0x2 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_19MHZ2 (0x3 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_20MHZ (0x4 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_24MHZ (0x5 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_50MHZ (0x7 << 0) + +#define EXYNOS_4x12_UPHYCLK_PHY0_ID_PULLUP BIT(3) +#define EXYNOS_4x12_UPHYCLK_PHY0_COMMON_ON BIT(4) +#define EXYNOS_4x12_UPHYCLK_PHY1_COMMON_ON BIT(7) + +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_MASK (0x7f << 10) +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_OFFSET 10 +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_12MHZ (0x24 << 10) +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_15MHZ (0x1c << 10) +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_16MHZ (0x1a << 10) +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_19MHZ2 (0x15 << 10) +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_20MHZ (0x14 << 10) + +/* PHY reset control */ +#define EXYNOS_4x12_UPHYRST 0x8 + +#define EXYNOS_4x12_URSTCON_PHY0 BIT(0) +#define EXYNOS_4x12_URSTCON_OTG_HLINK BIT(1) +#define EXYNOS_4x12_URSTCON_OTG_PHYLINK BIT(2) +#define EXYNOS_4x12_URSTCON_HOST_PHY BIT(3) +#define EXYNOS_4x12_URSTCON_PHY1 BIT(4) +#define EXYNOS_4x12_URSTCON_HSIC0 BIT(5) +#define EXYNOS_4x12_URSTCON_HSIC1 BIT(6) +#define EXYNOS_4x12_URSTCON_HOST_LINK_ALL BIT(7) +#define EXYNOS_4x12_URSTCON_HOST_LINK_P0 BIT(8) +#define EXYNOS_4x12_URSTCON_HOST_LINK_P1 BIT(9) +#define EXYNOS_4x12_URSTCON_HOST_LINK_P2 BIT(10) + +/* Isolation, configured in the power management unit */ +#define EXYNOS_4x12_USB_ISOL_OFFSET 0x704 +#define EXYNOS_4x12_USB_ISOL_OTG BIT(0) +#define EXYNOS_4x12_USB_ISOL_HSIC0_OFFSET 0x708 +#define EXYNOS_4x12_USB_ISOL_HSIC0 BIT(0) +#define EXYNOS_4x12_USB_ISOL_HSIC1_OFFSET 0x70c +#define EXYNOS_4x12_USB_ISOL_HSIC1 BIT(0) + +/* Mode switching SUB Device <-> Host */ +#define EXYNOS_4x12_MODE_SWITCH_OFFSET 0x21c +#define EXYNOS_4x12_MODE_SWITCH_MASK 1 +#define EXYNOS_4x12_MODE_SWITCH_DEVICE 0 +#define EXYNOS_4x12_MODE_SWITCH_HOST 1 + +enum exynos4x12_phy_id { + EXYNOS4x12_DEVICE, + EXYNOS4x12_HOST, + EXYNOS4x12_HSIC0, + EXYNOS4x12_HSIC1, + EXYNOS4x12_NUM_PHYS, +}; + +/* + * exynos4x12_rate_to_clk() converts the supplied clock rate to the value that + * can be written to the phy register. + */ +static int exynos4x12_rate_to_clk(unsigned long rate, u32 *reg) +{ + /* EXYNOS_4x12_UPHYCLK_PHYFSEL_MASK */ + + switch (rate) { + case 9600 * KHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_9MHZ6; + break; + case 10 * MHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_10MHZ; + break; + case 12 * MHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_12MHZ; + break; + case 19200 * KHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_19MHZ2; + break; + case 20 * MHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_20MHZ; + break; + case 24 * MHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_24MHZ; + break; + case 50 * MHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_50MHZ; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void exynos4x12_isol(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 offset; + u32 mask; + + switch (inst->cfg->id) { + case EXYNOS4x12_DEVICE: + case EXYNOS4x12_HOST: + offset = EXYNOS_4x12_USB_ISOL_OFFSET; + mask = EXYNOS_4x12_USB_ISOL_OTG; + break; + case EXYNOS4x12_HSIC0: + offset = EXYNOS_4x12_USB_ISOL_HSIC0_OFFSET; + mask = EXYNOS_4x12_USB_ISOL_HSIC0; + break; + case EXYNOS4x12_HSIC1: + offset = EXYNOS_4x12_USB_ISOL_HSIC1_OFFSET; + mask = EXYNOS_4x12_USB_ISOL_HSIC1; + break; + default: + return; + }; + + regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask); +} + +static void exynos4x12_setup_clk(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 clk; + + clk = readl(drv->reg_phy + EXYNOS_4x12_UPHYCLK); + clk &= ~EXYNOS_4x12_UPHYCLK_PHYFSEL_MASK; + clk |= drv->ref_reg_val << EXYNOS_4x12_UPHYCLK_PHYFSEL_OFFSET; + writel(clk, drv->reg_phy + EXYNOS_4x12_UPHYCLK); +} + +static void exynos4x12_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 rstbits = 0; + u32 phypwr = 0; + u32 rst; + u32 pwr; + u32 mode = 0; + u32 switch_mode = 0; + + switch (inst->cfg->id) { + case EXYNOS4x12_DEVICE: + phypwr = EXYNOS_4x12_UPHYPWR_PHY0; + rstbits = EXYNOS_4x12_URSTCON_PHY0; + mode = EXYNOS_4x12_MODE_SWITCH_DEVICE; + switch_mode = 1; + break; + case EXYNOS4x12_HOST: + phypwr = EXYNOS_4x12_UPHYPWR_PHY1; + rstbits = EXYNOS_4x12_URSTCON_HOST_PHY; + mode = EXYNOS_4x12_MODE_SWITCH_HOST; + switch_mode = 1; + break; + case EXYNOS4x12_HSIC0: + phypwr = EXYNOS_4x12_UPHYPWR_HSIC0; + rstbits = EXYNOS_4x12_URSTCON_HSIC1 | + EXYNOS_4x12_URSTCON_HOST_LINK_P0 | + EXYNOS_4x12_URSTCON_HOST_PHY; + break; + case EXYNOS4x12_HSIC1: + phypwr = EXYNOS_4x12_UPHYPWR_HSIC1; + rstbits = EXYNOS_4x12_URSTCON_HSIC1 | + EXYNOS_4x12_URSTCON_HOST_LINK_P1; + break; + }; + + if (on) { + if (switch_mode) + regmap_update_bits(drv->reg_sys, + EXYNOS_4x12_MODE_SWITCH_OFFSET, + EXYNOS_4x12_MODE_SWITCH_MASK, mode); + + pwr = readl(drv->reg_phy + EXYNOS_4x12_UPHYPWR); + pwr &= ~phypwr; + writel(pwr, drv->reg_phy + EXYNOS_4x12_UPHYPWR); + + rst = readl(drv->reg_phy + EXYNOS_4x12_UPHYRST); + rst |= rstbits; + writel(rst, drv->reg_phy + EXYNOS_4x12_UPHYRST); + udelay(10); + rst &= ~rstbits; + writel(rst, drv->reg_phy + EXYNOS_4x12_UPHYRST); + /* The following delay is necessary for the reset sequence to be + * completed */ + udelay(80); + } else { + pwr = readl(drv->reg_phy + EXYNOS_4x12_UPHYPWR); + pwr |= phypwr; + writel(pwr, drv->reg_phy + EXYNOS_4x12_UPHYPWR); + } +} + +static int exynos4x12_power_on(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + + inst->enabled = 1; + exynos4x12_setup_clk(inst); + exynos4x12_phy_pwr(inst, 1); + exynos4x12_isol(inst, 0); + + /* Power on the device, as it is necessary for HSIC to work */ + if (inst->cfg->id == EXYNOS4x12_HSIC0) { + struct samsung_usb2_phy_instance *device = + &drv->instances[EXYNOS4x12_DEVICE]; + exynos4x12_phy_pwr(device, 1); + exynos4x12_isol(device, 0); + } + + return 0; +} + +static int exynos4x12_power_off(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + struct samsung_usb2_phy_instance *device = + &drv->instances[EXYNOS4x12_DEVICE]; + + inst->enabled = 0; + exynos4x12_isol(inst, 1); + exynos4x12_phy_pwr(inst, 0); + + if (inst->cfg->id == EXYNOS4x12_HSIC0 && !device->enabled) { + exynos4x12_isol(device, 1); + exynos4x12_phy_pwr(device, 0); + } + + return 0; +} + + +static const struct samsung_usb2_common_phy exynos4x12_phys[] = { + { + .label = "device", + .id = EXYNOS4x12_DEVICE, + .power_on = exynos4x12_power_on, + .power_off = exynos4x12_power_off, + }, + { + .label = "host", + .id = EXYNOS4x12_HOST, + .power_on = exynos4x12_power_on, + .power_off = exynos4x12_power_off, + }, + { + .label = "hsic0", + .id = EXYNOS4x12_HSIC0, + .power_on = exynos4x12_power_on, + .power_off = exynos4x12_power_off, + }, + { + .label = "hsic1", + .id = EXYNOS4x12_HSIC1, + .power_on = exynos4x12_power_on, + .power_off = exynos4x12_power_off, + }, + {}, +}; + +const struct samsung_usb2_phy_config exynos4x12_usb2_phy_config = { + .has_mode_switch = 1, + .num_phys = EXYNOS4x12_NUM_PHYS, + .phys = exynos4x12_phys, + .rate_to_clk = exynos4x12_rate_to_clk, +}; diff --git a/drivers/phy/phy-samsung-usb2.c b/drivers/phy/phy-samsung-usb2.c new file mode 100644 index 00000000000..c3b771907f1 --- /dev/null +++ b/drivers/phy/phy-samsung-usb2.c @@ -0,0 +1,222 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Kamil Debski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "phy-samsung-usb2.h" + +static int samsung_usb2_phy_power_on(struct phy *phy) +{ + struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy); + struct samsung_usb2_phy_driver *drv = inst->drv; + int ret; + + dev_dbg(drv->dev, "Request to power_on \"%s\" usb phy\n", + inst->cfg->label); + ret = clk_prepare_enable(drv->clk); + if (ret) + goto err_main_clk; + ret = clk_prepare_enable(drv->ref_clk); + if (ret) + goto err_instance_clk; + if (inst->cfg->power_on) { + spin_lock(&drv->lock); + ret = inst->cfg->power_on(inst); + spin_unlock(&drv->lock); + } + + return 0; + +err_instance_clk: + clk_disable_unprepare(drv->clk); +err_main_clk: + return ret; +} + +static int samsung_usb2_phy_power_off(struct phy *phy) +{ + struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy); + struct samsung_usb2_phy_driver *drv = inst->drv; + int ret = 0; + + dev_dbg(drv->dev, "Request to power_off \"%s\" usb phy\n", + inst->cfg->label); + if (inst->cfg->power_off) { + spin_lock(&drv->lock); + ret = inst->cfg->power_off(inst); + spin_unlock(&drv->lock); + } + clk_disable_unprepare(drv->ref_clk); + clk_disable_unprepare(drv->clk); + return ret; +} + +static struct phy_ops samsung_usb2_phy_ops = { + .power_on = samsung_usb2_phy_power_on, + .power_off = samsung_usb2_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct phy *samsung_usb2_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct samsung_usb2_phy_driver *drv; + + drv = dev_get_drvdata(dev); + if (!drv) + return ERR_PTR(-EINVAL); + + if (WARN_ON(args->args[0] >= drv->cfg->num_phys)) + return ERR_PTR(-ENODEV); + + return drv->instances[args->args[0]].phy; +} + +static const struct of_device_id samsung_usb2_phy_of_match[] = { +#ifdef CONFIG_PHY_EXYNOS4210_USB2 + { + .compatible = "samsung,exynos4210-usb2-phy", + .data = &exynos4210_usb2_phy_config, + }, +#endif +#ifdef CONFIG_PHY_EXYNOS4X12_USB2 + { + .compatible = "samsung,exynos4x12-usb2-phy", + .data = &exynos4x12_usb2_phy_config, + }, +#endif + { }, +}; + +static int samsung_usb2_phy_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + const struct samsung_usb2_phy_config *cfg; + struct device *dev = &pdev->dev; + struct phy_provider *phy_provider; + struct resource *mem; + struct samsung_usb2_phy_driver *drv; + int i, ret; + + if (!pdev->dev.of_node) { + dev_err(dev, "This driver is required to be instantiated from device tree\n"); + return -EINVAL; + } + + match = of_match_node(samsung_usb2_phy_of_match, pdev->dev.of_node); + if (!match) { + dev_err(dev, "of_match_node() failed\n"); + return -EINVAL; + } + cfg = match->data; + + drv = devm_kzalloc(dev, sizeof(struct samsung_usb2_phy_driver) + + cfg->num_phys * sizeof(struct samsung_usb2_phy_instance), + GFP_KERNEL); + if (!drv) + return -ENOMEM; + + dev_set_drvdata(dev, drv); + spin_lock_init(&drv->lock); + + drv->cfg = cfg; + drv->dev = dev; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + drv->reg_phy = devm_ioremap_resource(dev, mem); + if (IS_ERR(drv->reg_phy)) { + dev_err(dev, "Failed to map register memory (phy)\n"); + return PTR_ERR(drv->reg_phy); + } + + drv->reg_pmu = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "samsung,pmureg-phandle"); + if (IS_ERR(drv->reg_pmu)) { + dev_err(dev, "Failed to map PMU registers (via syscon)\n"); + return PTR_ERR(drv->reg_pmu); + } + + if (drv->cfg->has_mode_switch) { + drv->reg_sys = syscon_regmap_lookup_by_phandle( + pdev->dev.of_node, "samsung,sysreg-phandle"); + if (IS_ERR(drv->reg_sys)) { + dev_err(dev, "Failed to map system registers (via syscon)\n"); + return PTR_ERR(drv->reg_sys); + } + } + + drv->clk = devm_clk_get(dev, "phy"); + if (IS_ERR(drv->clk)) { + dev_err(dev, "Failed to get clock of phy controller\n"); + return PTR_ERR(drv->clk); + } + + drv->ref_clk = devm_clk_get(dev, "ref"); + if (IS_ERR(drv->ref_clk)) { + dev_err(dev, "Failed to get reference clock for the phy controller\n"); + return PTR_ERR(drv->ref_clk); + } + + drv->ref_rate = clk_get_rate(drv->ref_clk); + if (drv->cfg->rate_to_clk) { + ret = drv->cfg->rate_to_clk(drv->ref_rate, &drv->ref_reg_val); + if (ret) + return ret; + } + + for (i = 0; i < drv->cfg->num_phys; i++) { + char *label = drv->cfg->phys[i].label; + struct samsung_usb2_phy_instance *p = &drv->instances[i]; + + dev_dbg(dev, "Creating phy \"%s\"\n", label); + p->phy = devm_phy_create(dev, &samsung_usb2_phy_ops, NULL); + if (IS_ERR(p->phy)) { + dev_err(drv->dev, "Failed to create usb2_phy \"%s\"\n", + label); + return PTR_ERR(p->phy); + } + + p->cfg = &drv->cfg->phys[i]; + p->drv = drv; + phy_set_bus_width(p->phy, 8); + phy_set_drvdata(p->phy, p); + } + + phy_provider = devm_of_phy_provider_register(dev, + samsung_usb2_phy_xlate); + if (IS_ERR(phy_provider)) { + dev_err(drv->dev, "Failed to register phy provider\n"); + return PTR_ERR(phy_provider); + } + + return 0; +} + +static struct platform_driver samsung_usb2_phy_driver = { + .probe = samsung_usb2_phy_probe, + .driver = { + .of_match_table = samsung_usb2_phy_of_match, + .name = "samsung-usb2-phy", + .owner = THIS_MODULE, + } +}; + +module_platform_driver(samsung_usb2_phy_driver); +MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC USB PHY driver"); +MODULE_AUTHOR("Kamil Debski "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:samsung-usb2-phy"); diff --git a/drivers/phy/phy-samsung-usb2.h b/drivers/phy/phy-samsung-usb2.h new file mode 100644 index 00000000000..51a16016a21 --- /dev/null +++ b/drivers/phy/phy-samsung-usb2.h @@ -0,0 +1,66 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Kamil Debski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _PHY_EXYNOS_USB2_H +#define _PHY_EXYNOS_USB2_H + +#include +#include +#include +#include +#include + +#define KHZ 1000 +#define MHZ (KHZ * KHZ) + +struct samsung_usb2_phy_driver; +struct samsung_usb2_phy_instance; +struct samsung_usb2_phy_config; + +struct samsung_usb2_phy_instance { + const struct samsung_usb2_common_phy *cfg; + struct phy *phy; + struct samsung_usb2_phy_driver *drv; + bool enabled; +}; + +struct samsung_usb2_phy_driver { + const struct samsung_usb2_phy_config *cfg; + struct clk *clk; + struct clk *ref_clk; + unsigned long ref_rate; + u32 ref_reg_val; + struct device *dev; + void __iomem *reg_phy; + struct regmap *reg_pmu; + struct regmap *reg_sys; + spinlock_t lock; + struct samsung_usb2_phy_instance instances[0]; +}; + +struct samsung_usb2_common_phy { + int (*power_on)(struct samsung_usb2_phy_instance *); + int (*power_off)(struct samsung_usb2_phy_instance *); + unsigned int id; + char *label; +}; + + +struct samsung_usb2_phy_config { + const struct samsung_usb2_common_phy *phys; + int (*rate_to_clk)(unsigned long, u32 *); + unsigned int num_phys; + bool has_mode_switch; +}; + +extern const struct samsung_usb2_phy_config exynos4210_usb2_phy_config; +extern const struct samsung_usb2_phy_config exynos4x12_usb2_phy_config; +#endif -- cgit v1.2.3-70-g09d2 From 64bf2b23697bcf9246cea4eea83c613ea791ed8a Mon Sep 17 00:00:00 2001 From: Kamil Debski Date: Thu, 6 Mar 2014 12:16:49 +0100 Subject: phy: Add Exynos 5250 support to the Exynos USB 2.0 PHY driver Add support for Exynos 5250. This driver is to replace the old USB 2.0 PHY driver. Signed-off-by: Kamil Debski Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/samsung-phy.txt | 1 + drivers/phy/Kconfig | 11 + drivers/phy/Makefile | 1 + drivers/phy/phy-exynos5250-usb2.c | 404 +++++++++++++++++++++ drivers/phy/phy-samsung-usb2.c | 6 + drivers/phy/phy-samsung-usb2.h | 1 + 6 files changed, 424 insertions(+) create mode 100644 drivers/phy/phy-exynos5250-usb2.c diff --git a/Documentation/devicetree/bindings/phy/samsung-phy.txt b/Documentation/devicetree/bindings/phy/samsung-phy.txt index bf955abba5d..28f9edb8f19 100644 --- a/Documentation/devicetree/bindings/phy/samsung-phy.txt +++ b/Documentation/devicetree/bindings/phy/samsung-phy.txt @@ -28,6 +28,7 @@ Required properties: - compatible : should be one of the listed compatibles: - "samsung,exynos4210-usb2-phy" - "samsung,exynos4x12-usb2-phy" + - "samsung,exynos5250-usb2-phy" - reg : a list of registers used by phy driver - first and obligatory is the location of phy modules registers - samsung,sysreg-phandle - handle to syscon used to control the system registers diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 33748365375..1b607d7cb03 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -136,4 +136,15 @@ config PHY_EXYNOS4X12_USB2 Samsung USB 2.0 PHY driver is enabled and means that support for this particular SoC is compiled in the driver. In case of Exynos 4x12 four phys are available - device, host, HSIC0 and HSIC1. + +config PHY_EXYNOS5250_USB2 + bool "Support for Exynos 5250" + depends on PHY_SAMSUNG_USB2 + depends on SOC_EXYNOS5250 + help + Enable USB PHY support for Exynos 5250. This option requires that + Samsung USB 2.0 PHY driver is enabled and means that support for this + particular SoC is compiled in the driver. In case of Exynos 5250 four + phys are available - device, host, HSIC0 and HSIC. + endmenu diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index c4ddd23d6d2..ecf0d3f5bf1 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -15,3 +15,4 @@ obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o obj-$(CONFIG_PHY_SAMSUNG_USB2) += phy-samsung-usb2.o obj-$(CONFIG_PHY_EXYNOS4210_USB2) += phy-exynos4210-usb2.o obj-$(CONFIG_PHY_EXYNOS4X12_USB2) += phy-exynos4x12-usb2.o +obj-$(CONFIG_PHY_EXYNOS5250_USB2) += phy-exynos5250-usb2.o diff --git a/drivers/phy/phy-exynos5250-usb2.c b/drivers/phy/phy-exynos5250-usb2.c new file mode 100644 index 00000000000..94179afda95 --- /dev/null +++ b/drivers/phy/phy-exynos5250-usb2.c @@ -0,0 +1,404 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver - Exynos 5250 support + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Kamil Debski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include "phy-samsung-usb2.h" + +/* Exynos USB PHY registers */ +#define EXYNOS_5250_REFCLKSEL_CRYSTAL 0x0 +#define EXYNOS_5250_REFCLKSEL_XO 0x1 +#define EXYNOS_5250_REFCLKSEL_CLKCORE 0x2 + +#define EXYNOS_5250_FSEL_9MHZ6 0x0 +#define EXYNOS_5250_FSEL_10MHZ 0x1 +#define EXYNOS_5250_FSEL_12MHZ 0x2 +#define EXYNOS_5250_FSEL_19MHZ2 0x3 +#define EXYNOS_5250_FSEL_20MHZ 0x4 +#define EXYNOS_5250_FSEL_24MHZ 0x5 +#define EXYNOS_5250_FSEL_50MHZ 0x7 + +/* Normal host */ +#define EXYNOS_5250_HOSTPHYCTRL0 0x0 + +#define EXYNOS_5250_HOSTPHYCTRL0_PHYSWRSTALL BIT(31) +#define EXYNOS_5250_HOSTPHYCTRL0_REFCLKSEL_SHIFT 19 +#define EXYNOS_5250_HOSTPHYCTRL0_REFCLKSEL_MASK \ + (0x3 << EXYNOS_5250_HOSTPHYCTRL0_REFCLKSEL_SHIFT) +#define EXYNOS_5250_HOSTPHYCTRL0_FSEL_SHIFT 16 +#define EXYNOS_5250_HOSTPHYCTRL0_FSEL_MASK \ + (0x7 << EXYNOS_5250_HOSTPHYCTRL0_FSEL_SHIFT) +#define EXYNOS_5250_HOSTPHYCTRL0_TESTBURNIN BIT(11) +#define EXYNOS_5250_HOSTPHYCTRL0_RETENABLE BIT(10) +#define EXYNOS_5250_HOSTPHYCTRL0_COMMON_ON_N BIT(9) +#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_MASK (0x3 << 7) +#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_DUAL (0x0 << 7) +#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_ID0 (0x1 << 7) +#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_ANALOGTEST (0x2 << 7) +#define EXYNOS_5250_HOSTPHYCTRL0_SIDDQ BIT(6) +#define EXYNOS_5250_HOSTPHYCTRL0_FORCESLEEP BIT(5) +#define EXYNOS_5250_HOSTPHYCTRL0_FORCESUSPEND BIT(4) +#define EXYNOS_5250_HOSTPHYCTRL0_WORDINTERFACE BIT(3) +#define EXYNOS_5250_HOSTPHYCTRL0_UTMISWRST BIT(2) +#define EXYNOS_5250_HOSTPHYCTRL0_LINKSWRST BIT(1) +#define EXYNOS_5250_HOSTPHYCTRL0_PHYSWRST BIT(0) + +/* HSIC0 & HSIC1 */ +#define EXYNOS_5250_HSICPHYCTRL1 0x10 +#define EXYNOS_5250_HSICPHYCTRL2 0x20 + +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_MASK (0x3 << 23) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_DEFAULT (0x2 << 23) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_MASK (0x7f << 16) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_12 (0x24 << 16) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_15 (0x1c << 16) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_16 (0x1a << 16) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_19_2 (0x15 << 16) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_20 (0x14 << 16) +#define EXYNOS_5250_HSICPHYCTRLX_SIDDQ BIT(6) +#define EXYNOS_5250_HSICPHYCTRLX_FORCESLEEP BIT(5) +#define EXYNOS_5250_HSICPHYCTRLX_FORCESUSPEND BIT(4) +#define EXYNOS_5250_HSICPHYCTRLX_WORDINTERFACE BIT(3) +#define EXYNOS_5250_HSICPHYCTRLX_UTMISWRST BIT(2) +#define EXYNOS_5250_HSICPHYCTRLX_PHYSWRST BIT(0) + +/* EHCI control */ +#define EXYNOS_5250_HOSTEHCICTRL 0x30 +#define EXYNOS_5250_HOSTEHCICTRL_ENAINCRXALIGN BIT(29) +#define EXYNOS_5250_HOSTEHCICTRL_ENAINCR4 BIT(28) +#define EXYNOS_5250_HOSTEHCICTRL_ENAINCR8 BIT(27) +#define EXYNOS_5250_HOSTEHCICTRL_ENAINCR16 BIT(26) +#define EXYNOS_5250_HOSTEHCICTRL_AUTOPPDONOVRCUREN BIT(25) +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_SHIFT 19 +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_MASK \ + (0x3f << EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_SHIFT) +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL1_SHIFT 13 +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL1_MASK \ + (0x3f << EXYNOS_5250_HOSTEHCICTRL_FLADJVAL1_SHIFT) +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL2_SHIFT 7 +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_MASK \ + (0x3f << EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_SHIFT) +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVALHOST_SHIFT 1 +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVALHOST_MASK \ + (0x1 << EXYNOS_5250_HOSTEHCICTRL_FLADJVALHOST_SHIFT) +#define EXYNOS_5250_HOSTEHCICTRL_SIMULATIONMODE BIT(0) + +/* OHCI control */ +#define EXYNOS_5250_HOSTOHCICTRL 0x34 +#define EXYNOS_5250_HOSTOHCICTRL_FRAMELENVAL_SHIFT 1 +#define EXYNOS_5250_HOSTOHCICTRL_FRAMELENVAL_MASK \ + (0x3ff << EXYNOS_5250_HOSTOHCICTRL_FRAMELENVAL_SHIFT) +#define EXYNOS_5250_HOSTOHCICTRL_FRAMELENVALEN BIT(0) + +/* USBOTG */ +#define EXYNOS_5250_USBOTGSYS 0x38 +#define EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET BIT(14) +#define EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG BIT(13) +#define EXYNOS_5250_USBOTGSYS_PHY_SW_RST BIT(12) +#define EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT 9 +#define EXYNOS_5250_USBOTGSYS_REFCLKSEL_MASK \ + (0x3 << EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT) +#define EXYNOS_5250_USBOTGSYS_ID_PULLUP BIT(8) +#define EXYNOS_5250_USBOTGSYS_COMMON_ON BIT(7) +#define EXYNOS_5250_USBOTGSYS_FSEL_SHIFT 4 +#define EXYNOS_5250_USBOTGSYS_FSEL_MASK \ + (0x3 << EXYNOS_5250_USBOTGSYS_FSEL_SHIFT) +#define EXYNOS_5250_USBOTGSYS_FORCE_SLEEP BIT(3) +#define EXYNOS_5250_USBOTGSYS_OTGDISABLE BIT(2) +#define EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG BIT(1) +#define EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND BIT(0) + +/* Isolation, configured in the power management unit */ +#define EXYNOS_5250_USB_ISOL_OTG_OFFSET 0x704 +#define EXYNOS_5250_USB_ISOL_OTG BIT(0) +#define EXYNOS_5250_USB_ISOL_HOST_OFFSET 0x708 +#define EXYNOS_5250_USB_ISOL_HOST BIT(0) + +/* Mode swtich register */ +#define EXYNOS_5250_MODE_SWITCH_OFFSET 0x230 +#define EXYNOS_5250_MODE_SWITCH_MASK 1 +#define EXYNOS_5250_MODE_SWITCH_DEVICE 0 +#define EXYNOS_5250_MODE_SWITCH_HOST 1 + +enum exynos4x12_phy_id { + EXYNOS5250_DEVICE, + EXYNOS5250_HOST, + EXYNOS5250_HSIC0, + EXYNOS5250_HSIC1, + EXYNOS5250_NUM_PHYS, +}; + +/* + * exynos5250_rate_to_clk() converts the supplied clock rate to the value that + * can be written to the phy register. + */ +static int exynos5250_rate_to_clk(unsigned long rate, u32 *reg) +{ + /* EXYNOS_5250_FSEL_MASK */ + + switch (rate) { + case 9600 * KHZ: + *reg = EXYNOS_5250_FSEL_9MHZ6; + break; + case 10 * MHZ: + *reg = EXYNOS_5250_FSEL_10MHZ; + break; + case 12 * MHZ: + *reg = EXYNOS_5250_FSEL_12MHZ; + break; + case 19200 * KHZ: + *reg = EXYNOS_5250_FSEL_19MHZ2; + break; + case 20 * MHZ: + *reg = EXYNOS_5250_FSEL_20MHZ; + break; + case 24 * MHZ: + *reg = EXYNOS_5250_FSEL_24MHZ; + break; + case 50 * MHZ: + *reg = EXYNOS_5250_FSEL_50MHZ; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void exynos5250_isol(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 offset; + u32 mask; + + switch (inst->cfg->id) { + case EXYNOS5250_DEVICE: + offset = EXYNOS_5250_USB_ISOL_OTG_OFFSET; + mask = EXYNOS_5250_USB_ISOL_OTG; + break; + case EXYNOS5250_HOST: + offset = EXYNOS_5250_USB_ISOL_HOST_OFFSET; + mask = EXYNOS_5250_USB_ISOL_HOST; + break; + default: + return; + }; + + regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask); +} + +static int exynos5250_power_on(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 ctrl0; + u32 otg; + u32 ehci; + u32 ohci; + u32 hsic; + + switch (inst->cfg->id) { + case EXYNOS5250_DEVICE: + regmap_update_bits(drv->reg_sys, + EXYNOS_5250_MODE_SWITCH_OFFSET, + EXYNOS_5250_MODE_SWITCH_MASK, + EXYNOS_5250_MODE_SWITCH_DEVICE); + + /* OTG configuration */ + otg = readl(drv->reg_phy + EXYNOS_5250_USBOTGSYS); + /* The clock */ + otg &= ~EXYNOS_5250_USBOTGSYS_FSEL_MASK; + otg |= drv->ref_reg_val << EXYNOS_5250_USBOTGSYS_FSEL_SHIFT; + /* Reset */ + otg &= ~(EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND | + EXYNOS_5250_USBOTGSYS_FORCE_SLEEP | + EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG); + otg |= EXYNOS_5250_USBOTGSYS_PHY_SW_RST | + EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET | + EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG | + EXYNOS_5250_USBOTGSYS_OTGDISABLE; + /* Ref clock */ + otg &= ~EXYNOS_5250_USBOTGSYS_REFCLKSEL_MASK; + otg |= EXYNOS_5250_REFCLKSEL_CLKCORE << + EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT; + writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS); + udelay(100); + otg &= ~(EXYNOS_5250_USBOTGSYS_PHY_SW_RST | + EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG | + EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET | + EXYNOS_5250_USBOTGSYS_OTGDISABLE); + writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS); + + + break; + case EXYNOS5250_HOST: + case EXYNOS5250_HSIC0: + case EXYNOS5250_HSIC1: + /* Host registers configuration */ + ctrl0 = readl(drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); + /* The clock */ + ctrl0 &= ~EXYNOS_5250_HOSTPHYCTRL0_FSEL_MASK; + ctrl0 |= drv->ref_reg_val << + EXYNOS_5250_HOSTPHYCTRL0_FSEL_SHIFT; + + /* Reset */ + ctrl0 &= ~(EXYNOS_5250_HOSTPHYCTRL0_PHYSWRST | + EXYNOS_5250_HOSTPHYCTRL0_PHYSWRSTALL | + EXYNOS_5250_HOSTPHYCTRL0_SIDDQ | + EXYNOS_5250_HOSTPHYCTRL0_FORCESUSPEND | + EXYNOS_5250_HOSTPHYCTRL0_FORCESLEEP); + ctrl0 |= EXYNOS_5250_HOSTPHYCTRL0_LINKSWRST | + EXYNOS_5250_HOSTPHYCTRL0_UTMISWRST | + EXYNOS_5250_HOSTPHYCTRL0_COMMON_ON_N; + writel(ctrl0, drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); + udelay(10); + ctrl0 &= ~(EXYNOS_5250_HOSTPHYCTRL0_LINKSWRST | + EXYNOS_5250_HOSTPHYCTRL0_UTMISWRST); + writel(ctrl0, drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); + + /* OTG configuration */ + otg = readl(drv->reg_phy + EXYNOS_5250_USBOTGSYS); + /* The clock */ + otg &= ~EXYNOS_5250_USBOTGSYS_FSEL_MASK; + otg |= drv->ref_reg_val << EXYNOS_5250_USBOTGSYS_FSEL_SHIFT; + /* Reset */ + otg &= ~(EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND | + EXYNOS_5250_USBOTGSYS_FORCE_SLEEP | + EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG); + otg |= EXYNOS_5250_USBOTGSYS_PHY_SW_RST | + EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET | + EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG | + EXYNOS_5250_USBOTGSYS_OTGDISABLE; + /* Ref clock */ + otg &= ~EXYNOS_5250_USBOTGSYS_REFCLKSEL_MASK; + otg |= EXYNOS_5250_REFCLKSEL_CLKCORE << + EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT; + writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS); + udelay(10); + otg &= ~(EXYNOS_5250_USBOTGSYS_PHY_SW_RST | + EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG | + EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET); + + /* HSIC phy configuration */ + hsic = (EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_12 | + EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_DEFAULT | + EXYNOS_5250_HSICPHYCTRLX_PHYSWRST); + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL1); + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL2); + udelay(10); + hsic &= ~EXYNOS_5250_HSICPHYCTRLX_PHYSWRST; + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL1); + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL2); + /* The following delay is necessary for the reset sequence to be + * completed */ + udelay(80); + + /* Enable EHCI DMA burst */ + ehci = readl(drv->reg_phy + EXYNOS_5250_HOSTEHCICTRL); + ehci |= EXYNOS_5250_HOSTEHCICTRL_ENAINCRXALIGN | + EXYNOS_5250_HOSTEHCICTRL_ENAINCR4 | + EXYNOS_5250_HOSTEHCICTRL_ENAINCR8 | + EXYNOS_5250_HOSTEHCICTRL_ENAINCR16; + writel(ehci, drv->reg_phy + EXYNOS_5250_HOSTEHCICTRL); + + /* OHCI settings */ + ohci = readl(drv->reg_phy + EXYNOS_5250_HOSTOHCICTRL); + /* Following code is based on the old driver */ + ohci |= 0x1 << 3; + writel(ohci, drv->reg_phy + EXYNOS_5250_HOSTOHCICTRL); + + break; + } + inst->enabled = 1; + exynos5250_isol(inst, 0); + + return 0; +} + +static int exynos5250_power_off(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 ctrl0; + u32 otg; + u32 hsic; + + inst->enabled = 0; + exynos5250_isol(inst, 1); + + switch (inst->cfg->id) { + case EXYNOS5250_DEVICE: + otg = readl(drv->reg_phy + EXYNOS_5250_USBOTGSYS); + otg |= (EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND | + EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG | + EXYNOS_5250_USBOTGSYS_FORCE_SLEEP); + writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS); + break; + case EXYNOS5250_HOST: + ctrl0 = readl(drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); + ctrl0 |= (EXYNOS_5250_HOSTPHYCTRL0_SIDDQ | + EXYNOS_5250_HOSTPHYCTRL0_FORCESUSPEND | + EXYNOS_5250_HOSTPHYCTRL0_FORCESLEEP | + EXYNOS_5250_HOSTPHYCTRL0_PHYSWRST | + EXYNOS_5250_HOSTPHYCTRL0_PHYSWRSTALL); + writel(ctrl0, drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); + break; + case EXYNOS5250_HSIC0: + case EXYNOS5250_HSIC1: + hsic = (EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_12 | + EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_DEFAULT | + EXYNOS_5250_HSICPHYCTRLX_SIDDQ | + EXYNOS_5250_HSICPHYCTRLX_FORCESLEEP | + EXYNOS_5250_HSICPHYCTRLX_FORCESUSPEND + ); + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL1); + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL2); + break; + } + + return 0; +} + + +static const struct samsung_usb2_common_phy exynos5250_phys[] = { + { + .label = "device", + .id = EXYNOS5250_DEVICE, + .power_on = exynos5250_power_on, + .power_off = exynos5250_power_off, + }, + { + .label = "host", + .id = EXYNOS5250_HOST, + .power_on = exynos5250_power_on, + .power_off = exynos5250_power_off, + }, + { + .label = "hsic0", + .id = EXYNOS5250_HSIC0, + .power_on = exynos5250_power_on, + .power_off = exynos5250_power_off, + }, + { + .label = "hsic1", + .id = EXYNOS5250_HSIC1, + .power_on = exynos5250_power_on, + .power_off = exynos5250_power_off, + }, + {}, +}; + +const struct samsung_usb2_phy_config exynos5250_usb2_phy_config = { + .has_mode_switch = 1, + .num_phys = EXYNOS5250_NUM_PHYS, + .phys = exynos5250_phys, + .rate_to_clk = exynos5250_rate_to_clk, +}; diff --git a/drivers/phy/phy-samsung-usb2.c b/drivers/phy/phy-samsung-usb2.c index c3b771907f1..8a8c6bc8709 100644 --- a/drivers/phy/phy-samsung-usb2.c +++ b/drivers/phy/phy-samsung-usb2.c @@ -98,6 +98,12 @@ static const struct of_device_id samsung_usb2_phy_of_match[] = { .compatible = "samsung,exynos4x12-usb2-phy", .data = &exynos4x12_usb2_phy_config, }, +#endif +#ifdef CONFIG_PHY_EXYNOS5250_USB2 + { + .compatible = "samsung,exynos5250-usb2-phy", + .data = &exynos5250_usb2_phy_config, + }, #endif { }, }; diff --git a/drivers/phy/phy-samsung-usb2.h b/drivers/phy/phy-samsung-usb2.h index 51a16016a21..45b3170652b 100644 --- a/drivers/phy/phy-samsung-usb2.h +++ b/drivers/phy/phy-samsung-usb2.h @@ -63,4 +63,5 @@ struct samsung_usb2_phy_config { extern const struct samsung_usb2_phy_config exynos4210_usb2_phy_config; extern const struct samsung_usb2_phy_config exynos4x12_usb2_phy_config; +extern const struct samsung_usb2_phy_config exynos5250_usb2_phy_config; #endif -- cgit v1.2.3-70-g09d2 From 04a378f36d9dc9e242ff206fcad23ba258dba818 Mon Sep 17 00:00:00 2001 From: Thomas Pugliese Date: Fri, 7 Mar 2014 15:37:34 -0600 Subject: usb: wusbcore: combine iso transfer result frame reads when possible When reading the transfer result data for an isochronous in request, if the current frame actual_length is contiguous with the next frame and actual_length is a multiple of the DTI endpoint max packet size, combine the current frame with the next frame in a single URB. This reduces the number of URBs that must be submitted in that case which increases performance and reduces CPU interrupt overhead. Signed-off-by: Thomas Pugliese Signed-off-by: Greg Kroah-Hartman --- drivers/usb/wusbcore/wa-xfer.c | 106 +++++++++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 30 deletions(-) diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c index 52c7259705e..f930bbb1a0f 100644 --- a/drivers/usb/wusbcore/wa-xfer.c +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -2160,22 +2160,59 @@ static void wa_complete_remaining_xfer_segs(struct wa_xfer *xfer, } /* Populate the wa->buf_in_urb based on the current isoc transfer state. */ -static void __wa_populate_buf_in_urb_isoc(struct wahc *wa, struct wa_xfer *xfer, - struct wa_seg *seg, int curr_iso_frame) +static int __wa_populate_buf_in_urb_isoc(struct wahc *wa, struct wa_xfer *xfer, + struct wa_seg *seg) { + int urb_start_frame = seg->isoc_frame_index + seg->isoc_frame_offset; + int seg_index, total_len = 0, urb_frame_index = urb_start_frame; + struct usb_iso_packet_descriptor *iso_frame_desc = + xfer->urb->iso_frame_desc; + const int dti_packet_size = usb_endpoint_maxp(wa->dti_epd); + int next_frame_contiguous; + struct usb_iso_packet_descriptor *iso_frame; + BUG_ON(wa->buf_in_urb->status == -EINPROGRESS); + /* + * If the current frame actual_length is contiguous with the next frame + * and actual_length is a multiple of the DTI endpoint max packet size, + * combine the current frame with the next frame in a single URB. This + * reduces the number of URBs that must be submitted in that case. + */ + seg_index = seg->isoc_frame_index; + do { + next_frame_contiguous = 0; + + iso_frame = &iso_frame_desc[urb_frame_index]; + total_len += iso_frame->actual_length; + ++urb_frame_index; + ++seg_index; + + if (seg_index < seg->isoc_frame_count) { + struct usb_iso_packet_descriptor *next_iso_frame; + + next_iso_frame = &iso_frame_desc[urb_frame_index]; + + if ((iso_frame->offset + iso_frame->actual_length) == + next_iso_frame->offset) + next_frame_contiguous = 1; + } + } while (next_frame_contiguous + && ((iso_frame->actual_length % dti_packet_size) == 0)); + /* this should always be 0 before a resubmit. */ wa->buf_in_urb->num_mapped_sgs = 0; wa->buf_in_urb->transfer_dma = xfer->urb->transfer_dma + - xfer->urb->iso_frame_desc[curr_iso_frame].offset; - wa->buf_in_urb->transfer_buffer_length = - xfer->urb->iso_frame_desc[curr_iso_frame].actual_length; + iso_frame_desc[urb_start_frame].offset; + wa->buf_in_urb->transfer_buffer_length = total_len; wa->buf_in_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; wa->buf_in_urb->transfer_buffer = NULL; wa->buf_in_urb->sg = NULL; wa->buf_in_urb->num_sgs = 0; wa->buf_in_urb->context = seg; + + /* return the number of frames included in this URB. */ + return seg_index - seg->isoc_frame_index; } /* Populate the wa->buf_in_urb based on the current transfer state. */ @@ -2458,19 +2495,20 @@ static int wa_process_iso_packet_status(struct wahc *wa, struct urb *urb) } if (xfer->is_inbound && data_frame_count) { - int result; + int result, urb_frame_count; seg->isoc_frame_index = first_frame_index; /* submit a read URB for the first frame with data. */ - __wa_populate_buf_in_urb_isoc(wa, xfer, seg, - seg->isoc_frame_index + seg->isoc_frame_offset); + urb_frame_count = __wa_populate_buf_in_urb_isoc(wa, xfer, seg); + /* advance index to start of next read URB. */ + seg->isoc_frame_index += urb_frame_count; result = usb_submit_urb(wa->buf_in_urb, GFP_ATOMIC); if (result < 0) { dev_err(dev, "DTI Error: Could not submit buf in URB (%d)", result); wa_reset_all(wa); - } else if (data_frame_count > 1) + } else if (data_frame_count > urb_frame_count) /* If we need to read multiple frames, set DTI busy. */ dti_busy = 1; } else { @@ -2511,8 +2549,9 @@ static void wa_buf_in_cb(struct urb *urb) struct wahc *wa; struct device *dev; struct wa_rpipe *rpipe; - unsigned rpipe_ready = 0, seg_index, isoc_data_frame_count = 0; + unsigned rpipe_ready = 0, isoc_data_frame_count = 0; unsigned long flags; + int resubmit_dti = 0; u8 done = 0; /* free the sg if it was used. */ @@ -2524,17 +2563,16 @@ static void wa_buf_in_cb(struct urb *urb) dev = &wa->usb_iface->dev; if (usb_pipeisoc(xfer->urb->pipe)) { + struct usb_iso_packet_descriptor *iso_frame_desc = + xfer->urb->iso_frame_desc; + int seg_index; + /* - * Find the next isoc frame with data. Bail out after - * isoc_data_frame_count > 1 since there is no need to walk - * the entire frame array. We just need to know if - * isoc_data_frame_count is 0, 1, or >1. + * Find the next isoc frame with data and count how many + * frames with data remain. */ - seg_index = seg->isoc_frame_index + 1; - while ((seg_index < seg->isoc_frame_count) - && (isoc_data_frame_count <= 1)) { - struct usb_iso_packet_descriptor *iso_frame_desc = - xfer->urb->iso_frame_desc; + seg_index = seg->isoc_frame_index; + while (seg_index < seg->isoc_frame_count) { const int urb_frame_index = seg->isoc_frame_offset + seg_index; @@ -2555,16 +2593,28 @@ static void wa_buf_in_cb(struct urb *urb) seg->result += urb->actual_length; if (isoc_data_frame_count > 0) { - int result; - /* submit a read URB for the first frame with data. */ - __wa_populate_buf_in_urb_isoc(wa, xfer, seg, - seg->isoc_frame_index + seg->isoc_frame_offset); + int result, urb_frame_count; + + /* submit a read URB for the next frame with data. */ + urb_frame_count = __wa_populate_buf_in_urb_isoc(wa, + xfer, seg); + /* advance index to start of next read URB. */ + seg->isoc_frame_index += urb_frame_count; result = usb_submit_urb(wa->buf_in_urb, GFP_ATOMIC); if (result < 0) { dev_err(dev, "DTI Error: Could not submit buf in URB (%d)", result); wa_reset_all(wa); } + /* + * If we are in this callback and + * isoc_data_frame_count > 0, it means that the dti_urb + * submission was delayed in wa_dti_cb. Once + * we submit the last buf_in_urb, we can submit the + * delayed dti_urb. + */ + resubmit_dti = (isoc_data_frame_count == + urb_frame_count); } else { rpipe = xfer->ep->hcpriv; dev_dbg(dev, @@ -2585,6 +2635,7 @@ static void wa_buf_in_cb(struct urb *urb) case -ENOENT: /* as it was done by the who unlinked us */ break; default: /* Other errors ... */ + resubmit_dti = 1; spin_lock_irqsave(&xfer->lock, flags); rpipe = xfer->ep->hcpriv; if (printk_ratelimit()) @@ -2607,13 +2658,8 @@ static void wa_buf_in_cb(struct urb *urb) if (rpipe_ready) wa_xfer_delayed_run(rpipe); } - /* - * If we are in this callback and isoc_data_frame_count > 0, it means - * that the dti_urb submission was delayed in wa_dti_cb. Once - * isoc_data_frame_count gets to 1, we can submit the deferred URB - * since the last buf_in_urb was just submitted. - */ - if (isoc_data_frame_count == 1) { + + if (resubmit_dti) { int result = usb_submit_urb(wa->dti_urb, GFP_ATOMIC); if (result < 0) { dev_err(dev, "DTI Error: Could not submit DTI URB (%d)\n", -- cgit v1.2.3-70-g09d2 From 335053fe8c94f50c7f1cd7011b3088547480df3c Mon Sep 17 00:00:00 2001 From: Thomas Pugliese Date: Fri, 7 Mar 2014 15:37:35 -0600 Subject: usb: wusbcore: use multiple urbs for HWA iso transfer result frame reads Submit multiple concurrent urbs for HWA isochronous transfer result data frame reads. This keeps the read pipeline full and significantly improves performance in cases where the frame reads cannot be combined because they are not contiguous or multiples of the max packet size. Signed-off-by: Thomas Pugliese Signed-off-by: Greg Kroah-Hartman --- drivers/usb/wusbcore/wa-hc.c | 2 - drivers/usb/wusbcore/wa-hc.h | 15 +++- drivers/usb/wusbcore/wa-xfer.c | 191 ++++++++++++++++++++++++++--------------- 3 files changed, 134 insertions(+), 74 deletions(-) diff --git a/drivers/usb/wusbcore/wa-hc.c b/drivers/usb/wusbcore/wa-hc.c index 368360f9a93..252c7bd9218 100644 --- a/drivers/usb/wusbcore/wa-hc.c +++ b/drivers/usb/wusbcore/wa-hc.c @@ -75,8 +75,6 @@ void __wa_destroy(struct wahc *wa) if (wa->dti_urb) { usb_kill_urb(wa->dti_urb); usb_put_urb(wa->dti_urb); - usb_kill_urb(wa->buf_in_urb); - usb_put_urb(wa->buf_in_urb); } kfree(wa->dti_buf); wa_nep_destroy(wa); diff --git a/drivers/usb/wusbcore/wa-hc.h b/drivers/usb/wusbcore/wa-hc.h index 7510960588d..f2a8d29e17b 100644 --- a/drivers/usb/wusbcore/wa-hc.h +++ b/drivers/usb/wusbcore/wa-hc.h @@ -125,7 +125,8 @@ struct wa_rpipe { enum wa_dti_state { WA_DTI_TRANSFER_RESULT_PENDING, - WA_DTI_ISOC_PACKET_STATUS_PENDING + WA_DTI_ISOC_PACKET_STATUS_PENDING, + WA_DTI_BUF_IN_DATA_PENDING }; enum wa_quirks { @@ -146,6 +147,8 @@ enum wa_vendor_specific_requests { WA_REQ_ALEREON_FEATURE_SET = 0x01, WA_REQ_ALEREON_FEATURE_CLEAR = 0x00, }; + +#define WA_MAX_BUF_IN_URBS 4 /** * Instance of a HWA Host Controller * @@ -216,7 +219,9 @@ struct wahc { u32 dti_isoc_xfer_in_progress; u8 dti_isoc_xfer_seg; struct urb *dti_urb; /* URB for reading xfer results */ - struct urb *buf_in_urb; /* URB for reading data in */ + /* URBs for reading data in */ + struct urb buf_in_urbs[WA_MAX_BUF_IN_URBS]; + int active_buf_in_urbs; /* number of buf_in_urbs active. */ struct edc dti_edc; /* DTI error density counter */ void *dti_buf; size_t dti_buf_size; @@ -286,6 +291,8 @@ static inline void wa_rpipe_init(struct wahc *wa) static inline void wa_init(struct wahc *wa) { + int index; + edc_init(&wa->nep_edc); atomic_set(&wa->notifs_queued, 0); wa->dti_state = WA_DTI_TRANSFER_RESULT_PENDING; @@ -299,6 +306,10 @@ static inline void wa_init(struct wahc *wa) INIT_WORK(&wa->xfer_error_work, wa_process_errored_transfers_run); wa->dto_in_use = 0; atomic_set(&wa->xfer_id_count, 1); + /* init the buf in URBs */ + for (index = 0; index < WA_MAX_BUF_IN_URBS; ++index) + usb_init_urb(&(wa->buf_in_urbs[index])); + wa->active_buf_in_urbs = 0; } /** diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c index f930bbb1a0f..c8e2a47d62a 100644 --- a/drivers/usb/wusbcore/wa-xfer.c +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -2159,9 +2159,9 @@ static void wa_complete_remaining_xfer_segs(struct wa_xfer *xfer, } } -/* Populate the wa->buf_in_urb based on the current isoc transfer state. */ -static int __wa_populate_buf_in_urb_isoc(struct wahc *wa, struct wa_xfer *xfer, - struct wa_seg *seg) +/* Populate the given urb based on the current isoc transfer state. */ +static int __wa_populate_buf_in_urb_isoc(struct wahc *wa, + struct urb *buf_in_urb, struct wa_xfer *xfer, struct wa_seg *seg) { int urb_start_frame = seg->isoc_frame_index + seg->isoc_frame_offset; int seg_index, total_len = 0, urb_frame_index = urb_start_frame; @@ -2171,7 +2171,7 @@ static int __wa_populate_buf_in_urb_isoc(struct wahc *wa, struct wa_xfer *xfer, int next_frame_contiguous; struct usb_iso_packet_descriptor *iso_frame; - BUG_ON(wa->buf_in_urb->status == -EINPROGRESS); + BUG_ON(buf_in_urb->status == -EINPROGRESS); /* * If the current frame actual_length is contiguous with the next frame @@ -2201,68 +2201,68 @@ static int __wa_populate_buf_in_urb_isoc(struct wahc *wa, struct wa_xfer *xfer, && ((iso_frame->actual_length % dti_packet_size) == 0)); /* this should always be 0 before a resubmit. */ - wa->buf_in_urb->num_mapped_sgs = 0; - wa->buf_in_urb->transfer_dma = xfer->urb->transfer_dma + + buf_in_urb->num_mapped_sgs = 0; + buf_in_urb->transfer_dma = xfer->urb->transfer_dma + iso_frame_desc[urb_start_frame].offset; - wa->buf_in_urb->transfer_buffer_length = total_len; - wa->buf_in_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; - wa->buf_in_urb->transfer_buffer = NULL; - wa->buf_in_urb->sg = NULL; - wa->buf_in_urb->num_sgs = 0; - wa->buf_in_urb->context = seg; + buf_in_urb->transfer_buffer_length = total_len; + buf_in_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + buf_in_urb->transfer_buffer = NULL; + buf_in_urb->sg = NULL; + buf_in_urb->num_sgs = 0; + buf_in_urb->context = seg; /* return the number of frames included in this URB. */ return seg_index - seg->isoc_frame_index; } -/* Populate the wa->buf_in_urb based on the current transfer state. */ -static int wa_populate_buf_in_urb(struct wahc *wa, struct wa_xfer *xfer, +/* Populate the given urb based on the current transfer state. */ +static int wa_populate_buf_in_urb(struct urb *buf_in_urb, struct wa_xfer *xfer, unsigned int seg_idx, unsigned int bytes_transferred) { int result = 0; struct wa_seg *seg = xfer->seg[seg_idx]; - BUG_ON(wa->buf_in_urb->status == -EINPROGRESS); + BUG_ON(buf_in_urb->status == -EINPROGRESS); /* this should always be 0 before a resubmit. */ - wa->buf_in_urb->num_mapped_sgs = 0; + buf_in_urb->num_mapped_sgs = 0; if (xfer->is_dma) { - wa->buf_in_urb->transfer_dma = xfer->urb->transfer_dma + buf_in_urb->transfer_dma = xfer->urb->transfer_dma + (seg_idx * xfer->seg_size); - wa->buf_in_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; - wa->buf_in_urb->transfer_buffer = NULL; - wa->buf_in_urb->sg = NULL; - wa->buf_in_urb->num_sgs = 0; + buf_in_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + buf_in_urb->transfer_buffer = NULL; + buf_in_urb->sg = NULL; + buf_in_urb->num_sgs = 0; } else { /* do buffer or SG processing. */ - wa->buf_in_urb->transfer_flags &= ~URB_NO_TRANSFER_DMA_MAP; + buf_in_urb->transfer_flags &= ~URB_NO_TRANSFER_DMA_MAP; if (xfer->urb->transfer_buffer) { - wa->buf_in_urb->transfer_buffer = + buf_in_urb->transfer_buffer = xfer->urb->transfer_buffer + (seg_idx * xfer->seg_size); - wa->buf_in_urb->sg = NULL; - wa->buf_in_urb->num_sgs = 0; + buf_in_urb->sg = NULL; + buf_in_urb->num_sgs = 0; } else { /* allocate an SG list to store seg_size bytes and copy the subset of the xfer->urb->sg that matches the buffer subset we are about to read. */ - wa->buf_in_urb->sg = wa_xfer_create_subset_sg( + buf_in_urb->sg = wa_xfer_create_subset_sg( xfer->urb->sg, seg_idx * xfer->seg_size, bytes_transferred, - &(wa->buf_in_urb->num_sgs)); + &(buf_in_urb->num_sgs)); - if (!(wa->buf_in_urb->sg)) { - wa->buf_in_urb->num_sgs = 0; + if (!(buf_in_urb->sg)) { + buf_in_urb->num_sgs = 0; result = -ENOMEM; } - wa->buf_in_urb->transfer_buffer = NULL; + buf_in_urb->transfer_buffer = NULL; } } - wa->buf_in_urb->transfer_buffer_length = bytes_transferred; - wa->buf_in_urb->context = seg; + buf_in_urb->transfer_buffer_length = bytes_transferred; + buf_in_urb->context = seg; return result; } @@ -2287,6 +2287,7 @@ static void wa_xfer_result_chew(struct wahc *wa, struct wa_xfer *xfer, u8 usb_status; unsigned rpipe_ready = 0; unsigned bytes_transferred = le32_to_cpu(xfer_result->dwTransferLength); + struct urb *buf_in_urb = &(wa->buf_in_urbs[0]); spin_lock_irqsave(&xfer->lock, flags); seg_idx = xfer_result->bTransferSegment & 0x7f; @@ -2337,13 +2338,16 @@ static void wa_xfer_result_chew(struct wahc *wa, struct wa_xfer *xfer, && (bytes_transferred > 0)) { /* IN data phase: read to buffer */ seg->status = WA_SEG_DTI_PENDING; - result = wa_populate_buf_in_urb(wa, xfer, seg_idx, + result = wa_populate_buf_in_urb(buf_in_urb, xfer, seg_idx, bytes_transferred); if (result < 0) goto error_buf_in_populate; - result = usb_submit_urb(wa->buf_in_urb, GFP_ATOMIC); - if (result < 0) + ++(wa->active_buf_in_urbs); + result = usb_submit_urb(buf_in_urb, GFP_ATOMIC); + if (result < 0) { + --(wa->active_buf_in_urbs); goto error_submit_buf_in; + } } else { /* OUT data phase or no data, complete it -- */ seg->result = bytes_transferred; @@ -2367,8 +2371,8 @@ error_submit_buf_in: dev_err(dev, "xfer %p#%u: can't submit DTI data phase: %d\n", xfer, seg_idx, result); seg->result = result; - kfree(wa->buf_in_urb->sg); - wa->buf_in_urb->sg = NULL; + kfree(buf_in_urb->sg); + buf_in_urb->sg = NULL; error_buf_in_populate: __wa_xfer_abort(xfer); seg->status = WA_SEG_ERROR; @@ -2477,16 +2481,16 @@ static int wa_process_iso_packet_status(struct wahc *wa, struct urb *urb) for (seg_index = 0; seg_index < seg->isoc_frame_count; ++seg_index) { struct usb_iso_packet_descriptor *iso_frame_desc = xfer->urb->iso_frame_desc; - const int urb_frame_index = + const int xfer_frame_index = seg->isoc_frame_offset + seg_index; - iso_frame_desc[urb_frame_index].status = + iso_frame_desc[xfer_frame_index].status = wa_xfer_status_to_errno( le16_to_cpu(status_array[seg_index].PacketStatus)); - iso_frame_desc[urb_frame_index].actual_length = + iso_frame_desc[xfer_frame_index].actual_length = le16_to_cpu(status_array[seg_index].PacketLength); /* track the number of frames successfully transferred. */ - if (iso_frame_desc[urb_frame_index].actual_length > 0) { + if (iso_frame_desc[xfer_frame_index].actual_length > 0) { /* save the starting frame index for buf_in_urb. */ if (!data_frame_count) first_frame_index = seg_index; @@ -2495,21 +2499,53 @@ static int wa_process_iso_packet_status(struct wahc *wa, struct urb *urb) } if (xfer->is_inbound && data_frame_count) { - int result, urb_frame_count; + int result, total_frames_read = 0, urb_index = 0; + struct urb *buf_in_urb; + /* IN data phase: read to buffer */ + seg->status = WA_SEG_DTI_PENDING; + + /* start with the first frame with data. */ seg->isoc_frame_index = first_frame_index; - /* submit a read URB for the first frame with data. */ - urb_frame_count = __wa_populate_buf_in_urb_isoc(wa, xfer, seg); - /* advance index to start of next read URB. */ - seg->isoc_frame_index += urb_frame_count; + /* submit up to WA_MAX_BUF_IN_URBS read URBs. */ + do { + int urb_frame_index, urb_frame_count; + struct usb_iso_packet_descriptor *iso_frame_desc; + + buf_in_urb = &(wa->buf_in_urbs[urb_index]); + urb_frame_count = __wa_populate_buf_in_urb_isoc(wa, + buf_in_urb, xfer, seg); + /* advance frame index to start of next read URB. */ + seg->isoc_frame_index += urb_frame_count; + total_frames_read += urb_frame_count; + + ++(wa->active_buf_in_urbs); + result = usb_submit_urb(buf_in_urb, GFP_ATOMIC); + + /* skip 0-byte frames. */ + urb_frame_index = + seg->isoc_frame_offset + seg->isoc_frame_index; + iso_frame_desc = + &(xfer->urb->iso_frame_desc[urb_frame_index]); + while ((seg->isoc_frame_index < + seg->isoc_frame_count) && + (iso_frame_desc->actual_length == 0)) { + ++(seg->isoc_frame_index); + ++iso_frame_desc; + } + ++urb_index; + + } while ((result == 0) && (urb_index < WA_MAX_BUF_IN_URBS) + && (seg->isoc_frame_index < + seg->isoc_frame_count)); - result = usb_submit_urb(wa->buf_in_urb, GFP_ATOMIC); if (result < 0) { + --(wa->active_buf_in_urbs); dev_err(dev, "DTI Error: Could not submit buf in URB (%d)", result); wa_reset_all(wa); - } else if (data_frame_count > urb_frame_count) - /* If we need to read multiple frames, set DTI busy. */ + } else if (data_frame_count > total_frames_read) + /* If we need to read more frames, set DTI busy. */ dti_busy = 1; } else { /* OUT transfer or no more IN data, complete it -- */ @@ -2517,7 +2553,10 @@ static int wa_process_iso_packet_status(struct wahc *wa, struct urb *urb) done = __wa_xfer_mark_seg_as_done(xfer, seg, WA_SEG_DONE); } spin_unlock_irqrestore(&xfer->lock, flags); - wa->dti_state = WA_DTI_TRANSFER_RESULT_PENDING; + if (dti_busy) + wa->dti_state = WA_DTI_BUF_IN_DATA_PENDING; + else + wa->dti_state = WA_DTI_TRANSFER_RESULT_PENDING; if (done) wa_xfer_completion(xfer); if (rpipe_ready) @@ -2551,7 +2590,7 @@ static void wa_buf_in_cb(struct urb *urb) struct wa_rpipe *rpipe; unsigned rpipe_ready = 0, isoc_data_frame_count = 0; unsigned long flags; - int resubmit_dti = 0; + int resubmit_dti = 0, active_buf_in_urbs; u8 done = 0; /* free the sg if it was used. */ @@ -2561,6 +2600,8 @@ static void wa_buf_in_cb(struct urb *urb) spin_lock_irqsave(&xfer->lock, flags); wa = xfer->wa; dev = &wa->usb_iface->dev; + --(wa->active_buf_in_urbs); + active_buf_in_urbs = wa->active_buf_in_urbs; if (usb_pipeisoc(xfer->urb->pipe)) { struct usb_iso_packet_descriptor *iso_frame_desc = @@ -2596,12 +2637,14 @@ static void wa_buf_in_cb(struct urb *urb) int result, urb_frame_count; /* submit a read URB for the next frame with data. */ - urb_frame_count = __wa_populate_buf_in_urb_isoc(wa, + urb_frame_count = __wa_populate_buf_in_urb_isoc(wa, urb, xfer, seg); /* advance index to start of next read URB. */ seg->isoc_frame_index += urb_frame_count; - result = usb_submit_urb(wa->buf_in_urb, GFP_ATOMIC); + ++(wa->active_buf_in_urbs); + result = usb_submit_urb(urb, GFP_ATOMIC); if (result < 0) { + --(wa->active_buf_in_urbs); dev_err(dev, "DTI Error: Could not submit buf in URB (%d)", result); wa_reset_all(wa); @@ -2615,7 +2658,7 @@ static void wa_buf_in_cb(struct urb *urb) */ resubmit_dti = (isoc_data_frame_count == urb_frame_count); - } else { + } else if (active_buf_in_urbs == 0) { rpipe = xfer->ep->hcpriv; dev_dbg(dev, "xfer %p 0x%08X#%u: data in done (%zu bytes)\n", @@ -2635,7 +2678,12 @@ static void wa_buf_in_cb(struct urb *urb) case -ENOENT: /* as it was done by the who unlinked us */ break; default: /* Other errors ... */ - resubmit_dti = 1; + /* + * Error on data buf read. Only resubmit DTI if it hasn't + * already been done by previously hitting this error or by a + * successful completion of the previous buf_in_urb. + */ + resubmit_dti = wa->dti_state != WA_DTI_TRANSFER_RESULT_PENDING; spin_lock_irqsave(&xfer->lock, flags); rpipe = xfer->ep->hcpriv; if (printk_ratelimit()) @@ -2650,8 +2698,11 @@ static void wa_buf_in_cb(struct urb *urb) } seg->result = urb->status; rpipe_ready = rpipe_avail_inc(rpipe); - __wa_xfer_abort(xfer); - done = __wa_xfer_mark_seg_as_done(xfer, seg, WA_SEG_ERROR); + if (active_buf_in_urbs == 0) + done = __wa_xfer_mark_seg_as_done(xfer, seg, + WA_SEG_ERROR); + else + __wa_xfer_abort(xfer); spin_unlock_irqrestore(&xfer->lock, flags); if (done) wa_xfer_completion(xfer); @@ -2660,7 +2711,11 @@ static void wa_buf_in_cb(struct urb *urb) } if (resubmit_dti) { - int result = usb_submit_urb(wa->dti_urb, GFP_ATOMIC); + int result; + + wa->dti_state = WA_DTI_TRANSFER_RESULT_PENDING; + + result = usb_submit_urb(wa->dti_urb, GFP_ATOMIC); if (result < 0) { dev_err(dev, "DTI Error: Could not submit DTI URB (%d)\n", result); @@ -2794,7 +2849,7 @@ int wa_dti_start(struct wahc *wa) { const struct usb_endpoint_descriptor *dti_epd = wa->dti_epd; struct device *dev = &wa->usb_iface->dev; - int result = -ENOMEM; + int result = -ENOMEM, index; if (wa->dti_urb != NULL) /* DTI URB already started */ goto out; @@ -2810,15 +2865,14 @@ int wa_dti_start(struct wahc *wa) wa->dti_buf, wa->dti_buf_size, wa_dti_cb, wa); - wa->buf_in_urb = usb_alloc_urb(0, GFP_KERNEL); - if (wa->buf_in_urb == NULL) { - dev_err(dev, "Can't allocate BUF-IN URB\n"); - goto error_buf_in_urb_alloc; + /* init the buf in URBs */ + for (index = 0; index < WA_MAX_BUF_IN_URBS; ++index) { + usb_fill_bulk_urb( + &(wa->buf_in_urbs[index]), wa->usb_dev, + usb_rcvbulkpipe(wa->usb_dev, + 0x80 | dti_epd->bEndpointAddress), + NULL, 0, wa_buf_in_cb, wa); } - usb_fill_bulk_urb( - wa->buf_in_urb, wa->usb_dev, - usb_rcvbulkpipe(wa->usb_dev, 0x80 | dti_epd->bEndpointAddress), - NULL, 0, wa_buf_in_cb, wa); result = usb_submit_urb(wa->dti_urb, GFP_KERNEL); if (result < 0) { dev_err(dev, "DTI Error: Could not submit DTI URB (%d) resetting\n", @@ -2829,9 +2883,6 @@ out: return 0; error_dti_urb_submit: - usb_put_urb(wa->buf_in_urb); - wa->buf_in_urb = NULL; -error_buf_in_urb_alloc: usb_put_urb(wa->dti_urb); wa->dti_urb = NULL; error_dti_urb_alloc: -- cgit v1.2.3-70-g09d2 From 09a0168de11a4a487c2d1c78366491b695e0c15a Mon Sep 17 00:00:00 2001 From: George Cherian Date: Thu, 6 Mar 2014 18:11:52 +0530 Subject: phy: omap-usb2: Adapt phy-omap-usb2 for AM437x Adapt phy-omap-usb2 driver for AM437x. - Add new comaptible "ti,am437x-usb2" for AM437x - Pass proper data to differentiate AM437x and others. - AM437x doesnot support set_vbus and start_srp. Signed-off-by: George Cherian Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-omap-usb2.c | 49 +++++++++++++++++++++++++++++++++----------- include/linux/phy/omap_usb.h | 9 ++++++++ 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/drivers/phy/phy-omap-usb2.c b/drivers/phy/phy-omap-usb2.c index 9c3f056b3dd..0c78f54d065 100644 --- a/drivers/phy/phy-omap-usb2.c +++ b/drivers/phy/phy-omap-usb2.c @@ -122,6 +122,31 @@ static struct phy_ops ops = { .owner = THIS_MODULE, }; +#ifdef CONFIG_OF +static const struct usb_phy_data omap_usb2_data = { + .label = "omap_usb2", + .flags = OMAP_USB2_HAS_START_SRP | OMAP_USB2_HAS_SET_VBUS, +}; + +static const struct usb_phy_data am437x_usb2_data = { + .label = "am437x_usb2", + .flags = 0, +}; + +static const struct of_device_id omap_usb2_id_table[] = { + { + .compatible = "ti,omap-usb2", + .data = &omap_usb2_data, + }, + { + .compatible = "ti,am437x-usb2", + .data = &am437x_usb2_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_usb2_id_table); +#endif + static int omap_usb2_probe(struct platform_device *pdev) { struct omap_usb *phy; @@ -131,10 +156,16 @@ static int omap_usb2_probe(struct platform_device *pdev) struct device_node *node = pdev->dev.of_node; struct device_node *control_node; struct platform_device *control_pdev; + const struct of_device_id *of_id; + struct usb_phy_data *phy_data; + + of_id = of_match_device(of_match_ptr(omap_usb2_id_table), &pdev->dev); - if (!node) + if (!of_id) return -EINVAL; + phy_data = (struct usb_phy_data *)of_id->data; + phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); if (!phy) { dev_err(&pdev->dev, "unable to allocate memory for USB2 PHY\n"); @@ -150,7 +181,7 @@ static int omap_usb2_probe(struct platform_device *pdev) phy->dev = &pdev->dev; phy->phy.dev = phy->dev; - phy->phy.label = "omap-usb2"; + phy->phy.label = phy_data->label; phy->phy.otg = otg; phy->phy.type = USB_PHY_TYPE_USB2; @@ -171,8 +202,10 @@ static int omap_usb2_probe(struct platform_device *pdev) otg->set_host = omap_usb_set_host; otg->set_peripheral = omap_usb_set_peripheral; - otg->set_vbus = omap_usb_set_vbus; - otg->start_srp = omap_usb_start_srp; + if (phy_data->flags & OMAP_USB2_HAS_SET_VBUS) + otg->set_vbus = omap_usb_set_vbus; + if (phy_data->flags & OMAP_USB2_HAS_START_SRP) + otg->start_srp = omap_usb_start_srp; otg->phy = &phy->phy; platform_set_drvdata(pdev, phy); @@ -272,14 +305,6 @@ static const struct dev_pm_ops omap_usb2_pm_ops = { #define DEV_PM_OPS NULL #endif -#ifdef CONFIG_OF -static const struct of_device_id omap_usb2_id_table[] = { - { .compatible = "ti,omap-usb2" }, - {} -}; -MODULE_DEVICE_TABLE(of, omap_usb2_id_table); -#endif - static struct platform_driver omap_usb2_driver = { .probe = omap_usb2_probe, .remove = omap_usb2_remove, diff --git a/include/linux/phy/omap_usb.h b/include/linux/phy/omap_usb.h index 19d343c37fb..35989a8fc53 100644 --- a/include/linux/phy/omap_usb.h +++ b/include/linux/phy/omap_usb.h @@ -39,6 +39,15 @@ struct omap_usb { struct clk *optclk; }; +struct usb_phy_data { + const char *label; + u8 flags; +}; + +/* Driver Flags */ +#define OMAP_USB2_HAS_START_SRP (1 << 0) +#define OMAP_USB2_HAS_SET_VBUS (1 << 1) + #define phy_to_omapusb(x) container_of((x), struct omap_usb, phy) #if defined(CONFIG_OMAP_USB2) || defined(CONFIG_OMAP_USB2_MODULE) -- cgit v1.2.3-70-g09d2 From 7e472402ca308287a2474d4c9011f69f33fa19cb Mon Sep 17 00:00:00 2001 From: Austin Beam Date: Thu, 6 Mar 2014 18:11:53 +0530 Subject: phy: omap-usb2: Provide workaround for USB2PHY false disconnect Enable the dra7x errata workaround for false disconnect problem with USB2PHY. False disconnects were detected with some of the devices. Reduce the sensitivity of the disconnect logic within the USB2PHY subsystem to enusre these false disconnects are not registered. [george.cherian@ti.com] While at that, pass proper flags for each SoC's. This is a common driver used across OMAP4,OMAP5,DRA7xx and AM437x USB2PHY. False disconnect workaround is currently applicable for only DRA7x. Signed-off-by: Austin Beam Signed-off-by: George Cherian Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-omap-usb2.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/phy/omap_usb.h | 4 ++++ 2 files changed, 48 insertions(+) diff --git a/drivers/phy/phy-omap-usb2.c b/drivers/phy/phy-omap-usb2.c index 0c78f54d065..b220202a3f3 100644 --- a/drivers/phy/phy-omap-usb2.c +++ b/drivers/phy/phy-omap-usb2.c @@ -31,6 +31,9 @@ #include #include +#define USB2PHY_DISCON_BYP_LATCH (1 << 31) +#define USB2PHY_ANA_CONFIG1 0x4c + /** * omap_usb2_set_comparator - links the comparator present in the sytem with * this phy @@ -116,7 +119,30 @@ static int omap_usb_power_on(struct phy *x) return 0; } +static int omap_usb_init(struct phy *x) +{ + struct omap_usb *phy = phy_get_drvdata(x); + u32 val; + + if (phy->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) { + /* + * + * Reduce the sensitivity of internal PHY by enabling the + * DISCON_BYP_LATCH of the USB2PHY_ANA_CONFIG1 register. This + * resolves issues with certain devices which can otherwise + * be prone to false disconnects. + * + */ + val = omap_usb_readl(phy->phy_base, USB2PHY_ANA_CONFIG1); + val |= USB2PHY_DISCON_BYP_LATCH; + omap_usb_writel(phy->phy_base, USB2PHY_ANA_CONFIG1, val); + } + + return 0; +} + static struct phy_ops ops = { + .init = omap_usb_init, .power_on = omap_usb_power_on, .power_off = omap_usb_power_off, .owner = THIS_MODULE, @@ -128,6 +154,11 @@ static const struct usb_phy_data omap_usb2_data = { .flags = OMAP_USB2_HAS_START_SRP | OMAP_USB2_HAS_SET_VBUS, }; +static const struct usb_phy_data dra7x_usb2_data = { + .label = "dra7x_usb2", + .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT, +}; + static const struct usb_phy_data am437x_usb2_data = { .label = "am437x_usb2", .flags = 0, @@ -138,6 +169,10 @@ static const struct of_device_id omap_usb2_id_table[] = { .compatible = "ti,omap-usb2", .data = &omap_usb2_data, }, + { + .compatible = "ti,dra7x-usb2", + .data = &dra7x_usb2_data, + }, { .compatible = "ti,am437x-usb2", .data = &am437x_usb2_data, @@ -151,6 +186,7 @@ static int omap_usb2_probe(struct platform_device *pdev) { struct omap_usb *phy; struct phy *generic_phy; + struct resource *res; struct phy_provider *phy_provider; struct usb_otg *otg; struct device_node *node = pdev->dev.of_node; @@ -185,6 +221,14 @@ static int omap_usb2_probe(struct platform_device *pdev) phy->phy.otg = otg; phy->phy.type = USB_PHY_TYPE_USB2; + if (phy_data->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + phy->phy_base = devm_ioremap_resource(&pdev->dev, res); + if (!phy->phy_base) + return -ENOMEM; + phy->flags |= OMAP_USB2_CALIBRATE_FALSE_DISCONNECT; + } + control_node = of_parse_phandle(node, "ctrl-module", 0); if (!control_node) { dev_err(&pdev->dev, "Failed to get control device phandle\n"); diff --git a/include/linux/phy/omap_usb.h b/include/linux/phy/omap_usb.h index 35989a8fc53..dc2c541a619 100644 --- a/include/linux/phy/omap_usb.h +++ b/include/linux/phy/omap_usb.h @@ -33,10 +33,13 @@ struct usb_dpll_params { struct omap_usb { struct usb_phy phy; struct phy_companion *comparator; + void __iomem *pll_ctrl_base; + void __iomem *phy_base; struct device *dev; struct device *control_dev; struct clk *wkupclk; struct clk *optclk; + u8 flags; }; struct usb_phy_data { @@ -47,6 +50,7 @@ struct usb_phy_data { /* Driver Flags */ #define OMAP_USB2_HAS_START_SRP (1 << 0) #define OMAP_USB2_HAS_SET_VBUS (1 << 1) +#define OMAP_USB2_CALIBRATE_FALSE_DISCONNECT (1 << 2) #define phy_to_omapusb(x) container_of((x), struct omap_usb, phy) -- cgit v1.2.3-70-g09d2 From db68e80f080a21b295f2262976520c40bc505f9d Mon Sep 17 00:00:00 2001 From: George Cherian Date: Thu, 6 Mar 2014 18:11:54 +0530 Subject: phy: omap-usb2: Add different compatible for OMAP5 Add a new compatible for OMAP5 since it does not use any of the OTG operations as of now. HAS_SRP and SET_VBUS functionalities are used only for OMAP4. Signed-off-by: George Cherian Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-omap-usb2.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/phy/phy-omap-usb2.c b/drivers/phy/phy-omap-usb2.c index b220202a3f3..3cc4aba6e50 100644 --- a/drivers/phy/phy-omap-usb2.c +++ b/drivers/phy/phy-omap-usb2.c @@ -154,6 +154,11 @@ static const struct usb_phy_data omap_usb2_data = { .flags = OMAP_USB2_HAS_START_SRP | OMAP_USB2_HAS_SET_VBUS, }; +static const struct usb_phy_data omap5_usb2_data = { + .label = "omap5_usb2", + .flags = 0, +}; + static const struct usb_phy_data dra7x_usb2_data = { .label = "dra7x_usb2", .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT, @@ -169,6 +174,10 @@ static const struct of_device_id omap_usb2_id_table[] = { .compatible = "ti,omap-usb2", .data = &omap_usb2_data, }, + { + .compatible = "ti,omap5-usb2", + .data = &omap5_usb2_data, + }, { .compatible = "ti,dra7x-usb2", .data = &dra7x_usb2_data, -- cgit v1.2.3-70-g09d2 From 14da699bc04212559d5dda19d3f07c807fb58dfd Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Thu, 6 Mar 2014 16:38:37 +0200 Subject: phy: rename struct omap_control_usb to struct omap_control_phy Rename struct omap_control_usb to struct omap_control_phy since it can be used to control PHY of USB, SATA and PCIE. Also move the driver and include files under *phy* and made the corresponding changes in the users of phy-omap-control. Signed-off-by: Kishon Vijay Abraham I Signed-off-by: Roger Quadros Acked-by: Felipe Balbi --- drivers/phy/Kconfig | 14 +- drivers/phy/Makefile | 1 + drivers/phy/phy-omap-control.c | 320 +++++++++++++++++++++++++++++++++++ drivers/phy/phy-omap-usb2.c | 8 +- drivers/phy/phy-ti-pipe3.c | 8 +- drivers/usb/musb/omap2430.c | 2 +- drivers/usb/phy/Kconfig | 10 -- drivers/usb/phy/Makefile | 1 - drivers/usb/phy/phy-omap-control.c | 319 ---------------------------------- include/linux/phy/omap_control_phy.h | 89 ++++++++++ include/linux/usb/omap_control_usb.h | 89 ---------- 11 files changed, 431 insertions(+), 430 deletions(-) create mode 100644 drivers/phy/phy-omap-control.c delete mode 100644 drivers/usb/phy/phy-omap-control.c create mode 100644 include/linux/phy/omap_control_phy.h delete mode 100644 include/linux/usb/omap_control_usb.h diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 1b607d7cb03..fe8c0096f9e 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -31,12 +31,22 @@ config PHY_MVEBU_SATA depends on OF select GENERIC_PHY +config OMAP_CONTROL_PHY + tristate "OMAP CONTROL PHY Driver" + help + Enable this to add support for the PHY part present in the control + module. This driver has API to power on the USB2 PHY and to write to + the mailbox. The mailbox is present only in omap4 and the register to + power on the USB2 PHY is present in OMAP4 and OMAP5. OMAP5 has an + additional register to power on USB3 PHY/SATA PHY/PCIE PHY + (PIPE3 PHY). + config OMAP_USB2 tristate "OMAP USB2 PHY Driver" depends on ARCH_OMAP2PLUS depends on USB_PHY select GENERIC_PHY - select OMAP_CONTROL_USB + select OMAP_CONTROL_PHY help Enable this to support the transceiver that is part of SOC. This driver takes care of all the PHY functionality apart from comparator. @@ -47,7 +57,7 @@ config TI_PIPE3 tristate "TI PIPE3 PHY Driver" depends on ARCH_OMAP2PLUS || COMPILE_TEST select GENERIC_PHY - select OMAP_CONTROL_USB + select OMAP_CONTROL_PHY help Enable this to support the PIPE3 PHY that is part of TI SOCs. This driver takes care of all the PHY functionality apart from comparator. diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index ecf0d3f5bf1..8da05a84111 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o obj-$(CONFIG_PHY_EXYNOS_DP_VIDEO) += phy-exynos-dp-video.o obj-$(CONFIG_PHY_EXYNOS_MIPI_VIDEO) += phy-exynos-mipi-video.o obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.o +obj-$(CONFIG_OMAP_CONTROL_PHY) += phy-omap-control.o obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o diff --git a/drivers/phy/phy-omap-control.c b/drivers/phy/phy-omap-control.c new file mode 100644 index 00000000000..17fc200210c --- /dev/null +++ b/drivers/phy/phy-omap-control.c @@ -0,0 +1,320 @@ +/* + * omap-control-phy.c - The PHY part of control module. + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Author: Kishon Vijay Abraham I + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * omap_control_phy_power - power on/off the phy using control module reg + * @dev: the control module device + * @on: 0 or 1, based on powering on or off the PHY + */ +void omap_control_phy_power(struct device *dev, int on) +{ + u32 val; + unsigned long rate; + struct omap_control_phy *control_phy; + + if (IS_ERR(dev) || !dev) { + pr_err("%s: invalid device\n", __func__); + return; + } + + control_phy = dev_get_drvdata(dev); + if (!control_phy) { + dev_err(dev, "%s: invalid control phy device\n", __func__); + return; + } + + if (control_phy->type == OMAP_CTRL_TYPE_OTGHS) + return; + + val = readl(control_phy->power); + + switch (control_phy->type) { + case OMAP_CTRL_TYPE_USB2: + if (on) + val &= ~OMAP_CTRL_DEV_PHY_PD; + else + val |= OMAP_CTRL_DEV_PHY_PD; + break; + + case OMAP_CTRL_TYPE_PIPE3: + rate = clk_get_rate(control_phy->sys_clk); + rate = rate/1000000; + + if (on) { + val &= ~(OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK | + OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK); + val |= OMAP_CTRL_PIPE3_PHY_TX_RX_POWERON << + OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; + val |= rate << + OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT; + } else { + val &= ~OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK; + val |= OMAP_CTRL_PIPE3_PHY_TX_RX_POWEROFF << + OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; + } + break; + + case OMAP_CTRL_TYPE_DRA7USB2: + if (on) + val &= ~OMAP_CTRL_USB2_PHY_PD; + else + val |= OMAP_CTRL_USB2_PHY_PD; + break; + + case OMAP_CTRL_TYPE_AM437USB2: + if (on) { + val &= ~(AM437X_CTRL_USB2_PHY_PD | + AM437X_CTRL_USB2_OTG_PD); + val |= (AM437X_CTRL_USB2_OTGVDET_EN | + AM437X_CTRL_USB2_OTGSESSEND_EN); + } else { + val &= ~(AM437X_CTRL_USB2_OTGVDET_EN | + AM437X_CTRL_USB2_OTGSESSEND_EN); + val |= (AM437X_CTRL_USB2_PHY_PD | + AM437X_CTRL_USB2_OTG_PD); + } + break; + default: + dev_err(dev, "%s: type %d not recognized\n", + __func__, control_phy->type); + break; + } + + writel(val, control_phy->power); +} +EXPORT_SYMBOL_GPL(omap_control_phy_power); + +/** + * omap_control_usb_host_mode - set AVALID, VBUSVALID and ID pin in grounded + * @ctrl_phy: struct omap_control_phy * + * + * Writes to the mailbox register to notify the usb core that a usb + * device has been connected. + */ +static void omap_control_usb_host_mode(struct omap_control_phy *ctrl_phy) +{ + u32 val; + + val = readl(ctrl_phy->otghs_control); + val &= ~(OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_SESSEND); + val |= OMAP_CTRL_DEV_AVALID | OMAP_CTRL_DEV_VBUSVALID; + writel(val, ctrl_phy->otghs_control); +} + +/** + * omap_control_usb_device_mode - set AVALID, VBUSVALID and ID pin in high + * impedance + * @ctrl_phy: struct omap_control_phy * + * + * Writes to the mailbox register to notify the usb core that it has been + * connected to a usb host. + */ +static void omap_control_usb_device_mode(struct omap_control_phy *ctrl_phy) +{ + u32 val; + + val = readl(ctrl_phy->otghs_control); + val &= ~OMAP_CTRL_DEV_SESSEND; + val |= OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_AVALID | + OMAP_CTRL_DEV_VBUSVALID; + writel(val, ctrl_phy->otghs_control); +} + +/** + * omap_control_usb_set_sessionend - Enable SESSIONEND and IDIG to high + * impedance + * @ctrl_phy: struct omap_control_phy * + * + * Writes to the mailbox register to notify the usb core it's now in + * disconnected state. + */ +static void omap_control_usb_set_sessionend(struct omap_control_phy *ctrl_phy) +{ + u32 val; + + val = readl(ctrl_phy->otghs_control); + val &= ~(OMAP_CTRL_DEV_AVALID | OMAP_CTRL_DEV_VBUSVALID); + val |= OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_SESSEND; + writel(val, ctrl_phy->otghs_control); +} + +/** + * omap_control_usb_set_mode - Calls to functions to set USB in one of host mode + * or device mode or to denote disconnected state + * @dev: the control module device + * @mode: The mode to which usb should be configured + * + * This is an API to write to the mailbox register to notify the usb core that + * a usb device has been connected. + */ +void omap_control_usb_set_mode(struct device *dev, + enum omap_control_usb_mode mode) +{ + struct omap_control_phy *ctrl_phy; + + if (IS_ERR(dev) || !dev) + return; + + ctrl_phy = dev_get_drvdata(dev); + + if (!ctrl_phy) { + dev_err(dev, "Invalid control phy device\n"); + return; + } + + if (ctrl_phy->type != OMAP_CTRL_TYPE_OTGHS) + return; + + switch (mode) { + case USB_MODE_HOST: + omap_control_usb_host_mode(ctrl_phy); + break; + case USB_MODE_DEVICE: + omap_control_usb_device_mode(ctrl_phy); + break; + case USB_MODE_DISCONNECT: + omap_control_usb_set_sessionend(ctrl_phy); + break; + default: + dev_vdbg(dev, "invalid omap control usb mode\n"); + } +} +EXPORT_SYMBOL_GPL(omap_control_usb_set_mode); + +#ifdef CONFIG_OF + +static const enum omap_control_phy_type otghs_data = OMAP_CTRL_TYPE_OTGHS; +static const enum omap_control_phy_type usb2_data = OMAP_CTRL_TYPE_USB2; +static const enum omap_control_phy_type pipe3_data = OMAP_CTRL_TYPE_PIPE3; +static const enum omap_control_phy_type dra7usb2_data = OMAP_CTRL_TYPE_DRA7USB2; +static const enum omap_control_phy_type am437usb2_data = OMAP_CTRL_TYPE_AM437USB2; + +static const struct of_device_id omap_control_phy_id_table[] = { + { + .compatible = "ti,control-phy-otghs", + .data = &otghs_data, + }, + { + .compatible = "ti,control-phy-usb2", + .data = &usb2_data, + }, + { + .compatible = "ti,control-phy-pipe3", + .data = &pipe3_data, + }, + { + .compatible = "ti,control-phy-dra7usb2", + .data = &dra7usb2_data, + }, + { + .compatible = "ti,control-phy-am437usb2", + .data = &am437usb2_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_control_phy_id_table); +#endif + + +static int omap_control_phy_probe(struct platform_device *pdev) +{ + struct resource *res; + const struct of_device_id *of_id; + struct omap_control_phy *control_phy; + + of_id = of_match_device(of_match_ptr(omap_control_phy_id_table), + &pdev->dev); + if (!of_id) + return -EINVAL; + + control_phy = devm_kzalloc(&pdev->dev, sizeof(*control_phy), + GFP_KERNEL); + if (!control_phy) { + dev_err(&pdev->dev, "unable to alloc memory for control phy\n"); + return -ENOMEM; + } + + control_phy->dev = &pdev->dev; + control_phy->type = *(enum omap_control_phy_type *)of_id->data; + + if (control_phy->type == OMAP_CTRL_TYPE_OTGHS) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "otghs_control"); + control_phy->otghs_control = devm_ioremap_resource( + &pdev->dev, res); + if (IS_ERR(control_phy->otghs_control)) + return PTR_ERR(control_phy->otghs_control); + } else { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "power"); + control_phy->power = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(control_phy->power)) { + dev_err(&pdev->dev, "Couldn't get power register\n"); + return PTR_ERR(control_phy->power); + } + } + + if (control_phy->type == OMAP_CTRL_TYPE_PIPE3) { + control_phy->sys_clk = devm_clk_get(control_phy->dev, + "sys_clkin"); + if (IS_ERR(control_phy->sys_clk)) { + pr_err("%s: unable to get sys_clkin\n", __func__); + return -EINVAL; + } + } + + dev_set_drvdata(control_phy->dev, control_phy); + + return 0; +} + +static struct platform_driver omap_control_phy_driver = { + .probe = omap_control_phy_probe, + .driver = { + .name = "omap-control-phy", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(omap_control_phy_id_table), + }, +}; + +static int __init omap_control_phy_init(void) +{ + return platform_driver_register(&omap_control_phy_driver); +} +subsys_initcall(omap_control_phy_init); + +static void __exit omap_control_phy_exit(void) +{ + platform_driver_unregister(&omap_control_phy_driver); +} +module_exit(omap_control_phy_exit); + +MODULE_ALIAS("platform: omap_control_phy"); +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("OMAP Control Module PHY Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-omap-usb2.c b/drivers/phy/phy-omap-usb2.c index 3cc4aba6e50..a2205a841e5 100644 --- a/drivers/phy/phy-omap-usb2.c +++ b/drivers/phy/phy-omap-usb2.c @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include @@ -105,7 +105,7 @@ static int omap_usb_power_off(struct phy *x) { struct omap_usb *phy = phy_get_drvdata(x); - omap_control_usb_phy_power(phy->control_dev, 0); + omap_control_phy_power(phy->control_dev, 0); return 0; } @@ -114,7 +114,7 @@ static int omap_usb_power_on(struct phy *x) { struct omap_usb *phy = phy_get_drvdata(x); - omap_control_usb_phy_power(phy->control_dev, 1); + omap_control_phy_power(phy->control_dev, 1); return 0; } @@ -251,7 +251,7 @@ static int omap_usb2_probe(struct platform_device *pdev) } phy->control_dev = &control_pdev->dev; - omap_control_usb_phy_power(phy->control_dev, 0); + omap_control_phy_power(phy->control_dev, 0); otg->set_host = omap_usb_set_host; otg->set_peripheral = omap_usb_set_peripheral; diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index c8d16746a5e..fd029b14cb3 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #define PLL_STATUS 0x00000004 @@ -134,7 +134,7 @@ static int ti_pipe3_power_off(struct phy *x) return -EBUSY; } - omap_control_usb_phy_power(phy->control_dev, 0); + omap_control_phy_power(phy->control_dev, 0); return 0; } @@ -232,7 +232,7 @@ static int ti_pipe3_init(struct phy *x) if (ret) return ret; - omap_control_usb_phy_power(phy->control_dev, 1); + omap_control_phy_power(phy->control_dev, 1); return 0; } @@ -304,7 +304,7 @@ static int ti_pipe3_probe(struct platform_device *pdev) phy->control_dev = &control_pdev->dev; - omap_control_usb_phy_power(phy->control_dev, 0); + omap_control_phy_power(phy->control_dev, 0); platform_set_drvdata(pdev, phy); pm_runtime_enable(phy->dev); diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index 8aa59a2c5eb..d341c149a2f 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -37,7 +37,7 @@ #include #include #include -#include +#include #include #include "musb_core.h" diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index c337ba2d066..416e0c8cf6f 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -75,16 +75,6 @@ config NOP_USB_XCEIV built-in with usb ip or which are autonomous and doesn't require any phy programming such as ISP1x04 etc. -config OMAP_CONTROL_USB - tristate "OMAP CONTROL USB Driver" - depends on ARCH_OMAP2PLUS || COMPILE_TEST - help - Enable this to add support for the USB part present in the control - module. This driver has API to power on the USB2 PHY and to write to - the mailbox. The mailbox is present only in omap4 and the register to - power on the USB2 PHY is present in OMAP4 and OMAP5. OMAP5 has an - additional register to power on USB3 PHY. - config AM335X_CONTROL_USB tristate diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index 15f1878388f..f8fa719a31b 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -13,7 +13,6 @@ obj-$(CONFIG_ISP1301_OMAP) += phy-isp1301-omap.o obj-$(CONFIG_MV_U3D_PHY) += phy-mv-u3d-usb.o obj-$(CONFIG_NOP_USB_XCEIV) += phy-generic.o obj-$(CONFIG_TAHVO_USB) += phy-tahvo.o -obj-$(CONFIG_OMAP_CONTROL_USB) += phy-omap-control.o obj-$(CONFIG_AM335X_CONTROL_USB) += phy-am335x-control.o obj-$(CONFIG_AM335X_PHY_USB) += phy-am335x.o obj-$(CONFIG_OMAP_OTG) += phy-omap-otg.o diff --git a/drivers/usb/phy/phy-omap-control.c b/drivers/usb/phy/phy-omap-control.c deleted file mode 100644 index e7253182e47..00000000000 --- a/drivers/usb/phy/phy-omap-control.c +++ /dev/null @@ -1,319 +0,0 @@ -/* - * omap-control-usb.c - The USB part of control module. - * - * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Author: Kishon Vijay Abraham I - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/** - * omap_control_usb_phy_power - power on/off the phy using control module reg - * @dev: the control module device - * @on: 0 or 1, based on powering on or off the PHY - */ -void omap_control_usb_phy_power(struct device *dev, int on) -{ - u32 val; - unsigned long rate; - struct omap_control_usb *control_usb; - - if (IS_ERR(dev) || !dev) { - pr_err("%s: invalid device\n", __func__); - return; - } - - control_usb = dev_get_drvdata(dev); - if (!control_usb) { - dev_err(dev, "%s: invalid control usb device\n", __func__); - return; - } - - if (control_usb->type == OMAP_CTRL_TYPE_OTGHS) - return; - - val = readl(control_usb->power); - - switch (control_usb->type) { - case OMAP_CTRL_TYPE_USB2: - if (on) - val &= ~OMAP_CTRL_DEV_PHY_PD; - else - val |= OMAP_CTRL_DEV_PHY_PD; - break; - - case OMAP_CTRL_TYPE_PIPE3: - rate = clk_get_rate(control_usb->sys_clk); - rate = rate/1000000; - - if (on) { - val &= ~(OMAP_CTRL_USB_PWRCTL_CLK_CMD_MASK | - OMAP_CTRL_USB_PWRCTL_CLK_FREQ_MASK); - val |= OMAP_CTRL_USB3_PHY_TX_RX_POWERON << - OMAP_CTRL_USB_PWRCTL_CLK_CMD_SHIFT; - val |= rate << OMAP_CTRL_USB_PWRCTL_CLK_FREQ_SHIFT; - } else { - val &= ~OMAP_CTRL_USB_PWRCTL_CLK_CMD_MASK; - val |= OMAP_CTRL_USB3_PHY_TX_RX_POWEROFF << - OMAP_CTRL_USB_PWRCTL_CLK_CMD_SHIFT; - } - break; - - case OMAP_CTRL_TYPE_DRA7USB2: - if (on) - val &= ~OMAP_CTRL_USB2_PHY_PD; - else - val |= OMAP_CTRL_USB2_PHY_PD; - break; - - case OMAP_CTRL_TYPE_AM437USB2: - if (on) { - val &= ~(AM437X_CTRL_USB2_PHY_PD | - AM437X_CTRL_USB2_OTG_PD); - val |= (AM437X_CTRL_USB2_OTGVDET_EN | - AM437X_CTRL_USB2_OTGSESSEND_EN); - } else { - val &= ~(AM437X_CTRL_USB2_OTGVDET_EN | - AM437X_CTRL_USB2_OTGSESSEND_EN); - val |= (AM437X_CTRL_USB2_PHY_PD | - AM437X_CTRL_USB2_OTG_PD); - } - break; - default: - dev_err(dev, "%s: type %d not recognized\n", - __func__, control_usb->type); - break; - } - - writel(val, control_usb->power); -} -EXPORT_SYMBOL_GPL(omap_control_usb_phy_power); - -/** - * omap_control_usb_host_mode - set AVALID, VBUSVALID and ID pin in grounded - * @ctrl_usb: struct omap_control_usb * - * - * Writes to the mailbox register to notify the usb core that a usb - * device has been connected. - */ -static void omap_control_usb_host_mode(struct omap_control_usb *ctrl_usb) -{ - u32 val; - - val = readl(ctrl_usb->otghs_control); - val &= ~(OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_SESSEND); - val |= OMAP_CTRL_DEV_AVALID | OMAP_CTRL_DEV_VBUSVALID; - writel(val, ctrl_usb->otghs_control); -} - -/** - * omap_control_usb_device_mode - set AVALID, VBUSVALID and ID pin in high - * impedance - * @ctrl_usb: struct omap_control_usb * - * - * Writes to the mailbox register to notify the usb core that it has been - * connected to a usb host. - */ -static void omap_control_usb_device_mode(struct omap_control_usb *ctrl_usb) -{ - u32 val; - - val = readl(ctrl_usb->otghs_control); - val &= ~OMAP_CTRL_DEV_SESSEND; - val |= OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_AVALID | - OMAP_CTRL_DEV_VBUSVALID; - writel(val, ctrl_usb->otghs_control); -} - -/** - * omap_control_usb_set_sessionend - Enable SESSIONEND and IDIG to high - * impedance - * @ctrl_usb: struct omap_control_usb * - * - * Writes to the mailbox register to notify the usb core it's now in - * disconnected state. - */ -static void omap_control_usb_set_sessionend(struct omap_control_usb *ctrl_usb) -{ - u32 val; - - val = readl(ctrl_usb->otghs_control); - val &= ~(OMAP_CTRL_DEV_AVALID | OMAP_CTRL_DEV_VBUSVALID); - val |= OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_SESSEND; - writel(val, ctrl_usb->otghs_control); -} - -/** - * omap_control_usb_set_mode - Calls to functions to set USB in one of host mode - * or device mode or to denote disconnected state - * @dev: the control module device - * @mode: The mode to which usb should be configured - * - * This is an API to write to the mailbox register to notify the usb core that - * a usb device has been connected. - */ -void omap_control_usb_set_mode(struct device *dev, - enum omap_control_usb_mode mode) -{ - struct omap_control_usb *ctrl_usb; - - if (IS_ERR(dev) || !dev) - return; - - ctrl_usb = dev_get_drvdata(dev); - - if (!ctrl_usb) { - dev_err(dev, "Invalid control usb device\n"); - return; - } - - if (ctrl_usb->type != OMAP_CTRL_TYPE_OTGHS) - return; - - switch (mode) { - case USB_MODE_HOST: - omap_control_usb_host_mode(ctrl_usb); - break; - case USB_MODE_DEVICE: - omap_control_usb_device_mode(ctrl_usb); - break; - case USB_MODE_DISCONNECT: - omap_control_usb_set_sessionend(ctrl_usb); - break; - default: - dev_vdbg(dev, "invalid omap control usb mode\n"); - } -} -EXPORT_SYMBOL_GPL(omap_control_usb_set_mode); - -#ifdef CONFIG_OF - -static const enum omap_control_usb_type otghs_data = OMAP_CTRL_TYPE_OTGHS; -static const enum omap_control_usb_type usb2_data = OMAP_CTRL_TYPE_USB2; -static const enum omap_control_usb_type pipe3_data = OMAP_CTRL_TYPE_PIPE3; -static const enum omap_control_usb_type dra7usb2_data = OMAP_CTRL_TYPE_DRA7USB2; -static const enum omap_control_usb_type am437usb2_data = OMAP_CTRL_TYPE_AM437USB2; - -static const struct of_device_id omap_control_usb_id_table[] = { - { - .compatible = "ti,control-phy-otghs", - .data = &otghs_data, - }, - { - .compatible = "ti,control-phy-usb2", - .data = &usb2_data, - }, - { - .compatible = "ti,control-phy-pipe3", - .data = &pipe3_data, - }, - { - .compatible = "ti,control-phy-dra7usb2", - .data = &dra7usb2_data, - }, - { - .compatible = "ti,control-phy-am437usb2", - .data = &am437usb2_data, - }, - {}, -}; -MODULE_DEVICE_TABLE(of, omap_control_usb_id_table); -#endif - - -static int omap_control_usb_probe(struct platform_device *pdev) -{ - struct resource *res; - const struct of_device_id *of_id; - struct omap_control_usb *control_usb; - - of_id = of_match_device(of_match_ptr(omap_control_usb_id_table), - &pdev->dev); - if (!of_id) - return -EINVAL; - - control_usb = devm_kzalloc(&pdev->dev, sizeof(*control_usb), - GFP_KERNEL); - if (!control_usb) { - dev_err(&pdev->dev, "unable to alloc memory for control usb\n"); - return -ENOMEM; - } - - control_usb->dev = &pdev->dev; - control_usb->type = *(enum omap_control_usb_type *)of_id->data; - - if (control_usb->type == OMAP_CTRL_TYPE_OTGHS) { - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "otghs_control"); - control_usb->otghs_control = devm_ioremap_resource( - &pdev->dev, res); - if (IS_ERR(control_usb->otghs_control)) - return PTR_ERR(control_usb->otghs_control); - } else { - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "power"); - control_usb->power = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(control_usb->power)) { - dev_err(&pdev->dev, "Couldn't get power register\n"); - return PTR_ERR(control_usb->power); - } - } - - if (control_usb->type == OMAP_CTRL_TYPE_PIPE3) { - control_usb->sys_clk = devm_clk_get(control_usb->dev, - "sys_clkin"); - if (IS_ERR(control_usb->sys_clk)) { - pr_err("%s: unable to get sys_clkin\n", __func__); - return -EINVAL; - } - } - - dev_set_drvdata(control_usb->dev, control_usb); - - return 0; -} - -static struct platform_driver omap_control_usb_driver = { - .probe = omap_control_usb_probe, - .driver = { - .name = "omap-control-usb", - .owner = THIS_MODULE, - .of_match_table = of_match_ptr(omap_control_usb_id_table), - }, -}; - -static int __init omap_control_usb_init(void) -{ - return platform_driver_register(&omap_control_usb_driver); -} -subsys_initcall(omap_control_usb_init); - -static void __exit omap_control_usb_exit(void) -{ - platform_driver_unregister(&omap_control_usb_driver); -} -module_exit(omap_control_usb_exit); - -MODULE_ALIAS("platform: omap_control_usb"); -MODULE_AUTHOR("Texas Instruments Inc."); -MODULE_DESCRIPTION("OMAP Control Module USB Driver"); -MODULE_LICENSE("GPL v2"); diff --git a/include/linux/phy/omap_control_phy.h b/include/linux/phy/omap_control_phy.h new file mode 100644 index 00000000000..5450403c754 --- /dev/null +++ b/include/linux/phy/omap_control_phy.h @@ -0,0 +1,89 @@ +/* + * omap_control_phy.h - Header file for the PHY part of control module. + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Author: Kishon Vijay Abraham I + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __OMAP_CONTROL_PHY_H__ +#define __OMAP_CONTROL_PHY_H__ + +enum omap_control_phy_type { + OMAP_CTRL_TYPE_OTGHS = 1, /* Mailbox OTGHS_CONTROL */ + OMAP_CTRL_TYPE_USB2, /* USB2_PHY, power down in CONTROL_DEV_CONF */ + OMAP_CTRL_TYPE_PIPE3, /* PIPE3 PHY, DPLL & seperate Rx/Tx power */ + OMAP_CTRL_TYPE_DRA7USB2, /* USB2 PHY, power and power_aux e.g. DRA7 */ + OMAP_CTRL_TYPE_AM437USB2, /* USB2 PHY, power e.g. AM437x */ +}; + +struct omap_control_phy { + struct device *dev; + + u32 __iomem *otghs_control; + u32 __iomem *power; + u32 __iomem *power_aux; + + struct clk *sys_clk; + + enum omap_control_phy_type type; +}; + +enum omap_control_usb_mode { + USB_MODE_UNDEFINED = 0, + USB_MODE_HOST, + USB_MODE_DEVICE, + USB_MODE_DISCONNECT, +}; + +#define OMAP_CTRL_DEV_PHY_PD BIT(0) + +#define OMAP_CTRL_DEV_AVALID BIT(0) +#define OMAP_CTRL_DEV_BVALID BIT(1) +#define OMAP_CTRL_DEV_VBUSVALID BIT(2) +#define OMAP_CTRL_DEV_SESSEND BIT(3) +#define OMAP_CTRL_DEV_IDDIG BIT(4) + +#define OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK 0x003FC000 +#define OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT 0xE + +#define OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK 0xFFC00000 +#define OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT 0x16 + +#define OMAP_CTRL_PIPE3_PHY_TX_RX_POWERON 0x3 +#define OMAP_CTRL_PIPE3_PHY_TX_RX_POWEROFF 0x0 + +#define OMAP_CTRL_USB2_PHY_PD BIT(28) + +#define AM437X_CTRL_USB2_PHY_PD BIT(0) +#define AM437X_CTRL_USB2_OTG_PD BIT(1) +#define AM437X_CTRL_USB2_OTGVDET_EN BIT(19) +#define AM437X_CTRL_USB2_OTGSESSEND_EN BIT(20) + +#if IS_ENABLED(CONFIG_OMAP_CONTROL_PHY) +void omap_control_phy_power(struct device *dev, int on); +void omap_control_usb_set_mode(struct device *dev, + enum omap_control_usb_mode mode); +#else + +static inline void omap_control_phy_power(struct device *dev, int on) +{ +} + +static inline void omap_control_usb_set_mode(struct device *dev, + enum omap_control_usb_mode mode) +{ +} +#endif + +#endif /* __OMAP_CONTROL_PHY_H__ */ diff --git a/include/linux/usb/omap_control_usb.h b/include/linux/usb/omap_control_usb.h deleted file mode 100644 index 69ae383ee3c..00000000000 --- a/include/linux/usb/omap_control_usb.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * omap_control_usb.h - Header file for the USB part of control module. - * - * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Author: Kishon Vijay Abraham I - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef __OMAP_CONTROL_USB_H__ -#define __OMAP_CONTROL_USB_H__ - -enum omap_control_usb_type { - OMAP_CTRL_TYPE_OTGHS = 1, /* Mailbox OTGHS_CONTROL */ - OMAP_CTRL_TYPE_USB2, /* USB2_PHY, power down in CONTROL_DEV_CONF */ - OMAP_CTRL_TYPE_PIPE3, /* PIPE3 PHY, DPLL & seperate Rx/Tx power */ - OMAP_CTRL_TYPE_DRA7USB2, /* USB2 PHY, power and power_aux e.g. DRA7 */ - OMAP_CTRL_TYPE_AM437USB2, /* USB2 PHY, power e.g. AM437x */ -}; - -struct omap_control_usb { - struct device *dev; - - u32 __iomem *otghs_control; - u32 __iomem *power; - u32 __iomem *power_aux; - - struct clk *sys_clk; - - enum omap_control_usb_type type; -}; - -enum omap_control_usb_mode { - USB_MODE_UNDEFINED = 0, - USB_MODE_HOST, - USB_MODE_DEVICE, - USB_MODE_DISCONNECT, -}; - -#define OMAP_CTRL_DEV_PHY_PD BIT(0) - -#define OMAP_CTRL_DEV_AVALID BIT(0) -#define OMAP_CTRL_DEV_BVALID BIT(1) -#define OMAP_CTRL_DEV_VBUSVALID BIT(2) -#define OMAP_CTRL_DEV_SESSEND BIT(3) -#define OMAP_CTRL_DEV_IDDIG BIT(4) - -#define OMAP_CTRL_USB_PWRCTL_CLK_CMD_MASK 0x003FC000 -#define OMAP_CTRL_USB_PWRCTL_CLK_CMD_SHIFT 0xE - -#define OMAP_CTRL_USB_PWRCTL_CLK_FREQ_MASK 0xFFC00000 -#define OMAP_CTRL_USB_PWRCTL_CLK_FREQ_SHIFT 0x16 - -#define OMAP_CTRL_USB3_PHY_TX_RX_POWERON 0x3 -#define OMAP_CTRL_USB3_PHY_TX_RX_POWEROFF 0x0 - -#define OMAP_CTRL_USB2_PHY_PD BIT(28) - -#define AM437X_CTRL_USB2_PHY_PD BIT(0) -#define AM437X_CTRL_USB2_OTG_PD BIT(1) -#define AM437X_CTRL_USB2_OTGVDET_EN BIT(19) -#define AM437X_CTRL_USB2_OTGSESSEND_EN BIT(20) - -#if IS_ENABLED(CONFIG_OMAP_CONTROL_USB) -extern void omap_control_usb_phy_power(struct device *dev, int on); -extern void omap_control_usb_set_mode(struct device *dev, - enum omap_control_usb_mode mode); -#else - -static inline void omap_control_usb_phy_power(struct device *dev, int on) -{ -} - -static inline void omap_control_usb_set_mode(struct device *dev, - enum omap_control_usb_mode mode) -{ -} -#endif - -#endif /* __OMAP_CONTROL_USB_H__ */ -- cgit v1.2.3-70-g09d2 From 51c9f4adaee3fb01bb6f2e2ed72c0753c2609912 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Fri, 7 Mar 2014 11:18:00 +0530 Subject: phy: omap-control: update dra7 and am437 usb2 bindings The dra7-usb2 and am437-usb2 bindings have not yet been used. Change them to be more elegant. Signed-off-by: Roger Quadros Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-omap-control.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/phy/phy-omap-control.c b/drivers/phy/phy-omap-control.c index 17fc200210c..311b4f9a513 100644 --- a/drivers/phy/phy-omap-control.c +++ b/drivers/phy/phy-omap-control.c @@ -228,11 +228,11 @@ static const struct of_device_id omap_control_phy_id_table[] = { .data = &pipe3_data, }, { - .compatible = "ti,control-phy-dra7usb2", + .compatible = "ti,control-phy-usb2-dra7", .data = &dra7usb2_data, }, { - .compatible = "ti,control-phy-am437usb2", + .compatible = "ti,control-phy-usb2-am437", .data = &am437usb2_data, }, {}, -- cgit v1.2.3-70-g09d2 From 1562864f0f034ea393230b470d104e1196b4a5f1 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Fri, 7 Mar 2014 11:27:09 +0530 Subject: phy: ti-pipe3: cleanup clock handling As this driver is no longer USB specific, use generic clock names. - Fix PLL_SD_SHIFT from 9 to 10 - Don't separate prepare/unprepare clock from enable/disable. This ensures optimal power savings. Signed-off-by: Roger Quadros Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-ti-pipe3.c | 55 +++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index fd029b14cb3..211703c87fb 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -45,7 +45,7 @@ #define PLL_SELFREQDCO_MASK 0x0000000E #define PLL_SELFREQDCO_SHIFT 0x1 #define PLL_SD_MASK 0x0003FC00 -#define PLL_SD_SHIFT 0x9 +#define PLL_SD_SHIFT 10 #define SET_PLL_GO 0x1 #define PLL_TICOPWDN 0x10000 #define PLL_LOCK 0x2 @@ -72,7 +72,7 @@ struct ti_pipe3 { struct device *control_dev; struct clk *wkupclk; struct clk *sys_clk; - struct clk *optclk; + struct clk *refclk; }; struct pipe3_dpll_map { @@ -270,23 +270,21 @@ static int ti_pipe3_probe(struct platform_device *pdev) phy->dev = &pdev->dev; - phy->wkupclk = devm_clk_get(phy->dev, "usb_phy_cm_clk32k"); + phy->wkupclk = devm_clk_get(phy->dev, "wkupclk"); if (IS_ERR(phy->wkupclk)) { - dev_err(&pdev->dev, "unable to get usb_phy_cm_clk32k\n"); + dev_err(&pdev->dev, "unable to get wkupclk\n"); return PTR_ERR(phy->wkupclk); } - clk_prepare(phy->wkupclk); - phy->optclk = devm_clk_get(phy->dev, "usb_otg_ss_refclk960m"); - if (IS_ERR(phy->optclk)) { - dev_err(&pdev->dev, "unable to get usb_otg_ss_refclk960m\n"); - return PTR_ERR(phy->optclk); + phy->refclk = devm_clk_get(phy->dev, "refclk"); + if (IS_ERR(phy->refclk)) { + dev_err(&pdev->dev, "unable to get refclk\n"); + return PTR_ERR(phy->refclk); } - clk_prepare(phy->optclk); - phy->sys_clk = devm_clk_get(phy->dev, "sys_clkin"); + phy->sys_clk = devm_clk_get(phy->dev, "sysclk"); if (IS_ERR(phy->sys_clk)) { - pr_err("%s: unable to get sys_clkin\n", __func__); + dev_err(&pdev->dev, "unable to get sysclk\n"); return -EINVAL; } @@ -326,10 +324,6 @@ static int ti_pipe3_probe(struct platform_device *pdev) static int ti_pipe3_remove(struct platform_device *pdev) { - struct ti_pipe3 *phy = platform_get_drvdata(pdev); - - clk_unprepare(phy->wkupclk); - clk_unprepare(phy->optclk); if (!pm_runtime_suspended(&pdev->dev)) pm_runtime_put(&pdev->dev); pm_runtime_disable(&pdev->dev); @@ -343,8 +337,10 @@ static int ti_pipe3_runtime_suspend(struct device *dev) { struct ti_pipe3 *phy = dev_get_drvdata(dev); - clk_disable(phy->wkupclk); - clk_disable(phy->optclk); + if (!IS_ERR(phy->wkupclk)) + clk_disable_unprepare(phy->wkupclk); + if (!IS_ERR(phy->refclk)) + clk_disable_unprepare(phy->refclk); return 0; } @@ -354,22 +350,27 @@ static int ti_pipe3_runtime_resume(struct device *dev) u32 ret = 0; struct ti_pipe3 *phy = dev_get_drvdata(dev); - ret = clk_enable(phy->optclk); - if (ret) { - dev_err(phy->dev, "Failed to enable optclk %d\n", ret); - goto err1; + if (!IS_ERR(phy->refclk)) { + ret = clk_prepare_enable(phy->refclk); + if (ret) { + dev_err(phy->dev, "Failed to enable refclk %d\n", ret); + goto err1; + } } - ret = clk_enable(phy->wkupclk); - if (ret) { - dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret); - goto err2; + if (!IS_ERR(phy->wkupclk)) { + ret = clk_prepare_enable(phy->wkupclk); + if (ret) { + dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret); + goto err2; + } } return 0; err2: - clk_disable(phy->optclk); + if (!IS_ERR(phy->refclk)) + clk_disable_unprepare(phy->refclk); err1: return ret; -- cgit v1.2.3-70-g09d2 From 61f546747715683c334ef231fd648522312dc5ff Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Fri, 7 Mar 2014 11:43:39 +0530 Subject: phy: ti-pipe3: Add SATA DPLL support USB and SATA DPLLs need different settings. Provide the SATA DPLL settings and use the proper DPLL settings based on device tree node's compatible_id. Signed-off-by: Roger Quadros Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-ti-pipe3.c | 76 +++++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index 211703c87fb..f141237131c 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -66,6 +66,11 @@ struct pipe3_dpll_params { u32 mf; }; +struct pipe3_dpll_map { + unsigned long rate; + struct pipe3_dpll_params params; +}; + struct ti_pipe3 { void __iomem *pll_ctrl_base; struct device *dev; @@ -73,20 +78,27 @@ struct ti_pipe3 { struct clk *wkupclk; struct clk *sys_clk; struct clk *refclk; + struct pipe3_dpll_map *dpll_map; }; -struct pipe3_dpll_map { - unsigned long rate; - struct pipe3_dpll_params params; -}; - -static struct pipe3_dpll_map dpll_map[] = { +static struct pipe3_dpll_map dpll_map_usb[] = { {12000000, {1250, 5, 4, 20, 0} }, /* 12 MHz */ {16800000, {3125, 20, 4, 20, 0} }, /* 16.8 MHz */ {19200000, {1172, 8, 4, 20, 65537} }, /* 19.2 MHz */ {20000000, {1000, 7, 4, 10, 0} }, /* 20 MHz */ {26000000, {1250, 12, 4, 20, 0} }, /* 26 MHz */ {38400000, {3125, 47, 4, 20, 92843} }, /* 38.4 MHz */ + { }, /* Terminator */ +}; + +static struct pipe3_dpll_map dpll_map_sata[] = { + {12000000, {1000, 7, 4, 6, 0} }, /* 12 MHz */ + {16800000, {714, 7, 4, 6, 0} }, /* 16.8 MHz */ + {19200000, {625, 7, 4, 6, 0} }, /* 19.2 MHz */ + {20000000, {600, 7, 4, 6, 0} }, /* 20 MHz */ + {26000000, {461, 7, 4, 6, 0} }, /* 26 MHz */ + {38400000, {312, 7, 4, 6, 0} }, /* 38.4 MHz */ + { }, /* Terminator */ }; static inline u32 ti_pipe3_readl(void __iomem *addr, unsigned offset) @@ -100,15 +112,20 @@ static inline void ti_pipe3_writel(void __iomem *addr, unsigned offset, __raw_writel(data, addr + offset); } -static struct pipe3_dpll_params *ti_pipe3_get_dpll_params(unsigned long rate) +static struct pipe3_dpll_params *ti_pipe3_get_dpll_params(struct ti_pipe3 *phy) { - int i; + unsigned long rate; + struct pipe3_dpll_map *dpll_map = phy->dpll_map; - for (i = 0; i < ARRAY_SIZE(dpll_map); i++) { - if (rate == dpll_map[i].rate) - return &dpll_map[i].params; + rate = clk_get_rate(phy->sys_clk); + + for (; dpll_map->rate; dpll_map++) { + if (rate == dpll_map->rate) + return &dpll_map->params; } + dev_err(phy->dev, "No DPLL configuration for %lu Hz SYS CLK\n", rate); + return NULL; } @@ -182,16 +199,11 @@ static void ti_pipe3_dpll_relock(struct ti_pipe3 *phy) static int ti_pipe3_dpll_lock(struct ti_pipe3 *phy) { u32 val; - unsigned long rate; struct pipe3_dpll_params *dpll_params; - rate = clk_get_rate(phy->sys_clk); - dpll_params = ti_pipe3_get_dpll_params(rate); - if (!dpll_params) { - dev_err(phy->dev, - "No DPLL configuration for %lu Hz SYS CLK\n", rate); + dpll_params = ti_pipe3_get_dpll_params(phy); + if (!dpll_params) return -EINVAL; - } val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1); val &= ~PLL_REGN_MASK; @@ -244,6 +256,10 @@ static struct phy_ops ops = { .owner = THIS_MODULE, }; +#ifdef CONFIG_OF +static const struct of_device_id ti_pipe3_id_table[]; +#endif + static int ti_pipe3_probe(struct platform_device *pdev) { struct ti_pipe3 *phy; @@ -253,8 +269,10 @@ static int ti_pipe3_probe(struct platform_device *pdev) struct device_node *node = pdev->dev.of_node; struct device_node *control_node; struct platform_device *control_pdev; + const struct of_device_id *match; - if (!node) + match = of_match_device(of_match_ptr(ti_pipe3_id_table), &pdev->dev); + if (!match) return -EINVAL; phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); @@ -263,6 +281,12 @@ static int ti_pipe3_probe(struct platform_device *pdev) return -ENOMEM; } + phy->dpll_map = (struct pipe3_dpll_map *)match->data; + if (!phy->dpll_map) { + dev_err(&pdev->dev, "no DPLL data\n"); + return -EINVAL; + } + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pll_ctrl"); phy->pll_ctrl_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(phy->pll_ctrl_base)) @@ -388,8 +412,18 @@ static const struct dev_pm_ops ti_pipe3_pm_ops = { #ifdef CONFIG_OF static const struct of_device_id ti_pipe3_id_table[] = { - { .compatible = "ti,phy-usb3" }, - { .compatible = "ti,omap-usb3" }, + { + .compatible = "ti,phy-usb3", + .data = dpll_map_usb, + }, + { + .compatible = "ti,omap-usb3", + .data = dpll_map_usb, + }, + { + .compatible = "ti,phy-pipe3-sata", + .data = dpll_map_sata, + }, {} }; MODULE_DEVICE_TABLE(of, ti_pipe3_id_table); -- cgit v1.2.3-70-g09d2 From 9c7f04436088b00727fa389e2b97b182d8488123 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Thu, 6 Mar 2014 16:38:42 +0200 Subject: phy: ti-pipe3: Don't get 'wkupclk' and 'refclk' for SATA PHY SATA PHY doesn't need 'wkupclk; and 'refclk' so don't try to get them for SATA PHY. Signed-off-by: Roger Quadros Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-ti-pipe3.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index f141237131c..baa3f78d62b 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -294,16 +294,22 @@ static int ti_pipe3_probe(struct platform_device *pdev) phy->dev = &pdev->dev; - phy->wkupclk = devm_clk_get(phy->dev, "wkupclk"); - if (IS_ERR(phy->wkupclk)) { - dev_err(&pdev->dev, "unable to get wkupclk\n"); - return PTR_ERR(phy->wkupclk); - } + if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) { + + phy->wkupclk = devm_clk_get(phy->dev, "wkupclk"); + if (IS_ERR(phy->wkupclk)) { + dev_err(&pdev->dev, "unable to get wkupclk\n"); + return PTR_ERR(phy->wkupclk); + } - phy->refclk = devm_clk_get(phy->dev, "refclk"); - if (IS_ERR(phy->refclk)) { - dev_err(&pdev->dev, "unable to get refclk\n"); - return PTR_ERR(phy->refclk); + phy->refclk = devm_clk_get(phy->dev, "refclk"); + if (IS_ERR(phy->refclk)) { + dev_err(&pdev->dev, "unable to get refclk\n"); + return PTR_ERR(phy->refclk); + } + } else { + phy->wkupclk = ERR_PTR(-ENODEV); + phy->refclk = ERR_PTR(-ENODEV); } phy->sys_clk = devm_clk_get(phy->dev, "sysclk"); -- cgit v1.2.3-70-g09d2 From 629138dbaec33cecbb61fda1f5c1f047a1374993 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Thu, 6 Mar 2014 16:38:43 +0200 Subject: phy: ti-pipe3: streamline PHY operations Limit .power_on() and .power_off() to just control the PHY power and not the DPLL. The DPLL will be enabled in .init() and idled in .exit(). Don't reprogram the DPLL if it has been already locked by the bootloader. This fixes a problem with SATA, where it fails if SATA was used by the bootloader. Signed-off-by: Roger Quadros Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-ti-pipe3.c | 114 +++++++++++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 51 deletions(-) diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index baa3f78d62b..12cc900491a 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -47,7 +47,8 @@ #define PLL_SD_MASK 0x0003FC00 #define PLL_SD_SHIFT 10 #define SET_PLL_GO 0x1 -#define PLL_TICOPWDN 0x10000 +#define PLL_LDOPWDN BIT(15) +#define PLL_TICOPWDN BIT(16) #define PLL_LOCK 0x2 #define PLL_IDLE 0x1 @@ -56,7 +57,8 @@ * value required for the PIPE3PHY_PLL_CONFIGURATION2.PLL_IDLE status * to be correctly reflected in the PIPE3PHY_PLL_STATUS register. */ -# define PLL_IDLE_TIME 100; +#define PLL_IDLE_TIME 100 /* in milliseconds */ +#define PLL_LOCK_TIME 100 /* in milliseconds */ struct pipe3_dpll_params { u16 m; @@ -132,24 +134,6 @@ static struct pipe3_dpll_params *ti_pipe3_get_dpll_params(struct ti_pipe3 *phy) static int ti_pipe3_power_off(struct phy *x) { struct ti_pipe3 *phy = phy_get_drvdata(x); - int val; - int timeout = PLL_IDLE_TIME; - - val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); - val |= PLL_IDLE; - ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); - - do { - val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); - if (val & PLL_TICOPWDN) - break; - udelay(5); - } while (--timeout); - - if (!timeout) { - dev_err(phy->dev, "power off failed\n"); - return -EBUSY; - } omap_control_phy_power(phy->control_dev, 0); @@ -159,44 +143,34 @@ static int ti_pipe3_power_off(struct phy *x) static int ti_pipe3_power_on(struct phy *x) { struct ti_pipe3 *phy = phy_get_drvdata(x); - int val; - int timeout = PLL_IDLE_TIME; - - val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); - val &= ~PLL_IDLE; - ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); - do { - val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); - if (!(val & PLL_TICOPWDN)) - break; - udelay(5); - } while (--timeout); - - if (!timeout) { - dev_err(phy->dev, "power on failed\n"); - return -EBUSY; - } + omap_control_phy_power(phy->control_dev, 1); return 0; } -static void ti_pipe3_dpll_relock(struct ti_pipe3 *phy) +static int ti_pipe3_dpll_wait_lock(struct ti_pipe3 *phy) { u32 val; unsigned long timeout; - ti_pipe3_writel(phy->pll_ctrl_base, PLL_GO, SET_PLL_GO); - - timeout = jiffies + msecs_to_jiffies(20); + timeout = jiffies + msecs_to_jiffies(PLL_LOCK_TIME); do { + cpu_relax(); val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); if (val & PLL_LOCK) break; - } while (!WARN_ON(time_after(jiffies, timeout))); + } while (!time_after(jiffies, timeout)); + + if (!(val & PLL_LOCK)) { + dev_err(phy->dev, "DPLL failed to lock\n"); + return -EBUSY; + } + + return 0; } -static int ti_pipe3_dpll_lock(struct ti_pipe3 *phy) +static int ti_pipe3_dpll_program(struct ti_pipe3 *phy) { u32 val; struct pipe3_dpll_params *dpll_params; @@ -230,27 +204,65 @@ static int ti_pipe3_dpll_lock(struct ti_pipe3 *phy) val |= dpll_params->sd << PLL_SD_SHIFT; ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION3, val); - ti_pipe3_dpll_relock(phy); + ti_pipe3_writel(phy->pll_ctrl_base, PLL_GO, SET_PLL_GO); - return 0; + return ti_pipe3_dpll_wait_lock(phy); } static int ti_pipe3_init(struct phy *x) { struct ti_pipe3 *phy = phy_get_drvdata(x); - int ret; + u32 val; + int ret = 0; - ret = ti_pipe3_dpll_lock(phy); - if (ret) - return ret; + /* Bring it out of IDLE if it is IDLE */ + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); + if (val & PLL_IDLE) { + val &= ~PLL_IDLE; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); + ret = ti_pipe3_dpll_wait_lock(phy); + } - omap_control_phy_power(phy->control_dev, 1); + /* Program the DPLL only if not locked */ + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); + if (!(val & PLL_LOCK)) + if (ti_pipe3_dpll_program(phy)) + return -EINVAL; - return 0; + return ret; } +static int ti_pipe3_exit(struct phy *x) +{ + struct ti_pipe3 *phy = phy_get_drvdata(x); + u32 val; + unsigned long timeout; + + /* Put DPLL in IDLE mode */ + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); + val |= PLL_IDLE; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); + + /* wait for LDO and Oscillator to power down */ + timeout = jiffies + msecs_to_jiffies(PLL_IDLE_TIME); + do { + cpu_relax(); + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); + if ((val & PLL_TICOPWDN) && (val & PLL_LDOPWDN)) + break; + } while (!time_after(jiffies, timeout)); + + if (!(val & PLL_TICOPWDN) || !(val & PLL_LDOPWDN)) { + dev_err(phy->dev, "Failed to power down: PLL_STATUS 0x%x\n", + val); + return -EBUSY; + } + + return 0; +} static struct phy_ops ops = { .init = ti_pipe3_init, + .exit = ti_pipe3_exit, .power_on = ti_pipe3_power_on, .power_off = ti_pipe3_power_off, .owner = THIS_MODULE, -- cgit v1.2.3-70-g09d2 From 56042e4e975ce7b0817ebc024d02fa96ee01e107 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Thu, 6 Mar 2014 16:38:44 +0200 Subject: phy: ti-pipe3: Fix suspend/resume and module reload Due to Errata i783, SATA breaks if its DPLL is idled. The recommeded workaround to issue a softreset to the SATA controller doesn't seem to work. Here we just prevent SATA DPLL from Idling and hence avoid the issue altogether. Signed-off-by: Roger Quadros Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-ti-pipe3.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index 12cc900491a..59136765461 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -238,6 +238,10 @@ static int ti_pipe3_exit(struct phy *x) u32 val; unsigned long timeout; + /* SATA DPLL can't be powered down due to Errata i783 */ + if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata")) + return 0; + /* Put DPLL in IDLE mode */ val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); val |= PLL_IDLE; -- cgit v1.2.3-70-g09d2 From 02133b9e54ed19e07348ff7abf75aeb92c94d901 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Thu, 6 Mar 2014 16:38:45 +0200 Subject: phy: omap: Depend on OMAP_OCP2SCP bus driver The OMAP_USB2 and OMAP_PIPE3 PHY devices will not be detected if the OMAP_OCP2SCP bus driver is not present. Make them depend on it. Signed-off-by: Roger Quadros Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index fe8c0096f9e..2aead8b7dc6 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -47,6 +47,7 @@ config OMAP_USB2 depends on USB_PHY select GENERIC_PHY select OMAP_CONTROL_PHY + depends on OMAP_OCP2SCP help Enable this to support the transceiver that is part of SOC. This driver takes care of all the PHY functionality apart from comparator. @@ -58,6 +59,7 @@ config TI_PIPE3 depends on ARCH_OMAP2PLUS || COMPILE_TEST select GENERIC_PHY select OMAP_CONTROL_PHY + depends on OMAP_OCP2SCP help Enable this to support the PIPE3 PHY that is part of TI SOCs. This driver takes care of all the PHY functionality apart from comparator. -- cgit v1.2.3-70-g09d2 From 1068320b513e658a917e591214d174b7af9d0cf1 Mon Sep 17 00:00:00 2001 From: Loc Ho Date: Fri, 7 Mar 2014 10:28:07 -0700 Subject: Documentation: Add APM X-Gene SoC 15Gbps Multi-purpose PHY driver binding documentation This patch adds the APM X-Gene SoC 15Gbps Multi-purpose PHY driver binding documentation. Signed-off-by: Loc Ho Signed-off-by: Tuan Phan Signed-off-by: Suman Tripathi Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/apm-xgene-phy.txt | 79 ++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/apm-xgene-phy.txt diff --git a/Documentation/devicetree/bindings/phy/apm-xgene-phy.txt b/Documentation/devicetree/bindings/phy/apm-xgene-phy.txt new file mode 100644 index 00000000000..5f3a65a9dd8 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/apm-xgene-phy.txt @@ -0,0 +1,79 @@ +* APM X-Gene 15Gbps Multi-purpose PHY nodes + +PHY nodes are defined to describe on-chip 15Gbps Multi-purpose PHY. Each +PHY (pair of lanes) has its own node. + +Required properties: +- compatible : Shall be "apm,xgene-phy". +- reg : PHY memory resource is the SDS PHY access resource. +- #phy-cells : Shall be 1 as it expects one argument for setting + the mode of the PHY. Possible values are 0 (SATA), + 1 (SGMII), 2 (PCIe), 3 (USB), and 4 (XFI). + +Optional properties: +- status : Shall be "ok" if enabled or "disabled" if disabled. + Default is "ok". +- clocks : Reference to the clock entry. +- apm,tx-eye-tuning : Manual control to fine tune the capture of the serial + bit lines from the automatic calibrated position. + Two set of 3-tuple setting for each (up to 3) + supported link speed on the host. Range from 0 to + 127 in unit of one bit period. Default is 10. +- apm,tx-eye-direction : Eye tuning manual control direction. 0 means sample + data earlier than the nominal sampling point. 1 means + sample data later than the nominal sampling point. + Two set of 3-tuple setting for each (up to 3) + supported link speed on the host. Default is 0. +- apm,tx-boost-gain : Frequency boost AC (LSB 3-bit) and DC (2-bit) + gain control. Two set of 3-tuple setting for each + (up to 3) supported link speed on the host. Range is + between 0 to 31 in unit of dB. Default is 3. +- apm,tx-amplitude : Amplitude control. Two set of 3-tuple setting for + each (up to 3) supported link speed on the host. + Range is between 0 to 199500 in unit of uV. + Default is 199500 uV. +- apm,tx-pre-cursor1 : 1st pre-cursor emphasis taps control. Two set of + 3-tuple setting for each (up to 3) supported link + speed on the host. Range is 0 to 273000 in unit of + uV. Default is 0. +- apm,tx-pre-cursor2 : 2st pre-cursor emphasis taps control. Two set of + 3-tuple setting for each (up to 3) supported link + speed on the host. Range is 0 to 127400 in unit uV. + Default is 0x0. +- apm,tx-post-cursor : Post-cursor emphasis taps control. Two set of + 3-tuple setting for Gen1, Gen2, and Gen3. Range is + between 0 to 0x1f in unit of 18.2mV. Default is 0xf. +- apm,tx-speed : Tx operating speed. One set of 3-tuple for each + supported link speed on the host. + 0 = 1-2Gbps + 1 = 2-4Gbps (1st tuple default) + 2 = 4-8Gbps + 3 = 8-15Gbps (2nd tuple default) + 4 = 2.5-4Gbps + 5 = 4-5Gbps + 6 = 5-6Gbps + 7 = 6-16Gbps (3rd tuple default) + +NOTE: PHY override parameters are board specific setting. + +Example: + phy1: phy@1f21a000 { + compatible = "apm,xgene-phy"; + reg = <0x0 0x1f21a000 0x0 0x100>; + #phy-cells = <1>; + status = "disabled"; + }; + + phy2: phy@1f22a000 { + compatible = "apm,xgene-phy"; + reg = <0x0 0x1f22a000 0x0 0x100>; + #phy-cells = <1>; + status = "ok"; + }; + + phy3: phy@1f23a000 { + compatible = "apm,xgene-phy"; + reg = <0x0 0x1f23a000 0x0 0x100>; + #phy-cells = <1>; + status = "ok"; + }; -- cgit v1.2.3-70-g09d2 From 88e670fe9d240c751fd9735ae3ee2906ed68e63d Mon Sep 17 00:00:00 2001 From: Loc Ho Date: Fri, 7 Mar 2014 10:28:08 -0700 Subject: PHY: add APM X-Gene SoC 15Gbps Multi-purpose PHY driver This patch adds support for the APM X-Gene SoC 15Gbps Multi-purpose PHY. This is the physical layer interface for the corresponding host controller. Currently, only external clock and Gen3 SATA mode are supported. Signed-off-by: Loc Ho Signed-off-by: Tuan Phan Signed-off-by: Suman Tripathi Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/Kconfig | 7 + drivers/phy/Makefile | 1 + drivers/phy/phy-xgene.c | 1750 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1758 insertions(+) create mode 100644 drivers/phy/phy-xgene.c diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 2aead8b7dc6..8d3c49cc500 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -159,4 +159,11 @@ config PHY_EXYNOS5250_USB2 particular SoC is compiled in the driver. In case of Exynos 5250 four phys are available - device, host, HSIC0 and HSIC. +config PHY_XGENE + tristate "APM X-Gene 15Gbps PHY support" + depends on HAS_IOMEM && OF && (ARM64 || COMPILE_TEST) + select GENERIC_PHY + help + This option enables support for APM X-Gene SoC multi-purpose PHY. + endmenu diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 8da05a84111..2faf78edc86 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_PHY_SAMSUNG_USB2) += phy-samsung-usb2.o obj-$(CONFIG_PHY_EXYNOS4210_USB2) += phy-exynos4210-usb2.o obj-$(CONFIG_PHY_EXYNOS4X12_USB2) += phy-exynos4x12-usb2.o obj-$(CONFIG_PHY_EXYNOS5250_USB2) += phy-exynos5250-usb2.o +obj-$(CONFIG_PHY_XGENE) += phy-xgene.o diff --git a/drivers/phy/phy-xgene.c b/drivers/phy/phy-xgene.c new file mode 100644 index 00000000000..4aa1ccd1511 --- /dev/null +++ b/drivers/phy/phy-xgene.c @@ -0,0 +1,1750 @@ +/* + * AppliedMicro X-Gene Multi-purpose PHY driver + * + * Copyright (c) 2014, Applied Micro Circuits Corporation + * Author: Loc Ho + * Tuan Phan + * Suman Tripathi + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * The APM X-Gene PHY consists of two PLL clock macro's (CMU) and lanes. + * The first PLL clock macro is used for internal reference clock. The second + * PLL clock macro is used to generate the clock for the PHY. This driver + * configures the first PLL CMU, the second PLL CMU, and programs the PHY to + * operate according to the mode of operation. The first PLL CMU is only + * required if internal clock is enabled. + * + * Logical Layer Out Of HW module units: + * + * ----------------- + * | Internal | |------| + * | Ref PLL CMU |----| | ------------- --------- + * ------------ ---- | MUX |-----|PHY PLL CMU|----| Serdes| + * | | | | --------- + * External Clock ------| | ------------- + * |------| + * + * The Ref PLL CMU CSR (Configuration System Registers) is accessed + * indirectly from the SDS offset at 0x2000. It is only required for + * internal reference clock. + * The PHY PLL CMU CSR is accessed indirectly from the SDS offset at 0x0000. + * The Serdes CSR is accessed indirectly from the SDS offset at 0x0400. + * + * The Ref PLL CMU can be located within the same PHY IP or outside the PHY IP + * due to shared Ref PLL CMU. For PHY with Ref PLL CMU shared with another IP, + * it is located outside the PHY IP. This is the case for the PHY located + * at 0x1f23a000 (SATA Port 4/5). For such PHY, another resource is required + * to located the SDS/Ref PLL CMU module and its clock for that IP enabled. + * + * Currently, this driver only supports Gen3 SATA mode with external clock. + */ +#include +#include +#include +#include +#include +#include + +/* Max 2 lanes per a PHY unit */ +#define MAX_LANE 2 + +/* Register offset inside the PHY */ +#define SERDES_PLL_INDIRECT_OFFSET 0x0000 +#define SERDES_PLL_REF_INDIRECT_OFFSET 0x2000 +#define SERDES_INDIRECT_OFFSET 0x0400 +#define SERDES_LANE_STRIDE 0x0200 + +/* Some default Serdes parameters */ +#define DEFAULT_SATA_TXBOOST_GAIN { 0x1e, 0x1e, 0x1e } +#define DEFAULT_SATA_TXEYEDIRECTION { 0x0, 0x0, 0x0 } +#define DEFAULT_SATA_TXEYETUNING { 0xa, 0xa, 0xa } +#define DEFAULT_SATA_SPD_SEL { 0x1, 0x3, 0x7 } +#define DEFAULT_SATA_TXAMP { 0x8, 0x8, 0x8 } +#define DEFAULT_SATA_TXCN1 { 0x2, 0x2, 0x2 } +#define DEFAULT_SATA_TXCN2 { 0x0, 0x0, 0x0 } +#define DEFAULT_SATA_TXCP1 { 0xa, 0xa, 0xa } + +#define SATA_SPD_SEL_GEN3 0x7 +#define SATA_SPD_SEL_GEN2 0x3 +#define SATA_SPD_SEL_GEN1 0x1 + +#define SSC_DISABLE 0 +#define SSC_ENABLE 1 + +#define FBDIV_VAL_50M 0x77 +#define REFDIV_VAL_50M 0x1 +#define FBDIV_VAL_100M 0x3B +#define REFDIV_VAL_100M 0x0 + +/* SATA Clock/Reset CSR */ +#define SATACLKENREG 0x00000000 +#define SATA0_CORE_CLKEN 0x00000002 +#define SATA1_CORE_CLKEN 0x00000004 +#define SATASRESETREG 0x00000004 +#define SATA_MEM_RESET_MASK 0x00000020 +#define SATA_MEM_RESET_RD(src) (((src) & 0x00000020) >> 5) +#define SATA_SDS_RESET_MASK 0x00000004 +#define SATA_CSR_RESET_MASK 0x00000001 +#define SATA_CORE_RESET_MASK 0x00000002 +#define SATA_PMCLK_RESET_MASK 0x00000010 +#define SATA_PCLK_RESET_MASK 0x00000008 + +/* SDS CSR used for PHY Indirect access */ +#define SATA_ENET_SDS_PCS_CTL0 0x00000000 +#define REGSPEC_CFG_I_TX_WORDMODE0_SET(dst, src) \ + (((dst) & ~0x00070000) | (((u32) (src) << 16) & 0x00070000)) +#define REGSPEC_CFG_I_RX_WORDMODE0_SET(dst, src) \ + (((dst) & ~0x00e00000) | (((u32) (src) << 21) & 0x00e00000)) +#define SATA_ENET_SDS_CTL0 0x0000000c +#define REGSPEC_CFG_I_CUSTOMER_PIN_MODE0_SET(dst, src) \ + (((dst) & ~0x00007fff) | (((u32) (src)) & 0x00007fff)) +#define SATA_ENET_SDS_CTL1 0x00000010 +#define CFG_I_SPD_SEL_CDR_OVR1_SET(dst, src) \ + (((dst) & ~0x0000000f) | (((u32) (src)) & 0x0000000f)) +#define SATA_ENET_SDS_RST_CTL 0x00000024 +#define SATA_ENET_SDS_IND_CMD_REG 0x0000003c +#define CFG_IND_WR_CMD_MASK 0x00000001 +#define CFG_IND_RD_CMD_MASK 0x00000002 +#define CFG_IND_CMD_DONE_MASK 0x00000004 +#define CFG_IND_ADDR_SET(dst, src) \ + (((dst) & ~0x003ffff0) | (((u32) (src) << 4) & 0x003ffff0)) +#define SATA_ENET_SDS_IND_RDATA_REG 0x00000040 +#define SATA_ENET_SDS_IND_WDATA_REG 0x00000044 +#define SATA_ENET_CLK_MACRO_REG 0x0000004c +#define I_RESET_B_SET(dst, src) \ + (((dst) & ~0x00000001) | (((u32) (src)) & 0x00000001)) +#define I_PLL_FBDIV_SET(dst, src) \ + (((dst) & ~0x001ff000) | (((u32) (src) << 12) & 0x001ff000)) +#define I_CUSTOMEROV_SET(dst, src) \ + (((dst) & ~0x00000f80) | (((u32) (src) << 7) & 0x00000f80)) +#define O_PLL_LOCK_RD(src) (((src) & 0x40000000) >> 30) +#define O_PLL_READY_RD(src) (((src) & 0x80000000) >> 31) + +/* PLL Clock Macro Unit (CMU) CSR accessing from SDS indirectly */ +#define CMU_REG0 0x00000 +#define CMU_REG0_PLL_REF_SEL_MASK 0x00002000 +#define CMU_REG0_PLL_REF_SEL_SET(dst, src) \ + (((dst) & ~0x00002000) | (((u32) (src) << 13) & 0x00002000)) +#define CMU_REG0_PDOWN_MASK 0x00004000 +#define CMU_REG0_CAL_COUNT_RESOL_SET(dst, src) \ + (((dst) & ~0x000000e0) | (((u32) (src) << 5) & 0x000000e0)) +#define CMU_REG1 0x00002 +#define CMU_REG1_PLL_CP_SET(dst, src) \ + (((dst) & ~0x00003c00) | (((u32) (src) << 10) & 0x00003c00)) +#define CMU_REG1_PLL_MANUALCAL_SET(dst, src) \ + (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008)) +#define CMU_REG1_PLL_CP_SEL_SET(dst, src) \ + (((dst) & ~0x000003e0) | (((u32) (src) << 5) & 0x000003e0)) +#define CMU_REG1_REFCLK_CMOS_SEL_MASK 0x00000001 +#define CMU_REG1_REFCLK_CMOS_SEL_SET(dst, src) \ + (((dst) & ~0x00000001) | (((u32) (src) << 0) & 0x00000001)) +#define CMU_REG2 0x00004 +#define CMU_REG2_PLL_REFDIV_SET(dst, src) \ + (((dst) & ~0x0000c000) | (((u32) (src) << 14) & 0x0000c000)) +#define CMU_REG2_PLL_LFRES_SET(dst, src) \ + (((dst) & ~0x0000001e) | (((u32) (src) << 1) & 0x0000001e)) +#define CMU_REG2_PLL_FBDIV_SET(dst, src) \ + (((dst) & ~0x00003fe0) | (((u32) (src) << 5) & 0x00003fe0)) +#define CMU_REG3 0x00006 +#define CMU_REG3_VCOVARSEL_SET(dst, src) \ + (((dst) & ~0x0000000f) | (((u32) (src) << 0) & 0x0000000f)) +#define CMU_REG3_VCO_MOMSEL_INIT_SET(dst, src) \ + (((dst) & ~0x000003f0) | (((u32) (src) << 4) & 0x000003f0)) +#define CMU_REG3_VCO_MANMOMSEL_SET(dst, src) \ + (((dst) & ~0x0000fc00) | (((u32) (src) << 10) & 0x0000fc00)) +#define CMU_REG4 0x00008 +#define CMU_REG5 0x0000a +#define CMU_REG5_PLL_LFSMCAP_SET(dst, src) \ + (((dst) & ~0x0000c000) | (((u32) (src) << 14) & 0x0000c000)) +#define CMU_REG5_PLL_LOCK_RESOLUTION_SET(dst, src) \ + (((dst) & ~0x0000000e) | (((u32) (src) << 1) & 0x0000000e)) +#define CMU_REG5_PLL_LFCAP_SET(dst, src) \ + (((dst) & ~0x00003000) | (((u32) (src) << 12) & 0x00003000)) +#define CMU_REG5_PLL_RESETB_MASK 0x00000001 +#define CMU_REG6 0x0000c +#define CMU_REG6_PLL_VREGTRIM_SET(dst, src) \ + (((dst) & ~0x00000600) | (((u32) (src) << 9) & 0x00000600)) +#define CMU_REG6_MAN_PVT_CAL_SET(dst, src) \ + (((dst) & ~0x00000004) | (((u32) (src) << 2) & 0x00000004)) +#define CMU_REG7 0x0000e +#define CMU_REG7_PLL_CALIB_DONE_RD(src) ((0x00004000 & (u32) (src)) >> 14) +#define CMU_REG7_VCO_CAL_FAIL_RD(src) ((0x00000c00 & (u32) (src)) >> 10) +#define CMU_REG8 0x00010 +#define CMU_REG9 0x00012 +#define CMU_REG9_WORD_LEN_8BIT 0x000 +#define CMU_REG9_WORD_LEN_10BIT 0x001 +#define CMU_REG9_WORD_LEN_16BIT 0x002 +#define CMU_REG9_WORD_LEN_20BIT 0x003 +#define CMU_REG9_WORD_LEN_32BIT 0x004 +#define CMU_REG9_WORD_LEN_40BIT 0x005 +#define CMU_REG9_WORD_LEN_64BIT 0x006 +#define CMU_REG9_WORD_LEN_66BIT 0x007 +#define CMU_REG9_TX_WORD_MODE_CH1_SET(dst, src) \ + (((dst) & ~0x00000380) | (((u32) (src) << 7) & 0x00000380)) +#define CMU_REG9_TX_WORD_MODE_CH0_SET(dst, src) \ + (((dst) & ~0x00000070) | (((u32) (src) << 4) & 0x00000070)) +#define CMU_REG9_PLL_POST_DIVBY2_SET(dst, src) \ + (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008)) +#define CMU_REG9_VBG_BYPASSB_SET(dst, src) \ + (((dst) & ~0x00000004) | (((u32) (src) << 2) & 0x00000004)) +#define CMU_REG9_IGEN_BYPASS_SET(dst, src) \ + (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002)) +#define CMU_REG10 0x00014 +#define CMU_REG10_VREG_REFSEL_SET(dst, src) \ + (((dst) & ~0x00000001) | (((u32) (src) << 0) & 0x00000001)) +#define CMU_REG11 0x00016 +#define CMU_REG12 0x00018 +#define CMU_REG12_STATE_DELAY9_SET(dst, src) \ + (((dst) & ~0x000000f0) | (((u32) (src) << 4) & 0x000000f0)) +#define CMU_REG13 0x0001a +#define CMU_REG14 0x0001c +#define CMU_REG15 0x0001e +#define CMU_REG16 0x00020 +#define CMU_REG16_PVT_DN_MAN_ENA_MASK 0x00000001 +#define CMU_REG16_PVT_UP_MAN_ENA_MASK 0x00000002 +#define CMU_REG16_VCOCAL_WAIT_BTW_CODE_SET(dst, src) \ + (((dst) & ~0x0000001c) | (((u32) (src) << 2) & 0x0000001c)) +#define CMU_REG16_CALIBRATION_DONE_OVERRIDE_SET(dst, src) \ + (((dst) & ~0x00000040) | (((u32) (src) << 6) & 0x00000040)) +#define CMU_REG16_BYPASS_PLL_LOCK_SET(dst, src) \ + (((dst) & ~0x00000020) | (((u32) (src) << 5) & 0x00000020)) +#define CMU_REG17 0x00022 +#define CMU_REG17_PVT_CODE_R2A_SET(dst, src) \ + (((dst) & ~0x00007f00) | (((u32) (src) << 8) & 0x00007f00)) +#define CMU_REG17_RESERVED_7_SET(dst, src) \ + (((dst) & ~0x000000e0) | (((u32) (src) << 5) & 0x000000e0)) +#define CMU_REG17_PVT_TERM_MAN_ENA_MASK 0x00008000 +#define CMU_REG18 0x00024 +#define CMU_REG19 0x00026 +#define CMU_REG20 0x00028 +#define CMU_REG21 0x0002a +#define CMU_REG22 0x0002c +#define CMU_REG23 0x0002e +#define CMU_REG24 0x00030 +#define CMU_REG25 0x00032 +#define CMU_REG26 0x00034 +#define CMU_REG26_FORCE_PLL_LOCK_SET(dst, src) \ + (((dst) & ~0x00000001) | (((u32) (src) << 0) & 0x00000001)) +#define CMU_REG27 0x00036 +#define CMU_REG28 0x00038 +#define CMU_REG29 0x0003a +#define CMU_REG30 0x0003c +#define CMU_REG30_LOCK_COUNT_SET(dst, src) \ + (((dst) & ~0x00000006) | (((u32) (src) << 1) & 0x00000006)) +#define CMU_REG30_PCIE_MODE_SET(dst, src) \ + (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008)) +#define CMU_REG31 0x0003e +#define CMU_REG32 0x00040 +#define CMU_REG32_FORCE_VCOCAL_START_MASK 0x00004000 +#define CMU_REG32_PVT_CAL_WAIT_SEL_SET(dst, src) \ + (((dst) & ~0x00000006) | (((u32) (src) << 1) & 0x00000006)) +#define CMU_REG32_IREF_ADJ_SET(dst, src) \ + (((dst) & ~0x00000180) | (((u32) (src) << 7) & 0x00000180)) +#define CMU_REG33 0x00042 +#define CMU_REG34 0x00044 +#define CMU_REG34_VCO_CAL_VTH_LO_MAX_SET(dst, src) \ + (((dst) & ~0x0000000f) | (((u32) (src) << 0) & 0x0000000f)) +#define CMU_REG34_VCO_CAL_VTH_HI_MAX_SET(dst, src) \ + (((dst) & ~0x00000f00) | (((u32) (src) << 8) & 0x00000f00)) +#define CMU_REG34_VCO_CAL_VTH_LO_MIN_SET(dst, src) \ + (((dst) & ~0x000000f0) | (((u32) (src) << 4) & 0x000000f0)) +#define CMU_REG34_VCO_CAL_VTH_HI_MIN_SET(dst, src) \ + (((dst) & ~0x0000f000) | (((u32) (src) << 12) & 0x0000f000)) +#define CMU_REG35 0x00046 +#define CMU_REG35_PLL_SSC_MOD_SET(dst, src) \ + (((dst) & ~0x0000fe00) | (((u32) (src) << 9) & 0x0000fe00)) +#define CMU_REG36 0x00048 +#define CMU_REG36_PLL_SSC_EN_SET(dst, src) \ + (((dst) & ~0x00000010) | (((u32) (src) << 4) & 0x00000010)) +#define CMU_REG36_PLL_SSC_VSTEP_SET(dst, src) \ + (((dst) & ~0x0000ffc0) | (((u32) (src) << 6) & 0x0000ffc0)) +#define CMU_REG36_PLL_SSC_DSMSEL_SET(dst, src) \ + (((dst) & ~0x00000020) | (((u32) (src) << 5) & 0x00000020)) +#define CMU_REG37 0x0004a +#define CMU_REG38 0x0004c +#define CMU_REG39 0x0004e + +/* PHY lane CSR accessing from SDS indirectly */ +#define RXTX_REG0 0x000 +#define RXTX_REG0_CTLE_EQ_HR_SET(dst, src) \ + (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800)) +#define RXTX_REG0_CTLE_EQ_QR_SET(dst, src) \ + (((dst) & ~0x000007c0) | (((u32) (src) << 6) & 0x000007c0)) +#define RXTX_REG0_CTLE_EQ_FR_SET(dst, src) \ + (((dst) & ~0x0000003e) | (((u32) (src) << 1) & 0x0000003e)) +#define RXTX_REG1 0x002 +#define RXTX_REG1_RXACVCM_SET(dst, src) \ + (((dst) & ~0x0000f000) | (((u32) (src) << 12) & 0x0000f000)) +#define RXTX_REG1_CTLE_EQ_SET(dst, src) \ + (((dst) & ~0x00000f80) | (((u32) (src) << 7) & 0x00000f80)) +#define RXTX_REG1_RXVREG1_SET(dst, src) \ + (((dst) & ~0x00000060) | (((u32) (src) << 5) & 0x00000060)) +#define RXTX_REG1_RXIREF_ADJ_SET(dst, src) \ + (((dst) & ~0x00000006) | (((u32) (src) << 1) & 0x00000006)) +#define RXTX_REG2 0x004 +#define RXTX_REG2_VTT_ENA_SET(dst, src) \ + (((dst) & ~0x00000100) | (((u32) (src) << 8) & 0x00000100)) +#define RXTX_REG2_TX_FIFO_ENA_SET(dst, src) \ + (((dst) & ~0x00000020) | (((u32) (src) << 5) & 0x00000020)) +#define RXTX_REG2_VTT_SEL_SET(dst, src) \ + (((dst) & ~0x000000c0) | (((u32) (src) << 6) & 0x000000c0)) +#define RXTX_REG4 0x008 +#define RXTX_REG4_TX_LOOPBACK_BUF_EN_MASK 0x00000040 +#define RXTX_REG4_TX_DATA_RATE_SET(dst, src) \ + (((dst) & ~0x0000c000) | (((u32) (src) << 14) & 0x0000c000)) +#define RXTX_REG4_TX_WORD_MODE_SET(dst, src) \ + (((dst) & ~0x00003800) | (((u32) (src) << 11) & 0x00003800)) +#define RXTX_REG5 0x00a +#define RXTX_REG5_TX_CN1_SET(dst, src) \ + (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800)) +#define RXTX_REG5_TX_CP1_SET(dst, src) \ + (((dst) & ~0x000007e0) | (((u32) (src) << 5) & 0x000007e0)) +#define RXTX_REG5_TX_CN2_SET(dst, src) \ + (((dst) & ~0x0000001f) | (((u32) (src) << 0) & 0x0000001f)) +#define RXTX_REG6 0x00c +#define RXTX_REG6_TXAMP_CNTL_SET(dst, src) \ + (((dst) & ~0x00000780) | (((u32) (src) << 7) & 0x00000780)) +#define RXTX_REG6_TXAMP_ENA_SET(dst, src) \ + (((dst) & ~0x00000040) | (((u32) (src) << 6) & 0x00000040)) +#define RXTX_REG6_RX_BIST_ERRCNT_RD_SET(dst, src) \ + (((dst) & ~0x00000001) | (((u32) (src) << 0) & 0x00000001)) +#define RXTX_REG6_TX_IDLE_SET(dst, src) \ + (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008)) +#define RXTX_REG6_RX_BIST_RESYNC_SET(dst, src) \ + (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002)) +#define RXTX_REG7 0x00e +#define RXTX_REG7_RESETB_RXD_MASK 0x00000100 +#define RXTX_REG7_RESETB_RXA_MASK 0x00000080 +#define RXTX_REG7_BIST_ENA_RX_SET(dst, src) \ + (((dst) & ~0x00000040) | (((u32) (src) << 6) & 0x00000040)) +#define RXTX_REG7_RX_WORD_MODE_SET(dst, src) \ + (((dst) & ~0x00003800) | (((u32) (src) << 11) & 0x00003800)) +#define RXTX_REG8 0x010 +#define RXTX_REG8_CDR_LOOP_ENA_SET(dst, src) \ + (((dst) & ~0x00004000) | (((u32) (src) << 14) & 0x00004000)) +#define RXTX_REG8_CDR_BYPASS_RXLOS_SET(dst, src) \ + (((dst) & ~0x00000800) | (((u32) (src) << 11) & 0x00000800)) +#define RXTX_REG8_SSC_ENABLE_SET(dst, src) \ + (((dst) & ~0x00000200) | (((u32) (src) << 9) & 0x00000200)) +#define RXTX_REG8_SD_VREF_SET(dst, src) \ + (((dst) & ~0x000000f0) | (((u32) (src) << 4) & 0x000000f0)) +#define RXTX_REG8_SD_DISABLE_SET(dst, src) \ + (((dst) & ~0x00000100) | (((u32) (src) << 8) & 0x00000100)) +#define RXTX_REG7 0x00e +#define RXTX_REG7_RESETB_RXD_SET(dst, src) \ + (((dst) & ~0x00000100) | (((u32) (src) << 8) & 0x00000100)) +#define RXTX_REG7_RESETB_RXA_SET(dst, src) \ + (((dst) & ~0x00000080) | (((u32) (src) << 7) & 0x00000080)) +#define RXTX_REG7_LOOP_BACK_ENA_CTLE_MASK 0x00004000 +#define RXTX_REG7_LOOP_BACK_ENA_CTLE_SET(dst, src) \ + (((dst) & ~0x00004000) | (((u32) (src) << 14) & 0x00004000)) +#define RXTX_REG11 0x016 +#define RXTX_REG11_PHASE_ADJUST_LIMIT_SET(dst, src) \ + (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800)) +#define RXTX_REG12 0x018 +#define RXTX_REG12_LATCH_OFF_ENA_SET(dst, src) \ + (((dst) & ~0x00002000) | (((u32) (src) << 13) & 0x00002000)) +#define RXTX_REG12_SUMOS_ENABLE_SET(dst, src) \ + (((dst) & ~0x00000004) | (((u32) (src) << 2) & 0x00000004)) +#define RXTX_REG12_RX_DET_TERM_ENABLE_MASK 0x00000002 +#define RXTX_REG12_RX_DET_TERM_ENABLE_SET(dst, src) \ + (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002)) +#define RXTX_REG13 0x01a +#define RXTX_REG14 0x01c +#define RXTX_REG14_CLTE_LATCAL_MAN_PROG_SET(dst, src) \ + (((dst) & ~0x0000003f) | (((u32) (src) << 0) & 0x0000003f)) +#define RXTX_REG14_CTLE_LATCAL_MAN_ENA_SET(dst, src) \ + (((dst) & ~0x00000040) | (((u32) (src) << 6) & 0x00000040)) +#define RXTX_REG26 0x034 +#define RXTX_REG26_PERIOD_ERROR_LATCH_SET(dst, src) \ + (((dst) & ~0x00003800) | (((u32) (src) << 11) & 0x00003800)) +#define RXTX_REG26_BLWC_ENA_SET(dst, src) \ + (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008)) +#define RXTX_REG21 0x02a +#define RXTX_REG21_DO_LATCH_CALOUT_RD(src) ((0x0000fc00 & (u32) (src)) >> 10) +#define RXTX_REG21_XO_LATCH_CALOUT_RD(src) ((0x000003f0 & (u32) (src)) >> 4) +#define RXTX_REG21_LATCH_CAL_FAIL_ODD_RD(src) ((0x0000000f & (u32)(src))) +#define RXTX_REG22 0x02c +#define RXTX_REG22_SO_LATCH_CALOUT_RD(src) ((0x000003f0 & (u32) (src)) >> 4) +#define RXTX_REG22_EO_LATCH_CALOUT_RD(src) ((0x0000fc00 & (u32) (src)) >> 10) +#define RXTX_REG22_LATCH_CAL_FAIL_EVEN_RD(src) ((0x0000000f & (u32)(src))) +#define RXTX_REG23 0x02e +#define RXTX_REG23_DE_LATCH_CALOUT_RD(src) ((0x0000fc00 & (u32) (src)) >> 10) +#define RXTX_REG23_XE_LATCH_CALOUT_RD(src) ((0x000003f0 & (u32) (src)) >> 4) +#define RXTX_REG24 0x030 +#define RXTX_REG24_EE_LATCH_CALOUT_RD(src) ((0x0000fc00 & (u32) (src)) >> 10) +#define RXTX_REG24_SE_LATCH_CALOUT_RD(src) ((0x000003f0 & (u32) (src)) >> 4) +#define RXTX_REG27 0x036 +#define RXTX_REG28 0x038 +#define RXTX_REG31 0x03e +#define RXTX_REG38 0x04c +#define RXTX_REG38_CUSTOMER_PINMODE_INV_SET(dst, src) \ + (((dst) & 0x0000fffe) | (((u32) (src) << 1) & 0x0000fffe)) +#define RXTX_REG39 0x04e +#define RXTX_REG40 0x050 +#define RXTX_REG41 0x052 +#define RXTX_REG42 0x054 +#define RXTX_REG43 0x056 +#define RXTX_REG44 0x058 +#define RXTX_REG45 0x05a +#define RXTX_REG46 0x05c +#define RXTX_REG47 0x05e +#define RXTX_REG48 0x060 +#define RXTX_REG49 0x062 +#define RXTX_REG50 0x064 +#define RXTX_REG51 0x066 +#define RXTX_REG52 0x068 +#define RXTX_REG53 0x06a +#define RXTX_REG54 0x06c +#define RXTX_REG55 0x06e +#define RXTX_REG61 0x07a +#define RXTX_REG61_ISCAN_INBERT_SET(dst, src) \ + (((dst) & ~0x00000010) | (((u32) (src) << 4) & 0x00000010)) +#define RXTX_REG61_LOADFREQ_SHIFT_SET(dst, src) \ + (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008)) +#define RXTX_REG61_EYE_COUNT_WIDTH_SEL_SET(dst, src) \ + (((dst) & ~0x000000c0) | (((u32) (src) << 6) & 0x000000c0)) +#define RXTX_REG61_SPD_SEL_CDR_SET(dst, src) \ + (((dst) & ~0x00003c00) | (((u32) (src) << 10) & 0x00003c00)) +#define RXTX_REG62 0x07c +#define RXTX_REG62_PERIOD_H1_QLATCH_SET(dst, src) \ + (((dst) & ~0x00003800) | (((u32) (src) << 11) & 0x00003800)) +#define RXTX_REG81 0x0a2 +#define RXTX_REG89_MU_TH7_SET(dst, src) \ + (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800)) +#define RXTX_REG89_MU_TH8_SET(dst, src) \ + (((dst) & ~0x000007c0) | (((u32) (src) << 6) & 0x000007c0)) +#define RXTX_REG89_MU_TH9_SET(dst, src) \ + (((dst) & ~0x0000003e) | (((u32) (src) << 1) & 0x0000003e)) +#define RXTX_REG96 0x0c0 +#define RXTX_REG96_MU_FREQ1_SET(dst, src) \ + (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800)) +#define RXTX_REG96_MU_FREQ2_SET(dst, src) \ + (((dst) & ~0x000007c0) | (((u32) (src) << 6) & 0x000007c0)) +#define RXTX_REG96_MU_FREQ3_SET(dst, src) \ + (((dst) & ~0x0000003e) | (((u32) (src) << 1) & 0x0000003e)) +#define RXTX_REG99 0x0c6 +#define RXTX_REG99_MU_PHASE1_SET(dst, src) \ + (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800)) +#define RXTX_REG99_MU_PHASE2_SET(dst, src) \ + (((dst) & ~0x000007c0) | (((u32) (src) << 6) & 0x000007c0)) +#define RXTX_REG99_MU_PHASE3_SET(dst, src) \ + (((dst) & ~0x0000003e) | (((u32) (src) << 1) & 0x0000003e)) +#define RXTX_REG102 0x0cc +#define RXTX_REG102_FREQLOOP_LIMIT_SET(dst, src) \ + (((dst) & ~0x00000060) | (((u32) (src) << 5) & 0x00000060)) +#define RXTX_REG114 0x0e4 +#define RXTX_REG121 0x0f2 +#define RXTX_REG121_SUMOS_CAL_CODE_RD(src) ((0x0000003e & (u32)(src)) >> 0x1) +#define RXTX_REG125 0x0fa +#define RXTX_REG125_PQ_REG_SET(dst, src) \ + (((dst) & ~0x0000fe00) | (((u32) (src) << 9) & 0x0000fe00)) +#define RXTX_REG125_SIGN_PQ_SET(dst, src) \ + (((dst) & ~0x00000100) | (((u32) (src) << 8) & 0x00000100)) +#define RXTX_REG125_SIGN_PQ_2C_SET(dst, src) \ + (((dst) & ~0x00000080) | (((u32) (src) << 7) & 0x00000080)) +#define RXTX_REG125_PHZ_MANUALCODE_SET(dst, src) \ + (((dst) & ~0x0000007c) | (((u32) (src) << 2) & 0x0000007c)) +#define RXTX_REG125_PHZ_MANUAL_SET(dst, src) \ + (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002)) +#define RXTX_REG127 0x0fe +#define RXTX_REG127_FORCE_SUM_CAL_START_MASK 0x00000002 +#define RXTX_REG127_FORCE_LAT_CAL_START_MASK 0x00000004 +#define RXTX_REG127_FORCE_SUM_CAL_START_SET(dst, src) \ + (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002)) +#define RXTX_REG127_FORCE_LAT_CAL_START_SET(dst, src) \ + (((dst) & ~0x00000004) | (((u32) (src) << 2) & 0x00000004)) +#define RXTX_REG127_LATCH_MAN_CAL_ENA_SET(dst, src) \ + (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008)) +#define RXTX_REG127_DO_LATCH_MANCAL_SET(dst, src) \ + (((dst) & ~0x0000fc00) | (((u32) (src) << 10) & 0x0000fc00)) +#define RXTX_REG127_XO_LATCH_MANCAL_SET(dst, src) \ + (((dst) & ~0x000003f0) | (((u32) (src) << 4) & 0x000003f0)) +#define RXTX_REG128 0x100 +#define RXTX_REG128_LATCH_CAL_WAIT_SEL_SET(dst, src) \ + (((dst) & ~0x0000000c) | (((u32) (src) << 2) & 0x0000000c)) +#define RXTX_REG128_EO_LATCH_MANCAL_SET(dst, src) \ + (((dst) & ~0x0000fc00) | (((u32) (src) << 10) & 0x0000fc00)) +#define RXTX_REG128_SO_LATCH_MANCAL_SET(dst, src) \ + (((dst) & ~0x000003f0) | (((u32) (src) << 4) & 0x000003f0)) +#define RXTX_REG129 0x102 +#define RXTX_REG129_DE_LATCH_MANCAL_SET(dst, src) \ + (((dst) & ~0x0000fc00) | (((u32) (src) << 10) & 0x0000fc00)) +#define RXTX_REG129_XE_LATCH_MANCAL_SET(dst, src) \ + (((dst) & ~0x000003f0) | (((u32) (src) << 4) & 0x000003f0)) +#define RXTX_REG130 0x104 +#define RXTX_REG130_EE_LATCH_MANCAL_SET(dst, src) \ + (((dst) & ~0x0000fc00) | (((u32) (src) << 10) & 0x0000fc00)) +#define RXTX_REG130_SE_LATCH_MANCAL_SET(dst, src) \ + (((dst) & ~0x000003f0) | (((u32) (src) << 4) & 0x000003f0)) +#define RXTX_REG145 0x122 +#define RXTX_REG145_TX_IDLE_SATA_SET(dst, src) \ + (((dst) & ~0x00000001) | (((u32) (src) << 0) & 0x00000001)) +#define RXTX_REG145_RXES_ENA_SET(dst, src) \ + (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002)) +#define RXTX_REG145_RXDFE_CONFIG_SET(dst, src) \ + (((dst) & ~0x0000c000) | (((u32) (src) << 14) & 0x0000c000)) +#define RXTX_REG145_RXVWES_LATENA_SET(dst, src) \ + (((dst) & ~0x00000004) | (((u32) (src) << 2) & 0x00000004)) +#define RXTX_REG147 0x126 +#define RXTX_REG148 0x128 + +/* Clock macro type */ +enum cmu_type_t { + REF_CMU = 0, /* Clock macro is the internal reference clock */ + PHY_CMU = 1, /* Clock macro is the PLL for the Serdes */ +}; + +enum mux_type_t { + MUX_SELECT_ATA = 0, /* Switch the MUX to ATA */ + MUX_SELECT_SGMMII = 0, /* Switch the MUX to SGMII */ +}; + +enum clk_type_t { + CLK_EXT_DIFF = 0, /* External differential */ + CLK_INT_DIFF = 1, /* Internal differential */ + CLK_INT_SING = 2, /* Internal single ended */ +}; + +enum phy_mode { + MODE_SATA = 0, /* List them for simple reference */ + MODE_SGMII = 1, + MODE_PCIE = 2, + MODE_USB = 3, + MODE_XFI = 4, + MODE_MAX +}; + +struct xgene_sata_override_param { + u32 speed[MAX_LANE]; /* Index for override parameter per lane */ + u32 txspeed[3]; /* Tx speed */ + u32 txboostgain[MAX_LANE*3]; /* Tx freq boost and gain control */ + u32 txeyetuning[MAX_LANE*3]; /* Tx eye tuning */ + u32 txeyedirection[MAX_LANE*3]; /* Tx eye tuning direction */ + u32 txamplitude[MAX_LANE*3]; /* Tx amplitude control */ + u32 txprecursor_cn1[MAX_LANE*3]; /* Tx emphasis taps 1st pre-cursor */ + u32 txprecursor_cn2[MAX_LANE*3]; /* Tx emphasis taps 2nd pre-cursor */ + u32 txpostcursor_cp1[MAX_LANE*3]; /* Tx emphasis taps post-cursor */ +}; + +struct xgene_phy_ctx { + struct device *dev; + struct phy *phy; + enum phy_mode mode; /* Mode of operation */ + enum clk_type_t clk_type; /* Input clock selection */ + void __iomem *sds_base; /* PHY CSR base addr */ + struct clk *clk; /* Optional clock */ + + /* Override Serdes parameters */ + struct xgene_sata_override_param sata_param; +}; + +/* + * For chip earlier than A3 version, enable this flag. + * To enable, pass boot argument phy_xgene.preA3Chip=1 + */ +static int preA3Chip; +MODULE_PARM_DESC(preA3Chip, "Enable pre-A3 chip support (1=enable 0=disable)"); +module_param_named(preA3Chip, preA3Chip, int, 0444); + +static void sds_wr(void __iomem *csr_base, u32 indirect_cmd_reg, + u32 indirect_data_reg, u32 addr, u32 data) +{ + unsigned long deadline = jiffies + HZ; + u32 val; + u32 cmd; + + cmd = CFG_IND_WR_CMD_MASK | CFG_IND_CMD_DONE_MASK; + cmd = CFG_IND_ADDR_SET(cmd, addr); + writel(data, csr_base + indirect_data_reg); + readl(csr_base + indirect_data_reg); /* Force a barrier */ + writel(cmd, csr_base + indirect_cmd_reg); + readl(csr_base + indirect_cmd_reg); /* Force a barrier */ + do { + val = readl(csr_base + indirect_cmd_reg); + } while (!(val & CFG_IND_CMD_DONE_MASK) && + time_before(jiffies, deadline)); + if (!(val & CFG_IND_CMD_DONE_MASK)) + pr_err("SDS WR timeout at 0x%p offset 0x%08X value 0x%08X\n", + csr_base + indirect_cmd_reg, addr, data); +} + +static void sds_rd(void __iomem *csr_base, u32 indirect_cmd_reg, + u32 indirect_data_reg, u32 addr, u32 *data) +{ + unsigned long deadline = jiffies + HZ; + u32 val; + u32 cmd; + + cmd = CFG_IND_RD_CMD_MASK | CFG_IND_CMD_DONE_MASK; + cmd = CFG_IND_ADDR_SET(cmd, addr); + writel(cmd, csr_base + indirect_cmd_reg); + readl(csr_base + indirect_cmd_reg); /* Force a barrier */ + do { + val = readl(csr_base + indirect_cmd_reg); + } while (!(val & CFG_IND_CMD_DONE_MASK) && + time_before(jiffies, deadline)); + *data = readl(csr_base + indirect_data_reg); + if (!(val & CFG_IND_CMD_DONE_MASK)) + pr_err("SDS WR timeout at 0x%p offset 0x%08X value 0x%08X\n", + csr_base + indirect_cmd_reg, addr, *data); +} + +static void cmu_wr(struct xgene_phy_ctx *ctx, enum cmu_type_t cmu_type, + u32 reg, u32 data) +{ + void __iomem *sds_base = ctx->sds_base; + u32 val; + + if (cmu_type == REF_CMU) + reg += SERDES_PLL_REF_INDIRECT_OFFSET; + else + reg += SERDES_PLL_INDIRECT_OFFSET; + sds_wr(sds_base, SATA_ENET_SDS_IND_CMD_REG, + SATA_ENET_SDS_IND_WDATA_REG, reg, data); + sds_rd(sds_base, SATA_ENET_SDS_IND_CMD_REG, + SATA_ENET_SDS_IND_RDATA_REG, reg, &val); + pr_debug("CMU WR addr 0x%X value 0x%08X <-> 0x%08X\n", reg, data, val); +} + +static void cmu_rd(struct xgene_phy_ctx *ctx, enum cmu_type_t cmu_type, + u32 reg, u32 *data) +{ + void __iomem *sds_base = ctx->sds_base; + + if (cmu_type == REF_CMU) + reg += SERDES_PLL_REF_INDIRECT_OFFSET; + else + reg += SERDES_PLL_INDIRECT_OFFSET; + sds_rd(sds_base, SATA_ENET_SDS_IND_CMD_REG, + SATA_ENET_SDS_IND_RDATA_REG, reg, data); + pr_debug("CMU RD addr 0x%X value 0x%08X\n", reg, *data); +} + +static void cmu_toggle1to0(struct xgene_phy_ctx *ctx, enum cmu_type_t cmu_type, + u32 reg, u32 bits) +{ + u32 val; + + cmu_rd(ctx, cmu_type, reg, &val); + val |= bits; + cmu_wr(ctx, cmu_type, reg, val); + cmu_rd(ctx, cmu_type, reg, &val); + val &= ~bits; + cmu_wr(ctx, cmu_type, reg, val); +} + +static void cmu_clrbits(struct xgene_phy_ctx *ctx, enum cmu_type_t cmu_type, + u32 reg, u32 bits) +{ + u32 val; + + cmu_rd(ctx, cmu_type, reg, &val); + val &= ~bits; + cmu_wr(ctx, cmu_type, reg, val); +} + +static void cmu_setbits(struct xgene_phy_ctx *ctx, enum cmu_type_t cmu_type, + u32 reg, u32 bits) +{ + u32 val; + + cmu_rd(ctx, cmu_type, reg, &val); + val |= bits; + cmu_wr(ctx, cmu_type, reg, val); +} + +static void serdes_wr(struct xgene_phy_ctx *ctx, int lane, u32 reg, u32 data) +{ + void __iomem *sds_base = ctx->sds_base; + u32 val; + + reg += SERDES_INDIRECT_OFFSET; + reg += lane * SERDES_LANE_STRIDE; + sds_wr(sds_base, SATA_ENET_SDS_IND_CMD_REG, + SATA_ENET_SDS_IND_WDATA_REG, reg, data); + sds_rd(sds_base, SATA_ENET_SDS_IND_CMD_REG, + SATA_ENET_SDS_IND_RDATA_REG, reg, &val); + pr_debug("SERDES WR addr 0x%X value 0x%08X <-> 0x%08X\n", reg, data, + val); +} + +static void serdes_rd(struct xgene_phy_ctx *ctx, int lane, u32 reg, u32 *data) +{ + void __iomem *sds_base = ctx->sds_base; + + reg += SERDES_INDIRECT_OFFSET; + reg += lane * SERDES_LANE_STRIDE; + sds_rd(sds_base, SATA_ENET_SDS_IND_CMD_REG, + SATA_ENET_SDS_IND_RDATA_REG, reg, data); + pr_debug("SERDES RD addr 0x%X value 0x%08X\n", reg, *data); +} + +static void serdes_clrbits(struct xgene_phy_ctx *ctx, int lane, u32 reg, + u32 bits) +{ + u32 val; + + serdes_rd(ctx, lane, reg, &val); + val &= ~bits; + serdes_wr(ctx, lane, reg, val); +} + +static void serdes_setbits(struct xgene_phy_ctx *ctx, int lane, u32 reg, + u32 bits) +{ + u32 val; + + serdes_rd(ctx, lane, reg, &val); + val |= bits; + serdes_wr(ctx, lane, reg, val); +} + +static void xgene_phy_cfg_cmu_clk_type(struct xgene_phy_ctx *ctx, + enum cmu_type_t cmu_type, + enum clk_type_t clk_type) +{ + u32 val; + + /* Set the reset sequence delay for TX ready assertion */ + cmu_rd(ctx, cmu_type, CMU_REG12, &val); + val = CMU_REG12_STATE_DELAY9_SET(val, 0x1); + cmu_wr(ctx, cmu_type, CMU_REG12, val); + /* Set the programmable stage delays between various enable stages */ + cmu_wr(ctx, cmu_type, CMU_REG13, 0x0222); + cmu_wr(ctx, cmu_type, CMU_REG14, 0x2225); + + /* Configure clock type */ + if (clk_type == CLK_EXT_DIFF) { + /* Select external clock mux */ + cmu_rd(ctx, cmu_type, CMU_REG0, &val); + val = CMU_REG0_PLL_REF_SEL_SET(val, 0x0); + cmu_wr(ctx, cmu_type, CMU_REG0, val); + /* Select CMOS as reference clock */ + cmu_rd(ctx, cmu_type, CMU_REG1, &val); + val = CMU_REG1_REFCLK_CMOS_SEL_SET(val, 0x0); + cmu_wr(ctx, cmu_type, CMU_REG1, val); + dev_dbg(ctx->dev, "Set external reference clock\n"); + } else if (clk_type == CLK_INT_DIFF) { + /* Select internal clock mux */ + cmu_rd(ctx, cmu_type, CMU_REG0, &val); + val = CMU_REG0_PLL_REF_SEL_SET(val, 0x1); + cmu_wr(ctx, cmu_type, CMU_REG0, val); + /* Select CMOS as reference clock */ + cmu_rd(ctx, cmu_type, CMU_REG1, &val); + val = CMU_REG1_REFCLK_CMOS_SEL_SET(val, 0x1); + cmu_wr(ctx, cmu_type, CMU_REG1, val); + dev_dbg(ctx->dev, "Set internal reference clock\n"); + } else if (clk_type == CLK_INT_SING) { + /* + * NOTE: This clock type is NOT support for controller + * whose internal clock shared in the PCIe controller + * + * Select internal clock mux + */ + cmu_rd(ctx, cmu_type, CMU_REG1, &val); + val = CMU_REG1_REFCLK_CMOS_SEL_SET(val, 0x1); + cmu_wr(ctx, cmu_type, CMU_REG1, val); + /* Select CML as reference clock */ + cmu_rd(ctx, cmu_type, CMU_REG1, &val); + val = CMU_REG1_REFCLK_CMOS_SEL_SET(val, 0x0); + cmu_wr(ctx, cmu_type, CMU_REG1, val); + dev_dbg(ctx->dev, + "Set internal single ended reference clock\n"); + } +} + +static void xgene_phy_sata_cfg_cmu_core(struct xgene_phy_ctx *ctx, + enum cmu_type_t cmu_type, + enum clk_type_t clk_type) +{ + u32 val; + int ref_100MHz; + + if (cmu_type == REF_CMU) { + /* Set VCO calibration voltage threshold */ + cmu_rd(ctx, cmu_type, CMU_REG34, &val); + val = CMU_REG34_VCO_CAL_VTH_LO_MAX_SET(val, 0x7); + val = CMU_REG34_VCO_CAL_VTH_HI_MAX_SET(val, 0xc); + val = CMU_REG34_VCO_CAL_VTH_LO_MIN_SET(val, 0x3); + val = CMU_REG34_VCO_CAL_VTH_HI_MIN_SET(val, 0x8); + cmu_wr(ctx, cmu_type, CMU_REG34, val); + } + + /* Set the VCO calibration counter */ + cmu_rd(ctx, cmu_type, CMU_REG0, &val); + if (cmu_type == REF_CMU || preA3Chip) + val = CMU_REG0_CAL_COUNT_RESOL_SET(val, 0x4); + else + val = CMU_REG0_CAL_COUNT_RESOL_SET(val, 0x7); + cmu_wr(ctx, cmu_type, CMU_REG0, val); + + /* Configure PLL for calibration */ + cmu_rd(ctx, cmu_type, CMU_REG1, &val); + val = CMU_REG1_PLL_CP_SET(val, 0x1); + if (cmu_type == REF_CMU || preA3Chip) + val = CMU_REG1_PLL_CP_SEL_SET(val, 0x5); + else + val = CMU_REG1_PLL_CP_SEL_SET(val, 0x3); + if (cmu_type == REF_CMU) + val = CMU_REG1_PLL_MANUALCAL_SET(val, 0x0); + else + val = CMU_REG1_PLL_MANUALCAL_SET(val, 0x1); + cmu_wr(ctx, cmu_type, CMU_REG1, val); + + if (cmu_type != REF_CMU) + cmu_clrbits(ctx, cmu_type, CMU_REG5, CMU_REG5_PLL_RESETB_MASK); + + /* Configure the PLL for either 100MHz or 50MHz */ + cmu_rd(ctx, cmu_type, CMU_REG2, &val); + if (cmu_type == REF_CMU) { + val = CMU_REG2_PLL_LFRES_SET(val, 0xa); + ref_100MHz = 1; + } else { + val = CMU_REG2_PLL_LFRES_SET(val, 0x3); + if (clk_type == CLK_EXT_DIFF) + ref_100MHz = 0; + else + ref_100MHz = 1; + } + if (ref_100MHz) { + val = CMU_REG2_PLL_FBDIV_SET(val, FBDIV_VAL_100M); + val = CMU_REG2_PLL_REFDIV_SET(val, REFDIV_VAL_100M); + } else { + val = CMU_REG2_PLL_FBDIV_SET(val, FBDIV_VAL_50M); + val = CMU_REG2_PLL_REFDIV_SET(val, REFDIV_VAL_50M); + } + cmu_wr(ctx, cmu_type, CMU_REG2, val); + + /* Configure the VCO */ + cmu_rd(ctx, cmu_type, CMU_REG3, &val); + if (cmu_type == REF_CMU) { + val = CMU_REG3_VCOVARSEL_SET(val, 0x3); + val = CMU_REG3_VCO_MOMSEL_INIT_SET(val, 0x10); + } else { + val = CMU_REG3_VCOVARSEL_SET(val, 0xF); + if (preA3Chip) + val = CMU_REG3_VCO_MOMSEL_INIT_SET(val, 0x15); + else + val = CMU_REG3_VCO_MOMSEL_INIT_SET(val, 0x1a); + val = CMU_REG3_VCO_MANMOMSEL_SET(val, 0x15); + } + cmu_wr(ctx, cmu_type, CMU_REG3, val); + + /* Disable force PLL lock */ + cmu_rd(ctx, cmu_type, CMU_REG26, &val); + val = CMU_REG26_FORCE_PLL_LOCK_SET(val, 0x0); + cmu_wr(ctx, cmu_type, CMU_REG26, val); + + /* Setup PLL loop filter */ + cmu_rd(ctx, cmu_type, CMU_REG5, &val); + val = CMU_REG5_PLL_LFSMCAP_SET(val, 0x3); + val = CMU_REG5_PLL_LFCAP_SET(val, 0x3); + if (cmu_type == REF_CMU || !preA3Chip) + val = CMU_REG5_PLL_LOCK_RESOLUTION_SET(val, 0x7); + else + val = CMU_REG5_PLL_LOCK_RESOLUTION_SET(val, 0x4); + cmu_wr(ctx, cmu_type, CMU_REG5, val); + + /* Enable or disable manual calibration */ + cmu_rd(ctx, cmu_type, CMU_REG6, &val); + val = CMU_REG6_PLL_VREGTRIM_SET(val, preA3Chip ? 0x0 : 0x2); + val = CMU_REG6_MAN_PVT_CAL_SET(val, preA3Chip ? 0x1 : 0x0); + cmu_wr(ctx, cmu_type, CMU_REG6, val); + + /* Configure lane for 20-bits */ + if (cmu_type == PHY_CMU) { + cmu_rd(ctx, cmu_type, CMU_REG9, &val); + val = CMU_REG9_TX_WORD_MODE_CH1_SET(val, + CMU_REG9_WORD_LEN_20BIT); + val = CMU_REG9_TX_WORD_MODE_CH0_SET(val, + CMU_REG9_WORD_LEN_20BIT); + val = CMU_REG9_PLL_POST_DIVBY2_SET(val, 0x1); + if (!preA3Chip) { + val = CMU_REG9_VBG_BYPASSB_SET(val, 0x0); + val = CMU_REG9_IGEN_BYPASS_SET(val , 0x0); + } + cmu_wr(ctx, cmu_type, CMU_REG9, val); + + if (!preA3Chip) { + cmu_rd(ctx, cmu_type, CMU_REG10, &val); + val = CMU_REG10_VREG_REFSEL_SET(val, 0x1); + cmu_wr(ctx, cmu_type, CMU_REG10, val); + } + } + + cmu_rd(ctx, cmu_type, CMU_REG16, &val); + val = CMU_REG16_CALIBRATION_DONE_OVERRIDE_SET(val, 0x1); + val = CMU_REG16_BYPASS_PLL_LOCK_SET(val, 0x1); + if (cmu_type == REF_CMU || preA3Chip) + val = CMU_REG16_VCOCAL_WAIT_BTW_CODE_SET(val, 0x4); + else + val = CMU_REG16_VCOCAL_WAIT_BTW_CODE_SET(val, 0x7); + cmu_wr(ctx, cmu_type, CMU_REG16, val); + + /* Configure for SATA */ + cmu_rd(ctx, cmu_type, CMU_REG30, &val); + val = CMU_REG30_PCIE_MODE_SET(val, 0x0); + val = CMU_REG30_LOCK_COUNT_SET(val, 0x3); + cmu_wr(ctx, cmu_type, CMU_REG30, val); + + /* Disable state machine bypass */ + cmu_wr(ctx, cmu_type, CMU_REG31, 0xF); + + cmu_rd(ctx, cmu_type, CMU_REG32, &val); + val = CMU_REG32_PVT_CAL_WAIT_SEL_SET(val, 0x3); + if (cmu_type == REF_CMU || preA3Chip) + val = CMU_REG32_IREF_ADJ_SET(val, 0x3); + else + val = CMU_REG32_IREF_ADJ_SET(val, 0x1); + cmu_wr(ctx, cmu_type, CMU_REG32, val); + + /* Set VCO calibration threshold */ + if (cmu_type != REF_CMU && preA3Chip) + cmu_wr(ctx, cmu_type, CMU_REG34, 0x8d27); + else + cmu_wr(ctx, cmu_type, CMU_REG34, 0x873c); + + /* Set CTLE Override and override waiting from state machine */ + cmu_wr(ctx, cmu_type, CMU_REG37, 0xF00F); +} + +static void xgene_phy_ssc_enable(struct xgene_phy_ctx *ctx, + enum cmu_type_t cmu_type) +{ + u32 val; + + /* Set SSC modulation value */ + cmu_rd(ctx, cmu_type, CMU_REG35, &val); + val = CMU_REG35_PLL_SSC_MOD_SET(val, 98); + cmu_wr(ctx, cmu_type, CMU_REG35, val); + + /* Enable SSC, set vertical step and DSM value */ + cmu_rd(ctx, cmu_type, CMU_REG36, &val); + val = CMU_REG36_PLL_SSC_VSTEP_SET(val, 30); + val = CMU_REG36_PLL_SSC_EN_SET(val, 1); + val = CMU_REG36_PLL_SSC_DSMSEL_SET(val, 1); + cmu_wr(ctx, cmu_type, CMU_REG36, val); + + /* Reset the PLL */ + cmu_clrbits(ctx, cmu_type, CMU_REG5, CMU_REG5_PLL_RESETB_MASK); + cmu_setbits(ctx, cmu_type, CMU_REG5, CMU_REG5_PLL_RESETB_MASK); + + /* Force VCO calibration to restart */ + cmu_toggle1to0(ctx, cmu_type, CMU_REG32, + CMU_REG32_FORCE_VCOCAL_START_MASK); +} + +static void xgene_phy_sata_cfg_lanes(struct xgene_phy_ctx *ctx) +{ + u32 val; + u32 reg; + int i; + int lane; + + for (lane = 0; lane < MAX_LANE; lane++) { + serdes_wr(ctx, lane, RXTX_REG147, 0x6); + + /* Set boost control for quarter, half, and full rate */ + serdes_rd(ctx, lane, RXTX_REG0, &val); + val = RXTX_REG0_CTLE_EQ_HR_SET(val, 0x10); + val = RXTX_REG0_CTLE_EQ_QR_SET(val, 0x10); + val = RXTX_REG0_CTLE_EQ_FR_SET(val, 0x10); + serdes_wr(ctx, lane, RXTX_REG0, val); + + /* Set boost control value */ + serdes_rd(ctx, lane, RXTX_REG1, &val); + val = RXTX_REG1_RXACVCM_SET(val, 0x7); + val = RXTX_REG1_CTLE_EQ_SET(val, + ctx->sata_param.txboostgain[lane * 3 + + ctx->sata_param.speed[lane]]); + serdes_wr(ctx, lane, RXTX_REG1, val); + + /* Latch VTT value based on the termination to ground and + enable TX FIFO */ + serdes_rd(ctx, lane, RXTX_REG2, &val); + val = RXTX_REG2_VTT_ENA_SET(val, 0x1); + val = RXTX_REG2_VTT_SEL_SET(val, 0x1); + val = RXTX_REG2_TX_FIFO_ENA_SET(val, 0x1); + serdes_wr(ctx, lane, RXTX_REG2, val); + + /* Configure Tx for 20-bits */ + serdes_rd(ctx, lane, RXTX_REG4, &val); + val = RXTX_REG4_TX_WORD_MODE_SET(val, CMU_REG9_WORD_LEN_20BIT); + serdes_wr(ctx, lane, RXTX_REG4, val); + + if (!preA3Chip) { + serdes_rd(ctx, lane, RXTX_REG1, &val); + val = RXTX_REG1_RXVREG1_SET(val, 0x2); + val = RXTX_REG1_RXIREF_ADJ_SET(val, 0x2); + serdes_wr(ctx, lane, RXTX_REG1, val); + } + + /* Set pre-emphasis first 1 and 2, and post-emphasis values */ + serdes_rd(ctx, lane, RXTX_REG5, &val); + val = RXTX_REG5_TX_CN1_SET(val, + ctx->sata_param.txprecursor_cn1[lane * 3 + + ctx->sata_param.speed[lane]]); + val = RXTX_REG5_TX_CP1_SET(val, + ctx->sata_param.txpostcursor_cp1[lane * 3 + + ctx->sata_param.speed[lane]]); + val = RXTX_REG5_TX_CN2_SET(val, + ctx->sata_param.txprecursor_cn2[lane * 3 + + ctx->sata_param.speed[lane]]); + serdes_wr(ctx, lane, RXTX_REG5, val); + + /* Set TX amplitude value */ + serdes_rd(ctx, lane, RXTX_REG6, &val); + val = RXTX_REG6_TXAMP_CNTL_SET(val, + ctx->sata_param.txamplitude[lane * 3 + + ctx->sata_param.speed[lane]]); + val = RXTX_REG6_TXAMP_ENA_SET(val, 0x1); + val = RXTX_REG6_TX_IDLE_SET(val, 0x0); + val = RXTX_REG6_RX_BIST_RESYNC_SET(val, 0x0); + val = RXTX_REG6_RX_BIST_ERRCNT_RD_SET(val, 0x0); + serdes_wr(ctx, lane, RXTX_REG6, val); + + /* Configure Rx for 20-bits */ + serdes_rd(ctx, lane, RXTX_REG7, &val); + val = RXTX_REG7_BIST_ENA_RX_SET(val, 0x0); + val = RXTX_REG7_RX_WORD_MODE_SET(val, CMU_REG9_WORD_LEN_20BIT); + serdes_wr(ctx, lane, RXTX_REG7, val); + + /* Set CDR and LOS values and enable Rx SSC */ + serdes_rd(ctx, lane, RXTX_REG8, &val); + val = RXTX_REG8_CDR_LOOP_ENA_SET(val, 0x1); + val = RXTX_REG8_CDR_BYPASS_RXLOS_SET(val, 0x0); + val = RXTX_REG8_SSC_ENABLE_SET(val, 0x1); + val = RXTX_REG8_SD_DISABLE_SET(val, 0x0); + val = RXTX_REG8_SD_VREF_SET(val, 0x4); + serdes_wr(ctx, lane, RXTX_REG8, val); + + /* Set phase adjust upper/lower limits */ + serdes_rd(ctx, lane, RXTX_REG11, &val); + val = RXTX_REG11_PHASE_ADJUST_LIMIT_SET(val, 0x0); + serdes_wr(ctx, lane, RXTX_REG11, val); + + /* Enable Latch Off; disable SUMOS and Tx termination */ + serdes_rd(ctx, lane, RXTX_REG12, &val); + val = RXTX_REG12_LATCH_OFF_ENA_SET(val, 0x1); + val = RXTX_REG12_SUMOS_ENABLE_SET(val, 0x0); + val = RXTX_REG12_RX_DET_TERM_ENABLE_SET(val, 0x0); + serdes_wr(ctx, lane, RXTX_REG12, val); + + /* Set period error latch to 512T and enable BWL */ + serdes_rd(ctx, lane, RXTX_REG26, &val); + val = RXTX_REG26_PERIOD_ERROR_LATCH_SET(val, 0x0); + val = RXTX_REG26_BLWC_ENA_SET(val, 0x1); + serdes_wr(ctx, lane, RXTX_REG26, val); + + serdes_wr(ctx, lane, RXTX_REG28, 0x0); + + /* Set DFE loop preset value */ + serdes_wr(ctx, lane, RXTX_REG31, 0x0); + + /* Set Eye Monitor counter width to 12-bit */ + serdes_rd(ctx, lane, RXTX_REG61, &val); + val = RXTX_REG61_ISCAN_INBERT_SET(val, 0x1); + val = RXTX_REG61_LOADFREQ_SHIFT_SET(val, 0x0); + val = RXTX_REG61_EYE_COUNT_WIDTH_SEL_SET(val, 0x0); + serdes_wr(ctx, lane, RXTX_REG61, val); + + serdes_rd(ctx, lane, RXTX_REG62, &val); + val = RXTX_REG62_PERIOD_H1_QLATCH_SET(val, 0x0); + serdes_wr(ctx, lane, RXTX_REG62, val); + + /* Set BW select tap X for DFE loop */ + for (i = 0; i < 9; i++) { + reg = RXTX_REG81 + i * 2; + serdes_rd(ctx, lane, reg, &val); + val = RXTX_REG89_MU_TH7_SET(val, 0xe); + val = RXTX_REG89_MU_TH8_SET(val, 0xe); + val = RXTX_REG89_MU_TH9_SET(val, 0xe); + serdes_wr(ctx, lane, reg, val); + } + + /* Set BW select tap X for frequency adjust loop */ + for (i = 0; i < 3; i++) { + reg = RXTX_REG96 + i * 2; + serdes_rd(ctx, lane, reg, &val); + val = RXTX_REG96_MU_FREQ1_SET(val, 0x10); + val = RXTX_REG96_MU_FREQ2_SET(val, 0x10); + val = RXTX_REG96_MU_FREQ3_SET(val, 0x10); + serdes_wr(ctx, lane, reg, val); + } + + /* Set BW select tap X for phase adjust loop */ + for (i = 0; i < 3; i++) { + reg = RXTX_REG99 + i * 2; + serdes_rd(ctx, lane, reg, &val); + val = RXTX_REG99_MU_PHASE1_SET(val, 0x7); + val = RXTX_REG99_MU_PHASE2_SET(val, 0x7); + val = RXTX_REG99_MU_PHASE3_SET(val, 0x7); + serdes_wr(ctx, lane, reg, val); + } + + serdes_rd(ctx, lane, RXTX_REG102, &val); + val = RXTX_REG102_FREQLOOP_LIMIT_SET(val, 0x0); + serdes_wr(ctx, lane, RXTX_REG102, val); + + serdes_wr(ctx, lane, RXTX_REG114, 0xffe0); + + serdes_rd(ctx, lane, RXTX_REG125, &val); + val = RXTX_REG125_SIGN_PQ_SET(val, + ctx->sata_param.txeyedirection[lane * 3 + + ctx->sata_param.speed[lane]]); + val = RXTX_REG125_PQ_REG_SET(val, + ctx->sata_param.txeyetuning[lane * 3 + + ctx->sata_param.speed[lane]]); + val = RXTX_REG125_PHZ_MANUAL_SET(val, 0x1); + serdes_wr(ctx, lane, RXTX_REG125, val); + + serdes_rd(ctx, lane, RXTX_REG127, &val); + val = RXTX_REG127_LATCH_MAN_CAL_ENA_SET(val, 0x0); + serdes_wr(ctx, lane, RXTX_REG127, val); + + serdes_rd(ctx, lane, RXTX_REG128, &val); + val = RXTX_REG128_LATCH_CAL_WAIT_SEL_SET(val, 0x3); + serdes_wr(ctx, lane, RXTX_REG128, val); + + serdes_rd(ctx, lane, RXTX_REG145, &val); + val = RXTX_REG145_RXDFE_CONFIG_SET(val, 0x3); + val = RXTX_REG145_TX_IDLE_SATA_SET(val, 0x0); + if (preA3Chip) { + val = RXTX_REG145_RXES_ENA_SET(val, 0x1); + val = RXTX_REG145_RXVWES_LATENA_SET(val, 0x1); + } else { + val = RXTX_REG145_RXES_ENA_SET(val, 0x0); + val = RXTX_REG145_RXVWES_LATENA_SET(val, 0x0); + } + serdes_wr(ctx, lane, RXTX_REG145, val); + + /* + * Set Rx LOS filter clock rate, sample rate, and threshold + * windows + */ + for (i = 0; i < 4; i++) { + reg = RXTX_REG148 + i * 2; + serdes_wr(ctx, lane, reg, 0xFFFF); + } + } +} + +static int xgene_phy_cal_rdy_chk(struct xgene_phy_ctx *ctx, + enum cmu_type_t cmu_type, + enum clk_type_t clk_type) +{ + void __iomem *csr_serdes = ctx->sds_base; + int loop; + u32 val; + + /* Release PHY main reset */ + writel(0xdf, csr_serdes + SATA_ENET_SDS_RST_CTL); + readl(csr_serdes + SATA_ENET_SDS_RST_CTL); /* Force a barrier */ + + if (cmu_type != REF_CMU) { + cmu_setbits(ctx, cmu_type, CMU_REG5, CMU_REG5_PLL_RESETB_MASK); + /* + * As per PHY design spec, the PLL reset requires a minimum + * of 800us. + */ + usleep_range(800, 1000); + + cmu_rd(ctx, cmu_type, CMU_REG1, &val); + val = CMU_REG1_PLL_MANUALCAL_SET(val, 0x0); + cmu_wr(ctx, cmu_type, CMU_REG1, val); + /* + * As per PHY design spec, the PLL auto calibration requires + * a minimum of 800us. + */ + usleep_range(800, 1000); + + cmu_toggle1to0(ctx, cmu_type, CMU_REG32, + CMU_REG32_FORCE_VCOCAL_START_MASK); + /* + * As per PHY design spec, the PLL requires a minimum of + * 800us to settle. + */ + usleep_range(800, 1000); + } + + if (!preA3Chip) + goto skip_manual_cal; + + /* + * Configure the termination resister calibration + * The serial receive pins, RXP/RXN, have TERMination resistor + * that is required to be calibrated. + */ + cmu_rd(ctx, cmu_type, CMU_REG17, &val); + val = CMU_REG17_PVT_CODE_R2A_SET(val, 0x12); + val = CMU_REG17_RESERVED_7_SET(val, 0x0); + cmu_wr(ctx, cmu_type, CMU_REG17, val); + cmu_toggle1to0(ctx, cmu_type, CMU_REG17, + CMU_REG17_PVT_TERM_MAN_ENA_MASK); + /* + * The serial transmit pins, TXP/TXN, have Pull-UP and Pull-DOWN + * resistors that are required to the calibrated. + * Configure the pull DOWN calibration + */ + cmu_rd(ctx, cmu_type, CMU_REG17, &val); + val = CMU_REG17_PVT_CODE_R2A_SET(val, 0x29); + val = CMU_REG17_RESERVED_7_SET(val, 0x0); + cmu_wr(ctx, cmu_type, CMU_REG17, val); + cmu_toggle1to0(ctx, cmu_type, CMU_REG16, + CMU_REG16_PVT_DN_MAN_ENA_MASK); + /* Configure the pull UP calibration */ + cmu_rd(ctx, cmu_type, CMU_REG17, &val); + val = CMU_REG17_PVT_CODE_R2A_SET(val, 0x28); + val = CMU_REG17_RESERVED_7_SET(val, 0x0); + cmu_wr(ctx, cmu_type, CMU_REG17, val); + cmu_toggle1to0(ctx, cmu_type, CMU_REG16, + CMU_REG16_PVT_UP_MAN_ENA_MASK); + +skip_manual_cal: + /* Poll the PLL calibration completion status for at least 1 ms */ + loop = 100; + do { + cmu_rd(ctx, cmu_type, CMU_REG7, &val); + if (CMU_REG7_PLL_CALIB_DONE_RD(val)) + break; + /* + * As per PHY design spec, PLL calibration status requires + * a minimum of 10us to be updated. + */ + usleep_range(10, 100); + } while (--loop > 0); + + cmu_rd(ctx, cmu_type, CMU_REG7, &val); + dev_dbg(ctx->dev, "PLL calibration %s\n", + CMU_REG7_PLL_CALIB_DONE_RD(val) ? "done" : "failed"); + if (CMU_REG7_VCO_CAL_FAIL_RD(val)) { + dev_err(ctx->dev, + "PLL calibration failed due to VCO failure\n"); + return -1; + } + dev_dbg(ctx->dev, "PLL calibration successful\n"); + + cmu_rd(ctx, cmu_type, CMU_REG15, &val); + dev_dbg(ctx->dev, "PHY Tx is %sready\n", val & 0x300 ? "" : "not "); + return 0; +} + +static void xgene_phy_pdwn_force_vco(struct xgene_phy_ctx *ctx, + enum cmu_type_t cmu_type, + enum clk_type_t clk_type) +{ + u32 val; + + dev_dbg(ctx->dev, "Reset VCO and re-start again\n"); + if (cmu_type == PHY_CMU) { + cmu_rd(ctx, cmu_type, CMU_REG16, &val); + val = CMU_REG16_VCOCAL_WAIT_BTW_CODE_SET(val, 0x7); + cmu_wr(ctx, cmu_type, CMU_REG16, val); + } + + cmu_toggle1to0(ctx, cmu_type, CMU_REG0, CMU_REG0_PDOWN_MASK); + cmu_toggle1to0(ctx, cmu_type, CMU_REG32, + CMU_REG32_FORCE_VCOCAL_START_MASK); +} + +static int xgene_phy_hw_init_sata(struct xgene_phy_ctx *ctx, + enum clk_type_t clk_type, int ssc_enable) +{ + void __iomem *sds_base = ctx->sds_base; + u32 val; + int i; + + /* Configure the PHY for operation */ + dev_dbg(ctx->dev, "Reset PHY\n"); + /* Place PHY into reset */ + writel(0x0, sds_base + SATA_ENET_SDS_RST_CTL); + val = readl(sds_base + SATA_ENET_SDS_RST_CTL); /* Force a barrier */ + /* Release PHY lane from reset (active high) */ + writel(0x20, sds_base + SATA_ENET_SDS_RST_CTL); + readl(sds_base + SATA_ENET_SDS_RST_CTL); /* Force a barrier */ + /* Release all PHY module out of reset except PHY main reset */ + writel(0xde, sds_base + SATA_ENET_SDS_RST_CTL); + readl(sds_base + SATA_ENET_SDS_RST_CTL); /* Force a barrier */ + + /* Set the operation speed */ + val = readl(sds_base + SATA_ENET_SDS_CTL1); + val = CFG_I_SPD_SEL_CDR_OVR1_SET(val, + ctx->sata_param.txspeed[ctx->sata_param.speed[0]]); + writel(val, sds_base + SATA_ENET_SDS_CTL1); + + dev_dbg(ctx->dev, "Set the customer pin mode to SATA\n"); + val = readl(sds_base + SATA_ENET_SDS_CTL0); + val = REGSPEC_CFG_I_CUSTOMER_PIN_MODE0_SET(val, 0x4421); + writel(val, sds_base + SATA_ENET_SDS_CTL0); + + /* Configure the clock macro unit (CMU) clock type */ + xgene_phy_cfg_cmu_clk_type(ctx, PHY_CMU, clk_type); + + /* Configure the clock macro */ + xgene_phy_sata_cfg_cmu_core(ctx, PHY_CMU, clk_type); + + /* Enable SSC if enabled */ + if (ssc_enable) + xgene_phy_ssc_enable(ctx, PHY_CMU); + + /* Configure PHY lanes */ + xgene_phy_sata_cfg_lanes(ctx); + + /* Set Rx/Tx 20-bit */ + val = readl(sds_base + SATA_ENET_SDS_PCS_CTL0); + val = REGSPEC_CFG_I_RX_WORDMODE0_SET(val, 0x3); + val = REGSPEC_CFG_I_TX_WORDMODE0_SET(val, 0x3); + writel(val, sds_base + SATA_ENET_SDS_PCS_CTL0); + + /* Start PLL calibration and try for three times */ + i = 10; + do { + if (!xgene_phy_cal_rdy_chk(ctx, PHY_CMU, clk_type)) + break; + /* If failed, toggle the VCO power signal and start again */ + xgene_phy_pdwn_force_vco(ctx, PHY_CMU, clk_type); + } while (--i > 0); + /* Even on failure, allow to continue any way */ + if (i <= 0) + dev_err(ctx->dev, "PLL calibration failed\n"); + + return 0; +} + +static int xgene_phy_hw_initialize(struct xgene_phy_ctx *ctx, + enum clk_type_t clk_type, + int ssc_enable) +{ + int rc; + + dev_dbg(ctx->dev, "PHY init clk type %d\n", clk_type); + + if (ctx->mode == MODE_SATA) { + rc = xgene_phy_hw_init_sata(ctx, clk_type, ssc_enable); + if (rc) + return rc; + } else { + dev_err(ctx->dev, "Un-supported customer pin mode %d\n", + ctx->mode); + return -ENODEV; + } + + return 0; +} + +/* + * Receiver Offset Calibration: + * + * Calibrate the receiver signal path offset in two steps - summar and + * latch calibrations + */ +static void xgene_phy_force_lat_summer_cal(struct xgene_phy_ctx *ctx, int lane) +{ + int i; + struct { + u32 reg; + u32 val; + } serdes_reg[] = { + {RXTX_REG38, 0x0}, + {RXTX_REG39, 0xff00}, + {RXTX_REG40, 0xffff}, + {RXTX_REG41, 0xffff}, + {RXTX_REG42, 0xffff}, + {RXTX_REG43, 0xffff}, + {RXTX_REG44, 0xffff}, + {RXTX_REG45, 0xffff}, + {RXTX_REG46, 0xffff}, + {RXTX_REG47, 0xfffc}, + {RXTX_REG48, 0x0}, + {RXTX_REG49, 0x0}, + {RXTX_REG50, 0x0}, + {RXTX_REG51, 0x0}, + {RXTX_REG52, 0x0}, + {RXTX_REG53, 0x0}, + {RXTX_REG54, 0x0}, + {RXTX_REG55, 0x0}, + }; + + /* Start SUMMER calibration */ + serdes_setbits(ctx, lane, RXTX_REG127, + RXTX_REG127_FORCE_SUM_CAL_START_MASK); + /* + * As per PHY design spec, the Summer calibration requires a minimum + * of 100us to complete. + */ + usleep_range(100, 500); + serdes_clrbits(ctx, lane, RXTX_REG127, + RXTX_REG127_FORCE_SUM_CAL_START_MASK); + /* + * As per PHY design spec, the auto calibration requires a minimum + * of 100us to complete. + */ + usleep_range(100, 500); + + /* Start latch calibration */ + serdes_setbits(ctx, lane, RXTX_REG127, + RXTX_REG127_FORCE_LAT_CAL_START_MASK); + /* + * As per PHY design spec, the latch calibration requires a minimum + * of 100us to complete. + */ + usleep_range(100, 500); + serdes_clrbits(ctx, lane, RXTX_REG127, + RXTX_REG127_FORCE_LAT_CAL_START_MASK); + + /* Configure the PHY lane for calibration */ + serdes_wr(ctx, lane, RXTX_REG28, 0x7); + serdes_wr(ctx, lane, RXTX_REG31, 0x7e00); + serdes_clrbits(ctx, lane, RXTX_REG4, + RXTX_REG4_TX_LOOPBACK_BUF_EN_MASK); + serdes_clrbits(ctx, lane, RXTX_REG7, + RXTX_REG7_LOOP_BACK_ENA_CTLE_MASK); + for (i = 0; i < ARRAY_SIZE(serdes_reg); i++) + serdes_wr(ctx, lane, serdes_reg[i].reg, + serdes_reg[i].val); +} + +static void xgene_phy_reset_rxd(struct xgene_phy_ctx *ctx, int lane) +{ + /* Reset digital Rx */ + serdes_clrbits(ctx, lane, RXTX_REG7, RXTX_REG7_RESETB_RXD_MASK); + /* As per PHY design spec, the reset requires a minimum of 100us. */ + usleep_range(100, 150); + serdes_setbits(ctx, lane, RXTX_REG7, RXTX_REG7_RESETB_RXD_MASK); +} + +static int xgene_phy_get_avg(int accum, int samples) +{ + return (accum + (samples / 2)) / samples; +} + +static void xgene_phy_gen_avg_val(struct xgene_phy_ctx *ctx, int lane) +{ + int max_loop = 10; + int avg_loop = 0; + int lat_do = 0, lat_xo = 0, lat_eo = 0, lat_so = 0; + int lat_de = 0, lat_xe = 0, lat_ee = 0, lat_se = 0; + int sum_cal = 0; + int lat_do_itr, lat_xo_itr, lat_eo_itr, lat_so_itr; + int lat_de_itr, lat_xe_itr, lat_ee_itr, lat_se_itr; + int sum_cal_itr; + int fail_even; + int fail_odd; + u32 val; + + dev_dbg(ctx->dev, "Generating avg calibration value for lane %d\n", + lane); + + /* Enable RX Hi-Z termination */ + serdes_setbits(ctx, lane, RXTX_REG12, + RXTX_REG12_RX_DET_TERM_ENABLE_MASK); + /* Turn off DFE */ + serdes_wr(ctx, lane, RXTX_REG28, 0x0000); + /* DFE Presets to zero */ + serdes_wr(ctx, lane, RXTX_REG31, 0x0000); + + /* + * Receiver Offset Calibration: + * Calibrate the receiver signal path offset in two steps - summar + * and latch calibration. + * Runs the "Receiver Offset Calibration multiple times to determine + * the average value to use. + */ + while (avg_loop < max_loop) { + /* Start the calibration */ + xgene_phy_force_lat_summer_cal(ctx, lane); + + serdes_rd(ctx, lane, RXTX_REG21, &val); + lat_do_itr = RXTX_REG21_DO_LATCH_CALOUT_RD(val); + lat_xo_itr = RXTX_REG21_XO_LATCH_CALOUT_RD(val); + fail_odd = RXTX_REG21_LATCH_CAL_FAIL_ODD_RD(val); + + serdes_rd(ctx, lane, RXTX_REG22, &val); + lat_eo_itr = RXTX_REG22_EO_LATCH_CALOUT_RD(val); + lat_so_itr = RXTX_REG22_SO_LATCH_CALOUT_RD(val); + fail_even = RXTX_REG22_LATCH_CAL_FAIL_EVEN_RD(val); + + serdes_rd(ctx, lane, RXTX_REG23, &val); + lat_de_itr = RXTX_REG23_DE_LATCH_CALOUT_RD(val); + lat_xe_itr = RXTX_REG23_XE_LATCH_CALOUT_RD(val); + + serdes_rd(ctx, lane, RXTX_REG24, &val); + lat_ee_itr = RXTX_REG24_EE_LATCH_CALOUT_RD(val); + lat_se_itr = RXTX_REG24_SE_LATCH_CALOUT_RD(val); + + serdes_rd(ctx, lane, RXTX_REG121, &val); + sum_cal_itr = RXTX_REG121_SUMOS_CAL_CODE_RD(val); + + /* Check for failure. If passed, sum them for averaging */ + if ((fail_even == 0 || fail_even == 1) && + (fail_odd == 0 || fail_odd == 1)) { + lat_do += lat_do_itr; + lat_xo += lat_xo_itr; + lat_eo += lat_eo_itr; + lat_so += lat_so_itr; + lat_de += lat_de_itr; + lat_xe += lat_xe_itr; + lat_ee += lat_ee_itr; + lat_se += lat_se_itr; + sum_cal += sum_cal_itr; + + dev_dbg(ctx->dev, "Iteration %d:\n", avg_loop); + dev_dbg(ctx->dev, "DO 0x%x XO 0x%x EO 0x%x SO 0x%x\n", + lat_do_itr, lat_xo_itr, lat_eo_itr, + lat_so_itr); + dev_dbg(ctx->dev, "DE 0x%x XE 0x%x EE 0x%x SE 0x%x\n", + lat_de_itr, lat_xe_itr, lat_ee_itr, + lat_se_itr); + dev_dbg(ctx->dev, "SUM 0x%x\n", sum_cal_itr); + ++avg_loop; + } else { + dev_err(ctx->dev, + "Receiver calibration failed at %d loop\n", + avg_loop); + } + xgene_phy_reset_rxd(ctx, lane); + } + + /* Update latch manual calibration with average value */ + serdes_rd(ctx, lane, RXTX_REG127, &val); + val = RXTX_REG127_DO_LATCH_MANCAL_SET(val, + xgene_phy_get_avg(lat_do, max_loop)); + val = RXTX_REG127_XO_LATCH_MANCAL_SET(val, + xgene_phy_get_avg(lat_xo, max_loop)); + serdes_wr(ctx, lane, RXTX_REG127, val); + + serdes_rd(ctx, lane, RXTX_REG128, &val); + val = RXTX_REG128_EO_LATCH_MANCAL_SET(val, + xgene_phy_get_avg(lat_eo, max_loop)); + val = RXTX_REG128_SO_LATCH_MANCAL_SET(val, + xgene_phy_get_avg(lat_so, max_loop)); + serdes_wr(ctx, lane, RXTX_REG128, val); + + serdes_rd(ctx, lane, RXTX_REG129, &val); + val = RXTX_REG129_DE_LATCH_MANCAL_SET(val, + xgene_phy_get_avg(lat_de, max_loop)); + val = RXTX_REG129_XE_LATCH_MANCAL_SET(val, + xgene_phy_get_avg(lat_xe, max_loop)); + serdes_wr(ctx, lane, RXTX_REG129, val); + + serdes_rd(ctx, lane, RXTX_REG130, &val); + val = RXTX_REG130_EE_LATCH_MANCAL_SET(val, + xgene_phy_get_avg(lat_ee, max_loop)); + val = RXTX_REG130_SE_LATCH_MANCAL_SET(val, + xgene_phy_get_avg(lat_se, max_loop)); + serdes_wr(ctx, lane, RXTX_REG130, val); + + /* Update SUMMER calibration with average value */ + serdes_rd(ctx, lane, RXTX_REG14, &val); + val = RXTX_REG14_CLTE_LATCAL_MAN_PROG_SET(val, + xgene_phy_get_avg(sum_cal, max_loop)); + serdes_wr(ctx, lane, RXTX_REG14, val); + + dev_dbg(ctx->dev, "Average Value:\n"); + dev_dbg(ctx->dev, "DO 0x%x XO 0x%x EO 0x%x SO 0x%x\n", + xgene_phy_get_avg(lat_do, max_loop), + xgene_phy_get_avg(lat_xo, max_loop), + xgene_phy_get_avg(lat_eo, max_loop), + xgene_phy_get_avg(lat_so, max_loop)); + dev_dbg(ctx->dev, "DE 0x%x XE 0x%x EE 0x%x SE 0x%x\n", + xgene_phy_get_avg(lat_de, max_loop), + xgene_phy_get_avg(lat_xe, max_loop), + xgene_phy_get_avg(lat_ee, max_loop), + xgene_phy_get_avg(lat_se, max_loop)); + dev_dbg(ctx->dev, "SUM 0x%x\n", + xgene_phy_get_avg(sum_cal, max_loop)); + + serdes_rd(ctx, lane, RXTX_REG14, &val); + val = RXTX_REG14_CTLE_LATCAL_MAN_ENA_SET(val, 0x1); + serdes_wr(ctx, lane, RXTX_REG14, val); + dev_dbg(ctx->dev, "Enable Manual Summer calibration\n"); + + serdes_rd(ctx, lane, RXTX_REG127, &val); + val = RXTX_REG127_LATCH_MAN_CAL_ENA_SET(val, 0x1); + dev_dbg(ctx->dev, "Enable Manual Latch calibration\n"); + serdes_wr(ctx, lane, RXTX_REG127, val); + + /* Disable RX Hi-Z termination */ + serdes_rd(ctx, lane, RXTX_REG12, &val); + val = RXTX_REG12_RX_DET_TERM_ENABLE_SET(val, 0); + serdes_wr(ctx, lane, RXTX_REG12, val); + /* Turn on DFE */ + serdes_wr(ctx, lane, RXTX_REG28, 0x0007); + /* Set DFE preset */ + serdes_wr(ctx, lane, RXTX_REG31, 0x7e00); +} + +static int xgene_phy_hw_init(struct phy *phy) +{ + struct xgene_phy_ctx *ctx = phy_get_drvdata(phy); + int rc; + int i; + + rc = xgene_phy_hw_initialize(ctx, CLK_EXT_DIFF, SSC_DISABLE); + if (rc) { + dev_err(ctx->dev, "PHY initialize failed %d\n", rc); + return rc; + } + + /* Setup clock properly after PHY configuration */ + if (!IS_ERR(ctx->clk)) { + /* HW requires an toggle of the clock */ + clk_prepare_enable(ctx->clk); + clk_disable_unprepare(ctx->clk); + clk_prepare_enable(ctx->clk); + } + + /* Compute average value */ + for (i = 0; i < MAX_LANE; i++) + xgene_phy_gen_avg_val(ctx, i); + + dev_dbg(ctx->dev, "PHY initialized\n"); + return 0; +} + +static const struct phy_ops xgene_phy_ops = { + .init = xgene_phy_hw_init, + .owner = THIS_MODULE, +}; + +static struct phy *xgene_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct xgene_phy_ctx *ctx = dev_get_drvdata(dev); + + if (args->args_count <= 0) + return ERR_PTR(-EINVAL); + if (args->args[0] < MODE_SATA || args->args[0] >= MODE_MAX) + return ERR_PTR(-EINVAL); + + ctx->mode = args->args[0]; + return ctx->phy; +} + +static void xgene_phy_get_param(struct platform_device *pdev, + const char *name, u32 *buffer, + int count, u32 *default_val, + u32 conv_factor) +{ + int i; + + if (!of_property_read_u32_array(pdev->dev.of_node, name, buffer, + count)) { + for (i = 0; i < count; i++) + buffer[i] /= conv_factor; + return; + } + /* Does not exist, load default */ + for (i = 0; i < count; i++) + buffer[i] = default_val[i % 3]; +} + +static int xgene_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct xgene_phy_ctx *ctx; + struct resource *res; + int rc = 0; + u32 default_spd[] = DEFAULT_SATA_SPD_SEL; + u32 default_txboost_gain[] = DEFAULT_SATA_TXBOOST_GAIN; + u32 default_txeye_direction[] = DEFAULT_SATA_TXEYEDIRECTION; + u32 default_txeye_tuning[] = DEFAULT_SATA_TXEYETUNING; + u32 default_txamp[] = DEFAULT_SATA_TXAMP; + u32 default_txcn1[] = DEFAULT_SATA_TXCN1; + u32 default_txcn2[] = DEFAULT_SATA_TXCN2; + u32 default_txcp1[] = DEFAULT_SATA_TXCP1; + int i; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ctx->sds_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ctx->sds_base)) { + rc = PTR_ERR(ctx->sds_base); + goto error; + } + + /* Retrieve optional clock */ + ctx->clk = clk_get(&pdev->dev, NULL); + + /* Load override paramaters */ + xgene_phy_get_param(pdev, "apm,tx-eye-tuning", + ctx->sata_param.txeyetuning, 6, default_txeye_tuning, 1); + xgene_phy_get_param(pdev, "apm,tx-eye-direction", + ctx->sata_param.txeyedirection, 6, default_txeye_direction, 1); + xgene_phy_get_param(pdev, "apm,tx-boost-gain", + ctx->sata_param.txboostgain, 6, default_txboost_gain, 1); + xgene_phy_get_param(pdev, "apm,tx-amplitude", + ctx->sata_param.txamplitude, 6, default_txamp, 13300); + xgene_phy_get_param(pdev, "apm,tx-pre-cursor1", + ctx->sata_param.txprecursor_cn1, 6, default_txcn1, 18200); + xgene_phy_get_param(pdev, "apm,tx-pre-cursor2", + ctx->sata_param.txprecursor_cn2, 6, default_txcn2, 18200); + xgene_phy_get_param(pdev, "apm,tx-post-cursor", + ctx->sata_param.txpostcursor_cp1, 6, default_txcp1, 18200); + xgene_phy_get_param(pdev, "apm,tx-speed", + ctx->sata_param.txspeed, 3, default_spd, 1); + for (i = 0; i < MAX_LANE; i++) + ctx->sata_param.speed[i] = 2; /* Default to Gen3 */ + + ctx->dev = &pdev->dev; + platform_set_drvdata(pdev, ctx); + + ctx->phy = devm_phy_create(ctx->dev, &xgene_phy_ops, NULL); + if (IS_ERR(ctx->phy)) { + dev_dbg(&pdev->dev, "Failed to create PHY\n"); + rc = PTR_ERR(ctx->phy); + goto error; + } + phy_set_drvdata(ctx->phy, ctx); + + phy_provider = devm_of_phy_provider_register(ctx->dev, + xgene_phy_xlate); + if (IS_ERR(phy_provider)) { + rc = PTR_ERR(phy_provider); + goto error; + } + + return 0; + +error: + return rc; +} + +static const struct of_device_id xgene_phy_of_match[] = { + {.compatible = "apm,xgene-phy",}, + {}, +}; +MODULE_DEVICE_TABLE(of, xgene_phy_of_match); + +static struct platform_driver xgene_phy_driver = { + .probe = xgene_phy_probe, + .driver = { + .name = "xgene-phy", + .owner = THIS_MODULE, + .of_match_table = xgene_phy_of_match, + }, +}; +module_platform_driver(xgene_phy_driver); + +MODULE_DESCRIPTION("APM X-Gene Multi-Purpose PHY driver"); +MODULE_AUTHOR("Loc Ho "); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.1"); -- cgit v1.2.3-70-g09d2 From d7b00e310b3342a6c5b8f648283ea55665614b67 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Tue, 11 Mar 2014 13:47:37 +0800 Subject: usb: chipidea: udc: refine isr_tr_complete_handler Matthieu CASTET and Michael Grzeschik mentioned isr_tr_complete_handler is a bit messy at below: http://marc.info/?l=linux-usb&m=139047775001152&w=2 This commit creates a new function isr_setup_packet_handler to handle setup packet, it makes isr_tr_complete_handler easy to read. This is no functional change at this commit, tested with g_mass_storage and g_ether. Cc: Michael Grzeschik Cc: Matthieu CASTET Signed-off-by: Peter Chen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/udc.c | 294 ++++++++++++++++++++++++--------------------- 1 file changed, 154 insertions(+), 140 deletions(-) diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 0dc56aebb80..7739c64ef25 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -948,6 +948,156 @@ __acquires(hwep->lock) return retval; } +/** + * isr_setup_packet_handler: setup packet handler + * @ci: UDC descriptor + * + * This function handles setup packet + */ +static void isr_setup_packet_handler(struct ci_hdrc *ci) +__releases(ci->lock) +__acquires(ci->lock) +{ + struct ci_hw_ep *hwep = &ci->ci_hw_ep[0]; + struct usb_ctrlrequest req; + int type, num, dir, err = -EINVAL; + u8 tmode = 0; + + /* + * Flush data and handshake transactions of previous + * setup packet. + */ + _ep_nuke(ci->ep0out); + _ep_nuke(ci->ep0in); + + /* read_setup_packet */ + do { + hw_test_and_set_setup_guard(ci); + memcpy(&req, &hwep->qh.ptr->setup, sizeof(req)); + } while (!hw_test_and_clear_setup_guard(ci)); + + type = req.bRequestType; + + ci->ep0_dir = (type & USB_DIR_IN) ? TX : RX; + + switch (req.bRequest) { + case USB_REQ_CLEAR_FEATURE: + if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && + le16_to_cpu(req.wValue) == + USB_ENDPOINT_HALT) { + if (req.wLength != 0) + break; + num = le16_to_cpu(req.wIndex); + dir = num & USB_ENDPOINT_DIR_MASK; + num &= USB_ENDPOINT_NUMBER_MASK; + if (dir) /* TX */ + num += ci->hw_ep_max / 2; + if (!ci->ci_hw_ep[num].wedge) { + spin_unlock(&ci->lock); + err = usb_ep_clear_halt( + &ci->ci_hw_ep[num].ep); + spin_lock(&ci->lock); + if (err) + break; + } + err = isr_setup_status_phase(ci); + } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE) && + le16_to_cpu(req.wValue) == + USB_DEVICE_REMOTE_WAKEUP) { + if (req.wLength != 0) + break; + ci->remote_wakeup = 0; + err = isr_setup_status_phase(ci); + } else { + goto delegate; + } + break; + case USB_REQ_GET_STATUS: + if (type != (USB_DIR_IN|USB_RECIP_DEVICE) && + type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && + type != (USB_DIR_IN|USB_RECIP_INTERFACE)) + goto delegate; + if (le16_to_cpu(req.wLength) != 2 || + le16_to_cpu(req.wValue) != 0) + break; + err = isr_get_status_response(ci, &req); + break; + case USB_REQ_SET_ADDRESS: + if (type != (USB_DIR_OUT|USB_RECIP_DEVICE)) + goto delegate; + if (le16_to_cpu(req.wLength) != 0 || + le16_to_cpu(req.wIndex) != 0) + break; + ci->address = (u8)le16_to_cpu(req.wValue); + ci->setaddr = true; + err = isr_setup_status_phase(ci); + break; + case USB_REQ_SET_FEATURE: + if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && + le16_to_cpu(req.wValue) == + USB_ENDPOINT_HALT) { + if (req.wLength != 0) + break; + num = le16_to_cpu(req.wIndex); + dir = num & USB_ENDPOINT_DIR_MASK; + num &= USB_ENDPOINT_NUMBER_MASK; + if (dir) /* TX */ + num += ci->hw_ep_max / 2; + + spin_unlock(&ci->lock); + err = usb_ep_set_halt(&ci->ci_hw_ep[num].ep); + spin_lock(&ci->lock); + if (!err) + isr_setup_status_phase(ci); + } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE)) { + if (req.wLength != 0) + break; + switch (le16_to_cpu(req.wValue)) { + case USB_DEVICE_REMOTE_WAKEUP: + ci->remote_wakeup = 1; + err = isr_setup_status_phase(ci); + break; + case USB_DEVICE_TEST_MODE: + tmode = le16_to_cpu(req.wIndex) >> 8; + switch (tmode) { + case TEST_J: + case TEST_K: + case TEST_SE0_NAK: + case TEST_PACKET: + case TEST_FORCE_EN: + ci->test_mode = tmode; + err = isr_setup_status_phase( + ci); + break; + default: + break; + } + default: + goto delegate; + } + } else { + goto delegate; + } + break; + default: +delegate: + if (req.wLength == 0) /* no data phase */ + ci->ep0_dir = TX; + + spin_unlock(&ci->lock); + err = ci->driver->setup(&ci->gadget, &req); + spin_lock(&ci->lock); + break; + } + + if (err < 0) { + spin_unlock(&ci->lock); + if (usb_ep_set_halt(&hwep->ep)) + dev_err(ci->dev, "error: ep_set_halt\n"); + spin_lock(&ci->lock); + } +} + /** * isr_tr_complete_handler: transaction complete interrupt handler * @ci: UDC descriptor @@ -959,12 +1109,10 @@ __releases(ci->lock) __acquires(ci->lock) { unsigned i; - u8 tmode = 0; + int err; for (i = 0; i < ci->hw_ep_max; i++) { struct ci_hw_ep *hwep = &ci->ci_hw_ep[i]; - int type, num, dir, err = -EINVAL; - struct usb_ctrlrequest req; if (hwep->ep.desc == NULL) continue; /* not configured */ @@ -985,143 +1133,9 @@ __acquires(ci->lock) } /* Only handle setup packet below */ - if (i != 0 || - !hw_test_and_clear(ci, OP_ENDPTSETUPSTAT, BIT(0))) - continue; - - /* - * Flush data and handshake transactions of previous - * setup packet. - */ - _ep_nuke(ci->ep0out); - _ep_nuke(ci->ep0in); - - /* read_setup_packet */ - do { - hw_test_and_set_setup_guard(ci); - memcpy(&req, &hwep->qh.ptr->setup, sizeof(req)); - } while (!hw_test_and_clear_setup_guard(ci)); - - type = req.bRequestType; - - ci->ep0_dir = (type & USB_DIR_IN) ? TX : RX; - - switch (req.bRequest) { - case USB_REQ_CLEAR_FEATURE: - if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && - le16_to_cpu(req.wValue) == - USB_ENDPOINT_HALT) { - if (req.wLength != 0) - break; - num = le16_to_cpu(req.wIndex); - dir = num & USB_ENDPOINT_DIR_MASK; - num &= USB_ENDPOINT_NUMBER_MASK; - if (dir) /* TX */ - num += ci->hw_ep_max/2; - if (!ci->ci_hw_ep[num].wedge) { - spin_unlock(&ci->lock); - err = usb_ep_clear_halt( - &ci->ci_hw_ep[num].ep); - spin_lock(&ci->lock); - if (err) - break; - } - err = isr_setup_status_phase(ci); - } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE) && - le16_to_cpu(req.wValue) == - USB_DEVICE_REMOTE_WAKEUP) { - if (req.wLength != 0) - break; - ci->remote_wakeup = 0; - err = isr_setup_status_phase(ci); - } else { - goto delegate; - } - break; - case USB_REQ_GET_STATUS: - if (type != (USB_DIR_IN|USB_RECIP_DEVICE) && - type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && - type != (USB_DIR_IN|USB_RECIP_INTERFACE)) - goto delegate; - if (le16_to_cpu(req.wLength) != 2 || - le16_to_cpu(req.wValue) != 0) - break; - err = isr_get_status_response(ci, &req); - break; - case USB_REQ_SET_ADDRESS: - if (type != (USB_DIR_OUT|USB_RECIP_DEVICE)) - goto delegate; - if (le16_to_cpu(req.wLength) != 0 || - le16_to_cpu(req.wIndex) != 0) - break; - ci->address = (u8)le16_to_cpu(req.wValue); - ci->setaddr = true; - err = isr_setup_status_phase(ci); - break; - case USB_REQ_SET_FEATURE: - if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && - le16_to_cpu(req.wValue) == - USB_ENDPOINT_HALT) { - if (req.wLength != 0) - break; - num = le16_to_cpu(req.wIndex); - dir = num & USB_ENDPOINT_DIR_MASK; - num &= USB_ENDPOINT_NUMBER_MASK; - if (dir) /* TX */ - num += ci->hw_ep_max/2; - - spin_unlock(&ci->lock); - err = usb_ep_set_halt(&ci->ci_hw_ep[num].ep); - spin_lock(&ci->lock); - if (!err) - isr_setup_status_phase(ci); - } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE)) { - if (req.wLength != 0) - break; - switch (le16_to_cpu(req.wValue)) { - case USB_DEVICE_REMOTE_WAKEUP: - ci->remote_wakeup = 1; - err = isr_setup_status_phase(ci); - break; - case USB_DEVICE_TEST_MODE: - tmode = le16_to_cpu(req.wIndex) >> 8; - switch (tmode) { - case TEST_J: - case TEST_K: - case TEST_SE0_NAK: - case TEST_PACKET: - case TEST_FORCE_EN: - ci->test_mode = tmode; - err = isr_setup_status_phase( - ci); - break; - default: - break; - } - default: - goto delegate; - } - } else { - goto delegate; - } - break; - default: -delegate: - if (req.wLength == 0) /* no data phase */ - ci->ep0_dir = TX; - - spin_unlock(&ci->lock); - err = ci->driver->setup(&ci->gadget, &req); - spin_lock(&ci->lock); - break; - } - - if (err < 0) { - spin_unlock(&ci->lock); - if (usb_ep_set_halt(&hwep->ep)) - dev_err(ci->dev, "error: ep_set_halt\n"); - spin_lock(&ci->lock); - } + if (i == 0 && + hw_test_and_clear(ci, OP_ENDPTSETUPSTAT, BIT(0))) + isr_setup_packet_handler(ci); } } -- cgit v1.2.3-70-g09d2 From c844d6c884f372dcde158c84b61c373ebc529519 Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Tue, 11 Mar 2014 13:47:38 +0800 Subject: usb: chipidea: imx: Use dev_name() for ci_hdrc name to distinguish USBs Use dev_name() for ci_hdrc name to distinguish USBs Signed-off-by: Peter Chen Signed-off-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci_hdrc_imx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index c00f77257d3..2e58f8dfd31 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -96,7 +96,7 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) { struct ci_hdrc_imx_data *data; struct ci_hdrc_platform_data pdata = { - .name = "ci_hdrc_imx", + .name = dev_name(&pdev->dev), .capoffset = DEF_CAPOFFSET, .flags = CI_HDRC_REQUIRE_TRANSCEIVER | CI_HDRC_DISABLE_STREAMING, -- cgit v1.2.3-70-g09d2 From 7b92e1ddb17f37caf37d4477483a62a425ba428f Mon Sep 17 00:00:00 2001 From: Daniel Tang Date: Tue, 11 Mar 2014 13:47:39 +0800 Subject: usb: chipidea: add support for USB OTG controller on LSI Zevio SoCs The USB controller in TI-NSPIRE calculators (LSI Zevio SoC) are based off either Freescale's USB OTG controller or the USB controller found in the IMX233, both of which are Chipidea compatible. This patch adds a device tree binding for the controller. Signed-off-by: Peter Chen Signed-off-by: Daniel Tang Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/Makefile | 1 + drivers/usb/chipidea/ci_hdrc_zevio.c | 72 ++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 drivers/usb/chipidea/ci_hdrc_zevio.c diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile index 7345d2115af..480bd4d5710 100644 --- a/drivers/usb/chipidea/Makefile +++ b/drivers/usb/chipidea/Makefile @@ -10,6 +10,7 @@ ci_hdrc-$(CONFIG_USB_CHIPIDEA_DEBUG) += debug.o # Glue/Bridge layers go here obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc_msm.o +obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc_zevio.o # PCI doesn't provide stubs, need to check ifneq ($(CONFIG_PCI),) diff --git a/drivers/usb/chipidea/ci_hdrc_zevio.c b/drivers/usb/chipidea/ci_hdrc_zevio.c new file mode 100644 index 00000000000..3bf6489ef5e --- /dev/null +++ b/drivers/usb/chipidea/ci_hdrc_zevio.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 Daniel Tang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * Based off drivers/usb/chipidea/ci_hdrc_msm.c + * + */ + +#include +#include +#include +#include + +#include "ci.h" + +static struct ci_hdrc_platform_data ci_hdrc_zevio_platdata = { + .name = "ci_hdrc_zevio", + .flags = CI_HDRC_REGS_SHARED, + .capoffset = DEF_CAPOFFSET, +}; + +static int ci_hdrc_zevio_probe(struct platform_device *pdev) +{ + struct platform_device *ci_pdev; + + dev_dbg(&pdev->dev, "ci_hdrc_zevio_probe\n"); + + ci_pdev = ci_hdrc_add_device(&pdev->dev, + pdev->resource, pdev->num_resources, + &ci_hdrc_zevio_platdata); + + if (IS_ERR(ci_pdev)) { + dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n"); + return PTR_ERR(ci_pdev); + } + + platform_set_drvdata(pdev, ci_pdev); + + return 0; +} + +static int ci_hdrc_zevio_remove(struct platform_device *pdev) +{ + struct platform_device *ci_pdev = platform_get_drvdata(pdev); + + ci_hdrc_remove_device(ci_pdev); + + return 0; +} + +static const struct of_device_id ci_hdrc_zevio_dt_ids[] = { + { .compatible = "lsi,zevio-usb", }, + { /* sentinel */ } +}; + +static struct platform_driver ci_hdrc_zevio_driver = { + .probe = ci_hdrc_zevio_probe, + .remove = ci_hdrc_zevio_remove, + .driver = { + .name = "zevio_usb", + .owner = THIS_MODULE, + .of_match_table = ci_hdrc_zevio_dt_ids, + }, +}; + +MODULE_DEVICE_TABLE(of, ci_hdrc_zevio_dt_ids); +module_platform_driver(ci_hdrc_zevio_driver); + +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-70-g09d2 From f61a7669f3986a0031c7cc780e2ccf2ec038ae33 Mon Sep 17 00:00:00 2001 From: Daniel Tang Date: Tue, 11 Mar 2014 13:47:40 +0800 Subject: devicetree: bindings: document lsi,zevio-usb Add documentation for zevio-usb Signed-off-by: Peter Chen Signed-off-by: Daniel Tang Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/ci-hdrc-zevio.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/ci-hdrc-zevio.txt diff --git a/Documentation/devicetree/bindings/usb/ci-hdrc-zevio.txt b/Documentation/devicetree/bindings/usb/ci-hdrc-zevio.txt new file mode 100644 index 00000000000..abbcb2aea38 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/ci-hdrc-zevio.txt @@ -0,0 +1,17 @@ +* LSI Zevio USB OTG Controller + +Required properties: +- compatible: Should be "lsi,zevio-usb" +- reg: Should contain registers location and length +- interrupts: Should contain controller interrupt + +Optional properties: +- vbus-supply: regulator for vbus + +Examples: + usb0: usb@b0000000 { + reg = <0xb0000000 0x1000>; + compatible = "lsi,zevio-usb"; + interrupts = <8>; + vbus-supply = <&vbus_reg>; + }; -- cgit v1.2.3-70-g09d2 From d7c933ae7da0d0b112bfc7e86424d780aaeb2d2c Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 12 Mar 2014 19:09:37 +0100 Subject: USB: cypress_m8: fix potential scheduling while atomic Remove erroneous call to usb_clear_halt which is blocking and cannot be used in interrupt context. This code has possibly never been executed as it would cause an oops if it was. Simply treat a stalled-endpoint error as any other error condition. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/cypress_m8.c | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/drivers/usb/serial/cypress_m8.c b/drivers/usb/serial/cypress_m8.c index bccb1223143..634f0d6605e 100644 --- a/drivers/usb/serial/cypress_m8.c +++ b/drivers/usb/serial/cypress_m8.c @@ -1224,7 +1224,6 @@ static void cypress_write_int_callback(struct urb *urb) struct usb_serial_port *port = urb->context; struct cypress_private *priv = usb_get_serial_port_data(port); struct device *dev = &urb->dev->dev; - int result; int status = urb->status; switch (status) { @@ -1239,21 +1238,9 @@ static void cypress_write_int_callback(struct urb *urb) __func__, status); priv->write_urb_in_use = 0; return; - case -EPIPE: /* no break needed; clear halt and resubmit */ - if (!priv->comm_is_ok) - break; - usb_clear_halt(port->serial->dev, 0x02); - /* error in the urb, so we have to resubmit it */ - dev_dbg(dev, "%s - nonzero write bulk status received: %d\n", - __func__, status); - port->interrupt_out_urb->transfer_buffer_length = 1; - result = usb_submit_urb(port->interrupt_out_urb, GFP_ATOMIC); - if (!result) - return; - dev_err(dev, "%s - failed resubmitting write urb, error %d\n", - __func__, result); - cypress_set_dead(port); - break; + case -EPIPE: + /* Cannot call usb_clear_halt while in_interrupt */ + /* FALLTHROUGH */ default: dev_err(dev, "%s - unexpected nonzero write status received: %d\n", __func__, status); -- cgit v1.2.3-70-g09d2 From 5083fd7bdfe6760577235a724cf6dccae13652c2 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 12 Mar 2014 19:09:38 +0100 Subject: USB: serial: make bulk_out_size a lower limit Drivers are allowed to override the default bulk-out buffer size (endpoint maximum packet size) in order to increase throughput, but it does not make much sense to allow buffers smaller than the default. Note that this is already how bulk_in_size is defined. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/usb-serial.c | 5 ++--- include/linux/usb/serial.h | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 7c9dc28640b..c68fc9fb759 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -923,9 +923,8 @@ static int usb_serial_probe(struct usb_interface *interface, port = serial->port[i]; if (kfifo_alloc(&port->write_fifo, PAGE_SIZE, GFP_KERNEL)) goto probe_error; - buffer_size = serial->type->bulk_out_size; - if (!buffer_size) - buffer_size = usb_endpoint_maxp(endpoint); + buffer_size = max_t(int, serial->type->bulk_out_size, + usb_endpoint_maxp(endpoint)); port->bulk_out_size = buffer_size; port->bulk_out_endpointAddress = endpoint->bEndpointAddress; diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h index 704a1ab8240..9bb547c7bce 100644 --- a/include/linux/usb/serial.h +++ b/include/linux/usb/serial.h @@ -190,7 +190,8 @@ static inline void usb_set_serial_data(struct usb_serial *serial, void *data) * @num_ports: the number of different ports this device will have. * @bulk_in_size: minimum number of bytes to allocate for bulk-in buffer * (0 = end-point size) - * @bulk_out_size: bytes to allocate for bulk-out buffer (0 = end-point size) + * @bulk_out_size: minimum number of bytes to allocate for bulk-out buffer + * (0 = end-point size) * @calc_num_ports: pointer to a function to determine how many ports this * device has dynamically. It will be called after the probe() * callback is called, but before attach() -- cgit v1.2.3-70-g09d2 From fc11efe2800f2f9ba2ccb268321642b7e9f73a65 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 12 Mar 2014 19:09:39 +0100 Subject: USB: serial: continue to read on errors Make sure to try to resubmit the read urb on errors. Currently a recoverable error would lead to reduced throughput as only one urb will be used until the port is closed and reopened (or resumed or unthrottled). Also upgrade error messages from debug to error log level. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/generic.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index b63ce023f96..d7f39ea7d6a 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -359,16 +359,29 @@ void usb_serial_generic_read_bulk_callback(struct urb *urb) dev_dbg(&port->dev, "%s - urb %d, len %d\n", __func__, i, urb->actual_length); - - if (urb->status) { - dev_dbg(&port->dev, "%s - non-zero urb status: %d\n", - __func__, urb->status); + switch (urb->status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + dev_dbg(&port->dev, "%s - urb stopped: %d\n", + __func__, urb->status); + return; + case -EPIPE: + dev_err(&port->dev, "%s - urb stopped: %d\n", + __func__, urb->status); return; + default: + dev_err(&port->dev, "%s - nonzero urb status: %d\n", + __func__, urb->status); + goto resubmit; } usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data); port->serial->type->process_read_urb(urb); +resubmit: /* Throttle the device if requested by tty */ spin_lock_irqsave(&port->lock, flags); port->throttled = port->throttle_req; -- cgit v1.2.3-70-g09d2 From bd58c7bd6fd5f803b4127717bda9cc6c30d0c806 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 12 Mar 2014 19:09:40 +0100 Subject: USB: serial: continue to write on errors Do not discard buffered data and make sure to try to resubmit the write urbs on errors. Currently a recoverable error would lead to more data than necessary being dropped. Also upgrade error messages from debug to error log level. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/generic.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index d7f39ea7d6a..33d7f409230 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -397,7 +397,6 @@ void usb_serial_generic_write_bulk_callback(struct urb *urb) { unsigned long flags; struct usb_serial_port *port = urb->context; - int status = urb->status; int i; for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) @@ -409,17 +408,27 @@ void usb_serial_generic_write_bulk_callback(struct urb *urb) set_bit(i, &port->write_urbs_free); spin_unlock_irqrestore(&port->lock, flags); - if (status) { - dev_dbg(&port->dev, "%s - non-zero urb status: %d\n", - __func__, status); - - spin_lock_irqsave(&port->lock, flags); - kfifo_reset_out(&port->write_fifo); - spin_unlock_irqrestore(&port->lock, flags); - } else { - usb_serial_generic_write_start(port, GFP_ATOMIC); + switch (urb->status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + dev_dbg(&port->dev, "%s - urb stopped: %d\n", + __func__, urb->status); + return; + case -EPIPE: + dev_err_console(port, "%s - urb stopped: %d\n", + __func__, urb->status); + return; + default: + dev_err_console(port, "%s - nonzero urb status: %d\n", + __func__, urb->status); + goto resubmit; } +resubmit: + usb_serial_generic_write_start(port, GFP_ATOMIC); usb_serial_port_softint(port); } EXPORT_SYMBOL_GPL(usb_serial_generic_write_bulk_callback); -- cgit v1.2.3-70-g09d2 From ca0400d2caf0d6f18445feea79c8c5a4ccf77e61 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 12 Mar 2014 19:09:41 +0100 Subject: USB: serial: add missing braces Add missing braces to conditional branches and one loop in usb-serial core and generic implementation. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/generic.c | 11 ++++++----- drivers/usb/serial/usb-serial.c | 8 ++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index 33d7f409230..1bd192290b0 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -332,9 +332,9 @@ void usb_serial_generic_process_read_urb(struct urb *urb) * stuff like 3G modems, so shortcircuit it in the 99.9999999% of * cases where the USB serial is not a console anyway. */ - if (!port->port.console || !port->sysrq) + if (!port->port.console || !port->sysrq) { tty_insert_flip_string(&port->port, ch, urb->actual_length); - else { + } else { for (i = 0; i < urb->actual_length; i++, ch++) { if (!usb_serial_handle_sysrq_char(port, *ch)) tty_insert_flip_char(&port->port, *ch, TTY_NORMAL); @@ -388,8 +388,9 @@ resubmit: if (!port->throttled) { spin_unlock_irqrestore(&port->lock, flags); usb_serial_generic_submit_read_urb(port, i, GFP_ATOMIC); - } else + } else { spin_unlock_irqrestore(&port->lock, flags); + } } EXPORT_SYMBOL_GPL(usb_serial_generic_read_bulk_callback); @@ -399,10 +400,10 @@ void usb_serial_generic_write_bulk_callback(struct urb *urb) struct usb_serial_port *port = urb->context; int i; - for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) + for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) { if (port->write_urbs[i] == urb) break; - + } spin_lock_irqsave(&port->lock, flags); port->tx_bytes -= urb->transfer_buffer_length; set_bit(i, &port->write_urbs_free); diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index c68fc9fb759..4c3aeaf56dc 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -1160,9 +1160,9 @@ static int usb_serial_reset_resume(struct usb_interface *intf) usb_serial_unpoison_port_urbs(serial); serial->suspending = 0; - if (serial->type->reset_resume) + if (serial->type->reset_resume) { rv = serial->type->reset_resume(serial); - else { + } else { rv = -EOPNOTSUPP; intf->needs_binding = 1; } @@ -1337,9 +1337,9 @@ static int usb_serial_register(struct usb_serial_driver *driver) if (retval) { pr_err("problem %d when registering driver %s\n", retval, driver->description); list_del(&driver->driver_list); - } else + } else { pr_info("USB Serial support registered for %s\n", driver->description); - + } mutex_unlock(&table_lock); return retval; } -- cgit v1.2.3-70-g09d2 From d9a38a8741fdffabc32e6d0943b1cdcf22712bec Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 12 Mar 2014 19:09:42 +0100 Subject: USB: serial: add missing newlines to dev_ messages. Add missing newlines to dev_ messages. Also make some messages less verbose where appropriate. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/ch341.c | 6 +++--- drivers/usb/serial/cyberjack.c | 2 +- drivers/usb/serial/cypress_m8.c | 2 +- drivers/usb/serial/iuu_phoenix.c | 2 +- drivers/usb/serial/keyspan_pda.c | 2 +- drivers/usb/serial/kl5kusb105.c | 4 ++-- drivers/usb/serial/kobil_sct.c | 3 ++- drivers/usb/serial/mos7720.c | 12 ++++++------ drivers/usb/serial/mos7840.c | 4 ++-- drivers/usb/serial/quatech2.c | 2 +- drivers/usb/serial/spcp8x5.c | 7 +++---- drivers/usb/serial/symbolserial.c | 4 +--- drivers/usb/serial/ti_usb_3410_5052.c | 4 ++-- drivers/usb/serial/usb-serial.c | 4 ++-- 14 files changed, 28 insertions(+), 30 deletions(-) diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c index 82371f61f23..2d72aa3564a 100644 --- a/drivers/usb/serial/ch341.c +++ b/drivers/usb/serial/ch341.c @@ -323,11 +323,11 @@ static int ch341_open(struct tty_struct *tty, struct usb_serial_port *port) if (r) goto out; - dev_dbg(&port->dev, "%s - submitting interrupt urb", __func__); + dev_dbg(&port->dev, "%s - submitting interrupt urb\n", __func__); r = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); if (r) { - dev_err(&port->dev, "%s - failed submitting interrupt urb," - " error %d\n", __func__, r); + dev_err(&port->dev, "%s - failed to submit interrupt urb: %d\n", + __func__, r); ch341_close(port); goto out; } diff --git a/drivers/usb/serial/cyberjack.c b/drivers/usb/serial/cyberjack.c index 0ac3b3b3236..2916dea3ede 100644 --- a/drivers/usb/serial/cyberjack.c +++ b/drivers/usb/serial/cyberjack.c @@ -220,7 +220,7 @@ static int cyberjack_write(struct tty_struct *tty, result = usb_submit_urb(port->write_urb, GFP_ATOMIC); if (result) { dev_err(&port->dev, - "%s - failed submitting write urb, error %d", + "%s - failed submitting write urb, error %d\n", __func__, result); /* Throw away data. No better idea what to do with it. */ priv->wrfilled = 0; diff --git a/drivers/usb/serial/cypress_m8.c b/drivers/usb/serial/cypress_m8.c index 634f0d6605e..01bf5339281 100644 --- a/drivers/usb/serial/cypress_m8.c +++ b/drivers/usb/serial/cypress_m8.c @@ -279,7 +279,7 @@ static int analyze_baud_rate(struct usb_serial_port *port, speed_t new_rate) * the generic firmware, but are not used with * NMEA and SiRF protocols */ dev_dbg(&port->dev, - "%s - failed setting baud rate, unsupported speed of %d on Earthmate GPS", + "%s - failed setting baud rate, unsupported speed of %d on Earthmate GPS\n", __func__, new_rate); return -1; } diff --git a/drivers/usb/serial/iuu_phoenix.c b/drivers/usb/serial/iuu_phoenix.c index d00dae17d52..5ad4a0fb4b2 100644 --- a/drivers/usb/serial/iuu_phoenix.c +++ b/drivers/usb/serial/iuu_phoenix.c @@ -1151,7 +1151,7 @@ static ssize_t vcc_mode_store(struct device *dev, goto fail_store_vcc_mode; } - dev_dbg(dev, "%s: setting vcc_mode = %ld", __func__, v); + dev_dbg(dev, "%s: setting vcc_mode = %ld\n", __func__, v); if ((v != 3) && (v != 5)) { dev_err(dev, "%s - vcc_mode %ld is invalid\n", __func__, v); diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index e972412b614..742d827f876 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -189,7 +189,7 @@ exit: retval = usb_submit_urb(urb, GFP_ATOMIC); if (retval) dev_err(&port->dev, - "%s - usb_submit_urb failed with result %d", + "%s - usb_submit_urb failed with result %d\n", __func__, retval); } diff --git a/drivers/usb/serial/kl5kusb105.c b/drivers/usb/serial/kl5kusb105.c index c88cc4966b2..d7440b7557a 100644 --- a/drivers/usb/serial/kl5kusb105.c +++ b/drivers/usb/serial/kl5kusb105.c @@ -201,7 +201,7 @@ static int klsi_105_get_line_state(struct usb_serial_port *port, else { status = get_unaligned_le16(status_buf); - dev_info(&port->serial->dev->dev, "read status %x %x", + dev_info(&port->serial->dev->dev, "read status %x %x\n", status_buf[0], status_buf[1]); *line_state_p = klsi_105_status2linestate(status); @@ -464,7 +464,7 @@ static void klsi_105_set_termios(struct tty_struct *tty, priv->cfg.baudrate = kl5kusb105a_sio_b115200; break; default: - dev_dbg(dev, "KLSI USB->Serial converter: unsupported baudrate request, using default of 9600"); + dev_dbg(dev, "unsupported baudrate, using 9600\n"); priv->cfg.baudrate = kl5kusb105a_sio_b9600; baud = 9600; break; diff --git a/drivers/usb/serial/kobil_sct.c b/drivers/usb/serial/kobil_sct.c index 618c1c1f227..fee242387f5 100644 --- a/drivers/usb/serial/kobil_sct.c +++ b/drivers/usb/serial/kobil_sct.c @@ -557,7 +557,8 @@ static int kobil_ioctl(struct tty_struct *tty, ); dev_dbg(&port->dev, - "%s - Send reset_all_queues (FLUSH) URB returns: %i", __func__, result); + "%s - Send reset_all_queues (FLUSH) URB returns: %i\n", + __func__, result); kfree(transfer_buffer); return (result < 0) ? -EIO: 0; default: diff --git a/drivers/usb/serial/mos7720.c b/drivers/usb/serial/mos7720.c index 4eb277225a7..dfd728a263d 100644 --- a/drivers/usb/serial/mos7720.c +++ b/drivers/usb/serial/mos7720.c @@ -209,7 +209,7 @@ static int write_mos_reg(struct usb_serial *serial, unsigned int serial_portnum, index, NULL, 0, MOS_WDR_TIMEOUT); if (status < 0) dev_err(&usbdev->dev, - "mos7720: usb_control_msg() failed: %d", status); + "mos7720: usb_control_msg() failed: %d\n", status); return status; } @@ -240,7 +240,7 @@ static int read_mos_reg(struct usb_serial *serial, unsigned int serial_portnum, *data = *buf; else if (status < 0) dev_err(&usbdev->dev, - "mos7720: usb_control_msg() failed: %d", status); + "mos7720: usb_control_msg() failed: %d\n", status); kfree(buf); return status; @@ -399,7 +399,7 @@ static int write_parport_reg_nonblock(struct mos7715_parport *mos_parport, &mos_parport->deferred_urbs); spin_unlock_irqrestore(&mos_parport->listlock, flags); tasklet_schedule(&mos_parport->urb_tasklet); - dev_dbg(&usbdev->dev, "tasklet scheduled"); + dev_dbg(&usbdev->dev, "tasklet scheduled\n"); return 0; } @@ -418,7 +418,7 @@ static int write_parport_reg_nonblock(struct mos7715_parport *mos_parport, mutex_unlock(&serial->disc_mutex); if (ret_val) { dev_err(&usbdev->dev, - "%s: submit_urb() failed: %d", __func__, ret_val); + "%s: submit_urb() failed: %d\n", __func__, ret_val); spin_lock_irqsave(&mos_parport->listlock, flags); list_del(&urbtrack->urblist_entry); spin_unlock_irqrestore(&mos_parport->listlock, flags); @@ -656,7 +656,7 @@ static size_t parport_mos7715_write_compat(struct parport *pp, parport_epilogue(pp); if (retval) { dev_err(&mos_parport->serial->dev->dev, - "mos7720: usb_bulk_msg() failed: %d", retval); + "mos7720: usb_bulk_msg() failed: %d\n", retval); return 0; } return actual_len; @@ -875,7 +875,7 @@ static void mos7715_interrupt_callback(struct urb *urb) if (!(iir & 0x01)) { /* serial port interrupt pending */ switch (iir & 0x0f) { case SERIAL_IIR_RLS: - dev_dbg(dev, "Serial Port: Receiver status error or address bit detected in 9-bit mode\n\n"); + dev_dbg(dev, "Serial Port: Receiver status error or address bit detected in 9-bit mode\n"); break; case SERIAL_IIR_CTI: dev_dbg(dev, "Serial Port: Receiver time out\n"); diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c index e9d967ff521..393be562d87 100644 --- a/drivers/usb/serial/mos7840.c +++ b/drivers/usb/serial/mos7840.c @@ -522,11 +522,11 @@ static void mos7840_set_led_callback(struct urb *urb) case -ENOENT: case -ESHUTDOWN: /* This urb is terminated, clean up */ - dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d", + dev_dbg(&urb->dev->dev, "%s - urb shutting down: %d\n", __func__, urb->status); break; default: - dev_dbg(&urb->dev->dev, "%s - nonzero urb status received: %d", + dev_dbg(&urb->dev->dev, "%s - nonzero urb status: %d\n", __func__, urb->status); } } diff --git a/drivers/usb/serial/quatech2.c b/drivers/usb/serial/quatech2.c index 7725ed261ed..504f5bff79c 100644 --- a/drivers/usb/serial/quatech2.c +++ b/drivers/usb/serial/quatech2.c @@ -372,7 +372,7 @@ static int qt2_open(struct tty_struct *tty, struct usb_serial_port *port) device_port, data, 2, QT2_USB_TIMEOUT); if (status < 0) { - dev_err(&port->dev, "%s - open port failed %i", __func__, + dev_err(&port->dev, "%s - open port failed %i\n", __func__, status); kfree(data); return status; diff --git a/drivers/usb/serial/spcp8x5.c b/drivers/usb/serial/spcp8x5.c index 4ec04f73c80..ef0dbf0703c 100644 --- a/drivers/usb/serial/spcp8x5.c +++ b/drivers/usb/serial/spcp8x5.c @@ -220,9 +220,9 @@ static int spcp8x5_get_msr(struct usb_serial_port *port, u8 *status) GET_UART_STATUS, GET_UART_STATUS_TYPE, 0, GET_UART_STATUS_MSR, buf, 1, 100); if (ret < 0) - dev_err(&port->dev, "failed to get modem status: %d", ret); + dev_err(&port->dev, "failed to get modem status: %d\n", ret); - dev_dbg(&port->dev, "0xc0:0x22:0:6 %d - 0x02%x", ret, *buf); + dev_dbg(&port->dev, "0xc0:0x22:0:6 %d - 0x02%x\n", ret, *buf); *status = *buf; kfree(buf); @@ -342,8 +342,7 @@ static void spcp8x5_set_termios(struct tty_struct *tty, case 1000000: buf[0] = 0x0b; break; default: - dev_err(&port->dev, "spcp825 driver does not support the " - "baudrate requested, using default of 9600.\n"); + dev_err(&port->dev, "unsupported baudrate, using 9600\n"); } /* Set Data Length : 00:5bit, 01:6bit, 10:7bit, 11:8bit */ diff --git a/drivers/usb/serial/symbolserial.c b/drivers/usb/serial/symbolserial.c index 9fa7dd413e8..8fceec7298e 100644 --- a/drivers/usb/serial/symbolserial.c +++ b/drivers/usb/serial/symbolserial.c @@ -74,9 +74,7 @@ static void symbol_int_callback(struct urb *urb) tty_insert_flip_string(&port->port, &data[1], data_length); tty_flip_buffer_push(&port->port); } else { - dev_dbg(&port->dev, - "Improper amount of data received from the device, " - "%d bytes", urb->actual_length); + dev_dbg(&port->dev, "%s - short packet\n", __func__); } exit: diff --git a/drivers/usb/serial/ti_usb_3410_5052.c b/drivers/usb/serial/ti_usb_3410_5052.c index ec7cea58566..3dd3ff8c50d 100644 --- a/drivers/usb/serial/ti_usb_3410_5052.c +++ b/drivers/usb/serial/ti_usb_3410_5052.c @@ -293,7 +293,7 @@ static int ti_startup(struct usb_serial *serial) int status; dev_dbg(&dev->dev, - "%s - product 0x%4X, num configurations %d, configuration value %d", + "%s - product 0x%4X, num configurations %d, configuration value %d\n", __func__, le16_to_cpu(dev->descriptor.idProduct), dev->descriptor.bNumConfigurations, dev->actconfig->desc.bConfigurationValue); @@ -803,7 +803,7 @@ static void ti_set_termios(struct tty_struct *tty, tty_encode_baud_rate(tty, baud, baud); dev_dbg(&port->dev, - "%s - BaudRate=%d, wBaudRate=%d, wFlags=0x%04X, bDataBits=%d, bParity=%d, bStopBits=%d, cXon=%d, cXoff=%d, bUartMode=%d", + "%s - BaudRate=%d, wBaudRate=%d, wFlags=0x%04X, bDataBits=%d, bParity=%d, bStopBits=%d, cXon=%d, cXoff=%d, bUartMode=%d\n", __func__, baud, config->wBaudRate, config->wFlags, config->bDataBits, config->bParity, config->bStopBits, config->cXon, config->cXoff, config->bUartMode); diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 4c3aeaf56dc..81fc0dfcfdc 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -868,7 +868,7 @@ static int usb_serial_probe(struct usb_interface *interface, max_endpoints = max(max_endpoints, (int)serial->num_ports); serial->num_port_pointers = max_endpoints; - dev_dbg(ddev, "setting up %d port structures for this device", max_endpoints); + dev_dbg(ddev, "setting up %d port structure(s)\n", max_endpoints); for (i = 0; i < max_endpoints; ++i) { port = kzalloc(sizeof(struct usb_serial_port), GFP_KERNEL); if (!port) @@ -1033,7 +1033,7 @@ static int usb_serial_probe(struct usb_interface *interface, for (i = 0; i < num_ports; ++i) { port = serial->port[i]; dev_set_name(&port->dev, "ttyUSB%d", port->minor); - dev_dbg(ddev, "registering %s", dev_name(&port->dev)); + dev_dbg(ddev, "registering %s\n", dev_name(&port->dev)); device_enable_async_suspend(&port->dev); retval = device_add(&port->dev); -- cgit v1.2.3-70-g09d2 From 36904592bc0d6da0e77278d9694e5e4c66bc0a11 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 12 Mar 2014 19:09:43 +0100 Subject: USB: keyspan: remove dead debugging code Remove out-commented and ifdeffed debugging code. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/keyspan.c | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/drivers/usb/serial/keyspan.c b/drivers/usb/serial/keyspan.c index 265c6776b08..d3acaead5a8 100644 --- a/drivers/usb/serial/keyspan.c +++ b/drivers/usb/serial/keyspan.c @@ -397,17 +397,6 @@ static void usa26_instat_callback(struct urb *urb) msg = (struct keyspan_usa26_portStatusMessage *)data; -#if 0 - dev_dbg(&urb->dev->dev, - "%s - port status: port %d cts %d dcd %d dsr %d ri %d toff %d txoff %d rxen %d cr %d", - __func__, msg->port, msg->hskia_cts, msg->gpia_dcd, msg->dsr, - msg->ri, msg->_txOff, msg->_txXoff, msg->rxEnabled, - msg->controlResponse); -#endif - - /* Now do something useful with the data */ - - /* Check port number from message and retrieve private data */ if (msg->port >= serial->num_ports) { dev_dbg(&urb->dev->dev, "%s - Unexpected port number %d\n", __func__, msg->port); @@ -523,9 +512,6 @@ static void usa28_instat_callback(struct urb *urb) goto exit; } - /*dev_dbg(&urb->dev->dev, "%s %12ph", __func__, data);*/ - - /* Now do something useful with the data */ msg = (struct keyspan_usa28_portStatusMessage *)data; /* Check port number from message and retrieve private data */ @@ -605,9 +591,6 @@ static void usa49_instat_callback(struct urb *urb) goto exit; } - /*dev_dbg(&urb->dev->dev, "%s: %11ph", __func__, data);*/ - - /* Now do something useful with the data */ msg = (struct keyspan_usa49_portStatusMessage *)data; /* Check port number from message and retrieve private data */ @@ -1793,12 +1776,6 @@ static int keyspan_usa28_send_setup(struct usb_serial *serial, err = usb_submit_urb(this_urb, GFP_ATOMIC); if (err != 0) dev_dbg(&port->dev, "%s - usb_submit_urb(setup) failed\n", __func__); -#if 0 - else { - dev_dbg(&port->dev, "%s - usb_submit_urb(setup) OK %d bytes\n", __func__, - this_urb->transfer_buffer_length); - } -#endif return 0; } @@ -1976,13 +1953,6 @@ static int keyspan_usa49_send_setup(struct usb_serial *serial, err = usb_submit_urb(this_urb, GFP_ATOMIC); if (err != 0) dev_dbg(&port->dev, "%s - usb_submit_urb(setup) failed (%d)\n", __func__, err); -#if 0 - else { - dev_dbg(&port->dev, "%s - usb_submit_urb(%d) OK %d bytes (end %d)\n", __func__, - outcont_urb, this_urb->transfer_buffer_length, - usb_pipeendpoint(this_urb->pipe)); - } -#endif return 0; } -- cgit v1.2.3-70-g09d2 From ade79d13a83a31cc0f68b47831bcbfe3a82f613f Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 10 Mar 2014 13:30:53 -0500 Subject: usb: gadget: lpc32xx_udc: fix wrong clk_put() sequence This patch fixes the following Coccinelle error: drivers/usb/gadget/lpc32xx_udc.c:3313:1-7: ERROR: \ missing clk_put; clk_get on line 3139 and \ execution via conditional on line 3146 Signed-off-by: Felipe Balbi Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/lpc32xx_udc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/lpc32xx_udc.c b/drivers/usb/gadget/lpc32xx_udc.c index 049ebab0d36..a139894c600 100644 --- a/drivers/usb/gadget/lpc32xx_udc.c +++ b/drivers/usb/gadget/lpc32xx_udc.c @@ -3295,9 +3295,9 @@ usb_clk_enable_fail: pll_set_fail: clk_disable(udc->usb_pll_clk); pll_enable_fail: - clk_put(udc->usb_slv_clk); -usb_otg_clk_get_fail: clk_put(udc->usb_otg_clk); +usb_otg_clk_get_fail: + clk_put(udc->usb_slv_clk); usb_clk_get_fail: clk_put(udc->usb_pll_clk); pll_get_fail: -- cgit v1.2.3-70-g09d2 From f3c73649828397ca6bae88bcb8cd70bbcbb65b18 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 10 Mar 2014 13:30:54 -0500 Subject: usb: gadget: f_subset: switch over to PTR_RET this patch fixes the following Coccinelle warning: drivers/usb/gadget/f_subset.c:279:8-14: WARNING: \ PTR_RET can be used Signed-off-by: Felipe Balbi Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/f_subset.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/f_subset.c b/drivers/usb/gadget/f_subset.c index f1a59190ac9..df4a0dcbc99 100644 --- a/drivers/usb/gadget/f_subset.c +++ b/drivers/usb/gadget/f_subset.c @@ -276,7 +276,7 @@ static int geth_set_alt(struct usb_function *f, unsigned intf, unsigned alt) } net = gether_connect(&geth->port); - return IS_ERR(net) ? PTR_ERR(net) : 0; + return PTR_RET(net); } static void geth_disable(struct usb_function *f) -- cgit v1.2.3-70-g09d2 From 3b74c73f8d6f053f422e85fce955b61fb181cfe7 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 10 Mar 2014 13:30:55 -0500 Subject: usb: gadget: inode: switch over to memdup_user() This patch fixes the following Coccinelle warning: drivers/usb/gadget/inode.c:442:8-15: WARNING \ opportunity for memdup_user Signed-off-by: Felipe Balbi Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/inode.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/usb/gadget/inode.c b/drivers/usb/gadget/inode.c index b94c049ab0d..b5be6f0308c 100644 --- a/drivers/usb/gadget/inode.c +++ b/drivers/usb/gadget/inode.c @@ -439,11 +439,9 @@ ep_write (struct file *fd, const char __user *buf, size_t len, loff_t *ptr) /* FIXME writebehind for O_NONBLOCK and poll(), qlen = 1 */ value = -ENOMEM; - kbuf = kmalloc (len, GFP_KERNEL); - if (!kbuf) - goto free1; - if (copy_from_user (kbuf, buf, len)) { - value = -EFAULT; + kbuf = memdup_user(buf, len); + if (!kbuf) { + value = PTR_ERR(kbuf); goto free1; } @@ -452,7 +450,6 @@ ep_write (struct file *fd, const char __user *buf, size_t len, loff_t *ptr) data->name, len, (int) value); free1: mutex_unlock(&data->lock); - kfree (kbuf); return value; } -- cgit v1.2.3-70-g09d2 From dad4babe419ef2f3e14447a650ce1f760a6ee9e0 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 10 Mar 2014 13:30:56 -0500 Subject: usb: gadget: composite: switch over to ERR_CAST() This patch fixes the following Coccinelle warning: drivers/usb/gadget/composite.c:1142:9-16: WARNING: \ ERR_CAST can be used with uc Signed-off-by: Felipe Balbi Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/composite.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index d742bed7a5f..fab906429b8 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -1139,7 +1139,7 @@ struct usb_string *usb_gstrings_attach(struct usb_composite_dev *cdev, uc = copy_gadget_strings(sp, n_gstrings, n_strings); if (IS_ERR(uc)) - return ERR_PTR(PTR_ERR(uc)); + return ERR_CAST(uc); n_gs = get_containers_gs(uc); ret = usb_string_ids_tab(cdev, n_gs[0]->strings); -- cgit v1.2.3-70-g09d2 From 48968f8d5f2234fb1768c55cf7d96d0b87446cd6 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Mon, 10 Mar 2014 09:33:37 +0100 Subject: usb: gadget: f_fs: add missing spinlock and mutex unlock This patch adds missing spin_unlock and mutex_unlock calls in error handling code. Signed-off-by: Robert Baldyga Acked-by: Michal Nazarewicz Acked-by: Felipe Balbi Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/f_fs.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index 10c086afa2f..2e164dca08e 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -802,7 +802,7 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) if (io_data->aio) { req = usb_ep_alloc_request(ep->ep, GFP_KERNEL); if (unlikely(!req)) - goto error; + goto error_lock; req->buf = data; req->length = io_data->len; @@ -817,7 +817,7 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC); if (unlikely(ret)) { usb_ep_free_request(ep->ep, req); - goto error; + goto error_lock; } ret = -EIOCBQUEUED; @@ -865,6 +865,10 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) mutex_unlock(&epfile->mutex); return ret; + +error_lock: + spin_unlock_irq(&epfile->ffs->eps_lock); + mutex_unlock(&epfile->mutex); error: kfree(data); return ret; -- cgit v1.2.3-70-g09d2 From aba37fd975f0dd58e025c99c2a79b61b20190831 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Tue, 11 Mar 2014 13:26:16 -0700 Subject: usb: gadget: tcm_usb_gadget: stop format strings This makes sure that the name coming out of configfs cannot be used accidentally as a format string. Signed-off-by: Kees Cook Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/tcm_usb_gadget.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/tcm_usb_gadget.c b/drivers/usb/gadget/tcm_usb_gadget.c index 0f8aad78b54..460c266b8e2 100644 --- a/drivers/usb/gadget/tcm_usb_gadget.c +++ b/drivers/usb/gadget/tcm_usb_gadget.c @@ -1613,7 +1613,7 @@ static struct se_wwn *usbg_make_tport( return ERR_PTR(-ENOMEM); } tport->tport_wwpn = wwpn; - snprintf(tport->tport_name, sizeof(tport->tport_name), wnn_name); + snprintf(tport->tport_name, sizeof(tport->tport_name), "%s", wnn_name); return &tport->tport_wwn; } -- cgit v1.2.3-70-g09d2 From ead5178bf442dbae4008ee54bf4f66a1f6a317c9 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Tue, 11 Mar 2014 13:23:14 +0100 Subject: usb: phy: Add ulpi IDs for SMSC USB3320 and TI TUSB1210 Add new ulpi IDs which are available on Xilinx Zynq boards. Signed-off-by: Michal Simek Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-ulpi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/phy/phy-ulpi.c b/drivers/usb/phy/phy-ulpi.c index 217339dd7a9..17ea3f271bd 100644 --- a/drivers/usb/phy/phy-ulpi.c +++ b/drivers/usb/phy/phy-ulpi.c @@ -47,6 +47,8 @@ struct ulpi_info { static struct ulpi_info ulpi_ids[] = { ULPI_INFO(ULPI_ID(0x04cc, 0x1504), "NXP ISP1504"), ULPI_INFO(ULPI_ID(0x0424, 0x0006), "SMSC USB331x"), + ULPI_INFO(ULPI_ID(0x0424, 0x0007), "SMSC USB3320"), + ULPI_INFO(ULPI_ID(0x0451, 0x1507), "TI TUSB1210"), }; static int ulpi_set_otg_flags(struct usb_phy *phy) -- cgit v1.2.3-70-g09d2 From 6aec044cc2f5670cf3b143c151c8be846499bd15 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 12 Mar 2014 11:30:38 -0400 Subject: USB: unbind all interfaces before rebinding any When a driver doesn't have pre_reset, post_reset, or reset_resume methods, the USB core unbinds that driver when its device undergoes a reset or a reset-resume, and then rebinds it afterward. The existing straightforward implementation can lead to problems, because each interface gets unbound and rebound before the next interface is handled. If a driver claims additional interfaces, the claim may fail because the old binding instance may still own the additional interface when the new instance tries to claim it. This patch fixes the problem by first unbinding all the interfaces that are marked (i.e., their needs_binding flag is set) and then rebinding all of them. The patch also makes the helper functions in driver.c a little more uniform and adjusts some out-of-date comments. Signed-off-by: Alan Stern Reported-and-tested-by: "Poulain, Loic" Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/driver.c | 94 ++++++++++++++++++++++++++++------------------- drivers/usb/core/hub.c | 5 ++- drivers/usb/core/usb.h | 2 +- 3 files changed, 60 insertions(+), 41 deletions(-) diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 08283d40616..888881e5f29 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1011,8 +1011,7 @@ EXPORT_SYMBOL_GPL(usb_deregister); * it doesn't support pre_reset/post_reset/reset_resume or * because it doesn't support suspend/resume. * - * The caller must hold @intf's device's lock, but not its pm_mutex - * and not @intf->dev.sem. + * The caller must hold @intf's device's lock, but not @intf's lock. */ void usb_forced_unbind_intf(struct usb_interface *intf) { @@ -1025,16 +1024,37 @@ void usb_forced_unbind_intf(struct usb_interface *intf) intf->needs_binding = 1; } +/* + * Unbind drivers for @udev's marked interfaces. These interfaces have + * the needs_binding flag set, for example by usb_resume_interface(). + * + * The caller must hold @udev's device lock. + */ +static void unbind_marked_interfaces(struct usb_device *udev) +{ + struct usb_host_config *config; + int i; + struct usb_interface *intf; + + config = udev->actconfig; + if (config) { + for (i = 0; i < config->desc.bNumInterfaces; ++i) { + intf = config->interface[i]; + if (intf->dev.driver && intf->needs_binding) + usb_forced_unbind_intf(intf); + } + } +} + /* Delayed forced unbinding of a USB interface driver and scan * for rebinding. * - * The caller must hold @intf's device's lock, but not its pm_mutex - * and not @intf->dev.sem. + * The caller must hold @intf's device's lock, but not @intf's lock. * * Note: Rebinds will be skipped if a system sleep transition is in * progress and the PM "complete" callback hasn't occurred yet. */ -void usb_rebind_intf(struct usb_interface *intf) +static void usb_rebind_intf(struct usb_interface *intf) { int rc; @@ -1051,68 +1071,66 @@ void usb_rebind_intf(struct usb_interface *intf) } } -#ifdef CONFIG_PM - -/* Unbind drivers for @udev's interfaces that don't support suspend/resume - * There is no check for reset_resume here because it can be determined - * only during resume whether reset_resume is needed. +/* + * Rebind drivers to @udev's marked interfaces. These interfaces have + * the needs_binding flag set. * * The caller must hold @udev's device lock. */ -static void unbind_no_pm_drivers_interfaces(struct usb_device *udev) +static void rebind_marked_interfaces(struct usb_device *udev) { struct usb_host_config *config; int i; struct usb_interface *intf; - struct usb_driver *drv; config = udev->actconfig; if (config) { for (i = 0; i < config->desc.bNumInterfaces; ++i) { intf = config->interface[i]; - - if (intf->dev.driver) { - drv = to_usb_driver(intf->dev.driver); - if (!drv->suspend || !drv->resume) - usb_forced_unbind_intf(intf); - } + if (intf->needs_binding) + usb_rebind_intf(intf); } } } -/* Unbind drivers for @udev's interfaces that failed to support reset-resume. - * These interfaces have the needs_binding flag set by usb_resume_interface(). +/* + * Unbind all of @udev's marked interfaces and then rebind all of them. + * This ordering is necessary because some drivers claim several interfaces + * when they are first probed. * * The caller must hold @udev's device lock. */ -static void unbind_no_reset_resume_drivers_interfaces(struct usb_device *udev) +void usb_unbind_and_rebind_marked_interfaces(struct usb_device *udev) { - struct usb_host_config *config; - int i; - struct usb_interface *intf; - - config = udev->actconfig; - if (config) { - for (i = 0; i < config->desc.bNumInterfaces; ++i) { - intf = config->interface[i]; - if (intf->dev.driver && intf->needs_binding) - usb_forced_unbind_intf(intf); - } - } + unbind_marked_interfaces(udev); + rebind_marked_interfaces(udev); } -static void do_rebind_interfaces(struct usb_device *udev) +#ifdef CONFIG_PM + +/* Unbind drivers for @udev's interfaces that don't support suspend/resume + * There is no check for reset_resume here because it can be determined + * only during resume whether reset_resume is needed. + * + * The caller must hold @udev's device lock. + */ +static void unbind_no_pm_drivers_interfaces(struct usb_device *udev) { struct usb_host_config *config; int i; struct usb_interface *intf; + struct usb_driver *drv; config = udev->actconfig; if (config) { for (i = 0; i < config->desc.bNumInterfaces; ++i) { intf = config->interface[i]; - if (intf->needs_binding) - usb_rebind_intf(intf); + + if (intf->dev.driver) { + drv = to_usb_driver(intf->dev.driver); + if (!drv->suspend || !drv->resume) + usb_forced_unbind_intf(intf); + } } } } @@ -1441,7 +1459,7 @@ int usb_resume_complete(struct device *dev) * whose needs_binding flag is set */ if (udev->state != USB_STATE_NOTATTACHED) - do_rebind_interfaces(udev); + rebind_marked_interfaces(udev); return 0; } @@ -1463,7 +1481,7 @@ int usb_resume(struct device *dev, pm_message_t msg) pm_runtime_disable(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); - unbind_no_reset_resume_drivers_interfaces(udev); + unbind_marked_interfaces(udev); } /* Avoid PM error messages for devices disconnected while suspended diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 5da5394127e..2d74dfb9c98 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -5380,10 +5380,11 @@ int usb_reset_device(struct usb_device *udev) else if (cintf->condition == USB_INTERFACE_BOUND) rebind = 1; + if (rebind) + cintf->needs_binding = 1; } - if (ret == 0 && rebind) - usb_rebind_intf(cintf); } + usb_unbind_and_rebind_marked_interfaces(udev); } usb_autosuspend_device(udev); diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 823857767a1..0923add72b5 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -55,7 +55,7 @@ extern int usb_match_one_id_intf(struct usb_device *dev, extern int usb_match_device(struct usb_device *dev, const struct usb_device_id *id); extern void usb_forced_unbind_intf(struct usb_interface *intf); -extern void usb_rebind_intf(struct usb_interface *intf); +extern void usb_unbind_and_rebind_marked_interfaces(struct usb_device *udev); extern int usb_hub_claim_port(struct usb_device *hdev, unsigned port, struct dev_state *owner); -- cgit v1.2.3-70-g09d2 From 1d10255c1c496557a5674e651c4ebbe0f61279f2 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 18 Mar 2014 10:39:05 -0400 Subject: USB: disable reset-resume when USB_QUIRK_RESET is set The USB_QUIRK_RESET flag indicates that a USB device changes its identity in some way when it is reset. It may lose its firmware, its descriptors may change, or it may switch back to a default mode of operation. If a device does this, the kernel needs to avoid resetting it. Resets are likely to fail, or worse, succeed while changing the device's state in a way the system can't detect. This means we should disable the reset-resume mechanism whenever this quirk flag is present. An attempted reset-resume will fail, the device will be logically disconnected, and later on the hub driver will rediscover and re-enumerate the device. This will cause the appropriate udev events to be generated, so that userspace will have a chance to switch the device into its normal operating mode, if necessary. Signed-off-by: Alan Stern CC: Oliver Neukum Reviewed-by: Hans de Goede Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 2d74dfb9c98..d670aaf13a3 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -3105,9 +3105,19 @@ static int finish_port_resume(struct usb_device *udev) * operation is carried out here, after the port has been * resumed. */ - if (udev->reset_resume) + if (udev->reset_resume) { + /* + * If the device morphs or switches modes when it is reset, + * we don't want to perform a reset-resume. We'll fail the + * resume, which will cause a logical disconnect, and then + * the device will be rediscovered. + */ retry_reset_resume: - status = usb_reset_and_verify_device(udev); + if (udev->quirks & USB_QUIRK_RESET) + status = -ENODEV; + else + status = usb_reset_and_verify_device(udev); + } /* 10.5.4.5 says be sure devices in the tree are still there. * For now let's assume the device didn't go crazy on resume, -- cgit v1.2.3-70-g09d2 From 03d85053b611b6611e027a54fd5a1f188844cae0 Mon Sep 17 00:00:00 2001 From: Sarah Sharp Date: Thu, 20 Mar 2014 16:20:56 -0700 Subject: xhci: Transition maintainership to Mathias Nyman. I'm transitioning maintainership of the xHCI driver to my colleague, Mathias Nyman. The xHCI driver is in good shape, and it's time for me to move on to the next shiny thing. :) There's a few known outstanding bugs that we have plans for how to fix: 1. Clear Halt issue that means some USB scanners fail after one scan 2. TD fragment issue that means USB ethernet scatter-gather doesn't work 3. xHCI command queue issues that cause the driver to die when a USB device doesn't respond to a Set Address control transfer when another command is outstanding. 4. USB port power off for Haswell-ULT is a complete disaster. Mathias is putting the finishing touches on a fix for #3, which will make it much easier to craft a solution for #1. Dan William has an ACKed RFC for #4 that may land in 3.16, after much testing. I'm working with Mathias to come up with an architectural solution for #2. I don't foresee very many big features coming down the pipe for USB (which is part of the reason it's a good time to change now). SSIC is mostly a hardware-level change (perhaps with some PHY drivers needed), USB 3.1 is again mostly a hardware-level change with some software engineering to communicate the speed increase to the device drivers, add new device descriptor parsing to lsusb, but definitely nothing as big as USB 3.0 was. Signed-off-by: Sarah Sharp Signed-off-by: Mathias Nyman --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index c00366ee178..d3764cd2d33 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9299,7 +9299,7 @@ S: Maintained F: drivers/net/wireless/rndis_wlan.c USB XHCI DRIVER -M: Sarah Sharp +M: Mathias Nyman L: linux-usb@vger.kernel.org S: Supported F: drivers/usb/host/xhci* -- cgit v1.2.3-70-g09d2