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@xxxxxxxxxx> > --- > 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 >
Attachment:
signature.asc
Description: PGP signature