]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
usb: cdnsp: Fix issue with resuming from L1
authorPawel Laszczak <pawell@cadence.com>
Fri, 18 Apr 2025 04:55:16 +0000 (04:55 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 18 May 2025 06:20:37 +0000 (08:20 +0200)
commit 241e2ce88e5a494be7a5d44c0697592f1632fbee upstream.

In very rare cases after resuming controller from L1 to L0 it reads
registers before the clock UTMI have been enabled and as the result
driver reads incorrect value.
Most of registers are in APB domain clock but some of them (e.g. PORTSC)
are in UTMI domain clock.
After entering to L1 state the UTMI clock can be disabled.
When controller transition from L1 to L0 the port status change event is
reported and in interrupt runtime function driver reads PORTSC.
During this read operation controller synchronize UTMI and APB domain
but UTMI clock is still disabled and in result it reads 0xFFFFFFFF value.
To fix this issue driver increases APB timeout value.

The issue is platform specific and if the default value of APB timeout
is not sufficient then this time should be set Individually for each
platform.

Fixes: 3d82904559f4 ("usb: cdnsp: cdns3 Add main part of Cadence USBSSP DRD Driver")
Cc: stable <stable@kernel.org>
Signed-off-by: Pawel Laszczak <pawell@cadence.com>
Acked-by: Peter Chen <peter.chen@kernel.org>
Link: https://lore.kernel.org/r/PH7PR07MB953846C57973E4DB134CAA71DDBF2@PH7PR07MB9538.namprd07.prod.outlook.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/cdns3/cdnsp-gadget.c
drivers/usb/cdns3/cdnsp-gadget.h
drivers/usb/cdns3/cdnsp-pci.c
drivers/usb/cdns3/core.h

index d9fb29eb99db12fb950330f0c304da8d24d52a3d..6f7aa495f929219f248472c779f38e9f7eefdd10 100644 (file)
@@ -138,6 +138,26 @@ static void cdnsp_clear_port_change_bit(struct cdnsp_device *pdev,
               (portsc & PORT_CHANGE_BITS), port_regs);
 }
 
+static void cdnsp_set_apb_timeout_value(struct cdnsp_device *pdev)
+{
+       struct cdns *cdns = dev_get_drvdata(pdev->dev);
+       __le32 __iomem *reg;
+       void __iomem *base;
+       u32 offset = 0;
+       u32 val;
+
+       if (!cdns->override_apb_timeout)
+               return;
+
+       base = &pdev->cap_regs->hc_capbase;
+       offset = cdnsp_find_next_ext_cap(base, offset, D_XEC_PRE_REGS_CAP);
+       reg = base + offset + REG_CHICKEN_BITS_3_OFFSET;
+
+       val  = le32_to_cpu(readl(reg));
+       val = CHICKEN_APB_TIMEOUT_SET(val, cdns->override_apb_timeout);
+       writel(cpu_to_le32(val), reg);
+}
+
 static void cdnsp_set_chicken_bits_2(struct cdnsp_device *pdev, u32 bit)
 {
        __le32 __iomem *reg;
@@ -1804,6 +1824,15 @@ static int cdnsp_gen_setup(struct cdnsp_device *pdev)
        pdev->hci_version = HC_VERSION(pdev->hcc_params);
        pdev->hcc_params = readl(&pdev->cap_regs->hcc_params);
 
+       /*
+        * Override the APB timeout value to give the controller more time for
+        * enabling UTMI clock and synchronizing APB and UTMI clock domains.
+        * This fix is platform specific and is required to fixes issue with
+        * reading incorrect value from PORTSC register after resuming
+        * from L1 state.
+        */
+       cdnsp_set_apb_timeout_value(pdev);
+
        cdnsp_get_rev_cap(pdev);
 
        /* Make sure the Device Controller is halted. */
index a61aef0dc273ca49b271ba8e73f4290189d4fb51..79376b1e3419ef449874cca27a35c6c8720f929d 100644 (file)
@@ -520,6 +520,9 @@ struct cdnsp_rev_cap {
 #define REG_CHICKEN_BITS_2_OFFSET      0x48
 #define CHICKEN_XDMA_2_TP_CACHE_DIS    BIT(28)
 
+#define REG_CHICKEN_BITS_3_OFFSET       0x4C
+#define CHICKEN_APB_TIMEOUT_SET(p, val) (((p) & ~GENMASK(21, 0)) | (val))
+
 /* XBUF Extended Capability ID. */
 #define XBUF_CAP_ID                    0xCB
 #define XBUF_RX_TAG_MASK_0_OFFSET      0x1C
index a85db23fa19f2361fe32a12843c9796892a6b4c4..b7a1f28faa1fece1fb4a282e5721003a25f347a6 100644 (file)
@@ -33,6 +33,8 @@
 #define CDNS_DRD_ID            0x0100
 #define CDNS_DRD_IF            (PCI_CLASS_SERIAL_USB << 8 | 0x80)
 
+#define CHICKEN_APB_TIMEOUT_VALUE       0x1C20
+
 static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev)
 {
        /*
@@ -144,6 +146,14 @@ static int cdnsp_pci_probe(struct pci_dev *pdev,
                cdnsp->otg_irq = pdev->irq;
        }
 
+       /*
+        * Cadence PCI based platform require some longer timeout for APB
+        * to fixes domain clock synchronization issue after resuming
+        * controller from L1 state.
+        */
+       cdnsp->override_apb_timeout = CHICKEN_APB_TIMEOUT_VALUE;
+       pci_set_drvdata(pdev, cdnsp);
+
        if (pci_is_enabled(func)) {
                cdnsp->dev = dev;
                cdnsp->gadget_init = cdnsp_gadget_init;
@@ -153,8 +163,6 @@ static int cdnsp_pci_probe(struct pci_dev *pdev,
                        goto free_cdnsp;
        }
 
-       pci_set_drvdata(pdev, cdnsp);
-
        device_wakeup_enable(&pdev->dev);
        if (pci_dev_run_wake(pdev))
                pm_runtime_put_noidle(&pdev->dev);
index 7d4b8311051d884b645cf7dc706efeed5aab2e92..847d738b909e08f522b7132fa8d894de5b48ef76 100644 (file)
@@ -79,6 +79,8 @@ struct cdns3_platform_data {
  * @pdata: platform data from glue layer
  * @lock: spinlock structure
  * @xhci_plat_data: xhci private data structure pointer
+ * @override_apb_timeout: hold value of APB timeout. For value 0 the default
+ *                        value in CHICKEN_BITS_3 will be preserved.
  * @gadget_init: pointer to gadget initialization function
  */
 struct cdns {
@@ -117,6 +119,7 @@ struct cdns {
        struct cdns3_platform_data      *pdata;
        spinlock_t                      lock;
        struct xhci_plat_priv           *xhci_plat_data;
+       u32                             override_apb_timeout;
 
        int (*gadget_init)(struct cdns *cdns);
 };