mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Gary Yang <gary.yang@cixtech.com>
To: pawell@cadence.com
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	Peter Chen <peter.chen@kernel.org>,
	linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org,
	stable@vger.kernel.org
Subject: Re: [PATCH v3] usb: cdnsp: fix wakeup from S3 after controller context loss
Date: Mon, 24 Aug 2026 11:22:05 +0800	[thread overview]
Message-ID: <aou43bEt96I07cE6@gary-System-Product-Name> (raw)
In-Reply-To: <20260820-suspend_resume_fix-v3-1-5a713098b977@cadence.com>

On 2026-08-20 12:45, Pawel Laszczak via B4 Relay wrote:

> [You don't often get email from devnull+pawell.cadence.com@kernel.org. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
> 
> EXTERNAL EMAIL
> 
> From: Pawel Laszczak <pawell@cadence.com>
> 
> 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: stable@vger.kernel.org
> Signed-off-by: Pawel Laszczak <pawell@cadence.com>

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 <pawell@cadence.com>
> 
> 

  reply	other threads:[~2026-08-24  3:22 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-20 10:45 Pawel Laszczak via B4 Relay
2026-08-24  3:22 ` Gary Yang [this message]
2026-08-24  6:16   ` Peter Chen
2026-08-24  6:16 ` Peter Chen

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=aou43bEt96I07cE6@gary-System-Product-Name \
    --to=gary.yang@cixtech.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=pawell@cadence.com \
    --cc=peter.chen@kernel.org \
    --cc=stable@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®