Message ID | 20200831044043.1561074-13-jckuo@nvidia.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | Tegra XHCI controller ELPG support | expand |
On Mon, Aug 31, 2020 at 12:40:43PM +0800, JC Kuo wrote: > This commit implements the complete programming sequence for ELPG > entry and exit. > > 1. At ELPG entry, invokes tegra_xusb_padctl_enable_phy_sleepwalk() > and tegra_xusb_padctl_enable_phy_wake() to configure XUSB PADCTL > sleepwalk and wake detection circuits to maintain USB lines level > and respond to wake events (wake-on-connect, wake-on-disconnect, > device-initiated-wake). > > 2. At ELPG exit, invokes tegra_xusb_padctl_disable_phy_sleepwalk() > and tegra_xusb_padctl_disable_phy_wake() to disarm sleepwalk and > wake detection circuits. > > At runtime suspend, XUSB host controller can enter ELPG to reduce > power consumption. When XUSB PADCTL wake detection circuit detects > a wake event, an interrupt will be raised. xhci-tegra driver then > will invoke pm_runtime_resume() for xhci-tegra. > > Runtime resume could also be triggered by protocol drivers, this is > the host-initiated-wake event. At runtime resume, xhci-tegra driver > brings XUSB host controller out of ELPG to handle the wake events. > > The same ELPG enter/exit procedure will be performed for system > suspend/resume path so USB devices can remain connected across SC7. > > Signed-off-by: JC Kuo <jckuo@nvidia.com> > --- > drivers/usb/host/xhci-tegra.c | 391 +++++++++++++++++++++++++++++++--- > 1 file changed, 361 insertions(+), 30 deletions(-) > > diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c > index ce6526c2caf6..9530cfc83f45 100644 > --- a/drivers/usb/host/xhci-tegra.c > +++ b/drivers/usb/host/xhci-tegra.c > @@ -15,9 +15,11 @@ > #include <linux/kernel.h> > #include <linux/module.h> > #include <linux/of_device.h> > +#include <linux/of_irq.h> > #include <linux/phy/phy.h> > #include <linux/phy/tegra/xusb.h> > #include <linux/platform_device.h> > +#include <linux/usb/ch9.h> > #include <linux/pm.h> > #include <linux/pm_domain.h> > #include <linux/pm_runtime.h> > @@ -224,6 +226,7 @@ struct tegra_xusb { > > int xhci_irq; > int mbox_irq; > + int padctl_irq; > > void __iomem *ipfs_base; > void __iomem *fpci_base; > @@ -268,10 +271,13 @@ struct tegra_xusb { > dma_addr_t phys; > } fw; > > + bool suspended; > struct tegra_xusb_context context; > }; > > static struct hc_driver __read_mostly tegra_xhci_hc_driver; > +static int tegra_xusb_exit_elpg(struct tegra_xusb *tegra, bool runtime); > +static int tegra_xusb_enter_elpg(struct tegra_xusb *tegra, bool runtime); > > static inline u32 fpci_readl(struct tegra_xusb *tegra, unsigned int offset) > { > @@ -657,6 +663,9 @@ static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data) > > mutex_lock(&tegra->lock); > > + if (pm_runtime_suspended(tegra->dev) || tegra->suspended) > + goto out; > + > value = fpci_readl(tegra, tegra->soc->mbox.data_out); > tegra_xusb_mbox_unpack(&msg, value); > > @@ -670,6 +679,7 @@ static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data) > > tegra_xusb_mbox_handle(tegra, &msg); > > +out: > mutex_unlock(&tegra->lock); > return IRQ_HANDLED; > } > @@ -812,12 +822,27 @@ static void tegra_xusb_phy_disable(struct tegra_xusb *tegra) > > static int tegra_xusb_runtime_suspend(struct device *dev) > { > - return 0; > + struct tegra_xusb *tegra = dev_get_drvdata(dev); > + int ret; > + > + synchronize_irq(tegra->mbox_irq); > + mutex_lock(&tegra->lock); > + ret = tegra_xusb_enter_elpg(tegra, true); > + mutex_unlock(&tegra->lock); > + > + return ret; > } > > static int tegra_xusb_runtime_resume(struct device *dev) > { > - return 0; > + struct tegra_xusb *tegra = dev_get_drvdata(dev); > + int err; > + > + mutex_lock(&tegra->lock); > + err = tegra_xusb_exit_elpg(tegra, true); > + mutex_unlock(&tegra->lock); > + > + return err; > } > > #ifdef CONFIG_PM_SLEEP > @@ -1121,6 +1146,22 @@ static int __tegra_xusb_enable_firmware_messages(struct tegra_xusb *tegra) > return err; > } > > +static irqreturn_t tegra_xusb_padctl_irq(int irq, void *data) > +{ > + struct tegra_xusb *tegra = data; > + > + mutex_lock(&tegra->lock); > + if (tegra->suspended) { > + mutex_unlock(&tegra->lock); > + return IRQ_HANDLED; > + } > + mutex_unlock(&tegra->lock); Blank lines before and after a block can help make this less cluttered. > + > + pm_runtime_resume(tegra->dev); > + > + return IRQ_HANDLED; > +} > + > static int tegra_xusb_enable_firmware_messages(struct tegra_xusb *tegra) > { > int err; > @@ -1244,6 +1285,51 @@ static void tegra_xhci_id_work(struct work_struct *work) > } > } > > +static bool is_usb2_otg_phy(struct tegra_xusb *tegra, int index) unsigned int index? > +{ > + return (tegra->usbphy[index] != NULL); > +} > + > +static bool is_usb3_otg_phy(struct tegra_xusb *tegra, int index) Here too. > +{ > + struct tegra_xusb_padctl *padctl = tegra->padctl; > + int i, port; These can also be unsigned. > + > + for (i = 0; i < tegra->num_usb_phys; i++) { > + if (is_usb2_otg_phy(tegra, i)) { > + port = tegra_xusb_padctl_get_usb3_companion(padctl,i); Space after ",". > + if (index == port) > + return true; > + } > + } > + > + return false; > +} > + > +static bool is_host_mode_phy(struct tegra_xusb *tegra, int phy_type, int index) > +{ > + if (strcmp(tegra->soc->phy_types[phy_type].name, "hsic") == 0) > + return true; > + > + if (strcmp(tegra->soc->phy_types[phy_type].name, "usb2") == 0) { > + if (is_usb2_otg_phy(tegra, index)) { > + return ((index == tegra->otg_usb2_port) && > + tegra->host_mode); > + } else > + return true; > + } > + > + if (strcmp(tegra->soc->phy_types[phy_type].name, "usb3") == 0) { > + if (is_usb3_otg_phy(tegra, index)) { > + return ((index == tegra->otg_usb3_port) && > + tegra->host_mode); > + } else > + return true; > + } > + > + return false; > +} > + > static int tegra_xusb_get_usb2_port(struct tegra_xusb *tegra, > struct usb_phy *usbphy) > { > @@ -1336,6 +1422,7 @@ static void tegra_xusb_deinit_usb_phy(struct tegra_xusb *tegra) > static int tegra_xusb_probe(struct platform_device *pdev) > { > struct tegra_xusb *tegra; > + struct device_node *np; > struct resource *regs; > struct xhci_hcd *xhci; > unsigned int i, j, k; > @@ -1383,6 +1470,14 @@ static int tegra_xusb_probe(struct platform_device *pdev) > if (IS_ERR(tegra->padctl)) > return PTR_ERR(tegra->padctl); > > + np = of_parse_phandle(pdev->dev.of_node, "nvidia,xusb-padctl", 0); > + if (!np) > + return -ENODEV; > + > + tegra->padctl_irq = of_irq_get(np, 0); > + if (tegra->padctl_irq < 0) > + return tegra->padctl_irq; > + > tegra->host_clk = devm_clk_get(&pdev->dev, "xusb_host"); > if (IS_ERR(tegra->host_clk)) { > err = PTR_ERR(tegra->host_clk); > @@ -1527,6 +1622,7 @@ static int tegra_xusb_probe(struct platform_device *pdev) > goto put_powerdomains; > } > > + tegra->hcd->skip_phy_initialization = 1; > tegra->hcd->regs = tegra->regs; > tegra->hcd->rsrc_start = regs->start; > tegra->hcd->rsrc_len = resource_size(regs); > @@ -1609,12 +1705,6 @@ static int tegra_xusb_probe(struct platform_device *pdev) > goto put_usb3; > } > > - err = tegra_xusb_enable_firmware_messages(tegra); > - if (err < 0) { > - dev_err(&pdev->dev, "failed to enable messages: %d\n", err); > - goto remove_usb3; > - } > - > err = devm_request_threaded_irq(&pdev->dev, tegra->mbox_irq, > tegra_xusb_mbox_irq, > tegra_xusb_mbox_thread, 0, > @@ -1624,12 +1714,40 @@ static int tegra_xusb_probe(struct platform_device *pdev) > goto remove_usb3; > } > > + err = devm_request_threaded_irq(&pdev->dev, tegra->padctl_irq, > + NULL, > + tegra_xusb_padctl_irq, > + IRQF_ONESHOT | > + IRQF_TRIGGER_HIGH, > + dev_name(&pdev->dev), tegra); The alignment here is off. Also, try to make good use of those 100 columns. > + if (err < 0) { > + dev_err(&pdev->dev, "failed to request padctl IRQ: %d\n", err); > + goto remove_usb3; > + } > + > + err = tegra_xusb_enable_firmware_messages(tegra); > + if (err < 0) { > + dev_err(&pdev->dev, "failed to enable messages: %d\n", err); > + goto remove_usb3; > + } > + > err = tegra_xusb_init_usb_phy(tegra); > if (err < 0) { > dev_err(&pdev->dev, "failed to init USB PHY: %d\n", err); > goto remove_usb3; > } > > + /* Enable wake for both USB 2.0 and USB 3.0 roothubs */ > + device_init_wakeup(&tegra->hcd->self.root_hub->dev, true); > + device_init_wakeup(&xhci->shared_hcd->self.root_hub->dev, true); > + device_init_wakeup(tegra->dev, true); > + > + pm_runtime_use_autosuspend(tegra->dev); > + pm_runtime_set_autosuspend_delay(tegra->dev, 2000); > + pm_runtime_mark_last_busy(tegra->dev); > + pm_runtime_set_active(tegra->dev); > + pm_runtime_enable(tegra->dev); > + > return 0; > > remove_usb3: > @@ -1665,6 +1783,7 @@ static int tegra_xusb_remove(struct platform_device *pdev) > > tegra_xusb_deinit_usb_phy(tegra); > > + pm_runtime_get_sync(&pdev->dev); > usb_remove_hcd(xhci->shared_hcd); > usb_put_hcd(xhci->shared_hcd); > xhci->shared_hcd = NULL; > @@ -1674,8 +1793,8 @@ static int tegra_xusb_remove(struct platform_device *pdev) > dma_free_coherent(&pdev->dev, tegra->fw.size, tegra->fw.virt, > tegra->fw.phys); > > - pm_runtime_put_sync(&pdev->dev); > pm_runtime_disable(&pdev->dev); > + pm_runtime_put(&pdev->dev); > > tegra_xusb_powergate_partitions(tegra); > > @@ -1717,9 +1836,17 @@ static bool xhci_hub_ports_suspended(struct xhci_hub *hub) > static int tegra_xusb_check_ports(struct tegra_xusb *tegra) > { > struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); > + struct xhci_hub *rhub = xhci_get_rhub(xhci->main_hcd); > + struct xhci_bus_state *bus_state = &rhub->bus_state; > unsigned long flags; > int err = 0; > > + if (bus_state->bus_suspended) { > + /* xusb_hub_suspend() has just directed one or more USB2 port(s) > + * to U3 state, it takes 3ms to enter U3. */ > + usleep_range(3000, 4000); > + } > + > spin_lock_irqsave(&xhci->lock, flags); > > if (!xhci_hub_ports_suspended(&xhci->usb2_rhub) || > @@ -1765,45 +1892,184 @@ static void tegra_xusb_restore_context(struct tegra_xusb *tegra) > } > } > > -static int tegra_xusb_enter_elpg(struct tegra_xusb *tegra, bool wakeup) > +static enum usb_device_speed > +tegra_xhci_portsc_to_speed(struct tegra_xusb *tegra, u32 portsc) > +{ > + if (DEV_LOWSPEED(portsc)) > + return USB_SPEED_LOW; > + else if (DEV_HIGHSPEED(portsc)) > + return USB_SPEED_HIGH; > + else if (DEV_FULLSPEED(portsc)) > + return USB_SPEED_FULL; > + else if (DEV_SUPERSPEED_ANY(portsc)) > + return USB_SPEED_SUPER; > + else > + return USB_SPEED_UNKNOWN; > +} As in a prior patch you can make this simpler by dropping the elses. > + > +static void tegra_xhci_enable_phy_sleepwalk_wake(struct tegra_xusb *tegra) > +{ > + struct tegra_xusb_padctl *padctl = tegra->padctl; > + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); > + enum usb_device_speed speed; > + struct phy *phy; > + int index, offset; > + int i, j, k; It looks like these can all be unsigned int. > + struct xhci_hub *rhub; > + u32 portsc; > + > + for (i = 0, k = 0; i < tegra->soc->num_types; i++) { > + if (strcmp(tegra->soc->phy_types[i].name, "usb3") == 0) > + rhub = &xhci->usb3_rhub; > + else > + rhub = &xhci->usb2_rhub; > + > + if (strcmp(tegra->soc->phy_types[i].name, "hsic") == 0) > + offset = tegra->soc->ports.usb2.count; > + else > + offset = 0; > + > + for (j = 0; j < tegra->soc->phy_types[i].num; j++) { > + phy = tegra->phys[k++]; > + > + if (!phy) > + continue; > + > + index = j + offset; > + > + if (index >= rhub->num_ports) > + continue; > + > + if (!is_host_mode_phy(tegra, i, j)) > + continue; > + > + portsc = readl(rhub->ports[index]->addr); > + speed = tegra_xhci_portsc_to_speed(tegra, portsc); > + tegra_xusb_padctl_enable_phy_sleepwalk(padctl, phy, > + speed); > + tegra_xusb_padctl_enable_phy_wake(padctl, phy); > + } > + } > +} > + > +static void tegra_xhci_disable_phy_wake(struct tegra_xusb *tegra) > +{ > + struct tegra_xusb_padctl *padctl = tegra->padctl; > + int i; Same here. > + > + for (i = 0; i < tegra->num_phys; i++) { > + if (!tegra->phys[i]) > + continue; > + > + tegra_xusb_padctl_disable_phy_wake(padctl, tegra->phys[i]); > + } > +} > + > +static void tegra_xhci_disable_phy_sleepwalk(struct tegra_xusb *tegra) > +{ > + struct tegra_xusb_padctl *padctl = tegra->padctl; > + int i; And here. > + > + for (i = 0; i < tegra->num_phys; i++) { > + if (!tegra->phys[i]) > + continue; > + > + tegra_xusb_padctl_disable_phy_sleepwalk(padctl, tegra->phys[i]); > + } > +} > + > +static int tegra_xusb_enter_elpg(struct tegra_xusb *tegra, bool runtime) > { > struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); > + struct device *dev = tegra->dev; > + bool wakeup = runtime ? true : device_may_wakeup(dev); > + unsigned int i; > int err; > + u32 usbcmd; > + > + dev_dbg(dev, "entering ELPG\n"); > + > + usbcmd = readl(&xhci->op_regs->command); > + usbcmd &= ~CMD_EIE; > + writel(usbcmd, &xhci->op_regs->command); > > err = tegra_xusb_check_ports(tegra); > if (err < 0) { > dev_err(tegra->dev, "not all ports suspended: %d\n", err); > - return err; > + goto out; > } > > err = xhci_suspend(xhci, wakeup); > if (err < 0) { > dev_err(tegra->dev, "failed to suspend XHCI: %d\n", err); > - return err; > + goto out; > } > > tegra_xusb_save_context(tegra); > - tegra_xusb_phy_disable(tegra); > + > + if (wakeup) > + tegra_xhci_enable_phy_sleepwalk_wake(tegra); > + > + tegra_xusb_powergate_partitions(tegra); > + > + for (i = 0; i < tegra->num_phys; i++) { > + if (!tegra->phys[i]) > + continue; > + > + phy_power_off(tegra->phys[i]); > + if (!wakeup) > + phy_exit(tegra->phys[i]); > + } > tegra_xusb_clk_disable(tegra); Use blank lines to separate blocks of code. > > - return 0; > +out: > + if (!err) > + dev_dbg(tegra->dev, "entering ELPG done\n"); > + else { > + usbcmd = readl(&xhci->op_regs->command); > + usbcmd |= CMD_EIE; > + writel(usbcmd, &xhci->op_regs->command); > + > + dev_dbg(tegra->dev, "entering ELPG failed\n"); > + pm_runtime_mark_last_busy(tegra->dev); > + } > + > + return err; > } > > -static int tegra_xusb_exit_elpg(struct tegra_xusb *tegra, bool wakeup) > +static int tegra_xusb_exit_elpg(struct tegra_xusb *tegra, bool runtime) > { > struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); > + struct device *dev = tegra->dev; > + bool wakeup = runtime ? true : device_may_wakeup(dev); > + unsigned int i; > + u32 usbcmd; > int err; > > + dev_dbg(dev, "exiting ELPG\n"); > + pm_runtime_mark_last_busy(tegra->dev); > + > err = tegra_xusb_clk_enable(tegra); > if (err < 0) { > dev_err(tegra->dev, "failed to enable clocks: %d\n", err); > - return err; > + goto out; > } > > - err = tegra_xusb_phy_enable(tegra); > - if (err < 0) { > - dev_err(tegra->dev, "failed to enable PHYs: %d\n", err); > - goto disable_clk; > + err = tegra_xusb_unpowergate_partitions(tegra); > + if (err) > + goto disable_clks; > + > + if (wakeup) > + tegra_xhci_disable_phy_wake(tegra); > + > + for (i = 0; i < tegra->num_phys; i++) { > + if (!tegra->phys[i]) > + continue; > + > + if (!wakeup) > + phy_init(tegra->phys[i]); > + > + phy_power_on(tegra->phys[i]); > } > > tegra_xusb_config(tegra); > @@ -1821,31 +2087,78 @@ static int tegra_xusb_exit_elpg(struct tegra_xusb *tegra, bool wakeup) > goto disable_phy; > } > > - err = xhci_resume(xhci, true); > + if (wakeup) > + tegra_xhci_disable_phy_sleepwalk(tegra); > + > + err = xhci_resume(xhci, 0); > if (err < 0) { > dev_err(tegra->dev, "failed to resume XHCI: %d\n", err); > goto disable_phy; > } > > - return 0; > + usbcmd = readl(&xhci->op_regs->command); > + usbcmd |= CMD_EIE; > + writel(usbcmd, &xhci->op_regs->command); > + > + goto out; > > disable_phy: > - tegra_xusb_phy_disable(tegra); > -disable_clk: > + for (i = 0; i < tegra->num_phys; i++) { > + if (!tegra->phys[i]) > + continue; > + > + phy_power_off(tegra->phys[i]); > + if (!wakeup) > + phy_exit(tegra->phys[i]); > + } > + tegra_xusb_powergate_partitions(tegra); > +disable_clks: > tegra_xusb_clk_disable(tegra); > +out: > + if (!err) > + dev_dbg(dev, "exiting ELPG done\n"); > + else > + dev_dbg(dev, "exiting ELPG failed\n"); > + > return err; > } > > static int tegra_xusb_suspend(struct device *dev) > { > struct tegra_xusb *tegra = dev_get_drvdata(dev); > - bool wakeup = device_may_wakeup(dev); > int err; > > synchronize_irq(tegra->mbox_irq); > - I think that blank line actually helped with readability. Thierry > mutex_lock(&tegra->lock); > - err = tegra_xusb_enter_elpg(tegra, wakeup); > + > + if (pm_runtime_suspended(dev)) { > + err = tegra_xusb_exit_elpg(tegra, true); > + if (err < 0) > + goto out; > + } > + > + err = tegra_xusb_enter_elpg(tegra, false); > + if (err < 0) { > + if (pm_runtime_suspended(dev)) { > + pm_runtime_disable(dev); > + pm_runtime_set_active(dev); > + pm_runtime_enable(dev); > + } > + > + goto out; > + } > + > +out: > + if (!err) { > + tegra->suspended = true; > + pm_runtime_disable(dev); > + > + if (device_may_wakeup(dev)) { > + if (enable_irq_wake(tegra->padctl_irq)) > + dev_err(dev, "failed to enable padctl wakes\n"); > + } > + } > + > mutex_unlock(&tegra->lock); > > return err; > @@ -1854,14 +2167,32 @@ static int tegra_xusb_suspend(struct device *dev) > static int tegra_xusb_resume(struct device *dev) > { > struct tegra_xusb *tegra = dev_get_drvdata(dev); > - bool wakeup = device_may_wakeup(dev); > int err; > > mutex_lock(&tegra->lock); > - err = tegra_xusb_exit_elpg(tegra, wakeup); > + > + if (!tegra->suspended) { > + mutex_unlock(&tegra->lock); > + return 0; > + } > + > + err = tegra_xusb_exit_elpg(tegra, false); > + if (err < 0) { > + mutex_unlock(&tegra->lock); > + return err; > + } > + > + if (device_may_wakeup(dev)) { > + if (disable_irq_wake(tegra->padctl_irq)) > + dev_err(dev, "failed to disable padctl wakes\n"); > + } > + tegra->suspended = false; > mutex_unlock(&tegra->lock); > > - return err; > + pm_runtime_set_active(dev); > + pm_runtime_enable(dev); > + > + return 0; > } > #endif > > -- > 2.25.1 >
31.08.2020 07:40, JC Kuo пишет: > + err = devm_request_threaded_irq(&pdev->dev, tegra->padctl_irq, > + NULL, > + tegra_xusb_padctl_irq, > + IRQF_ONESHOT | > + IRQF_TRIGGER_HIGH, Specifying trigger levels is meaningless for interrupts coming from a device-tree because DT levels always take precedence.
Hi Thierry, Thanks for review. I will amend accordingly and submit a new revision. JC On 8/31/20 8:50 PM, Thierry Reding wrote: > On Mon, Aug 31, 2020 at 12:40:43PM +0800, JC Kuo wrote: >> This commit implements the complete programming sequence for ELPG >> entry and exit. >> >> 1. At ELPG entry, invokes tegra_xusb_padctl_enable_phy_sleepwalk() >> and tegra_xusb_padctl_enable_phy_wake() to configure XUSB PADCTL >> sleepwalk and wake detection circuits to maintain USB lines level >> and respond to wake events (wake-on-connect, wake-on-disconnect, >> device-initiated-wake). >> >> 2. At ELPG exit, invokes tegra_xusb_padctl_disable_phy_sleepwalk() >> and tegra_xusb_padctl_disable_phy_wake() to disarm sleepwalk and >> wake detection circuits. >> >> At runtime suspend, XUSB host controller can enter ELPG to reduce >> power consumption. When XUSB PADCTL wake detection circuit detects >> a wake event, an interrupt will be raised. xhci-tegra driver then >> will invoke pm_runtime_resume() for xhci-tegra. >> >> Runtime resume could also be triggered by protocol drivers, this is >> the host-initiated-wake event. At runtime resume, xhci-tegra driver >> brings XUSB host controller out of ELPG to handle the wake events. >> >> The same ELPG enter/exit procedure will be performed for system >> suspend/resume path so USB devices can remain connected across SC7. >> >> Signed-off-by: JC Kuo <jckuo@nvidia.com> >> --- >> drivers/usb/host/xhci-tegra.c | 391 +++++++++++++++++++++++++++++++--- >> 1 file changed, 361 insertions(+), 30 deletions(-) >> >> diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c >> index ce6526c2caf6..9530cfc83f45 100644 >> --- a/drivers/usb/host/xhci-tegra.c >> +++ b/drivers/usb/host/xhci-tegra.c >> @@ -15,9 +15,11 @@ >> #include <linux/kernel.h> >> #include <linux/module.h> >> #include <linux/of_device.h> >> +#include <linux/of_irq.h> >> #include <linux/phy/phy.h> >> #include <linux/phy/tegra/xusb.h> >> #include <linux/platform_device.h> >> +#include <linux/usb/ch9.h> >> #include <linux/pm.h> >> #include <linux/pm_domain.h> >> #include <linux/pm_runtime.h> >> @@ -224,6 +226,7 @@ struct tegra_xusb { >> >> int xhci_irq; >> int mbox_irq; >> + int padctl_irq; >> >> void __iomem *ipfs_base; >> void __iomem *fpci_base; >> @@ -268,10 +271,13 @@ struct tegra_xusb { >> dma_addr_t phys; >> } fw; >> >> + bool suspended; >> struct tegra_xusb_context context; >> }; >> >> static struct hc_driver __read_mostly tegra_xhci_hc_driver; >> +static int tegra_xusb_exit_elpg(struct tegra_xusb *tegra, bool runtime); >> +static int tegra_xusb_enter_elpg(struct tegra_xusb *tegra, bool runtime); >> >> static inline u32 fpci_readl(struct tegra_xusb *tegra, unsigned int offset) >> { >> @@ -657,6 +663,9 @@ static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data) >> >> mutex_lock(&tegra->lock); >> >> + if (pm_runtime_suspended(tegra->dev) || tegra->suspended) >> + goto out; >> + >> value = fpci_readl(tegra, tegra->soc->mbox.data_out); >> tegra_xusb_mbox_unpack(&msg, value); >> >> @@ -670,6 +679,7 @@ static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data) >> >> tegra_xusb_mbox_handle(tegra, &msg); >> >> +out: >> mutex_unlock(&tegra->lock); >> return IRQ_HANDLED; >> } >> @@ -812,12 +822,27 @@ static void tegra_xusb_phy_disable(struct tegra_xusb *tegra) >> >> static int tegra_xusb_runtime_suspend(struct device *dev) >> { >> - return 0; >> + struct tegra_xusb *tegra = dev_get_drvdata(dev); >> + int ret; >> + >> + synchronize_irq(tegra->mbox_irq); >> + mutex_lock(&tegra->lock); >> + ret = tegra_xusb_enter_elpg(tegra, true); >> + mutex_unlock(&tegra->lock); >> + >> + return ret; >> } >> >> static int tegra_xusb_runtime_resume(struct device *dev) >> { >> - return 0; >> + struct tegra_xusb *tegra = dev_get_drvdata(dev); >> + int err; >> + >> + mutex_lock(&tegra->lock); >> + err = tegra_xusb_exit_elpg(tegra, true); >> + mutex_unlock(&tegra->lock); >> + >> + return err; >> } >> >> #ifdef CONFIG_PM_SLEEP >> @@ -1121,6 +1146,22 @@ static int __tegra_xusb_enable_firmware_messages(struct tegra_xusb *tegra) >> return err; >> } >> >> +static irqreturn_t tegra_xusb_padctl_irq(int irq, void *data) >> +{ >> + struct tegra_xusb *tegra = data; >> + >> + mutex_lock(&tegra->lock); >> + if (tegra->suspended) { >> + mutex_unlock(&tegra->lock); >> + return IRQ_HANDLED; >> + } >> + mutex_unlock(&tegra->lock); > > Blank lines before and after a block can help make this less cluttered. > >> + >> + pm_runtime_resume(tegra->dev); >> + >> + return IRQ_HANDLED; >> +} >> + >> static int tegra_xusb_enable_firmware_messages(struct tegra_xusb *tegra) >> { >> int err; >> @@ -1244,6 +1285,51 @@ static void tegra_xhci_id_work(struct work_struct *work) >> } >> } >> >> +static bool is_usb2_otg_phy(struct tegra_xusb *tegra, int index) > > unsigned int index? > >> +{ >> + return (tegra->usbphy[index] != NULL); >> +} >> + >> +static bool is_usb3_otg_phy(struct tegra_xusb *tegra, int index) > > Here too. > >> +{ >> + struct tegra_xusb_padctl *padctl = tegra->padctl; >> + int i, port; > > These can also be unsigned. > >> + >> + for (i = 0; i < tegra->num_usb_phys; i++) { >> + if (is_usb2_otg_phy(tegra, i)) { >> + port = tegra_xusb_padctl_get_usb3_companion(padctl,i); > > Space after ",". > >> + if (index == port) >> + return true; >> + } >> + } >> + >> + return false; >> +} >> + >> +static bool is_host_mode_phy(struct tegra_xusb *tegra, int phy_type, int index) >> +{ >> + if (strcmp(tegra->soc->phy_types[phy_type].name, "hsic") == 0) >> + return true; >> + >> + if (strcmp(tegra->soc->phy_types[phy_type].name, "usb2") == 0) { >> + if (is_usb2_otg_phy(tegra, index)) { >> + return ((index == tegra->otg_usb2_port) && >> + tegra->host_mode); >> + } else >> + return true; >> + } >> + >> + if (strcmp(tegra->soc->phy_types[phy_type].name, "usb3") == 0) { >> + if (is_usb3_otg_phy(tegra, index)) { >> + return ((index == tegra->otg_usb3_port) && >> + tegra->host_mode); >> + } else >> + return true; >> + } >> + >> + return false; >> +} >> + >> static int tegra_xusb_get_usb2_port(struct tegra_xusb *tegra, >> struct usb_phy *usbphy) >> { >> @@ -1336,6 +1422,7 @@ static void tegra_xusb_deinit_usb_phy(struct tegra_xusb *tegra) >> static int tegra_xusb_probe(struct platform_device *pdev) >> { >> struct tegra_xusb *tegra; >> + struct device_node *np; >> struct resource *regs; >> struct xhci_hcd *xhci; >> unsigned int i, j, k; >> @@ -1383,6 +1470,14 @@ static int tegra_xusb_probe(struct platform_device *pdev) >> if (IS_ERR(tegra->padctl)) >> return PTR_ERR(tegra->padctl); >> >> + np = of_parse_phandle(pdev->dev.of_node, "nvidia,xusb-padctl", 0); >> + if (!np) >> + return -ENODEV; >> + >> + tegra->padctl_irq = of_irq_get(np, 0); >> + if (tegra->padctl_irq < 0) >> + return tegra->padctl_irq; >> + >> tegra->host_clk = devm_clk_get(&pdev->dev, "xusb_host"); >> if (IS_ERR(tegra->host_clk)) { >> err = PTR_ERR(tegra->host_clk); >> @@ -1527,6 +1622,7 @@ static int tegra_xusb_probe(struct platform_device *pdev) >> goto put_powerdomains; >> } >> >> + tegra->hcd->skip_phy_initialization = 1; >> tegra->hcd->regs = tegra->regs; >> tegra->hcd->rsrc_start = regs->start; >> tegra->hcd->rsrc_len = resource_size(regs); >> @@ -1609,12 +1705,6 @@ static int tegra_xusb_probe(struct platform_device *pdev) >> goto put_usb3; >> } >> >> - err = tegra_xusb_enable_firmware_messages(tegra); >> - if (err < 0) { >> - dev_err(&pdev->dev, "failed to enable messages: %d\n", err); >> - goto remove_usb3; >> - } >> - >> err = devm_request_threaded_irq(&pdev->dev, tegra->mbox_irq, >> tegra_xusb_mbox_irq, >> tegra_xusb_mbox_thread, 0, >> @@ -1624,12 +1714,40 @@ static int tegra_xusb_probe(struct platform_device *pdev) >> goto remove_usb3; >> } >> >> + err = devm_request_threaded_irq(&pdev->dev, tegra->padctl_irq, >> + NULL, >> + tegra_xusb_padctl_irq, >> + IRQF_ONESHOT | >> + IRQF_TRIGGER_HIGH, >> + dev_name(&pdev->dev), tegra); > > The alignment here is off. Also, try to make good use of those 100 > columns. > >> + if (err < 0) { >> + dev_err(&pdev->dev, "failed to request padctl IRQ: %d\n", err); >> + goto remove_usb3; >> + } >> + >> + err = tegra_xusb_enable_firmware_messages(tegra); >> + if (err < 0) { >> + dev_err(&pdev->dev, "failed to enable messages: %d\n", err); >> + goto remove_usb3; >> + } >> + >> err = tegra_xusb_init_usb_phy(tegra); >> if (err < 0) { >> dev_err(&pdev->dev, "failed to init USB PHY: %d\n", err); >> goto remove_usb3; >> } >> >> + /* Enable wake for both USB 2.0 and USB 3.0 roothubs */ >> + device_init_wakeup(&tegra->hcd->self.root_hub->dev, true); >> + device_init_wakeup(&xhci->shared_hcd->self.root_hub->dev, true); >> + device_init_wakeup(tegra->dev, true); >> + >> + pm_runtime_use_autosuspend(tegra->dev); >> + pm_runtime_set_autosuspend_delay(tegra->dev, 2000); >> + pm_runtime_mark_last_busy(tegra->dev); >> + pm_runtime_set_active(tegra->dev); >> + pm_runtime_enable(tegra->dev); >> + >> return 0; >> >> remove_usb3: >> @@ -1665,6 +1783,7 @@ static int tegra_xusb_remove(struct platform_device *pdev) >> >> tegra_xusb_deinit_usb_phy(tegra); >> >> + pm_runtime_get_sync(&pdev->dev); >> usb_remove_hcd(xhci->shared_hcd); >> usb_put_hcd(xhci->shared_hcd); >> xhci->shared_hcd = NULL; >> @@ -1674,8 +1793,8 @@ static int tegra_xusb_remove(struct platform_device *pdev) >> dma_free_coherent(&pdev->dev, tegra->fw.size, tegra->fw.virt, >> tegra->fw.phys); >> >> - pm_runtime_put_sync(&pdev->dev); >> pm_runtime_disable(&pdev->dev); >> + pm_runtime_put(&pdev->dev); >> >> tegra_xusb_powergate_partitions(tegra); >> >> @@ -1717,9 +1836,17 @@ static bool xhci_hub_ports_suspended(struct xhci_hub *hub) >> static int tegra_xusb_check_ports(struct tegra_xusb *tegra) >> { >> struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); >> + struct xhci_hub *rhub = xhci_get_rhub(xhci->main_hcd); >> + struct xhci_bus_state *bus_state = &rhub->bus_state; >> unsigned long flags; >> int err = 0; >> >> + if (bus_state->bus_suspended) { >> + /* xusb_hub_suspend() has just directed one or more USB2 port(s) >> + * to U3 state, it takes 3ms to enter U3. */ >> + usleep_range(3000, 4000); >> + } >> + >> spin_lock_irqsave(&xhci->lock, flags); >> >> if (!xhci_hub_ports_suspended(&xhci->usb2_rhub) || >> @@ -1765,45 +1892,184 @@ static void tegra_xusb_restore_context(struct tegra_xusb *tegra) >> } >> } >> >> -static int tegra_xusb_enter_elpg(struct tegra_xusb *tegra, bool wakeup) >> +static enum usb_device_speed >> +tegra_xhci_portsc_to_speed(struct tegra_xusb *tegra, u32 portsc) >> +{ >> + if (DEV_LOWSPEED(portsc)) >> + return USB_SPEED_LOW; >> + else if (DEV_HIGHSPEED(portsc)) >> + return USB_SPEED_HIGH; >> + else if (DEV_FULLSPEED(portsc)) >> + return USB_SPEED_FULL; >> + else if (DEV_SUPERSPEED_ANY(portsc)) >> + return USB_SPEED_SUPER; >> + else >> + return USB_SPEED_UNKNOWN; >> +} > > As in a prior patch you can make this simpler by dropping the elses. > >> + >> +static void tegra_xhci_enable_phy_sleepwalk_wake(struct tegra_xusb *tegra) >> +{ >> + struct tegra_xusb_padctl *padctl = tegra->padctl; >> + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); >> + enum usb_device_speed speed; >> + struct phy *phy; >> + int index, offset; >> + int i, j, k; > > It looks like these can all be unsigned int. > >> + struct xhci_hub *rhub; >> + u32 portsc; >> + >> + for (i = 0, k = 0; i < tegra->soc->num_types; i++) { >> + if (strcmp(tegra->soc->phy_types[i].name, "usb3") == 0) >> + rhub = &xhci->usb3_rhub; >> + else >> + rhub = &xhci->usb2_rhub; >> + >> + if (strcmp(tegra->soc->phy_types[i].name, "hsic") == 0) >> + offset = tegra->soc->ports.usb2.count; >> + else >> + offset = 0; >> + >> + for (j = 0; j < tegra->soc->phy_types[i].num; j++) { >> + phy = tegra->phys[k++]; >> + >> + if (!phy) >> + continue; >> + >> + index = j + offset; >> + >> + if (index >= rhub->num_ports) >> + continue; >> + >> + if (!is_host_mode_phy(tegra, i, j)) >> + continue; >> + >> + portsc = readl(rhub->ports[index]->addr); >> + speed = tegra_xhci_portsc_to_speed(tegra, portsc); >> + tegra_xusb_padctl_enable_phy_sleepwalk(padctl, phy, >> + speed); >> + tegra_xusb_padctl_enable_phy_wake(padctl, phy); >> + } >> + } >> +} >> + >> +static void tegra_xhci_disable_phy_wake(struct tegra_xusb *tegra) >> +{ >> + struct tegra_xusb_padctl *padctl = tegra->padctl; >> + int i; > > Same here. > >> + >> + for (i = 0; i < tegra->num_phys; i++) { >> + if (!tegra->phys[i]) >> + continue; >> + >> + tegra_xusb_padctl_disable_phy_wake(padctl, tegra->phys[i]); >> + } >> +} >> + >> +static void tegra_xhci_disable_phy_sleepwalk(struct tegra_xusb *tegra) >> +{ >> + struct tegra_xusb_padctl *padctl = tegra->padctl; >> + int i; > > And here. > >> + >> + for (i = 0; i < tegra->num_phys; i++) { >> + if (!tegra->phys[i]) >> + continue; >> + >> + tegra_xusb_padctl_disable_phy_sleepwalk(padctl, tegra->phys[i]); >> + } >> +} >> + >> +static int tegra_xusb_enter_elpg(struct tegra_xusb *tegra, bool runtime) >> { >> struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); >> + struct device *dev = tegra->dev; >> + bool wakeup = runtime ? true : device_may_wakeup(dev); >> + unsigned int i; >> int err; >> + u32 usbcmd; >> + >> + dev_dbg(dev, "entering ELPG\n"); >> + >> + usbcmd = readl(&xhci->op_regs->command); >> + usbcmd &= ~CMD_EIE; >> + writel(usbcmd, &xhci->op_regs->command); >> >> err = tegra_xusb_check_ports(tegra); >> if (err < 0) { >> dev_err(tegra->dev, "not all ports suspended: %d\n", err); >> - return err; >> + goto out; >> } >> >> err = xhci_suspend(xhci, wakeup); >> if (err < 0) { >> dev_err(tegra->dev, "failed to suspend XHCI: %d\n", err); >> - return err; >> + goto out; >> } >> >> tegra_xusb_save_context(tegra); >> - tegra_xusb_phy_disable(tegra); >> + >> + if (wakeup) >> + tegra_xhci_enable_phy_sleepwalk_wake(tegra); >> + >> + tegra_xusb_powergate_partitions(tegra); >> + >> + for (i = 0; i < tegra->num_phys; i++) { >> + if (!tegra->phys[i]) >> + continue; >> + >> + phy_power_off(tegra->phys[i]); >> + if (!wakeup) >> + phy_exit(tegra->phys[i]); >> + } >> tegra_xusb_clk_disable(tegra); > > Use blank lines to separate blocks of code. > >> >> - return 0; >> +out: >> + if (!err) >> + dev_dbg(tegra->dev, "entering ELPG done\n"); >> + else { >> + usbcmd = readl(&xhci->op_regs->command); >> + usbcmd |= CMD_EIE; >> + writel(usbcmd, &xhci->op_regs->command); >> + >> + dev_dbg(tegra->dev, "entering ELPG failed\n"); >> + pm_runtime_mark_last_busy(tegra->dev); >> + } >> + >> + return err; >> } >> >> -static int tegra_xusb_exit_elpg(struct tegra_xusb *tegra, bool wakeup) >> +static int tegra_xusb_exit_elpg(struct tegra_xusb *tegra, bool runtime) >> { >> struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); >> + struct device *dev = tegra->dev; >> + bool wakeup = runtime ? true : device_may_wakeup(dev); >> + unsigned int i; >> + u32 usbcmd; >> int err; >> >> + dev_dbg(dev, "exiting ELPG\n"); >> + pm_runtime_mark_last_busy(tegra->dev); >> + >> err = tegra_xusb_clk_enable(tegra); >> if (err < 0) { >> dev_err(tegra->dev, "failed to enable clocks: %d\n", err); >> - return err; >> + goto out; >> } >> >> - err = tegra_xusb_phy_enable(tegra); >> - if (err < 0) { >> - dev_err(tegra->dev, "failed to enable PHYs: %d\n", err); >> - goto disable_clk; >> + err = tegra_xusb_unpowergate_partitions(tegra); >> + if (err) >> + goto disable_clks; >> + >> + if (wakeup) >> + tegra_xhci_disable_phy_wake(tegra); >> + >> + for (i = 0; i < tegra->num_phys; i++) { >> + if (!tegra->phys[i]) >> + continue; >> + >> + if (!wakeup) >> + phy_init(tegra->phys[i]); >> + >> + phy_power_on(tegra->phys[i]); >> } >> >> tegra_xusb_config(tegra); >> @@ -1821,31 +2087,78 @@ static int tegra_xusb_exit_elpg(struct tegra_xusb *tegra, bool wakeup) >> goto disable_phy; >> } >> >> - err = xhci_resume(xhci, true); >> + if (wakeup) >> + tegra_xhci_disable_phy_sleepwalk(tegra); >> + >> + err = xhci_resume(xhci, 0); >> if (err < 0) { >> dev_err(tegra->dev, "failed to resume XHCI: %d\n", err); >> goto disable_phy; >> } >> >> - return 0; >> + usbcmd = readl(&xhci->op_regs->command); >> + usbcmd |= CMD_EIE; >> + writel(usbcmd, &xhci->op_regs->command); >> + >> + goto out; >> >> disable_phy: >> - tegra_xusb_phy_disable(tegra); >> -disable_clk: >> + for (i = 0; i < tegra->num_phys; i++) { >> + if (!tegra->phys[i]) >> + continue; >> + >> + phy_power_off(tegra->phys[i]); >> + if (!wakeup) >> + phy_exit(tegra->phys[i]); >> + } >> + tegra_xusb_powergate_partitions(tegra); >> +disable_clks: >> tegra_xusb_clk_disable(tegra); >> +out: >> + if (!err) >> + dev_dbg(dev, "exiting ELPG done\n"); >> + else >> + dev_dbg(dev, "exiting ELPG failed\n"); >> + >> return err; >> } >> >> static int tegra_xusb_suspend(struct device *dev) >> { >> struct tegra_xusb *tegra = dev_get_drvdata(dev); >> - bool wakeup = device_may_wakeup(dev); >> int err; >> >> synchronize_irq(tegra->mbox_irq); >> - > > I think that blank line actually helped with readability. > > Thierry > >> mutex_lock(&tegra->lock); >> - err = tegra_xusb_enter_elpg(tegra, wakeup); >> + >> + if (pm_runtime_suspended(dev)) { >> + err = tegra_xusb_exit_elpg(tegra, true); >> + if (err < 0) >> + goto out; >> + } >> + >> + err = tegra_xusb_enter_elpg(tegra, false); >> + if (err < 0) { >> + if (pm_runtime_suspended(dev)) { >> + pm_runtime_disable(dev); >> + pm_runtime_set_active(dev); >> + pm_runtime_enable(dev); >> + } >> + >> + goto out; >> + } >> + >> +out: >> + if (!err) { >> + tegra->suspended = true; >> + pm_runtime_disable(dev); >> + >> + if (device_may_wakeup(dev)) { >> + if (enable_irq_wake(tegra->padctl_irq)) >> + dev_err(dev, "failed to enable padctl wakes\n"); >> + } >> + } >> + >> mutex_unlock(&tegra->lock); >> >> return err; >> @@ -1854,14 +2167,32 @@ static int tegra_xusb_suspend(struct device *dev) >> static int tegra_xusb_resume(struct device *dev) >> { >> struct tegra_xusb *tegra = dev_get_drvdata(dev); >> - bool wakeup = device_may_wakeup(dev); >> int err; >> >> mutex_lock(&tegra->lock); >> - err = tegra_xusb_exit_elpg(tegra, wakeup); >> + >> + if (!tegra->suspended) { >> + mutex_unlock(&tegra->lock); >> + return 0; >> + } >> + >> + err = tegra_xusb_exit_elpg(tegra, false); >> + if (err < 0) { >> + mutex_unlock(&tegra->lock); >> + return err; >> + } >> + >> + if (device_may_wakeup(dev)) { >> + if (disable_irq_wake(tegra->padctl_irq)) >> + dev_err(dev, "failed to disable padctl wakes\n"); >> + } >> + tegra->suspended = false; >> mutex_unlock(&tegra->lock); >> >> - return err; >> + pm_runtime_set_active(dev); >> + pm_runtime_enable(dev); >> + >> + return 0; >> } >> #endif >> >> -- >> 2.25.1 >>
Thanks Dmitry. I will remove this. On 9/2/20 4:33 AM, Dmitry Osipenko wrote: > 31.08.2020 07:40, JC Kuo пишет: >> + err = devm_request_threaded_irq(&pdev->dev, tegra->padctl_irq, >> + NULL, >> + tegra_xusb_padctl_irq, >> + IRQF_ONESHOT | > >> + IRQF_TRIGGER_HIGH, > > Specifying trigger levels is meaningless for interrupts coming from a > device-tree because DT levels always take precedence. >
diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index ce6526c2caf6..9530cfc83f45 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -15,9 +15,11 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/of_device.h> +#include <linux/of_irq.h> #include <linux/phy/phy.h> #include <linux/phy/tegra/xusb.h> #include <linux/platform_device.h> +#include <linux/usb/ch9.h> #include <linux/pm.h> #include <linux/pm_domain.h> #include <linux/pm_runtime.h> @@ -224,6 +226,7 @@ struct tegra_xusb { int xhci_irq; int mbox_irq; + int padctl_irq; void __iomem *ipfs_base; void __iomem *fpci_base; @@ -268,10 +271,13 @@ struct tegra_xusb { dma_addr_t phys; } fw; + bool suspended; struct tegra_xusb_context context; }; static struct hc_driver __read_mostly tegra_xhci_hc_driver; +static int tegra_xusb_exit_elpg(struct tegra_xusb *tegra, bool runtime); +static int tegra_xusb_enter_elpg(struct tegra_xusb *tegra, bool runtime); static inline u32 fpci_readl(struct tegra_xusb *tegra, unsigned int offset) { @@ -657,6 +663,9 @@ static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data) mutex_lock(&tegra->lock); + if (pm_runtime_suspended(tegra->dev) || tegra->suspended) + goto out; + value = fpci_readl(tegra, tegra->soc->mbox.data_out); tegra_xusb_mbox_unpack(&msg, value); @@ -670,6 +679,7 @@ static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data) tegra_xusb_mbox_handle(tegra, &msg); +out: mutex_unlock(&tegra->lock); return IRQ_HANDLED; } @@ -812,12 +822,27 @@ static void tegra_xusb_phy_disable(struct tegra_xusb *tegra) static int tegra_xusb_runtime_suspend(struct device *dev) { - return 0; + struct tegra_xusb *tegra = dev_get_drvdata(dev); + int ret; + + synchronize_irq(tegra->mbox_irq); + mutex_lock(&tegra->lock); + ret = tegra_xusb_enter_elpg(tegra, true); + mutex_unlock(&tegra->lock); + + return ret; } static int tegra_xusb_runtime_resume(struct device *dev) { - return 0; + struct tegra_xusb *tegra = dev_get_drvdata(dev); + int err; + + mutex_lock(&tegra->lock); + err = tegra_xusb_exit_elpg(tegra, true); + mutex_unlock(&tegra->lock); + + return err; } #ifdef CONFIG_PM_SLEEP @@ -1121,6 +1146,22 @@ static int __tegra_xusb_enable_firmware_messages(struct tegra_xusb *tegra) return err; } +static irqreturn_t tegra_xusb_padctl_irq(int irq, void *data) +{ + struct tegra_xusb *tegra = data; + + mutex_lock(&tegra->lock); + if (tegra->suspended) { + mutex_unlock(&tegra->lock); + return IRQ_HANDLED; + } + mutex_unlock(&tegra->lock); + + pm_runtime_resume(tegra->dev); + + return IRQ_HANDLED; +} + static int tegra_xusb_enable_firmware_messages(struct tegra_xusb *tegra) { int err; @@ -1244,6 +1285,51 @@ static void tegra_xhci_id_work(struct work_struct *work) } } +static bool is_usb2_otg_phy(struct tegra_xusb *tegra, int index) +{ + return (tegra->usbphy[index] != NULL); +} + +static bool is_usb3_otg_phy(struct tegra_xusb *tegra, int index) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + int i, port; + + for (i = 0; i < tegra->num_usb_phys; i++) { + if (is_usb2_otg_phy(tegra, i)) { + port = tegra_xusb_padctl_get_usb3_companion(padctl,i); + if (index == port) + return true; + } + } + + return false; +} + +static bool is_host_mode_phy(struct tegra_xusb *tegra, int phy_type, int index) +{ + if (strcmp(tegra->soc->phy_types[phy_type].name, "hsic") == 0) + return true; + + if (strcmp(tegra->soc->phy_types[phy_type].name, "usb2") == 0) { + if (is_usb2_otg_phy(tegra, index)) { + return ((index == tegra->otg_usb2_port) && + tegra->host_mode); + } else + return true; + } + + if (strcmp(tegra->soc->phy_types[phy_type].name, "usb3") == 0) { + if (is_usb3_otg_phy(tegra, index)) { + return ((index == tegra->otg_usb3_port) && + tegra->host_mode); + } else + return true; + } + + return false; +} + static int tegra_xusb_get_usb2_port(struct tegra_xusb *tegra, struct usb_phy *usbphy) { @@ -1336,6 +1422,7 @@ static void tegra_xusb_deinit_usb_phy(struct tegra_xusb *tegra) static int tegra_xusb_probe(struct platform_device *pdev) { struct tegra_xusb *tegra; + struct device_node *np; struct resource *regs; struct xhci_hcd *xhci; unsigned int i, j, k; @@ -1383,6 +1470,14 @@ static int tegra_xusb_probe(struct platform_device *pdev) if (IS_ERR(tegra->padctl)) return PTR_ERR(tegra->padctl); + np = of_parse_phandle(pdev->dev.of_node, "nvidia,xusb-padctl", 0); + if (!np) + return -ENODEV; + + tegra->padctl_irq = of_irq_get(np, 0); + if (tegra->padctl_irq < 0) + return tegra->padctl_irq; + tegra->host_clk = devm_clk_get(&pdev->dev, "xusb_host"); if (IS_ERR(tegra->host_clk)) { err = PTR_ERR(tegra->host_clk); @@ -1527,6 +1622,7 @@ static int tegra_xusb_probe(struct platform_device *pdev) goto put_powerdomains; } + tegra->hcd->skip_phy_initialization = 1; tegra->hcd->regs = tegra->regs; tegra->hcd->rsrc_start = regs->start; tegra->hcd->rsrc_len = resource_size(regs); @@ -1609,12 +1705,6 @@ static int tegra_xusb_probe(struct platform_device *pdev) goto put_usb3; } - err = tegra_xusb_enable_firmware_messages(tegra); - if (err < 0) { - dev_err(&pdev->dev, "failed to enable messages: %d\n", err); - goto remove_usb3; - } - err = devm_request_threaded_irq(&pdev->dev, tegra->mbox_irq, tegra_xusb_mbox_irq, tegra_xusb_mbox_thread, 0, @@ -1624,12 +1714,40 @@ static int tegra_xusb_probe(struct platform_device *pdev) goto remove_usb3; } + err = devm_request_threaded_irq(&pdev->dev, tegra->padctl_irq, + NULL, + tegra_xusb_padctl_irq, + IRQF_ONESHOT | + IRQF_TRIGGER_HIGH, + dev_name(&pdev->dev), tegra); + if (err < 0) { + dev_err(&pdev->dev, "failed to request padctl IRQ: %d\n", err); + goto remove_usb3; + } + + err = tegra_xusb_enable_firmware_messages(tegra); + if (err < 0) { + dev_err(&pdev->dev, "failed to enable messages: %d\n", err); + goto remove_usb3; + } + err = tegra_xusb_init_usb_phy(tegra); if (err < 0) { dev_err(&pdev->dev, "failed to init USB PHY: %d\n", err); goto remove_usb3; } + /* Enable wake for both USB 2.0 and USB 3.0 roothubs */ + device_init_wakeup(&tegra->hcd->self.root_hub->dev, true); + device_init_wakeup(&xhci->shared_hcd->self.root_hub->dev, true); + device_init_wakeup(tegra->dev, true); + + pm_runtime_use_autosuspend(tegra->dev); + pm_runtime_set_autosuspend_delay(tegra->dev, 2000); + pm_runtime_mark_last_busy(tegra->dev); + pm_runtime_set_active(tegra->dev); + pm_runtime_enable(tegra->dev); + return 0; remove_usb3: @@ -1665,6 +1783,7 @@ static int tegra_xusb_remove(struct platform_device *pdev) tegra_xusb_deinit_usb_phy(tegra); + pm_runtime_get_sync(&pdev->dev); usb_remove_hcd(xhci->shared_hcd); usb_put_hcd(xhci->shared_hcd); xhci->shared_hcd = NULL; @@ -1674,8 +1793,8 @@ static int tegra_xusb_remove(struct platform_device *pdev) dma_free_coherent(&pdev->dev, tegra->fw.size, tegra->fw.virt, tegra->fw.phys); - pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); + pm_runtime_put(&pdev->dev); tegra_xusb_powergate_partitions(tegra); @@ -1717,9 +1836,17 @@ static bool xhci_hub_ports_suspended(struct xhci_hub *hub) static int tegra_xusb_check_ports(struct tegra_xusb *tegra) { struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct xhci_hub *rhub = xhci_get_rhub(xhci->main_hcd); + struct xhci_bus_state *bus_state = &rhub->bus_state; unsigned long flags; int err = 0; + if (bus_state->bus_suspended) { + /* xusb_hub_suspend() has just directed one or more USB2 port(s) + * to U3 state, it takes 3ms to enter U3. */ + usleep_range(3000, 4000); + } + spin_lock_irqsave(&xhci->lock, flags); if (!xhci_hub_ports_suspended(&xhci->usb2_rhub) || @@ -1765,45 +1892,184 @@ static void tegra_xusb_restore_context(struct tegra_xusb *tegra) } } -static int tegra_xusb_enter_elpg(struct tegra_xusb *tegra, bool wakeup) +static enum usb_device_speed +tegra_xhci_portsc_to_speed(struct tegra_xusb *tegra, u32 portsc) +{ + if (DEV_LOWSPEED(portsc)) + return USB_SPEED_LOW; + else if (DEV_HIGHSPEED(portsc)) + return USB_SPEED_HIGH; + else if (DEV_FULLSPEED(portsc)) + return USB_SPEED_FULL; + else if (DEV_SUPERSPEED_ANY(portsc)) + return USB_SPEED_SUPER; + else + return USB_SPEED_UNKNOWN; +} + +static void tegra_xhci_enable_phy_sleepwalk_wake(struct tegra_xusb *tegra) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + enum usb_device_speed speed; + struct phy *phy; + int index, offset; + int i, j, k; + struct xhci_hub *rhub; + u32 portsc; + + for (i = 0, k = 0; i < tegra->soc->num_types; i++) { + if (strcmp(tegra->soc->phy_types[i].name, "usb3") == 0) + rhub = &xhci->usb3_rhub; + else + rhub = &xhci->usb2_rhub; + + if (strcmp(tegra->soc->phy_types[i].name, "hsic") == 0) + offset = tegra->soc->ports.usb2.count; + else + offset = 0; + + for (j = 0; j < tegra->soc->phy_types[i].num; j++) { + phy = tegra->phys[k++]; + + if (!phy) + continue; + + index = j + offset; + + if (index >= rhub->num_ports) + continue; + + if (!is_host_mode_phy(tegra, i, j)) + continue; + + portsc = readl(rhub->ports[index]->addr); + speed = tegra_xhci_portsc_to_speed(tegra, portsc); + tegra_xusb_padctl_enable_phy_sleepwalk(padctl, phy, + speed); + tegra_xusb_padctl_enable_phy_wake(padctl, phy); + } + } +} + +static void tegra_xhci_disable_phy_wake(struct tegra_xusb *tegra) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + int i; + + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + tegra_xusb_padctl_disable_phy_wake(padctl, tegra->phys[i]); + } +} + +static void tegra_xhci_disable_phy_sleepwalk(struct tegra_xusb *tegra) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + int i; + + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + tegra_xusb_padctl_disable_phy_sleepwalk(padctl, tegra->phys[i]); + } +} + +static int tegra_xusb_enter_elpg(struct tegra_xusb *tegra, bool runtime) { struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct device *dev = tegra->dev; + bool wakeup = runtime ? true : device_may_wakeup(dev); + unsigned int i; int err; + u32 usbcmd; + + dev_dbg(dev, "entering ELPG\n"); + + usbcmd = readl(&xhci->op_regs->command); + usbcmd &= ~CMD_EIE; + writel(usbcmd, &xhci->op_regs->command); err = tegra_xusb_check_ports(tegra); if (err < 0) { dev_err(tegra->dev, "not all ports suspended: %d\n", err); - return err; + goto out; } err = xhci_suspend(xhci, wakeup); if (err < 0) { dev_err(tegra->dev, "failed to suspend XHCI: %d\n", err); - return err; + goto out; } tegra_xusb_save_context(tegra); - tegra_xusb_phy_disable(tegra); + + if (wakeup) + tegra_xhci_enable_phy_sleepwalk_wake(tegra); + + tegra_xusb_powergate_partitions(tegra); + + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + phy_power_off(tegra->phys[i]); + if (!wakeup) + phy_exit(tegra->phys[i]); + } tegra_xusb_clk_disable(tegra); - return 0; +out: + if (!err) + dev_dbg(tegra->dev, "entering ELPG done\n"); + else { + usbcmd = readl(&xhci->op_regs->command); + usbcmd |= CMD_EIE; + writel(usbcmd, &xhci->op_regs->command); + + dev_dbg(tegra->dev, "entering ELPG failed\n"); + pm_runtime_mark_last_busy(tegra->dev); + } + + return err; } -static int tegra_xusb_exit_elpg(struct tegra_xusb *tegra, bool wakeup) +static int tegra_xusb_exit_elpg(struct tegra_xusb *tegra, bool runtime) { struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct device *dev = tegra->dev; + bool wakeup = runtime ? true : device_may_wakeup(dev); + unsigned int i; + u32 usbcmd; int err; + dev_dbg(dev, "exiting ELPG\n"); + pm_runtime_mark_last_busy(tegra->dev); + err = tegra_xusb_clk_enable(tegra); if (err < 0) { dev_err(tegra->dev, "failed to enable clocks: %d\n", err); - return err; + goto out; } - err = tegra_xusb_phy_enable(tegra); - if (err < 0) { - dev_err(tegra->dev, "failed to enable PHYs: %d\n", err); - goto disable_clk; + err = tegra_xusb_unpowergate_partitions(tegra); + if (err) + goto disable_clks; + + if (wakeup) + tegra_xhci_disable_phy_wake(tegra); + + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + if (!wakeup) + phy_init(tegra->phys[i]); + + phy_power_on(tegra->phys[i]); } tegra_xusb_config(tegra); @@ -1821,31 +2087,78 @@ static int tegra_xusb_exit_elpg(struct tegra_xusb *tegra, bool wakeup) goto disable_phy; } - err = xhci_resume(xhci, true); + if (wakeup) + tegra_xhci_disable_phy_sleepwalk(tegra); + + err = xhci_resume(xhci, 0); if (err < 0) { dev_err(tegra->dev, "failed to resume XHCI: %d\n", err); goto disable_phy; } - return 0; + usbcmd = readl(&xhci->op_regs->command); + usbcmd |= CMD_EIE; + writel(usbcmd, &xhci->op_regs->command); + + goto out; disable_phy: - tegra_xusb_phy_disable(tegra); -disable_clk: + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + phy_power_off(tegra->phys[i]); + if (!wakeup) + phy_exit(tegra->phys[i]); + } + tegra_xusb_powergate_partitions(tegra); +disable_clks: tegra_xusb_clk_disable(tegra); +out: + if (!err) + dev_dbg(dev, "exiting ELPG done\n"); + else + dev_dbg(dev, "exiting ELPG failed\n"); + return err; } static int tegra_xusb_suspend(struct device *dev) { struct tegra_xusb *tegra = dev_get_drvdata(dev); - bool wakeup = device_may_wakeup(dev); int err; synchronize_irq(tegra->mbox_irq); - mutex_lock(&tegra->lock); - err = tegra_xusb_enter_elpg(tegra, wakeup); + + if (pm_runtime_suspended(dev)) { + err = tegra_xusb_exit_elpg(tegra, true); + if (err < 0) + goto out; + } + + err = tegra_xusb_enter_elpg(tegra, false); + if (err < 0) { + if (pm_runtime_suspended(dev)) { + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } + + goto out; + } + +out: + if (!err) { + tegra->suspended = true; + pm_runtime_disable(dev); + + if (device_may_wakeup(dev)) { + if (enable_irq_wake(tegra->padctl_irq)) + dev_err(dev, "failed to enable padctl wakes\n"); + } + } + mutex_unlock(&tegra->lock); return err; @@ -1854,14 +2167,32 @@ static int tegra_xusb_suspend(struct device *dev) static int tegra_xusb_resume(struct device *dev) { struct tegra_xusb *tegra = dev_get_drvdata(dev); - bool wakeup = device_may_wakeup(dev); int err; mutex_lock(&tegra->lock); - err = tegra_xusb_exit_elpg(tegra, wakeup); + + if (!tegra->suspended) { + mutex_unlock(&tegra->lock); + return 0; + } + + err = tegra_xusb_exit_elpg(tegra, false); + if (err < 0) { + mutex_unlock(&tegra->lock); + return err; + } + + if (device_may_wakeup(dev)) { + if (disable_irq_wake(tegra->padctl_irq)) + dev_err(dev, "failed to disable padctl wakes\n"); + } + tegra->suspended = false; mutex_unlock(&tegra->lock); - return err; + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; } #endif
This commit implements the complete programming sequence for ELPG entry and exit. 1. At ELPG entry, invokes tegra_xusb_padctl_enable_phy_sleepwalk() and tegra_xusb_padctl_enable_phy_wake() to configure XUSB PADCTL sleepwalk and wake detection circuits to maintain USB lines level and respond to wake events (wake-on-connect, wake-on-disconnect, device-initiated-wake). 2. At ELPG exit, invokes tegra_xusb_padctl_disable_phy_sleepwalk() and tegra_xusb_padctl_disable_phy_wake() to disarm sleepwalk and wake detection circuits. At runtime suspend, XUSB host controller can enter ELPG to reduce power consumption. When XUSB PADCTL wake detection circuit detects a wake event, an interrupt will be raised. xhci-tegra driver then will invoke pm_runtime_resume() for xhci-tegra. Runtime resume could also be triggered by protocol drivers, this is the host-initiated-wake event. At runtime resume, xhci-tegra driver brings XUSB host controller out of ELPG to handle the wake events. The same ELPG enter/exit procedure will be performed for system suspend/resume path so USB devices can remain connected across SC7. Signed-off-by: JC Kuo <jckuo@nvidia.com> --- drivers/usb/host/xhci-tegra.c | 391 +++++++++++++++++++++++++++++++--- 1 file changed, 361 insertions(+), 30 deletions(-)