206 lines
7.0 KiB
Diff
206 lines
7.0 KiB
Diff
|
From f1c0db40a3ade1f1a39e5794d728f2953d817322 Mon Sep 17 00:00:00 2001
|
||
|
From: Al Cooper <alcooperx@gmail.com>
|
||
|
Date: Fri, 3 Jan 2020 13:18:02 -0500
|
||
|
Subject: [PATCH] phy: usb: Add "wake on" functionality
|
||
|
|
||
|
Add the ability to handle USB wake events from USB devices when
|
||
|
in S2 mode. Typically there is some additional configuration
|
||
|
needed to tell the USB device to generate the wake event when
|
||
|
suspended but this varies with the different USB device classes.
|
||
|
For example, on USB Ethernet dongles, ethtool should be used to
|
||
|
enable the magic packet wake functionality in the dongle.
|
||
|
NOTE: This requires that the "power/wakeup" sysfs entry for
|
||
|
the USB device generating the wakeup be set to "enabled".
|
||
|
|
||
|
This functionality requires a special hardware sideband path that
|
||
|
will trigger the AON_PM_L2 interrupt needed to wake the system from
|
||
|
S2 even though the USB host controllers are in IDDQ (low power state)
|
||
|
and most USB related clocks are shut off. For the sideband signaling
|
||
|
to work we need to leave the usbx_freerun clock running, but this
|
||
|
clock consumes very little power by design. There's a bug in the
|
||
|
XHCI wake hardware so only EHCI/OHCI wake is currently supported.
|
||
|
|
||
|
Signed-off-by: Al Cooper <alcooperx@gmail.com>
|
||
|
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
|
||
|
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
|
||
|
---
|
||
|
drivers/phy/broadcom/phy-brcm-usb-init.c | 17 +++++++++
|
||
|
drivers/phy/broadcom/phy-brcm-usb-init.h | 1 +
|
||
|
drivers/phy/broadcom/phy-brcm-usb.c | 48 ++++++++++++++++++++++--
|
||
|
3 files changed, 63 insertions(+), 3 deletions(-)
|
||
|
|
||
|
--- a/drivers/phy/broadcom/phy-brcm-usb-init.c
|
||
|
+++ b/drivers/phy/broadcom/phy-brcm-usb-init.c
|
||
|
@@ -58,6 +58,8 @@
|
||
|
#define USB_CTRL_USB_PM_SOFT_RESET_MASK 0x40000000 /* option */
|
||
|
#define USB_CTRL_USB_PM_USB20_HC_RESETB_MASK 0x30000000 /* option */
|
||
|
#define USB_CTRL_USB_PM_USB20_HC_RESETB_VAR_MASK 0x00300000 /* option */
|
||
|
+#define USB_CTRL_USB_PM_RMTWKUP_EN_MASK 0x00000001
|
||
|
+#define USB_CTRL_USB_PM_STATUS 0x38
|
||
|
#define USB_CTRL_USB30_CTL1 0x60
|
||
|
#define USB_CTRL_USB30_CTL1_PHY3_PLL_SEQ_START_MASK 0x00000010
|
||
|
#define USB_CTRL_USB30_CTL1_PHY3_RESETB_MASK 0x00010000
|
||
|
@@ -855,6 +857,10 @@ void brcm_usb_init_common(struct brcm_us
|
||
|
u32 reg;
|
||
|
void __iomem *ctrl = params->ctrl_regs;
|
||
|
|
||
|
+ /* Clear any pending wake conditions */
|
||
|
+ reg = brcmusb_readl(USB_CTRL_REG(ctrl, USB_PM_STATUS));
|
||
|
+ brcmusb_writel(reg, USB_CTRL_REG(ctrl, USB_PM_STATUS));
|
||
|
+
|
||
|
/* Take USB out of power down */
|
||
|
if (USB_CTRL_MASK_FAMILY(params, PLL_CTL, PLL_IDDQ_PWRDN)) {
|
||
|
USB_CTRL_UNSET_FAMILY(params, PLL_CTL, PLL_IDDQ_PWRDN);
|
||
|
@@ -1010,6 +1016,17 @@ void brcm_usb_uninit_xhci(struct brcm_us
|
||
|
USB_CTRL_SET(params->ctrl_regs, USB30_PCTL, PHY3_IDDQ_OVERRIDE);
|
||
|
}
|
||
|
|
||
|
+void brcm_usb_wake_enable(struct brcm_usb_init_params *params,
|
||
|
+ int enable)
|
||
|
+{
|
||
|
+ void __iomem *ctrl = params->ctrl_regs;
|
||
|
+
|
||
|
+ if (enable)
|
||
|
+ USB_CTRL_SET(ctrl, USB_PM, RMTWKUP_EN);
|
||
|
+ else
|
||
|
+ USB_CTRL_UNSET(ctrl, USB_PM, RMTWKUP_EN);
|
||
|
+}
|
||
|
+
|
||
|
void brcm_usb_set_family_map(struct brcm_usb_init_params *params)
|
||
|
{
|
||
|
int fam;
|
||
|
--- a/drivers/phy/broadcom/phy-brcm-usb-init.h
|
||
|
+++ b/drivers/phy/broadcom/phy-brcm-usb-init.h
|
||
|
@@ -38,5 +38,6 @@ void brcm_usb_init_xhci(struct brcm_usb_
|
||
|
void brcm_usb_uninit_common(struct brcm_usb_init_params *ini);
|
||
|
void brcm_usb_uninit_eohci(struct brcm_usb_init_params *ini);
|
||
|
void brcm_usb_uninit_xhci(struct brcm_usb_init_params *ini);
|
||
|
+void brcm_usb_wake_enable(struct brcm_usb_init_params *params, int enable);
|
||
|
|
||
|
#endif /* _USB_BRCM_COMMON_INIT_H */
|
||
|
--- a/drivers/phy/broadcom/phy-brcm-usb.c
|
||
|
+++ b/drivers/phy/broadcom/phy-brcm-usb.c
|
||
|
@@ -57,11 +57,22 @@ struct brcm_usb_phy_data {
|
||
|
bool has_xhci;
|
||
|
struct clk *usb_20_clk;
|
||
|
struct clk *usb_30_clk;
|
||
|
+ struct clk *suspend_clk;
|
||
|
struct mutex mutex; /* serialize phy init */
|
||
|
int init_count;
|
||
|
+ int wake_irq;
|
||
|
struct brcm_usb_phy phys[BRCM_USB_PHY_ID_MAX];
|
||
|
};
|
||
|
|
||
|
+static irqreturn_t brcm_usb_phy_wake_isr(int irq, void *dev_id)
|
||
|
+{
|
||
|
+ struct phy *gphy = dev_id;
|
||
|
+
|
||
|
+ pm_wakeup_event(&gphy->dev, 0);
|
||
|
+
|
||
|
+ return IRQ_HANDLED;
|
||
|
+}
|
||
|
+
|
||
|
static int brcm_usb_phy_init(struct phy *gphy)
|
||
|
{
|
||
|
struct brcm_usb_phy *phy = phy_get_drvdata(gphy);
|
||
|
@@ -76,6 +87,7 @@ static int brcm_usb_phy_init(struct phy
|
||
|
if (priv->init_count++ == 0) {
|
||
|
clk_prepare_enable(priv->usb_20_clk);
|
||
|
clk_prepare_enable(priv->usb_30_clk);
|
||
|
+ clk_prepare_enable(priv->suspend_clk);
|
||
|
brcm_usb_init_common(&priv->ini);
|
||
|
}
|
||
|
mutex_unlock(&priv->mutex);
|
||
|
@@ -108,6 +120,7 @@ static int brcm_usb_phy_exit(struct phy
|
||
|
brcm_usb_uninit_common(&priv->ini);
|
||
|
clk_disable_unprepare(priv->usb_20_clk);
|
||
|
clk_disable_unprepare(priv->usb_30_clk);
|
||
|
+ clk_disable_unprepare(priv->suspend_clk);
|
||
|
}
|
||
|
mutex_unlock(&priv->mutex);
|
||
|
phy->inited = false;
|
||
|
@@ -228,11 +241,12 @@ static const struct attribute_group brcm
|
||
|
.attrs = brcm_usb_phy_attrs,
|
||
|
};
|
||
|
|
||
|
-static int brcm_usb_phy_dvr_init(struct device *dev,
|
||
|
+static int brcm_usb_phy_dvr_init(struct platform_device *pdev,
|
||
|
struct brcm_usb_phy_data *priv,
|
||
|
struct device_node *dn)
|
||
|
{
|
||
|
- struct phy *gphy;
|
||
|
+ struct device *dev = &pdev->dev;
|
||
|
+ struct phy *gphy = NULL;
|
||
|
int err;
|
||
|
|
||
|
priv->usb_20_clk = of_clk_get_by_name(dn, "sw_usb");
|
||
|
@@ -275,6 +289,28 @@ static int brcm_usb_phy_dvr_init(struct
|
||
|
if (err)
|
||
|
return err;
|
||
|
}
|
||
|
+
|
||
|
+ priv->suspend_clk = clk_get(dev, "usb0_freerun");
|
||
|
+ if (IS_ERR(priv->suspend_clk)) {
|
||
|
+ dev_err(dev, "Suspend Clock not found in Device Tree\n");
|
||
|
+ priv->suspend_clk = NULL;
|
||
|
+ }
|
||
|
+
|
||
|
+ priv->wake_irq = platform_get_irq_byname(pdev, "wake");
|
||
|
+ if (priv->wake_irq < 0)
|
||
|
+ priv->wake_irq = platform_get_irq_byname(pdev, "wakeup");
|
||
|
+ if (priv->wake_irq >= 0) {
|
||
|
+ err = devm_request_irq(dev, priv->wake_irq,
|
||
|
+ brcm_usb_phy_wake_isr, 0,
|
||
|
+ dev_name(dev), gphy);
|
||
|
+ if (err < 0)
|
||
|
+ return err;
|
||
|
+ device_set_wakeup_capable(dev, 1);
|
||
|
+ } else {
|
||
|
+ dev_info(dev,
|
||
|
+ "Wake interrupt missing, system wake not supported\n");
|
||
|
+ }
|
||
|
+
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
@@ -335,7 +371,7 @@ static int brcm_usb_phy_probe(struct pla
|
||
|
if (of_property_read_bool(dn, "brcm,has-eohci"))
|
||
|
priv->has_eohci = true;
|
||
|
|
||
|
- err = brcm_usb_phy_dvr_init(dev, priv, dn);
|
||
|
+ err = brcm_usb_phy_dvr_init(pdev, priv, dn);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
@@ -386,10 +422,13 @@ static int brcm_usb_phy_suspend(struct d
|
||
|
if (priv->phys[BRCM_USB_PHY_2_0].inited)
|
||
|
brcm_usb_uninit_eohci(&priv->ini);
|
||
|
brcm_usb_uninit_common(&priv->ini);
|
||
|
+ brcm_usb_wake_enable(&priv->ini, true);
|
||
|
if (priv->phys[BRCM_USB_PHY_3_0].inited)
|
||
|
clk_disable_unprepare(priv->usb_30_clk);
|
||
|
if (priv->phys[BRCM_USB_PHY_2_0].inited)
|
||
|
clk_disable_unprepare(priv->usb_20_clk);
|
||
|
+ if (priv->wake_irq >= 0)
|
||
|
+ enable_irq_wake(priv->wake_irq);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
@@ -400,6 +439,7 @@ static int brcm_usb_phy_resume(struct de
|
||
|
|
||
|
clk_prepare_enable(priv->usb_20_clk);
|
||
|
clk_prepare_enable(priv->usb_30_clk);
|
||
|
+ brcm_usb_wake_enable(&priv->ini, false);
|
||
|
brcm_usb_init_ipp(&priv->ini);
|
||
|
|
||
|
/*
|
||
|
@@ -407,6 +447,8 @@ static int brcm_usb_phy_resume(struct de
|
||
|
* Uninitialize anything that wasn't previously initialized.
|
||
|
*/
|
||
|
if (priv->init_count) {
|
||
|
+ if (priv->wake_irq >= 0)
|
||
|
+ disable_irq_wake(priv->wake_irq);
|
||
|
brcm_usb_init_common(&priv->ini);
|
||
|
if (priv->phys[BRCM_USB_PHY_2_0].inited) {
|
||
|
brcm_usb_init_eohci(&priv->ini);
|