b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame] | 1 | From f1c0db40a3ade1f1a39e5794d728f2953d817322 Mon Sep 17 00:00:00 2001 |
| 2 | From: Al Cooper <alcooperx@gmail.com> |
| 3 | Date: Fri, 3 Jan 2020 13:18:02 -0500 |
| 4 | Subject: [PATCH] phy: usb: Add "wake on" functionality |
| 5 | |
| 6 | Add the ability to handle USB wake events from USB devices when |
| 7 | in S2 mode. Typically there is some additional configuration |
| 8 | needed to tell the USB device to generate the wake event when |
| 9 | suspended but this varies with the different USB device classes. |
| 10 | For example, on USB Ethernet dongles, ethtool should be used to |
| 11 | enable the magic packet wake functionality in the dongle. |
| 12 | NOTE: This requires that the "power/wakeup" sysfs entry for |
| 13 | the USB device generating the wakeup be set to "enabled". |
| 14 | |
| 15 | This functionality requires a special hardware sideband path that |
| 16 | will trigger the AON_PM_L2 interrupt needed to wake the system from |
| 17 | S2 even though the USB host controllers are in IDDQ (low power state) |
| 18 | and most USB related clocks are shut off. For the sideband signaling |
| 19 | to work we need to leave the usbx_freerun clock running, but this |
| 20 | clock consumes very little power by design. There's a bug in the |
| 21 | XHCI wake hardware so only EHCI/OHCI wake is currently supported. |
| 22 | |
| 23 | Signed-off-by: Al Cooper <alcooperx@gmail.com> |
| 24 | Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> |
| 25 | Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com> |
| 26 | --- |
| 27 | drivers/phy/broadcom/phy-brcm-usb-init.c | 17 +++++++++ |
| 28 | drivers/phy/broadcom/phy-brcm-usb-init.h | 1 + |
| 29 | drivers/phy/broadcom/phy-brcm-usb.c | 48 ++++++++++++++++++++++-- |
| 30 | 3 files changed, 63 insertions(+), 3 deletions(-) |
| 31 | |
| 32 | --- a/drivers/phy/broadcom/phy-brcm-usb-init.c |
| 33 | +++ b/drivers/phy/broadcom/phy-brcm-usb-init.c |
| 34 | @@ -58,6 +58,8 @@ |
| 35 | #define USB_CTRL_USB_PM_SOFT_RESET_MASK 0x40000000 /* option */ |
| 36 | #define USB_CTRL_USB_PM_USB20_HC_RESETB_MASK 0x30000000 /* option */ |
| 37 | #define USB_CTRL_USB_PM_USB20_HC_RESETB_VAR_MASK 0x00300000 /* option */ |
| 38 | +#define USB_CTRL_USB_PM_RMTWKUP_EN_MASK 0x00000001 |
| 39 | +#define USB_CTRL_USB_PM_STATUS 0x38 |
| 40 | #define USB_CTRL_USB30_CTL1 0x60 |
| 41 | #define USB_CTRL_USB30_CTL1_PHY3_PLL_SEQ_START_MASK 0x00000010 |
| 42 | #define USB_CTRL_USB30_CTL1_PHY3_RESETB_MASK 0x00010000 |
| 43 | @@ -855,6 +857,10 @@ void brcm_usb_init_common(struct brcm_us |
| 44 | u32 reg; |
| 45 | void __iomem *ctrl = params->ctrl_regs; |
| 46 | |
| 47 | + /* Clear any pending wake conditions */ |
| 48 | + reg = brcmusb_readl(USB_CTRL_REG(ctrl, USB_PM_STATUS)); |
| 49 | + brcmusb_writel(reg, USB_CTRL_REG(ctrl, USB_PM_STATUS)); |
| 50 | + |
| 51 | /* Take USB out of power down */ |
| 52 | if (USB_CTRL_MASK_FAMILY(params, PLL_CTL, PLL_IDDQ_PWRDN)) { |
| 53 | USB_CTRL_UNSET_FAMILY(params, PLL_CTL, PLL_IDDQ_PWRDN); |
| 54 | @@ -1010,6 +1016,17 @@ void brcm_usb_uninit_xhci(struct brcm_us |
| 55 | USB_CTRL_SET(params->ctrl_regs, USB30_PCTL, PHY3_IDDQ_OVERRIDE); |
| 56 | } |
| 57 | |
| 58 | +void brcm_usb_wake_enable(struct brcm_usb_init_params *params, |
| 59 | + int enable) |
| 60 | +{ |
| 61 | + void __iomem *ctrl = params->ctrl_regs; |
| 62 | + |
| 63 | + if (enable) |
| 64 | + USB_CTRL_SET(ctrl, USB_PM, RMTWKUP_EN); |
| 65 | + else |
| 66 | + USB_CTRL_UNSET(ctrl, USB_PM, RMTWKUP_EN); |
| 67 | +} |
| 68 | + |
| 69 | void brcm_usb_set_family_map(struct brcm_usb_init_params *params) |
| 70 | { |
| 71 | int fam; |
| 72 | --- a/drivers/phy/broadcom/phy-brcm-usb-init.h |
| 73 | +++ b/drivers/phy/broadcom/phy-brcm-usb-init.h |
| 74 | @@ -38,5 +38,6 @@ void brcm_usb_init_xhci(struct brcm_usb_ |
| 75 | void brcm_usb_uninit_common(struct brcm_usb_init_params *ini); |
| 76 | void brcm_usb_uninit_eohci(struct brcm_usb_init_params *ini); |
| 77 | void brcm_usb_uninit_xhci(struct brcm_usb_init_params *ini); |
| 78 | +void brcm_usb_wake_enable(struct brcm_usb_init_params *params, int enable); |
| 79 | |
| 80 | #endif /* _USB_BRCM_COMMON_INIT_H */ |
| 81 | --- a/drivers/phy/broadcom/phy-brcm-usb.c |
| 82 | +++ b/drivers/phy/broadcom/phy-brcm-usb.c |
| 83 | @@ -57,11 +57,22 @@ struct brcm_usb_phy_data { |
| 84 | bool has_xhci; |
| 85 | struct clk *usb_20_clk; |
| 86 | struct clk *usb_30_clk; |
| 87 | + struct clk *suspend_clk; |
| 88 | struct mutex mutex; /* serialize phy init */ |
| 89 | int init_count; |
| 90 | + int wake_irq; |
| 91 | struct brcm_usb_phy phys[BRCM_USB_PHY_ID_MAX]; |
| 92 | }; |
| 93 | |
| 94 | +static irqreturn_t brcm_usb_phy_wake_isr(int irq, void *dev_id) |
| 95 | +{ |
| 96 | + struct phy *gphy = dev_id; |
| 97 | + |
| 98 | + pm_wakeup_event(&gphy->dev, 0); |
| 99 | + |
| 100 | + return IRQ_HANDLED; |
| 101 | +} |
| 102 | + |
| 103 | static int brcm_usb_phy_init(struct phy *gphy) |
| 104 | { |
| 105 | struct brcm_usb_phy *phy = phy_get_drvdata(gphy); |
| 106 | @@ -76,6 +87,7 @@ static int brcm_usb_phy_init(struct phy |
| 107 | if (priv->init_count++ == 0) { |
| 108 | clk_prepare_enable(priv->usb_20_clk); |
| 109 | clk_prepare_enable(priv->usb_30_clk); |
| 110 | + clk_prepare_enable(priv->suspend_clk); |
| 111 | brcm_usb_init_common(&priv->ini); |
| 112 | } |
| 113 | mutex_unlock(&priv->mutex); |
| 114 | @@ -108,6 +120,7 @@ static int brcm_usb_phy_exit(struct phy |
| 115 | brcm_usb_uninit_common(&priv->ini); |
| 116 | clk_disable_unprepare(priv->usb_20_clk); |
| 117 | clk_disable_unprepare(priv->usb_30_clk); |
| 118 | + clk_disable_unprepare(priv->suspend_clk); |
| 119 | } |
| 120 | mutex_unlock(&priv->mutex); |
| 121 | phy->inited = false; |
| 122 | @@ -228,11 +241,12 @@ static const struct attribute_group brcm |
| 123 | .attrs = brcm_usb_phy_attrs, |
| 124 | }; |
| 125 | |
| 126 | -static int brcm_usb_phy_dvr_init(struct device *dev, |
| 127 | +static int brcm_usb_phy_dvr_init(struct platform_device *pdev, |
| 128 | struct brcm_usb_phy_data *priv, |
| 129 | struct device_node *dn) |
| 130 | { |
| 131 | - struct phy *gphy; |
| 132 | + struct device *dev = &pdev->dev; |
| 133 | + struct phy *gphy = NULL; |
| 134 | int err; |
| 135 | |
| 136 | priv->usb_20_clk = of_clk_get_by_name(dn, "sw_usb"); |
| 137 | @@ -275,6 +289,28 @@ static int brcm_usb_phy_dvr_init(struct |
| 138 | if (err) |
| 139 | return err; |
| 140 | } |
| 141 | + |
| 142 | + priv->suspend_clk = clk_get(dev, "usb0_freerun"); |
| 143 | + if (IS_ERR(priv->suspend_clk)) { |
| 144 | + dev_err(dev, "Suspend Clock not found in Device Tree\n"); |
| 145 | + priv->suspend_clk = NULL; |
| 146 | + } |
| 147 | + |
| 148 | + priv->wake_irq = platform_get_irq_byname(pdev, "wake"); |
| 149 | + if (priv->wake_irq < 0) |
| 150 | + priv->wake_irq = platform_get_irq_byname(pdev, "wakeup"); |
| 151 | + if (priv->wake_irq >= 0) { |
| 152 | + err = devm_request_irq(dev, priv->wake_irq, |
| 153 | + brcm_usb_phy_wake_isr, 0, |
| 154 | + dev_name(dev), gphy); |
| 155 | + if (err < 0) |
| 156 | + return err; |
| 157 | + device_set_wakeup_capable(dev, 1); |
| 158 | + } else { |
| 159 | + dev_info(dev, |
| 160 | + "Wake interrupt missing, system wake not supported\n"); |
| 161 | + } |
| 162 | + |
| 163 | return 0; |
| 164 | } |
| 165 | |
| 166 | @@ -335,7 +371,7 @@ static int brcm_usb_phy_probe(struct pla |
| 167 | if (of_property_read_bool(dn, "brcm,has-eohci")) |
| 168 | priv->has_eohci = true; |
| 169 | |
| 170 | - err = brcm_usb_phy_dvr_init(dev, priv, dn); |
| 171 | + err = brcm_usb_phy_dvr_init(pdev, priv, dn); |
| 172 | if (err) |
| 173 | return err; |
| 174 | |
| 175 | @@ -386,10 +422,13 @@ static int brcm_usb_phy_suspend(struct d |
| 176 | if (priv->phys[BRCM_USB_PHY_2_0].inited) |
| 177 | brcm_usb_uninit_eohci(&priv->ini); |
| 178 | brcm_usb_uninit_common(&priv->ini); |
| 179 | + brcm_usb_wake_enable(&priv->ini, true); |
| 180 | if (priv->phys[BRCM_USB_PHY_3_0].inited) |
| 181 | clk_disable_unprepare(priv->usb_30_clk); |
| 182 | if (priv->phys[BRCM_USB_PHY_2_0].inited) |
| 183 | clk_disable_unprepare(priv->usb_20_clk); |
| 184 | + if (priv->wake_irq >= 0) |
| 185 | + enable_irq_wake(priv->wake_irq); |
| 186 | } |
| 187 | return 0; |
| 188 | } |
| 189 | @@ -400,6 +439,7 @@ static int brcm_usb_phy_resume(struct de |
| 190 | |
| 191 | clk_prepare_enable(priv->usb_20_clk); |
| 192 | clk_prepare_enable(priv->usb_30_clk); |
| 193 | + brcm_usb_wake_enable(&priv->ini, false); |
| 194 | brcm_usb_init_ipp(&priv->ini); |
| 195 | |
| 196 | /* |
| 197 | @@ -407,6 +447,8 @@ static int brcm_usb_phy_resume(struct de |
| 198 | * Uninitialize anything that wasn't previously initialized. |
| 199 | */ |
| 200 | if (priv->init_count) { |
| 201 | + if (priv->wake_irq >= 0) |
| 202 | + disable_irq_wake(priv->wake_irq); |
| 203 | brcm_usb_init_common(&priv->ini); |
| 204 | if (priv->phys[BRCM_USB_PHY_2_0].inited) { |
| 205 | brcm_usb_init_eohci(&priv->ini); |