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>
This commit is contained in:
Al Cooper 2020-01-03 13:18:02 -05:00 committed by Kishon Vijay Abraham I
parent 6597af4e48
commit f1c0db40a3
3 changed files with 63 additions and 3 deletions

View File

@ -58,6 +58,8 @@
#define USB_CTRL_USB_PM_SOFT_RESET_MASK 0x40000000 /* option */ #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_MASK 0x30000000 /* option */
#define USB_CTRL_USB_PM_USB20_HC_RESETB_VAR_MASK 0x00300000 /* 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 0x60
#define USB_CTRL_USB30_CTL1_PHY3_PLL_SEQ_START_MASK 0x00000010 #define USB_CTRL_USB30_CTL1_PHY3_PLL_SEQ_START_MASK 0x00000010
#define USB_CTRL_USB30_CTL1_PHY3_RESETB_MASK 0x00010000 #define USB_CTRL_USB30_CTL1_PHY3_RESETB_MASK 0x00010000
@ -855,6 +857,10 @@ void brcm_usb_init_common(struct brcm_usb_init_params *params)
u32 reg; u32 reg;
void __iomem *ctrl = params->ctrl_regs; 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 */ /* Take USB out of power down */
if (USB_CTRL_MASK_FAMILY(params, PLL_CTL, PLL_IDDQ_PWRDN)) { if (USB_CTRL_MASK_FAMILY(params, PLL_CTL, PLL_IDDQ_PWRDN)) {
USB_CTRL_UNSET_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_usb_init_params *params)
USB_CTRL_SET(params->ctrl_regs, USB30_PCTL, PHY3_IDDQ_OVERRIDE); 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) void brcm_usb_set_family_map(struct brcm_usb_init_params *params)
{ {
int fam; int fam;

View File

@ -38,5 +38,6 @@ void brcm_usb_init_xhci(struct brcm_usb_init_params *ini);
void brcm_usb_uninit_common(struct brcm_usb_init_params *ini); 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_eohci(struct brcm_usb_init_params *ini);
void brcm_usb_uninit_xhci(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 */ #endif /* _USB_BRCM_COMMON_INIT_H */

View File

@ -57,11 +57,22 @@ struct brcm_usb_phy_data {
bool has_xhci; bool has_xhci;
struct clk *usb_20_clk; struct clk *usb_20_clk;
struct clk *usb_30_clk; struct clk *usb_30_clk;
struct clk *suspend_clk;
struct mutex mutex; /* serialize phy init */ struct mutex mutex; /* serialize phy init */
int init_count; int init_count;
int wake_irq;
struct brcm_usb_phy phys[BRCM_USB_PHY_ID_MAX]; 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) static int brcm_usb_phy_init(struct phy *gphy)
{ {
struct brcm_usb_phy *phy = phy_get_drvdata(gphy); struct brcm_usb_phy *phy = phy_get_drvdata(gphy);
@ -76,6 +87,7 @@ static int brcm_usb_phy_init(struct phy *gphy)
if (priv->init_count++ == 0) { if (priv->init_count++ == 0) {
clk_prepare_enable(priv->usb_20_clk); clk_prepare_enable(priv->usb_20_clk);
clk_prepare_enable(priv->usb_30_clk); clk_prepare_enable(priv->usb_30_clk);
clk_prepare_enable(priv->suspend_clk);
brcm_usb_init_common(&priv->ini); brcm_usb_init_common(&priv->ini);
} }
mutex_unlock(&priv->mutex); mutex_unlock(&priv->mutex);
@ -108,6 +120,7 @@ static int brcm_usb_phy_exit(struct phy *gphy)
brcm_usb_uninit_common(&priv->ini); brcm_usb_uninit_common(&priv->ini);
clk_disable_unprepare(priv->usb_20_clk); clk_disable_unprepare(priv->usb_20_clk);
clk_disable_unprepare(priv->usb_30_clk); clk_disable_unprepare(priv->usb_30_clk);
clk_disable_unprepare(priv->suspend_clk);
} }
mutex_unlock(&priv->mutex); mutex_unlock(&priv->mutex);
phy->inited = false; phy->inited = false;
@ -228,11 +241,12 @@ static const struct attribute_group brcm_usb_phy_group = {
.attrs = brcm_usb_phy_attrs, .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 brcm_usb_phy_data *priv,
struct device_node *dn) struct device_node *dn)
{ {
struct phy *gphy; struct device *dev = &pdev->dev;
struct phy *gphy = NULL;
int err; int err;
priv->usb_20_clk = of_clk_get_by_name(dn, "sw_usb"); priv->usb_20_clk = of_clk_get_by_name(dn, "sw_usb");
@ -275,6 +289,28 @@ static int brcm_usb_phy_dvr_init(struct device *dev,
if (err) if (err)
return 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; return 0;
} }
@ -335,7 +371,7 @@ static int brcm_usb_phy_probe(struct platform_device *pdev)
if (of_property_read_bool(dn, "brcm,has-eohci")) if (of_property_read_bool(dn, "brcm,has-eohci"))
priv->has_eohci = true; priv->has_eohci = true;
err = brcm_usb_phy_dvr_init(dev, priv, dn); err = brcm_usb_phy_dvr_init(pdev, priv, dn);
if (err) if (err)
return err; return err;
@ -386,10 +422,13 @@ static int brcm_usb_phy_suspend(struct device *dev)
if (priv->phys[BRCM_USB_PHY_2_0].inited) if (priv->phys[BRCM_USB_PHY_2_0].inited)
brcm_usb_uninit_eohci(&priv->ini); brcm_usb_uninit_eohci(&priv->ini);
brcm_usb_uninit_common(&priv->ini); brcm_usb_uninit_common(&priv->ini);
brcm_usb_wake_enable(&priv->ini, true);
if (priv->phys[BRCM_USB_PHY_3_0].inited) if (priv->phys[BRCM_USB_PHY_3_0].inited)
clk_disable_unprepare(priv->usb_30_clk); clk_disable_unprepare(priv->usb_30_clk);
if (priv->phys[BRCM_USB_PHY_2_0].inited) if (priv->phys[BRCM_USB_PHY_2_0].inited)
clk_disable_unprepare(priv->usb_20_clk); clk_disable_unprepare(priv->usb_20_clk);
if (priv->wake_irq >= 0)
enable_irq_wake(priv->wake_irq);
} }
return 0; return 0;
} }
@ -400,6 +439,7 @@ static int brcm_usb_phy_resume(struct device *dev)
clk_prepare_enable(priv->usb_20_clk); clk_prepare_enable(priv->usb_20_clk);
clk_prepare_enable(priv->usb_30_clk); clk_prepare_enable(priv->usb_30_clk);
brcm_usb_wake_enable(&priv->ini, false);
brcm_usb_init_ipp(&priv->ini); brcm_usb_init_ipp(&priv->ini);
/* /*
@ -407,6 +447,8 @@ static int brcm_usb_phy_resume(struct device *dev)
* Uninitialize anything that wasn't previously initialized. * Uninitialize anything that wasn't previously initialized.
*/ */
if (priv->init_count) { if (priv->init_count) {
if (priv->wake_irq >= 0)
disable_irq_wake(priv->wake_irq);
brcm_usb_init_common(&priv->ini); brcm_usb_init_common(&priv->ini);
if (priv->phys[BRCM_USB_PHY_2_0].inited) { if (priv->phys[BRCM_USB_PHY_2_0].inited) {
brcm_usb_init_eohci(&priv->ini); brcm_usb_init_eohci(&priv->ini);