Re: [PATCH v3] usb: cdnsp: fix wakeup from S3 after controller context loss
Gary Yang <[email protected]>
| Newsgroups | org.kernel.vger.linux-kernel,org.kernel.vger.linux-usb,org.kernel.vger.stable |
|---|---|
| Message-ID | <aou43bEt96I07cE6@gary-System-Product-Name> |
On 2026-08-20 12:45, Pawel Laszczak via B4 Relay wrote: > [You don't often get email from [email protected]. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ] > > EXTERNAL EMAIL > > From: Pawel Laszczak <[email protected]> > > CDNSP controller may lose its runtime register programming across S3 > suspend/resume, depending on SoC power domain configuration. After > resume the operational and interrupter registers may contain reset > values, which prevents the gadget side from recovering correctly and > breaks wakeup from S3. > > Fix this by detecting whether the controller lost its register context > after resume and handling both cases: > - If context was lost (CFG_3XPORT_U1_PIPE_CLK_GATE_EN set or power > lost): reset the controller and reprogram the state required for > normal operation, including the command ring, DCBAA pointer, doorbell > base, event ring, ERST base/size and event ring dequeue pointer. > - If context was retained: restart the controller directly without > reprogramming registers. Issue a wakeup if the link was in U3 before > suspend. > > Move the basic controller register programming out of the one-time memory > initialization path and make it reusable from the resume path. Also > separate ring allocation from ring initialization so that rings can be > reinitialized without reallocating DMA memory. > > Always perform the full suspend sequence regardless of the current link > state. Previously, if the device was already in U3, the suspend callback > returned early without stopping the controller, which could lead to > commands being issued on a disabled slot during resume. > > Fixes: 3d82904559f4 ("usb: cdnsp: cdns3 Add main part of Cadence USBSSP DRD Driver") > Cc: [email protected] > Signed-off-by: Pawel Laszczak <[email protected]> hi Peter: We have performed validation. After STR stree testing on CIX P1 platform, the usb gadget functionality works as expected. Best Regards, Gary > --- > Changes in v3: > - Remove CSS/CRS sequences: not applicable to the device controller. > - Remove cdnsp_save_registers(), cdnsp_restore_registers() and > struct cdnsp_s3_save, now unused. > - Remove STS_CNR polling from the power-retained resume path: it was > not present before this fix and is not required. > - Move __cdnsp_gadget_wakeup() call to the power-retained path only, > where the link state is meaningful. > > Changes in v2: > - Clarify commit message to reflect SoC-dependent context loss behavior > - Removed in_lpm from cdnsp_irq_handler - the code is unnecessary > - Fix suspend: always perform full suspend sequence regardless of link state > --- > drivers/usb/cdns3/cdnsp-gadget.c | 111 +++++++++++++++++++++++++++++++++++++-- > drivers/usb/cdns3/cdnsp-gadget.h | 1 + > drivers/usb/cdns3/cdnsp-mem.c | 98 ++++++++++++---------------------- > 3 files changed, 142 insertions(+), 68 deletions(-) > > diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c > index a5275c2fb43b..63f605de068b 100644 > --- a/drivers/usb/cdns3/cdnsp-gadget.c > +++ b/drivers/usb/cdns3/cdnsp-gadget.c > @@ -1338,7 +1338,6 @@ static int cdnsp_run(struct cdnsp_device *pdev, > > cdnsp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); > > - > ret = cdnsp_start(pdev); > if (ret) { > ret = -ENODEV; > @@ -1837,6 +1836,82 @@ static void cdnsp_get_rev_cap(struct cdnsp_device *pdev) > readl(&pdev->rev_cap->tx_buff_size)); > } > > +static void cdnsp_set_event_deq(struct cdnsp_device *pdev) > +{ > + dma_addr_t deq; > + u64 temp; > + > + deq = cdnsp_trb_virt_to_dma(pdev->event_ring->deq_seg, > + pdev->event_ring->dequeue); > + > + /* Update controller event ring dequeue pointer */ > + temp = cdnsp_read_64(&pdev->ir_set->erst_dequeue); > + temp &= ERST_PTR_MASK; > + > + /* > + * Don't clear the EHB bit (which is RW1C) because > + * there might be more events to service. > + */ > + temp &= ~ERST_EHB; > + > + cdnsp_write_64(((u64)deq & (u64)~ERST_PTR_MASK) | temp, > + &pdev->ir_set->erst_dequeue); > +} > + > +static void cdnsp_add_interrupter(struct cdnsp_device *pdev) > +{ > + u64 erst_base; > + u32 erst_size; > + > + /* Set ERST count with the number of entries in the segment table. */ > + erst_size = readl(&pdev->ir_set->erst_size); > + erst_size &= ERST_SIZE_MASK; > + erst_size |= ERST_NUM_SEGS; > + writel(erst_size, &pdev->ir_set->erst_size); > + > + /* Set the segment table base address. */ > + erst_base = cdnsp_read_64(&pdev->ir_set->erst_base); > + erst_base &= ERST_PTR_MASK; > + erst_base |= (pdev->erst.erst_dma_addr & (u64)~ERST_PTR_MASK); > + cdnsp_write_64(erst_base, &pdev->ir_set->erst_base); > + > + /* Set the event ring dequeue address. */ > + cdnsp_set_event_deq(pdev); > +} > + > +/* Set up basic CDNSP registers */ > +static void cdnsp_init(struct cdnsp_device *pdev) > +{ > + unsigned int val; > + u64 val_64; > + > + val = readl(&pdev->op_regs->config_reg); > + val |= ((val & ~MAX_DEVS) | CDNSP_DEV_MAX_SLOTS) | CONFIG_U3E; > + writel(val, &pdev->op_regs->config_reg); > + > + /* Initialize the Command ring */ > + cdnsp_ring_init(pdev, pdev->cmd_ring); > + > + /* Set the address in the Command Ring Control register */ > + val_64 = cdnsp_read_64(&pdev->op_regs->cmd_ring); > + val_64 = (val_64 & (u64)CMD_RING_RSVD_BITS) | > + (pdev->cmd_ring->first_seg->dma & (u64)~CMD_RING_RSVD_BITS) | > + pdev->cmd_ring->cycle_state; > + cdnsp_write_64(val_64, &pdev->op_regs->cmd_ring); > + > + /* Set Device Context Base Address Array pointer */ > + cdnsp_write_64(pdev->dcbaa->dma, &pdev->op_regs->dcbaa_ptr); > + > + /* Set Doorbell array pointer */ > + val = readl(&pdev->cap_regs->db_off); > + val &= DBOFF_MASK; > + pdev->dba = (void __iomem *)pdev->cap_regs + val; > + > + /* Initialize the Primary interrupter */ > + cdnsp_ring_init(pdev, pdev->event_ring); > + cdnsp_add_interrupter(pdev); > +} > + > static int cdnsp_gen_setup(struct cdnsp_device *pdev) > { > int ret; > @@ -1902,6 +1977,8 @@ static int cdnsp_gen_setup(struct cdnsp_device *pdev) > if (ret) > return ret; > > + cdnsp_init(pdev); > + > /* > * Software workaround for U1: after transition > * to U1 the controller starts gating clock, and in some cases, > @@ -2031,9 +2108,6 @@ static int cdnsp_gadget_suspend(struct cdns *cdns, bool do_wakeup) > struct cdnsp_device *pdev = cdns->gadget_dev; > unsigned long flags; > > - if (pdev->link_state == XDEV_U3) > - return 0; > - > spin_lock_irqsave(&pdev->lock, flags); > cdnsp_disconnect_gadget(pdev); > cdnsp_stop(pdev); > @@ -2047,12 +2121,38 @@ static int cdnsp_gadget_resume(struct cdns *cdns, bool lost_power) > struct cdnsp_device *pdev = cdns->gadget_dev; > enum usb_device_speed max_speed; > unsigned long flags; > + bool context_lost; > + u32 val; > int ret; > > if (!pdev->gadget_driver) > return 0; > > spin_lock_irqsave(&pdev->lock, flags); > + val = readl(&pdev->port3x_regs->mode_2); > + context_lost = !!(val & CFG_3XPORT_U1_PIPE_CLK_GATE_EN) || lost_power; > + > + if (context_lost) { > + cdnsp_halt(pdev); > + cdnsp_set_apb_timeout_value(pdev); > + > + /* Reset the internal controller memory state and registers. */ > + ret = cdnsp_reset(pdev); > + if (ret) > + goto unlock; > + > + val = readl(&pdev->port3x_regs->mode_2); > + val &= ~CFG_3XPORT_U1_PIPE_CLK_GATE_EN; > + writel(val, &pdev->port3x_regs->mode_2); > + > + cdnsp_clear_cmd_ring(pdev); > + > + memset(pdev->event_ring->first_seg->trbs, 0, > + sizeof(union cdnsp_trb) * (TRBS_PER_SEGMENT)); > + > + cdnsp_init(pdev); > + } > + > max_speed = pdev->gadget_driver->max_speed; > > /* Limit speed if necessary. */ > @@ -2060,9 +2160,10 @@ static int cdnsp_gadget_resume(struct cdns *cdns, bool lost_power) > > ret = cdnsp_run(pdev, max_speed); > > - if (pdev->link_state == XDEV_U3) > + if (!context_lost && pdev->link_state == XDEV_U3) > __cdnsp_gadget_wakeup(pdev); > > +unlock: > spin_unlock_irqrestore(&pdev->lock, flags); > > return ret; > diff --git a/drivers/usb/cdns3/cdnsp-gadget.h b/drivers/usb/cdns3/cdnsp-gadget.h > index c44bca348a41..c3ae5040f9cc 100644 > --- a/drivers/usb/cdns3/cdnsp-gadget.h > +++ b/drivers/usb/cdns3/cdnsp-gadget.h > @@ -1510,6 +1510,7 @@ int cdnsp_endpoint_init(struct cdnsp_device *pdev, > int cdnsp_ring_expansion(struct cdnsp_device *pdev, > struct cdnsp_ring *ring, > unsigned int num_trbs, gfp_t flags); > +void cdnsp_ring_init(struct cdnsp_device *pdev, struct cdnsp_ring *ring); > struct cdnsp_ring *cdnsp_dma_to_transfer_ring(struct cdnsp_ep *ep, u64 address); > int cdnsp_alloc_stream_info(struct cdnsp_device *pdev, > struct cdnsp_ep *pep, > diff --git a/drivers/usb/cdns3/cdnsp-mem.c b/drivers/usb/cdns3/cdnsp-mem.c > index 5d8cdc91927d..0be917f52bbb 100644 > --- a/drivers/usb/cdns3/cdnsp-mem.c > +++ b/drivers/usb/cdns3/cdnsp-mem.c > @@ -394,13 +394,6 @@ static struct cdnsp_ring *cdnsp_ring_alloc(struct cdnsp_device *pdev, > if (ret) > goto fail; > > - /* Only event ring does not use link TRB. */ > - if (type != TYPE_EVENT) > - ring->last_seg->trbs[TRBS_PER_SEGMENT - 1].link.control |= > - cpu_to_le32(LINK_TOGGLE); > - > - cdnsp_initialize_ring_info(ring); > - trace_cdnsp_ring_alloc(ring); > return ring; > fail: > kfree(ring); > @@ -603,6 +596,7 @@ int cdnsp_alloc_stream_info(struct cdnsp_device *pdev, > if (!cur_ring) > goto cleanup_rings; > > + cdnsp_ring_init(pdev, cur_ring); > cur_ring->stream_id = cur_stream; > cur_ring->trb_address_map = &stream_info->trb_address_map; > > @@ -696,6 +690,8 @@ static int cdnsp_alloc_priv_device(struct cdnsp_device *pdev) > if (!pdev->eps[0].ring) > goto fail; > > + cdnsp_ring_init(pdev, pdev->eps[0].ring); > + > /* Point to output device context in dcbaa. */ > pdev->dcbaa->dev_context_ptrs[1] = cpu_to_le64(pdev->out_ctx.dma); > pdev->cmd.in_ctx = &pdev->in_ctx; > @@ -989,6 +985,8 @@ int cdnsp_endpoint_init(struct cdnsp_device *pdev, > if (!pep->ring) > return -ENOMEM; > > + cdnsp_ring_init(pdev, pep->ring); > + > pep->skip = false; > > /* Fill the endpoint context */ > @@ -1094,28 +1092,6 @@ void cdnsp_mem_cleanup(struct cdnsp_device *pdev) > pdev->active_port = NULL; > } > > -static void cdnsp_set_event_deq(struct cdnsp_device *pdev) > -{ > - dma_addr_t deq; > - u64 temp; > - > - deq = cdnsp_trb_virt_to_dma(pdev->event_ring->deq_seg, > - pdev->event_ring->dequeue); > - > - /* Update controller event ring dequeue pointer */ > - temp = cdnsp_read_64(&pdev->ir_set->erst_dequeue); > - temp &= ERST_PTR_MASK; > - > - /* > - * Don't clear the EHB bit (which is RW1C) because > - * there might be more events to service. > - */ > - temp &= ~ERST_EHB; > - > - cdnsp_write_64(((u64)deq & (u64)~ERST_PTR_MASK) | temp, > - &pdev->ir_set->erst_dequeue); > -} > - > static void cdnsp_add_in_port(struct cdnsp_device *pdev, > struct cdnsp_port *port, > __le32 __iomem *addr) > @@ -1224,6 +1200,36 @@ static int cdnsp_setup_port_arrays(struct cdnsp_device *pdev) > return 0; > } > > +static void cdnsp_initialize_ring_segments(struct cdnsp_device *pdev, struct cdnsp_ring *ring) > +{ > + struct cdnsp_segment *seg; > + > + /* Only event ring does not use link TRB. */ > + if (ring->type == TYPE_EVENT) > + return; > + > + seg = ring->first_seg; > + > + while (seg) { > + struct cdnsp_segment *next = seg->next; > + > + cdnsp_link_segments(pdev, seg, next, ring->type); > + if (next == ring->first_seg) > + break; > + > + seg = next; > + } > + > + ring->last_seg->trbs[TRBS_PER_SEGMENT - 1].link.control |= cpu_to_le32(LINK_TOGGLE); > +} > + > +void cdnsp_ring_init(struct cdnsp_device *pdev, struct cdnsp_ring *ring) > +{ > + cdnsp_initialize_ring_segments(pdev, ring); > + cdnsp_initialize_ring_info(ring); > + trace_cdnsp_ring_alloc(ring); > +} > + > /* > * Initialize memory for CDNSP (one-time init). > * > @@ -1235,10 +1241,8 @@ int cdnsp_mem_init(struct cdnsp_device *pdev) > { > struct device *dev = pdev->dev; > int ret = -ENOMEM; > - unsigned int val; > dma_addr_t dma; > u32 page_size; > - u64 val_64; > > /* > * Use 4K pages, since that's common and the minimum the > @@ -1246,10 +1250,6 @@ int cdnsp_mem_init(struct cdnsp_device *pdev) > */ > page_size = 1 << 12; > > - val = readl(&pdev->op_regs->config_reg); > - val |= ((val & ~MAX_DEVS) | CDNSP_DEV_MAX_SLOTS) | CONFIG_U3E; > - writel(val, &pdev->op_regs->config_reg); > - > /* > * Doorbell array must be physically contiguous > * and 64-byte (cache line) aligned. > @@ -1261,8 +1261,6 @@ int cdnsp_mem_init(struct cdnsp_device *pdev) > > pdev->dcbaa->dma = dma; > > - cdnsp_write_64(dma, &pdev->op_regs->dcbaa_ptr); > - > /* > * Initialize the ring segment pool. The ring must be a contiguous > * structure comprised of TRBs. The TRBs must be 16 byte aligned, > @@ -1288,17 +1286,6 @@ int cdnsp_mem_init(struct cdnsp_device *pdev) > if (!pdev->cmd_ring) > goto destroy_device_pool; > > - /* Set the address in the Command Ring Control register */ > - val_64 = cdnsp_read_64(&pdev->op_regs->cmd_ring); > - val_64 = (val_64 & (u64)CMD_RING_RSVD_BITS) | > - (pdev->cmd_ring->first_seg->dma & (u64)~CMD_RING_RSVD_BITS) | > - pdev->cmd_ring->cycle_state; > - cdnsp_write_64(val_64, &pdev->op_regs->cmd_ring); > - > - val = readl(&pdev->cap_regs->db_off); > - val &= DBOFF_MASK; > - pdev->dba = (void __iomem *)pdev->cap_regs + val; > - > /* Set ir_set to interrupt register set 0 */ > pdev->ir_set = &pdev->run_regs->ir_set[0]; > > @@ -1315,21 +1302,6 @@ int cdnsp_mem_init(struct cdnsp_device *pdev) > if (ret) > goto free_event_ring; > > - /* Set ERST count with the number of entries in the segment table. */ > - val = readl(&pdev->ir_set->erst_size); > - val &= ERST_SIZE_MASK; > - val |= ERST_NUM_SEGS; > - writel(val, &pdev->ir_set->erst_size); > - > - /* Set the segment table base address. */ > - val_64 = cdnsp_read_64(&pdev->ir_set->erst_base); > - val_64 &= ERST_PTR_MASK; > - val_64 |= (pdev->erst.erst_dma_addr & (u64)~ERST_PTR_MASK); > - cdnsp_write_64(val_64, &pdev->ir_set->erst_base); > - > - /* Set the event ring dequeue address. */ > - cdnsp_set_event_deq(pdev); > - > ret = cdnsp_setup_port_arrays(pdev); > if (ret) > goto free_erst; > > --- > base-commit: abe651837cb394f76d738a7a747322fca3bf17ba > change-id: 20260723-suspend_resume_fix-bfd5327d52aa > > Best regards, > -- > Pawel Laszczak <[email protected]> > >