From 8407fc2415e073bda301acd7151c3a35b17d987a Mon Sep 17 00:00:00 2001 From: Han Xu Date: Mon, 18 Oct 2021 00:46:05 -0500 Subject: [PATCH] LF-4798: spi: spi-nxp-fspi: fix the octal ddr with odd address issue When FSPI running in octal ddr mode, it won't access the odd start address A, but to access the previous 2 byte aligned start address A-1, when access the nor chip via IPS. The code change fix the issue and tested with mt35xu512aba, which is 128K erase size and easy to trigger the issue when running UBIFS test. After enabled the OCTAL DDR mode for i.MX8DXL, found kernel dump during UBIFS bonnie++ test, at the step of mounting. the error logs are as followings: Volume ID 0, size 508 LEBs (66519552 bytes, 63.4 MiB), LEB size 130944 bytes (127.8 KiB), dynamic, name "test", alignment 1 [ 168.170373] UBIFS (ubi0:0): default file-system created [ 168.181626] UBIFS (ubi0:0): Mounting in unauthenticated mode [ 168.189532] UBIFS (ubi0:0): background thread "ubifs_bgt0_0" started, PID 387 [ 168.244961] UBIFS error (ubi0:0 pid 386): check_lpt_type.constprop.22: invalid type (4) in LPT node type 2 [ 168.254733] CPU: 0 PID: 386 Comm: mount Not tainted 5.10.72-01963-g6f6d787f2e3-dirty #49 [ 168.262826] Hardware name: Freescale i.MX8DXL EVK (DT) [ 168.267970] Call trace: [ 168.270427] dump_backtrace+0x0/0x1c0 [ 168.274097] show_stack+0x18/0x68 [ 168.277414] dump_stack+0xd8/0x134 [ 168.280822] check_lpt_type.constprop.22+0x58/0x60 [ 168.285614] ubifs_lpt_init+0x37c/0x558 [ 168.289456] ubifs_mount+0xee8/0x13b8 [ 168.293123] legacy_get_tree+0x30/0x60 [ 168.296874] vfs_get_tree+0x2c/0x118 [ 168.300455] path_mount+0x744/0x9b0 [ 168.303944] do_mount+0x9c/0xb8 [ 168.307090] __arm64_sys_mount+0x12c/0x2a0 [ 168.311194] el0_svc_common.constprop.4+0x68/0x188 [ 168.315987] do_el0_svc+0x24/0x90 [ 168.319307] el0_svc+0x14/0x20 [ 168.322364] el0_sync_handler+0x90/0xb8 [ 168.326203] el0_sync+0x160/0x180 [ 168.330403] UBIFS (ubi0:0): background thread "ubifs_bgt0_0" stops mount: /home/root/tmp: wrong fs type, bad option, bad superblock on ubi0:test, missing codepage or helper program, or other er. After debugging the UBIFS lpt code, found it accesses the nor chip from an odd address, such as address A, but FSPI controller force to read from 2 byte aligned address A-1, so it can't read the correct data and causes UBIFS mount failed. This should be a common issue but not found on i.MX8ULP, becuase the ubifs_info pnode_sz is an even number due to the chip mounted on i.MX8ULP is a 64K erase size macronix nor chip, the 128K erase size micron nor chip on i.MX8DXL can easily trigger the issue. The code change can fix the read on odd address issue but it's still a workaround. There are some cases can NOT be fixed, theoretically. For instance, read CR register in OCTAL DTR mode with odd address. Since the nor chip can only outputs the same bytes repeatedly when continuously read, so there is no SW workaround, but we haven't seen any real cases till now. Signed-off-by: Han Xu Reviewed-by: Haibo Chen --- drivers/spi/spi-nxp-fspi.c | 55 +++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-nxp-fspi.c b/drivers/spi/spi-nxp-fspi.c index f55d4150a70f4..6eb1e06b97a85 100644 --- a/drivers/spi/spi-nxp-fspi.c +++ b/drivers/spi/spi-nxp-fspi.c @@ -384,6 +384,7 @@ struct nxp_fspi { int selected; #define FSPI_INITILIZED (1 << 0) #define FSPI_RXCLKSRC_3 (1 << 1) +#define FSPI_DTR_ODD_ADDR (1 << 2) int flags; }; @@ -802,22 +803,42 @@ static void nxp_fspi_read_rxfifo(struct nxp_fspi *f, { void __iomem *base = f->iobase; int i, ret; - int len = op->data.nbytes; + int len, cnt; u8 *buf = (u8 *) op->data.buf.in; + /* DTR with ODD address need read one more byte */ + len = (f->flags & FSPI_DTR_ODD_ADDR) ? op-> data.nbytes + 1 : op->data.nbytes; + /* * Default value of water mark level is 8 bytes, hence in single * read request controller can read max 8 bytes of data. */ - for (i = 0; i < ALIGN_DOWN(len, 8); i += 8) { + cnt = ALIGN_DOWN(len, 8); + + for (i = 0; i < cnt;) { /* Wait for RXFIFO available */ ret = fspi_readl_poll_tout(f, f->iobase + FSPI_INTR, FSPI_INTR_IPRXWA, 0, POLL_TOUT, true); WARN_ON(ret); - *(u32 *)(buf + i) = fspi_readl(f, base + FSPI_RFDR); - *(u32 *)(buf + i + 4) = fspi_readl(f, base + FSPI_RFDR + 4); + if (f->flags & FSPI_DTR_ODD_ADDR && !i) { + /* + * DTR read always start from 2bytes alignment address, + * if read from an odd address A, it actually read from + * address A-1, need to abandon the first byte here + */ + u8 tmp[8]; + *(u32 *)tmp = fspi_readl(f, base + FSPI_RFDR); + *(u32 *)(tmp + 4) = fspi_readl(f, base + FSPI_RFDR + 4); + memcpy(buf, tmp + 1, 7); + i += 7; + f->flags &= ~FSPI_DTR_ODD_ADDR; + } else { + *(u32 *)(buf + i) = fspi_readl(f, base + FSPI_RFDR); + *(u32 *)(buf + i + 4) = fspi_readl(f, base + FSPI_RFDR + 4); + i += 8; + } /* move the FIFO pointer */ fspi_writel(f, FSPI_INTR_IPRXWA, base + FSPI_INTR); } @@ -827,13 +848,13 @@ static void nxp_fspi_read_rxfifo(struct nxp_fspi *f, int size, j; buf = op->data.buf.in + i; + len -= i; /* Wait for RXFIFO available */ ret = fspi_readl_poll_tout(f, f->iobase + FSPI_INTR, FSPI_INTR_IPRXWA, 0, POLL_TOUT, true); WARN_ON(ret); - len = op->data.nbytes - i; for (j = 0; j < op->data.nbytes - i; j += 4) { tmp = fspi_readl(f, base + FSPI_RFDR + j); size = min(len, 4); @@ -864,15 +885,31 @@ static int nxp_fspi_do_op(struct nxp_fspi *f, const struct spi_mem_op *op) init_completion(&f->c); fspi_writel(f, op->addr.val, base + FSPI_IPCR0); + /* * Always start the sequence at the same index since we update * the LUT at each exec_op() call. And also specify the DATA * length, since it's has not been specified in the LUT. + * + * OCTAL DTR read always start from 2bytes alignment address, + * if read from an odd address A, it actually read from + * address A-1, need to read one more byte to get all + * data needed. */ - fspi_writel(f, op->data.nbytes | - (SEQID_LUT << FSPI_IPCR1_SEQID_SHIFT) | - (seqnum << FSPI_IPCR1_SEQNUM_SHIFT), - base + FSPI_IPCR1); + + if ((op->addr.val & 1) && (op->data.dir == SPI_MEM_DATA_IN) && + op->cmd.dtr && op->addr.dtr && op->dummy.dtr && op->data.dtr) { + f->flags |= FSPI_DTR_ODD_ADDR; + fspi_writel(f, (op->data.nbytes + 1) | + (SEQID_LUT << FSPI_IPCR1_SEQID_SHIFT) | + (seqnum << FSPI_IPCR1_SEQNUM_SHIFT), + base + FSPI_IPCR1); + } else { + fspi_writel(f, op->data.nbytes | + (SEQID_LUT << FSPI_IPCR1_SEQID_SHIFT) | + (seqnum << FSPI_IPCR1_SEQNUM_SHIFT), + base + FSPI_IPCR1); + } /* Trigger the LUT now. */ fspi_writel(f, FSPI_IPCMD_TRG, base + FSPI_IPCMD);