[PATCH 09/12] phy: tegra: xusb: t186: support wake and sleepwalk

From: JC Kuo
Date: Thu Aug 27 2020 - 21:12:56 EST


This commit implements Tegra186/Tegra194 XUSB PADCTL/AO wake and
sleepwalk operations.

Signed-off-by: JC Kuo <jckuo@xxxxxxxxxx>
---
drivers/phy/tegra/xusb-tegra186.c | 656 ++++++++++++++++++++++++++++++
1 file changed, 656 insertions(+)

diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c
index 5d64f69b39a9..ffa6bfb1e1ee 100644
--- a/drivers/phy/tegra/xusb-tegra186.c
+++ b/drivers/phy/tegra/xusb-tegra186.c
@@ -113,6 +113,117 @@
#define ID_OVERRIDE_FLOATING ID_OVERRIDE(8)
#define ID_OVERRIDE_GROUNDED ID_OVERRIDE(0)

+/* XUSB AO registers */
+#define XUSB_AO_USB_DEBOUNCE_DEL (0x4)
+#define UHSIC_LINE_DEB_CNT(x) (((x) & 0xf) << 4)
+#define UTMIP_LINE_DEB_CNT(x) ((x) & 0xf)
+
+#define XUSB_AO_UTMIP_TRIGGERS(x) (0x40 + (x) * 4)
+#define CLR_WALK_PTR (1 << 0)
+#define CAP_CFG (1 << 1)
+#define CLR_WAKE_ALARM (1 << 3)
+
+#define XUSB_AO_UHSIC_TRIGGERS(x) (0x60 + (x) * 4)
+#define HSIC_CLR_WALK_PTR (1 << 0)
+#define HSIC_CLR_WAKE_ALARM (1 << 3)
+#define HSIC_CAP_CFG (1 << 4)
+
+#define XUSB_AO_UTMIP_SAVED_STATE(x) (0x70 + (x) * 4)
+#define SPEED(x) ((x) & 0x3)
+#define UTMI_HS SPEED(0)
+#define UTMI_FS SPEED(1)
+#define UTMI_LS SPEED(2)
+#define UTMI_RST SPEED(3)
+
+#define XUSB_AO_UHSIC_SAVED_STATE(x) (0x90 + (x) * 4)
+#define MODE(x) ((x) & 0x1)
+#define MODE_HS MODE(0)
+#define MODE_RST MODE(1)
+
+#define XUSB_AO_UTMIP_SLEEPWALK_CFG(x) (0xd0 + (x) * 4)
+#define XUSB_AO_UHSIC_SLEEPWALK_CFG(x) (0xf0 + (x) * 4)
+#define FAKE_USBOP_VAL (1 << 0)
+#define FAKE_USBON_VAL (1 << 1)
+#define FAKE_USBOP_EN (1 << 2)
+#define FAKE_USBON_EN (1 << 3)
+#define FAKE_STROBE_VAL (1 << 0)
+#define FAKE_DATA_VAL (1 << 1)
+#define FAKE_STROBE_EN (1 << 2)
+#define FAKE_DATA_EN (1 << 3)
+#define WAKE_WALK_EN (1 << 14)
+#define MASTER_ENABLE (1 << 15)
+#define LINEVAL_WALK_EN (1 << 16)
+#define WAKE_VAL(x) (((x) & 0xf) << 17)
+#define WAKE_VAL_NONE WAKE_VAL(12)
+#define WAKE_VAL_ANY WAKE_VAL(15)
+#define WAKE_VAL_DS10 WAKE_VAL(2)
+#define LINE_WAKEUP_EN (1 << 21)
+#define MASTER_CFG_SEL (1 << 22)
+
+#define XUSB_AO_UTMIP_SLEEPWALK(x) (0x100 + (x) * 4)
+/* phase A */
+#define USBOP_RPD_A (1 << 0)
+#define USBON_RPD_A (1 << 1)
+#define AP_A (1 << 4)
+#define AN_A (1 << 5)
+#define HIGHZ_A (1 << 6)
+/* phase B */
+#define USBOP_RPD_B (1 << 8)
+#define USBON_RPD_B (1 << 9)
+#define AP_B (1 << 12)
+#define AN_B (1 << 13)
+#define HIGHZ_B (1 << 14)
+/* phase C */
+#define USBOP_RPD_C (1 << 16)
+#define USBON_RPD_C (1 << 17)
+#define AP_C (1 << 20)
+#define AN_C (1 << 21)
+#define HIGHZ_C (1 << 22)
+/* phase D */
+#define USBOP_RPD_D (1 << 24)
+#define USBON_RPD_D (1 << 25)
+#define AP_D (1 << 28)
+#define AN_D (1 << 29)
+#define HIGHZ_D (1 << 30)
+
+#define XUSB_AO_UHSIC_SLEEPWALK(x) (0x120 + (x) * 4)
+/* phase A */
+#define RPD_STROBE_A (1 << 0)
+#define RPD_DATA0_A (1 << 1)
+#define RPU_STROBE_A (1 << 2)
+#define RPU_DATA0_A (1 << 3)
+/* phase B */
+#define RPD_STROBE_B (1 << 8)
+#define RPD_DATA0_B (1 << 9)
+#define RPU_STROBE_B (1 << 10)
+#define RPU_DATA0_B (1 << 11)
+/* phase C */
+#define RPD_STROBE_C (1 << 16)
+#define RPD_DATA0_C (1 << 17)
+#define RPU_STROBE_C (1 << 18)
+#define RPU_DATA0_C (1 << 19)
+/* phase D */
+#define RPD_STROBE_D (1 << 24)
+#define RPD_DATA0_D (1 << 25)
+#define RPU_STROBE_D (1 << 26)
+#define RPU_DATA0_D (1 << 27)
+
+#define XUSB_AO_UTMIP_PAD_CFG(x) (0x130 + (x) * 4)
+#define FSLS_USE_XUSB_AO (1 << 3)
+#define TRK_CTRL_USE_XUSB_AO (1 << 4)
+#define RPD_CTRL_USE_XUSB_AO (1 << 5)
+#define RPU_USE_XUSB_AO (1 << 6)
+#define VREG_USE_XUSB_AO (1 << 7)
+#define USBOP_VAL_PD (1 << 8)
+#define USBON_VAL_PD (1 << 9)
+#define E_DPD_OVRD_EN (1 << 10)
+#define E_DPD_OVRD_VAL (1 << 11)
+
+#define XUSB_AO_UHSIC_PAD_CFG(x) (0x150 + (x) * 4)
+#define STROBE_VAL_PD (1 << 0)
+#define DATA0_VAL_PD (1 << 1)
+#define USE_XUSB_AO (1 << 4)
+
#define TEGRA186_LANE(_name, _offset, _shift, _mask, _type) \
{ \
.name = _name, \
@@ -130,16 +241,43 @@ struct tegra_xusb_fuse_calibration {
u32 rpd_ctrl;
};

+struct tegra186_xusb_padctl_context {
+ u32 vbus_id;
+ u32 usb2_pad_mux;
+ u32 usb2_port_cap;
+ u32 ss_port_cap;
+};
+
struct tegra186_xusb_padctl {
struct tegra_xusb_padctl base;
+ void __iomem *ao_regs;

struct tegra_xusb_fuse_calibration calib;

/* UTMI bias and tracking */
struct clk *usb2_trk_clk;
unsigned int bias_pad_enable;
+
+ /* padctl context */
+ struct tegra186_xusb_padctl_context context;
};

+static inline void ao_writel(struct tegra186_xusb_padctl *priv, u32 value,
+ unsigned long offset)
+{
+ dev_dbg(priv->base.dev, "ao %08lx < %08x\n", offset, value);
+ writel(value, priv->ao_regs + offset);
+}
+
+static inline u32 ao_readl(struct tegra186_xusb_padctl *priv,
+ unsigned long offset)
+{
+ u32 value = readl(priv->ao_regs + offset);
+
+ dev_dbg(priv->base.dev, "ao %08lx > %08x\n", offset, value);
+ return value;
+}
+
static inline struct tegra186_xusb_padctl *
to_tegra186_xusb_padctl(struct tegra_xusb_padctl *padctl)
{
@@ -475,6 +613,238 @@ static int tegra186_utmi_phy_power_off(struct phy *phy)
return 0;
}

+static int tegra186_utmi_phy_enable_sleepwalk(struct phy *phy,
+ enum usb_device_speed speed)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ struct tegra186_xusb_padctl *priv = to_tegra186_xusb_padctl(padctl);
+ unsigned int index = lane->index;
+ struct device *dev = padctl->dev;
+ u32 value;
+
+ dev_dbg(dev, "phy enable sleepwalk UTMI %d speed %d\n", index, speed);
+
+ mutex_lock(&padctl->lock);
+
+ /* ensure sleepwalk logic is disabled */
+ value = ao_readl(priv, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+ value &= ~MASTER_ENABLE;
+ ao_writel(priv, value, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+
+ /* ensure sleepwalk logics are in low power mode */
+ value = ao_readl(priv, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+ value |= MASTER_CFG_SEL;
+ ao_writel(priv, value, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+
+ /* set debounce time */
+ value = ao_readl(priv, XUSB_AO_USB_DEBOUNCE_DEL);
+ value &= ~UTMIP_LINE_DEB_CNT(~0);
+ value |= UTMIP_LINE_DEB_CNT(1);
+ ao_writel(priv, value, XUSB_AO_USB_DEBOUNCE_DEL);
+
+ /* ensure fake events of sleepwalk logic are desiabled */
+ value = ao_readl(priv, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+ value &= ~(FAKE_USBOP_VAL | FAKE_USBON_VAL |
+ FAKE_USBOP_EN | FAKE_USBON_EN);
+ ao_writel(priv, value, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+
+ /* ensure wake events of sleepwalk logic are not latched */
+ value = ao_readl(priv, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+ value &= ~LINE_WAKEUP_EN;
+ ao_writel(priv, value, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+
+ /* disable wake event triggers of sleepwalk logic */
+ value = ao_readl(priv, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+ value &= ~WAKE_VAL(~0);
+ value |= WAKE_VAL_NONE;
+ ao_writel(priv, value, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+
+ /* power down the line state detectors of the pad */
+ value = ao_readl(priv, XUSB_AO_UTMIP_PAD_CFG(index));
+ value |= (USBOP_VAL_PD | USBON_VAL_PD);
+ ao_writel(priv, value, XUSB_AO_UTMIP_PAD_CFG(index));
+
+ /* save state per speed */
+ value = ao_readl(priv, XUSB_AO_UTMIP_SAVED_STATE(index));
+ value &= ~SPEED(~0);
+ if (speed == USB_SPEED_HIGH)
+ value |= UTMI_HS;
+ else if (speed == USB_SPEED_FULL)
+ value |= UTMI_FS;
+ else if (speed == USB_SPEED_LOW)
+ value |= UTMI_LS;
+ else
+ value |= UTMI_RST;
+ ao_writel(priv, value, XUSB_AO_UTMIP_SAVED_STATE(index));
+
+ /* enable the trigger of the sleepwalk logic */
+ value = ao_readl(priv, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+ value |= LINEVAL_WALK_EN;
+ value &= ~WAKE_WALK_EN;
+ ao_writel(priv, value, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+
+ /* reset the walk pointer and clear the alarm of the sleepwalk logic,
+ * as well as capture the configuration of the USB2.0 pad
+ */
+ value = ao_readl(priv, XUSB_AO_UTMIP_TRIGGERS(index));
+ value |= (CLR_WALK_PTR | CLR_WAKE_ALARM | CAP_CFG);
+ ao_writel(priv, value, XUSB_AO_UTMIP_TRIGGERS(index));
+
+ /* setup the pull-ups and pull-downs of the signals during the four
+ * stages of sleepwalk.
+ * if device is connected, program sleepwalk logic to maintain a J and
+ * keep driving K upon seeing remote wake.
+ */
+ value = (USBOP_RPD_A | USBOP_RPD_B | USBOP_RPD_C | USBOP_RPD_D);
+ value |= (USBON_RPD_A | USBON_RPD_B | USBON_RPD_C | USBON_RPD_D);
+ if (speed == USB_SPEED_UNKNOWN) {
+ value |= (HIGHZ_A | HIGHZ_B | HIGHZ_C | HIGHZ_D);
+ } else if ((speed == USB_SPEED_HIGH) || (speed == USB_SPEED_FULL)) {
+ /* J state: D+/D- = high/low, K state: D+/D- = low/high */
+ value |= HIGHZ_A;
+ value |= (AP_A);
+ value |= (AN_B | AN_C | AN_D);
+ } else if (speed == USB_SPEED_LOW) {
+ /* J state: D+/D- = low/high, K state: D+/D- = high/low */
+ value |= HIGHZ_A;
+ value |= AN_A;
+ value |= (AP_B | AP_C | AP_D);
+ }
+ ao_writel(priv, value, XUSB_AO_UTMIP_SLEEPWALK(index));
+
+ /* power up the line state detectors of the pad */
+ value = ao_readl(priv, XUSB_AO_UTMIP_PAD_CFG(index));
+ value &= ~(USBOP_VAL_PD | USBON_VAL_PD);
+ ao_writel(priv, value, XUSB_AO_UTMIP_PAD_CFG(index));
+
+ usleep_range(150, 200);
+
+ /* switch the electric control of the USB2.0 pad to XUSB_AO */
+ value = ao_readl(priv, XUSB_AO_UTMIP_PAD_CFG(index));
+ value |= (FSLS_USE_XUSB_AO | TRK_CTRL_USE_XUSB_AO |
+ RPD_CTRL_USE_XUSB_AO | RPU_USE_XUSB_AO | VREG_USE_XUSB_AO);
+ ao_writel(priv, value, XUSB_AO_UTMIP_PAD_CFG(index));
+
+ /* set the wake signaling trigger events */
+ value = ao_readl(priv, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+ value &= ~WAKE_VAL(~0);
+ value |= WAKE_VAL_ANY;
+ ao_writel(priv, value, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+
+ /* enable the wake detection */
+ value = ao_readl(priv, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+ value |= (MASTER_ENABLE | LINE_WAKEUP_EN);
+ ao_writel(priv, value, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+
+ mutex_unlock(&padctl->lock);
+
+ return 0;
+}
+
+static int tegra186_utmi_phy_disable_sleepwalk(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ struct tegra186_xusb_padctl *priv = to_tegra186_xusb_padctl(padctl);
+ unsigned int index = lane->index;
+ struct device *dev = padctl->dev;
+ u32 value;
+
+ dev_dbg(dev, "phy disable sleepwalk UTMI %d\n", index);
+
+ mutex_lock(&padctl->lock);
+
+ /* disable the wake detection */
+ value = ao_readl(priv, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+ value &= ~(MASTER_ENABLE | LINE_WAKEUP_EN);
+ ao_writel(priv, value, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+
+ /* switch the electric control of the USB2.0 pad to XUSB vcore logic */
+ value = ao_readl(priv, XUSB_AO_UTMIP_PAD_CFG(index));
+ value &= ~(FSLS_USE_XUSB_AO | TRK_CTRL_USE_XUSB_AO |
+ RPD_CTRL_USE_XUSB_AO | RPU_USE_XUSB_AO | VREG_USE_XUSB_AO);
+ ao_writel(priv, value, XUSB_AO_UTMIP_PAD_CFG(index));
+
+ /* disable wake event triggers of sleepwalk logic */
+ value = ao_readl(priv, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+ value &= ~WAKE_VAL(~0);
+ value |= WAKE_VAL_NONE;
+ ao_writel(priv, value, XUSB_AO_UTMIP_SLEEPWALK_CFG(index));
+
+ /* power down the line state detectors of the port */
+ value = ao_readl(priv, XUSB_AO_UTMIP_PAD_CFG(index));
+ value |= (USBOP_VAL_PD | USBON_VAL_PD);
+ ao_writel(priv, value, XUSB_AO_UTMIP_PAD_CFG(index));
+
+ /* clear alarm of the sleepwalk logic */
+ value = ao_readl(priv, XUSB_AO_UTMIP_TRIGGERS(index));
+ value |= CLR_WAKE_ALARM;
+ ao_writel(priv, value, XUSB_AO_UTMIP_TRIGGERS(index));
+
+ mutex_unlock(&padctl->lock);
+
+ return 0;
+}
+
+static int tegra186_utmi_phy_enable_wake(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ unsigned int index = lane->index;
+ struct device *dev = padctl->dev;
+ u32 value;
+
+ dev_dbg(dev, "phy enable wake UTMI %d\n", index);
+
+ mutex_lock(&padctl->lock);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value &= ~ALL_WAKE_EVENTS;
+ value |= USB2_PORT_WAKEUP_EVENT(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ usleep_range(10, 20);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value &= ~ALL_WAKE_EVENTS;
+ value |= USB2_PORT_WAKE_INTERRUPT_ENABLE(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ mutex_unlock(&padctl->lock);
+
+ return 0;
+}
+
+static int tegra186_utmi_phy_disable_wake(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ unsigned int index = lane->index;
+ struct device *dev = padctl->dev;
+ u32 value;
+
+ dev_dbg(dev, "phy disable wake UTMI %d\n", index);
+
+ mutex_lock(&padctl->lock);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value &= ~ALL_WAKE_EVENTS;
+ value &= ~USB2_PORT_WAKE_INTERRUPT_ENABLE(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ usleep_range(10, 20);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value &= ~ALL_WAKE_EVENTS;
+ value |= USB2_PORT_WAKEUP_EVENT(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ mutex_unlock(&padctl->lock);
+
+ return 0;
+}
+
static int tegra186_utmi_phy_init(struct phy *phy)
{
struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
@@ -538,6 +908,11 @@ static const struct phy_ops utmi_phy_ops = {
.owner = THIS_MODULE,
};

+static inline bool is_utmi_phy(struct phy *phy)
+{
+ return phy->ops == &utmi_phy_ops;
+}
+
static struct tegra_xusb_pad *
tegra186_usb2_pad_probe(struct tegra_xusb_padctl *padctl,
const struct tegra_xusb_pad_soc *soc,
@@ -790,6 +1165,120 @@ static int tegra186_usb3_phy_power_off(struct phy *phy)
return 0;
}

+static int tegra186_usb3_phy_enable_sleepwalk(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ unsigned int index = lane->index;
+ struct device *dev = padctl->dev;
+ u32 value;
+
+ dev_dbg(dev, "phy enable sleepwalk USB3 %d\n", index);
+
+ mutex_lock(&padctl->lock);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
+ value |= SSPX_ELPG_CLAMP_EN_EARLY(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
+ value |= SSPX_ELPG_CLAMP_EN(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
+
+ usleep_range(250, 350);
+
+ mutex_unlock(&padctl->lock);
+
+ return 0;
+}
+
+static int tegra186_usb3_phy_disable_sleepwalk(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ unsigned int index = lane->index;
+ struct device *dev = padctl->dev;
+ u32 value;
+
+ dev_dbg(dev, "phy disable sleepwalk USB3 %d\n", index);
+
+ mutex_lock(&padctl->lock);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
+ value &= ~SSPX_ELPG_CLAMP_EN_EARLY(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
+ value &= ~SSPX_ELPG_CLAMP_EN(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
+
+ mutex_unlock(&padctl->lock);
+
+ return 0;
+}
+
+static int tegra186_usb3_phy_enable_wake(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ unsigned int index = lane->index;
+ struct device *dev = padctl->dev;
+ u32 value;
+
+ dev_dbg(dev, "phy enable wake USB3 %d\n", index);
+
+ mutex_lock(&padctl->lock);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value &= ~ALL_WAKE_EVENTS;
+ value |= SS_PORT_WAKEUP_EVENT(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ usleep_range(10, 20);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value &= ~ALL_WAKE_EVENTS;
+ value |= SS_PORT_WAKE_INTERRUPT_ENABLE(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ mutex_unlock(&padctl->lock);
+
+ return 0;
+}
+
+static int tegra186_usb3_phy_disable_wake(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ unsigned int index = lane->index;
+ struct device *dev = padctl->dev;
+ u32 value;
+
+ dev_dbg(dev, "phy disable wake USB3 %d\n", index);
+
+ mutex_lock(&padctl->lock);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value &= ~ALL_WAKE_EVENTS;
+ value &= ~SS_PORT_WAKE_INTERRUPT_ENABLE(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ usleep_range(10, 20);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value &= ~ALL_WAKE_EVENTS;
+ value |= SS_PORT_WAKEUP_EVENT(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ mutex_unlock(&padctl->lock);
+
+ return 0;
+}
+
static int tegra186_usb3_phy_init(struct phy *phy)
{
return 0;
@@ -808,6 +1297,11 @@ static const struct phy_ops usb3_phy_ops = {
.owner = THIS_MODULE,
};

+static inline bool is_usb3_phy(struct phy *phy)
+{
+ return phy->ops == &usb3_phy_ops;
+}
+
static struct tegra_xusb_pad *
tegra186_usb3_pad_probe(struct tegra_xusb_padctl *padctl,
const struct tegra_xusb_pad_soc *soc,
@@ -913,7 +1407,9 @@ static struct tegra_xusb_padctl *
tegra186_xusb_padctl_probe(struct device *dev,
const struct tegra_xusb_padctl_soc *soc)
{
+ struct platform_device *pdev = to_platform_device(dev);
struct tegra186_xusb_padctl *priv;
+ struct resource *res;
int err;

priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
@@ -923,6 +1419,11 @@ tegra186_xusb_padctl_probe(struct device *dev,
priv->base.dev = dev;
priv->base.soc = soc;

+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ao");
+ priv->ao_regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(priv->ao_regs))
+ return priv->ao_regs;
+
err = tegra186_xusb_read_fuse_calibration(priv);
if (err < 0)
return ERR_PTR(err);
@@ -930,6 +1431,154 @@ tegra186_xusb_padctl_probe(struct device *dev,
return &priv->base;
}

+static void tegra186_xusb_padctl_save(struct tegra_xusb_padctl *padctl)
+{
+ struct tegra186_xusb_padctl *priv = to_tegra186_xusb_padctl(padctl);
+
+ priv->context.vbus_id = padctl_readl(padctl, USB2_VBUS_ID);
+ priv->context.usb2_pad_mux =
+ padctl_readl(padctl, XUSB_PADCTL_USB2_PAD_MUX);
+ priv->context.usb2_port_cap =
+ padctl_readl(padctl, XUSB_PADCTL_USB2_PORT_CAP);
+ priv->context.ss_port_cap =
+ padctl_readl(padctl, XUSB_PADCTL_SS_PORT_CAP);
+}
+
+static void tegra186_xusb_padctl_restore(struct tegra_xusb_padctl *padctl)
+{
+ struct tegra186_xusb_padctl *priv = to_tegra186_xusb_padctl(padctl);
+
+ padctl_writel(padctl, priv->context.usb2_pad_mux,
+ XUSB_PADCTL_USB2_PAD_MUX);
+ padctl_writel(padctl, priv->context.usb2_port_cap,
+ XUSB_PADCTL_USB2_PORT_CAP);
+ padctl_writel(padctl, priv->context.ss_port_cap,
+ XUSB_PADCTL_SS_PORT_CAP);
+ padctl_writel(padctl, priv->context.vbus_id, USB2_VBUS_ID);
+}
+
+static int tegra186_xusb_padctl_suspend_noirq(struct tegra_xusb_padctl *padctl)
+{
+ tegra186_xusb_padctl_save(padctl);
+
+ return 0;
+}
+
+static int tegra186_xusb_padctl_resume_noirq(struct tegra_xusb_padctl *padctl)
+{
+ tegra186_xusb_padctl_restore(padctl);
+
+ return 0;
+}
+
+static int
+tegra186_xusb_padctl_enable_phy_sleepwalk(struct tegra_xusb_padctl *padctl,
+ struct phy *phy,
+ enum usb_device_speed speed)
+{
+ if (!phy)
+ return 0;
+
+ if (is_usb3_phy(phy))
+ return tegra186_usb3_phy_enable_sleepwalk(phy);
+ else if (is_utmi_phy(phy))
+ return tegra186_utmi_phy_enable_sleepwalk(phy, speed);
+ else
+ return -EINVAL;
+}
+
+static int
+tegra186_xusb_padctl_disable_phy_sleepwalk(struct tegra_xusb_padctl *padctl,
+ struct phy *phy)
+{
+ if (!phy)
+ return 0;
+
+ if (is_usb3_phy(phy))
+ return tegra186_usb3_phy_disable_sleepwalk(phy);
+ else if (is_utmi_phy(phy))
+ return tegra186_utmi_phy_disable_sleepwalk(phy);
+ else
+ return -EINVAL;
+}
+
+static int
+tegra186_xusb_padctl_enable_phy_wake(struct tegra_xusb_padctl *padctl,
+ struct phy *phy)
+{
+ if (!phy)
+ return 0;
+
+ if (is_usb3_phy(phy))
+ return tegra186_usb3_phy_enable_wake(phy);
+ else if (is_utmi_phy(phy))
+ return tegra186_utmi_phy_enable_wake(phy);
+ else
+ return -EINVAL;
+}
+
+static int
+tegra186_xusb_padctl_disable_phy_wake(struct tegra_xusb_padctl *padctl,
+ struct phy *phy)
+{
+ if (!phy)
+ return 0;
+
+ if (is_usb3_phy(phy))
+ return tegra186_usb3_phy_disable_wake(phy);
+ else if (is_utmi_phy(phy))
+ return tegra186_utmi_phy_disable_wake(phy);
+ else
+ return -EINVAL;
+}
+
+static int tegra186_usb3_phy_remote_wake_detected(
+ struct tegra_xusb_padctl *padctl, int port)
+{
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ if ((value & SS_PORT_WAKE_INTERRUPT_ENABLE(port)) &&
+ (value & SS_PORT_WAKEUP_EVENT(port)))
+ return true;
+ else
+ return false;
+}
+
+static int tegra186_utmi_phy_remote_wake_detected(
+ struct tegra_xusb_padctl *padctl, int port)
+{
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ if ((value & USB2_PORT_WAKE_INTERRUPT_ENABLE(port)) &&
+ (value & USB2_PORT_WAKEUP_EVENT(port)))
+ return true;
+ else
+ return false;
+}
+
+int tegra186_xusb_padctl_remote_wake_detected(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane;
+ struct tegra_xusb_padctl *padctl;
+ unsigned int index;
+
+ if (!phy)
+ return 0;
+
+ lane = phy_get_drvdata(phy);
+ padctl = lane->pad->padctl;
+ index = lane->index;
+
+ if (is_utmi_phy(phy))
+ return tegra186_utmi_phy_remote_wake_detected(padctl, index);
+ else if (is_usb3_phy(phy))
+ return tegra186_usb3_phy_remote_wake_detected(padctl, index);
+ else
+ return -EINVAL;
+}
+
static void tegra186_xusb_padctl_remove(struct tegra_xusb_padctl *padctl)
{
}
@@ -937,7 +1586,14 @@ static void tegra186_xusb_padctl_remove(struct tegra_xusb_padctl *padctl)
static const struct tegra_xusb_padctl_ops tegra186_xusb_padctl_ops = {
.probe = tegra186_xusb_padctl_probe,
.remove = tegra186_xusb_padctl_remove,
+ .suspend_noirq = tegra186_xusb_padctl_suspend_noirq,
+ .resume_noirq = tegra186_xusb_padctl_resume_noirq,
.vbus_override = tegra186_xusb_padctl_vbus_override,
+ .enable_phy_sleepwalk = tegra186_xusb_padctl_enable_phy_sleepwalk,
+ .disable_phy_sleepwalk = tegra186_xusb_padctl_disable_phy_sleepwalk,
+ .enable_phy_wake = tegra186_xusb_padctl_enable_phy_wake,
+ .disable_phy_wake = tegra186_xusb_padctl_disable_phy_wake,
+ .remote_wake_detected = tegra186_xusb_padctl_remote_wake_detected,
};

#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC)
--
2.25.1