To dump internal LSP and endpoint state debug registers, we must write to GDBGLSPMUX register. This patch correctly dump LSP and endpoint states from the debug registers. If the controller is in device mode, all LSP and endpoint state registers will be dumped via the debugfs attribute "lsp_dump". In host mode, the user has to write the LSP number to "lsp_dump" to dump a specific LSP selection. Fixes: 80b776340c78 ("usb: dwc3: Dump LSP and BMU debug info") Signed-off-by: Thinh Nguyen <thinhn@xxxxxxxxxxxx> --- Change in v2: - Some cleanup and fix locking drivers/usb/dwc3/core.h | 13 ++++ drivers/usb/dwc3/debugfs.c | 145 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 154 insertions(+), 4 deletions(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 2ba034b5da07..7b17bb6a353c 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -174,6 +174,12 @@ #define DWC3_GSBUSCFG0_INCRBRSTENA (1 << 0) /* undefined length enable */ #define DWC3_GSBUSCFG0_INCRBRST_MASK 0xff +/* Global Debug LSP MUX Select */ +#define DWC3_GDBGLSPMUX_ENDBC BIT(15) /* Host only */ +#define DWC3_GDBGLSPMUX_HOSTSELECT(n) ((n) & 0x3fff) +#define DWC3_GDBGLSPMUX_DEVSELECT(n) (((n) & 0xf) << 4) +#define DWC3_GDBGLSPMUX_EPSELECT(n) ((n) & 0xf) + /* Global Debug Queue/FIFO Space Available Register */ #define DWC3_GDBGFIFOSPACE_NUM(n) ((n) & 0x1f) #define DWC3_GDBGFIFOSPACE_TYPE(n) (((n) << 5) & 0x1e0) @@ -253,6 +259,9 @@ #define DWC3_GSTS_DEVICE_IP BIT(6) #define DWC3_GSTS_CSR_TIMEOUT BIT(5) #define DWC3_GSTS_BUS_ERR_ADDR_VLD BIT(4) +#define DWC3_GSTS_CURMOD(n) ((n) & 0x3) +#define DWC3_GSTS_CURMOD_DEVICE 0 +#define DWC3_GSTS_CURMOD_HOST 1 /* Global USB2 PHY Configuration Register */ #define DWC3_GUSB2PHYCFG_PHYSOFTRST BIT(31) @@ -321,6 +330,7 @@ #define DWC3_GHWPARAMS1_EN_PWROPT_HIB 2 #define DWC3_GHWPARAMS1_PWROPT(n) ((n) << 24) #define DWC3_GHWPARAMS1_PWROPT_MASK DWC3_GHWPARAMS1_PWROPT(3) +#define DWC3_GHWPARAMS1_ENDBC BIT(31) /* Global HWPARAMS3 Register */ #define DWC3_GHWPARAMS3_SSPHY_IFC(n) ((n) & 3) @@ -945,6 +955,7 @@ struct dwc3_scratchpad_array { * @hwparams: copy of hwparams registers * @root: debugfs root folder pointer * @regset: debugfs pointer to regdump file + * @dbg_lsp_select: current debug lsp mux register selection * @test_mode: true when we're entering a USB test mode * @test_mode_nr: test feature selector * @lpm_nyet_threshold: LPM NYET response threshold @@ -1121,6 +1132,8 @@ struct dwc3 { struct dentry *root; struct debugfs_regset32 *regset; + u32 dbg_lsp_select; + u8 test_mode; u8 test_mode_nr; u8 lpm_nyet_threshold; diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c index bd3d75b2f8bc..1da012f105d7 100644 --- a/drivers/usb/dwc3/debugfs.c +++ b/drivers/usb/dwc3/debugfs.c @@ -25,6 +25,8 @@ #include "io.h" #include "debug.h" +#define DWC3_LSP_MUX_UNSELECTED 0xfffff + #define dump_register(nm) \ { \ .name = __stringify(nm), \ @@ -82,10 +84,6 @@ static const struct debugfs_reg32 dwc3_regs[] = { dump_register(GDBGFIFOSPACE), dump_register(GDBGLTSSM), dump_register(GDBGBMU), - dump_register(GDBGLSPMUX), - dump_register(GDBGLSP), - dump_register(GDBGEPINFO0), - dump_register(GDBGEPINFO1), dump_register(GPRTBIMAP_HS0), dump_register(GPRTBIMAP_HS1), dump_register(GPRTBIMAP_FS0), @@ -279,6 +277,114 @@ static const struct debugfs_reg32 dwc3_regs[] = { dump_register(OSTS), }; +static void dwc3_host_lsp(struct seq_file *s) +{ + struct dwc3 *dwc = s->private; + bool dbc_enabled; + u32 sel; + u32 reg; + u32 val; + + dbc_enabled = !!(dwc->hwparams.hwparams1 & DWC3_GHWPARAMS1_ENDBC); + + sel = dwc->dbg_lsp_select; + if (sel == DWC3_LSP_MUX_UNSELECTED) { + seq_puts(s, "Write LSP selection to print for host\n"); + return; + } + + reg = DWC3_GDBGLSPMUX_HOSTSELECT(sel); + + dwc3_writel(dwc->regs, DWC3_GDBGLSPMUX, reg); + val = dwc3_readl(dwc->regs, DWC3_GDBGLSP); + seq_printf(s, "GDBGLSP[%d] = 0x%08x\n", sel, val); + + if (dbc_enabled && sel < 256) { + reg |= DWC3_GDBGLSPMUX_ENDBC; + dwc3_writel(dwc->regs, DWC3_GDBGLSPMUX, reg); + val = dwc3_readl(dwc->regs, DWC3_GDBGLSP); + seq_printf(s, "GDBGLSP_DBC[%d] = 0x%08x\n", sel, val); + } +} + +static void dwc3_gadget_lsp(struct seq_file *s) +{ + struct dwc3 *dwc = s->private; + int i; + u32 reg; + + for (i = 0; i < 16; i++) { + reg = DWC3_GDBGLSPMUX_DEVSELECT(i); + dwc3_writel(dwc->regs, DWC3_GDBGLSPMUX, reg); + reg = dwc3_readl(dwc->regs, DWC3_GDBGLSP); + seq_printf(s, "GDBGLSP[%d] = 0x%08x\n", i, reg); + } +} + +static int dwc3_lsp_show(struct seq_file *s, void *unused) +{ + struct dwc3 *dwc = s->private; + unsigned int current_mode; + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&dwc->lock, flags); + reg = dwc3_readl(dwc->regs, DWC3_GSTS); + current_mode = DWC3_GSTS_CURMOD(reg); + + switch (current_mode) { + case DWC3_GSTS_CURMOD_HOST: + dwc3_host_lsp(s); + break; + case DWC3_GSTS_CURMOD_DEVICE: + dwc3_gadget_lsp(s); + break; + default: + seq_puts(s, "Mode is unknown, no LSP register printed\n"); + break; + } + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} + +static int dwc3_lsp_open(struct inode *inode, struct file *file) +{ + return single_open(file, dwc3_lsp_show, inode->i_private); +} + +static ssize_t dwc3_lsp_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct dwc3 *dwc = s->private; + unsigned long flags; + char buf[32] = { 0 }; + u32 sel; + int ret; + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + ret = kstrtouint(buf, 0, &sel); + if (ret) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + dwc->dbg_lsp_select = sel; + spin_unlock_irqrestore(&dwc->lock, flags); + + return count; +} + +static const struct file_operations dwc3_lsp_fops = { + .open = dwc3_lsp_open, + .write = dwc3_lsp_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + static int dwc3_mode_show(struct seq_file *s, void *unused) { struct dwc3 *dwc = s->private; @@ -683,6 +789,30 @@ static int dwc3_trb_ring_show(struct seq_file *s, void *unused) return 0; } +static int dwc3_ep_info_register_show(struct seq_file *s, void *unused) +{ + struct dwc3_ep *dep = s->private; + struct dwc3 *dwc = dep->dwc; + unsigned long flags; + u64 ep_info; + u32 lower_32_bits; + u32 upper_32_bits; + u32 reg; + + spin_lock_irqsave(&dwc->lock, flags); + reg = DWC3_GDBGLSPMUX_EPSELECT(dep->number); + dwc3_writel(dwc->regs, DWC3_GDBGLSPMUX, reg); + + lower_32_bits = dwc3_readl(dwc->regs, DWC3_GDBGEPINFO0); + upper_32_bits = dwc3_readl(dwc->regs, DWC3_GDBGEPINFO1); + + ep_info = ((u64)upper_32_bits << 32) | lower_32_bits; + seq_printf(s, "0x%016llx\n", ep_info); + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} + DEFINE_SHOW_ATTRIBUTE(dwc3_tx_fifo_size); DEFINE_SHOW_ATTRIBUTE(dwc3_rx_fifo_size); DEFINE_SHOW_ATTRIBUTE(dwc3_tx_request_queue); @@ -692,6 +822,7 @@ DEFINE_SHOW_ATTRIBUTE(dwc3_descriptor_fetch_queue); DEFINE_SHOW_ATTRIBUTE(dwc3_event_queue); DEFINE_SHOW_ATTRIBUTE(dwc3_transfer_type); DEFINE_SHOW_ATTRIBUTE(dwc3_trb_ring); +DEFINE_SHOW_ATTRIBUTE(dwc3_ep_info_register); static const struct dwc3_ep_file_map dwc3_ep_file_map[] = { { "tx_fifo_size", &dwc3_tx_fifo_size_fops, }, @@ -703,6 +834,7 @@ static const struct dwc3_ep_file_map dwc3_ep_file_map[] = { { "event_queue", &dwc3_event_queue_fops, }, { "transfer_type", &dwc3_transfer_type_fops, }, { "trb_ring", &dwc3_trb_ring_fops, }, + { "GDBGEPINFO", &dwc3_ep_info_register_fops, }, }; static void dwc3_debugfs_create_endpoint_files(struct dwc3_ep *dep, @@ -750,6 +882,8 @@ void dwc3_debugfs_init(struct dwc3 *dwc) if (!dwc->regset) return; + dwc->dbg_lsp_select = DWC3_LSP_MUX_UNSELECTED; + dwc->regset->regs = dwc3_regs; dwc->regset->nregs = ARRAY_SIZE(dwc3_regs); dwc->regset->base = dwc->regs - DWC3_GLOBALS_REGS_START; @@ -759,6 +893,9 @@ void dwc3_debugfs_init(struct dwc3 *dwc) debugfs_create_regset32("regdump", S_IRUGO, root, dwc->regset); + debugfs_create_file("lsp_dump", S_IRUGO | S_IWUSR, root, dwc, + &dwc3_lsp_fops); + if (IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)) { debugfs_create_file("mode", S_IRUGO | S_IWUSR, root, dwc, &dwc3_mode_fops); -- 2.11.0