diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 52c2b415a8e4..ce6f601c67de 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -170,7 +170,7 @@ #define GEN2_U3_EXIT_RSP_RX_CLK_MASK GEN2_U3_EXIT_RSP_RX_CLK(0xff) #define GEN1_U3_EXIT_RSP_RX_CLK(n) (n) #define GEN1_U3_EXIT_RSP_RX_CLK_MASK GEN1_U3_EXIT_RSP_RX_CLK(0xff) -#define DWC31_LINK_GDBGLTSSM 0xd050 +#define DWC31_LINK_GDBGLTSSM(n) (0xd050 + ((n) * 0x80)) /* DWC 3.1 Tx De-emphasis Registers */ #define DWC31_LCSR_TX_DEEMPH(n) (0xd060 + ((n) * 0x80)) diff --git a/drivers/usb/dwc3/dwc3-msm.c b/drivers/usb/dwc3/dwc3-msm.c index 8964d018555a..f93605dec987 100644 --- a/drivers/usb/dwc3/dwc3-msm.c +++ b/drivers/usb/dwc3/dwc3-msm.c @@ -74,6 +74,7 @@ #define CGCTL_REG (QSCRATCH_REG_OFFSET + 0x28) #define PWR_EVNT_IRQ_STAT_REG (QSCRATCH_REG_OFFSET + 0x58) #define PWR_EVNT_IRQ_MASK_REG (QSCRATCH_REG_OFFSET + 0x5C) +#define PWR_EVNT_IRQ_STAT_REG1 (QSCRATCH_REG_OFFSET + 0x1DC) #define PWR_EVNT_POWERDOWN_IN_P3_MASK BIT(2) #define PWR_EVNT_POWERDOWN_OUT_P3_MASK BIT(3) @@ -302,6 +303,9 @@ enum msm_usb_irq { DP_HS_PHY_IRQ, DM_HS_PHY_IRQ, SS_PHY_IRQ, + DP_HS_PHY_IRQ_1, + DM_HS_PHY_IRQ_1, + SS_PHY_IRQ_1, USB_MAX_IRQ }; @@ -364,6 +368,19 @@ static const struct usb_irq_info usb_irq_info[USB_MAX_IRQ] = { IRQF_EARLY_RESUME, false, }, + { "dp_hs_phy_irq1", + IRQF_TRIGGER_RISING | IRQF_ONESHOT | IRQF_EARLY_RESUME, + false, + }, + { "dm_hs_phy_irq1", + IRQF_TRIGGER_RISING | IRQF_ONESHOT | IRQF_EARLY_RESUME, + false, + }, + { "ss_phy_irq1", + IRQF_TRIGGER_HIGH | IRQF_ONESHOT | IRQ_TYPE_LEVEL_HIGH | + IRQF_EARLY_RESUME, + false, + }, }; struct usb_irq { @@ -2564,7 +2581,7 @@ static void dwc3_dis_sleep_mode(struct dwc3_msm *mdwc) static void dwc3_msm_power_collapse_por(struct dwc3_msm *mdwc) { struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); - u32 val; + u32 val = 0, val1 = 0; int ret; /* Configure AHB2PHY for one wait state read/write */ @@ -2616,15 +2633,27 @@ static void dwc3_msm_power_collapse_por(struct dwc3_msm *mdwc) __func__, ret); /* Get initial P3 status and enable IN_P3 event */ - if (dwc3_is_usb31(dwc)) + if (dwc3_is_usb31(dwc)) { val = dwc3_msm_read_reg_field(mdwc->base, - DWC31_LINK_GDBGLTSSM, + DWC31_LINK_GDBGLTSSM(0), DWC3_GDBGLTSSM_LINKSTATE_MASK); - else + if (mdwc->dual_port) + val1 = dwc3_msm_read_reg_field(mdwc->base, + DWC31_LINK_GDBGLTSSM(1), + DWC3_GDBGLTSSM_LINKSTATE_MASK); + } else { val = dwc3_msm_read_reg_field(mdwc->base, DWC3_GDBGLTSSM, DWC3_GDBGLTSSM_LINKSTATE_MASK); - atomic_set(&mdwc->in_p3, val == DWC3_LINK_STATE_U3); - dwc3_msm_write_reg_field(mdwc->base, PWR_EVNT_IRQ_MASK_REG, + } + + if (!mdwc->dual_port) + val1 = DWC3_LINK_STATE_U3; + + atomic_set(&mdwc->in_p3, (val == DWC3_LINK_STATE_U3) && + (val1 == DWC3_LINK_STATE_U3)); + + if (!mdwc->dual_port) + dwc3_msm_write_reg_field(mdwc->base, PWR_EVNT_IRQ_MASK_REG, PWR_EVNT_POWERDOWN_IN_P3_MASK, 1); /* Set the core in host mode if it was in host mode during pm_suspend */ @@ -2636,13 +2665,58 @@ static void dwc3_msm_power_collapse_por(struct dwc3_msm *mdwc) } +static void dwc3_msm_dp_ssphy_autosuspend(struct dwc3_msm *mdwc) +{ + unsigned long timeout; + u32 reg = 0, reg1 = 0; + + /* pwr_evt_irq not configured for dual port; clear in_p3 explicitly */ + atomic_set(&mdwc->in_p3, 0); + /* Clear previous P3 events */ + dwc3_msm_write_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG, + PWR_EVNT_POWERDOWN_IN_P3_MASK | PWR_EVNT_POWERDOWN_OUT_P3_MASK); + dwc3_msm_write_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG1, + PWR_EVNT_POWERDOWN_IN_P3_MASK | PWR_EVNT_POWERDOWN_OUT_P3_MASK); + + /* Prepare SSPHYs for suspend */ + reg = dwc3_msm_read_reg(mdwc->base, DWC3_GUSB3PIPECTL(0)); + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB3PIPECTL(0), + reg | DWC3_GUSB3PIPECTL_SUSPHY); + reg = dwc3_msm_read_reg(mdwc->base, DWC3_GUSB3PIPECTL(1)); + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB3PIPECTL(1), + reg | DWC3_GUSB3PIPECTL_SUSPHY); + + /* Wait for PHYs to go into P3 */ + timeout = jiffies + msecs_to_jiffies(5); + while (!time_after(jiffies, timeout)) { + reg = dwc3_msm_read_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG); + reg1 = dwc3_msm_read_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG1); + if ((reg & PWR_EVNT_POWERDOWN_IN_P3_MASK) && + (reg1 & PWR_EVNT_POWERDOWN_IN_P3_MASK)) { + atomic_set(&mdwc->in_p3, 1); + break; + } + } + + /* Clear P3 event bit */ + dwc3_msm_write_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG, + PWR_EVNT_POWERDOWN_IN_P3_MASK); + dwc3_msm_write_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG1, + PWR_EVNT_POWERDOWN_IN_P3_MASK); +} + static int dwc3_msm_prepare_suspend(struct dwc3_msm *mdwc, bool ignore_p3_state) { unsigned long timeout; u32 reg = 0; + /* Allow SSPHYs to go to P3 for dual port controllers */ + if (mdwc->dual_port) + dwc3_msm_dp_ssphy_autosuspend(mdwc); + if (!ignore_p3_state && ((mdwc->in_host_mode || mdwc->in_device_mode) - && dwc3_msm_is_superspeed(mdwc) && !mdwc->in_restart)) { + && (dwc3_msm_is_superspeed(mdwc) || mdwc->dual_port) && + !mdwc->in_restart)) { if (!atomic_read(&mdwc->in_p3)) { dev_err(mdwc->dev, "Not in P3,aborting LPM sequence\n"); return -EBUSY; @@ -2672,6 +2746,33 @@ static int dwc3_msm_prepare_suspend(struct dwc3_msm *mdwc, bool ignore_p3_state) dwc3_msm_write_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG, PWR_EVNT_LPM_IN_L2_MASK); + /* Handling for dual port core */ + if (!mdwc->dual_port) + return 0; + + /* Clear previous L2 events */ + dwc3_msm_write_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG1, + PWR_EVNT_LPM_IN_L2_MASK | PWR_EVNT_LPM_OUT_L2_MASK); + + /* Prepare HSPHY for suspend */ + reg = dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(1)); + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB2PHYCFG(1), + reg | DWC3_GUSB2PHYCFG_ENBLSLPM | DWC3_GUSB2PHYCFG_SUSPHY); + + /* Wait for PHY to go into L2 */ + timeout = jiffies + msecs_to_jiffies(5); + while (!time_after(jiffies, timeout)) { + reg = dwc3_msm_read_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG1); + if (reg & PWR_EVNT_LPM_IN_L2_MASK) + break; + } + if (!(reg & PWR_EVNT_LPM_IN_L2_MASK)) + dev_err(mdwc->dev, "could not transition HS PHY1 to L2\n"); + + /* Clear L2 event bit */ + dwc3_msm_write_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG1, + PWR_EVNT_LPM_IN_L2_MASK); + return 0; } @@ -2848,12 +2949,40 @@ static void enable_usb_pdc_interrupt(struct dwc3_msm *mdwc, bool enable) IRQ_TYPE_EDGE_RISING, true); } + if (mdwc->dual_port) { + if (mdwc->hs_phy1->flags & PHY_LS_MODE) { + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[DM_HS_PHY_IRQ_1], + IRQ_TYPE_EDGE_FALLING, enable); + } else if (mdwc->hs_phy1->flags & PHY_HSFS_MODE) { + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[DP_HS_PHY_IRQ_1], + IRQ_TYPE_EDGE_FALLING, enable); + } else { + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[DP_HS_PHY_IRQ_1], + IRQ_TYPE_EDGE_RISING, true); + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[DM_HS_PHY_IRQ_1], + IRQ_TYPE_EDGE_RISING, true); + } + } + configure_usb_wakeup_interrupt(mdwc, &mdwc->wakeup_irq[SS_PHY_IRQ], IRQF_TRIGGER_HIGH | IRQ_TYPE_LEVEL_HIGH, enable); + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[SS_PHY_IRQ_1], + IRQF_TRIGGER_HIGH | IRQ_TYPE_LEVEL_HIGH, enable); return; disable_usb_irq: + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[DP_HS_PHY_IRQ_1], 0, enable); + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[DM_HS_PHY_IRQ_1], 0, enable); + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[SS_PHY_IRQ_1], 0, enable); configure_usb_wakeup_interrupt(mdwc, &mdwc->wakeup_irq[DP_HS_PHY_IRQ], 0, enable); configure_usb_wakeup_interrupt(mdwc, @@ -3254,6 +3383,21 @@ static int dwc3_msm_resume(struct dwc3_msm *mdwc) dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(0)) & ~DWC3_GUSB2PHYCFG_SUSPHY); + if (mdwc->dual_port) + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB2PHYCFG(1), + dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(1)) & + ~DWC3_GUSB2PHYCFG_SUSPHY); + + if (dwc->dis_u3_susphy_quirk) { + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB3PIPECTL(0), + dwc3_msm_read_reg(mdwc->base, DWC3_GUSB3PIPECTL(0)) & + ~DWC3_GUSB3PIPECTL_SUSPHY); + if (mdwc->dual_port) + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB3PIPECTL(1), + dwc3_msm_read_reg(mdwc->base, DWC3_GUSB3PIPECTL(1)) & + ~DWC3_GUSB3PIPECTL_SUSPHY); + } + /* Disable wakeup capable for HS_PHY IRQ & SS_PHY_IRQ if enabled */ if (mdwc->lpm_flags & MDWC3_ASYNC_IRQ_WAKE_CAPABILITY) { if (mdwc->use_pdc_interrupts) { @@ -3277,7 +3421,8 @@ static int dwc3_msm_resume(struct dwc3_msm *mdwc) * Handle other power events that could not have been handled during * Low Power Mode */ - dwc3_pwr_event_handler(mdwc); + if (!mdwc->dual_port) + dwc3_pwr_event_handler(mdwc); if (pm_qos_request_active(&mdwc->pm_qos_req_dma)) schedule_delayed_work(&mdwc->perf_vote_work, @@ -3471,7 +3616,7 @@ static void dwc3_pwr_event_handler(struct dwc3_msm *mdwc) /* Can't tell if entered or exit P3, so check LINKSTATE */ if (dwc3_is_usb31(dwc)) ls = dwc3_msm_read_reg_field(mdwc->base, - DWC31_LINK_GDBGLTSSM, + DWC31_LINK_GDBGLTSSM(0), DWC3_GDBGLTSSM_LINKSTATE_MASK); else ls = dwc3_msm_read_reg_field(mdwc->base, @@ -4352,9 +4497,9 @@ static int dwc3_msm_probe(struct platform_device *pdev) for (i = 0; i < USB_MAX_IRQ; i++) { mdwc->wakeup_irq[i].irq = platform_get_irq_byname(pdev, usb_irq_info[i].name); + /* pwr_evnt_irq is only mandatory irq for single port core */ if (mdwc->wakeup_irq[i].irq < 0) { - /* pwr_evnt_irq is only mandatory irq */ - if (usb_irq_info[i].required) { + if (!mdwc->dual_port && usb_irq_info[i].required) { dev_err(&pdev->dev, "get_irq for %s failed\n\n", usb_irq_info[i].name); ret = -EINVAL; @@ -4429,7 +4574,8 @@ static int dwc3_msm_probe(struct platform_device *pdev) /* Add power event if the dbm indicates coming out of L1 by interrupt */ if (!mdwc->dbm_is_1p4) { - if (!mdwc->wakeup_irq[PWR_EVNT_IRQ].irq) { + /* Dual port core does not need pwr_evnt_irq */ + if (!mdwc->dual_port && !mdwc->wakeup_irq[PWR_EVNT_IRQ].irq) { dev_err(&pdev->dev, "need pwr_event_irq exiting L1\n"); ret = -EINVAL; @@ -4779,6 +4925,12 @@ static int dwc3_msm_remove(struct platform_device *pdev) disable_irq(mdwc->wakeup_irq[DM_HS_PHY_IRQ].irq); if (mdwc->wakeup_irq[SS_PHY_IRQ].irq) disable_irq(mdwc->wakeup_irq[SS_PHY_IRQ].irq); + if (mdwc->wakeup_irq[DP_HS_PHY_IRQ_1].irq) + disable_irq(mdwc->wakeup_irq[DP_HS_PHY_IRQ_1].irq); + if (mdwc->wakeup_irq[DM_HS_PHY_IRQ_1].irq) + disable_irq(mdwc->wakeup_irq[DM_HS_PHY_IRQ_1].irq); + if (mdwc->wakeup_irq[SS_PHY_IRQ_1].irq) + disable_irq(mdwc->wakeup_irq[SS_PHY_IRQ_1].irq); disable_irq(mdwc->wakeup_irq[PWR_EVNT_IRQ].irq); clk_disable_unprepare(mdwc->utmi_clk);