Skip to content

Commit

Permalink
LF-4798: spi: spi-nxp-fspi: fix the octal ddr with odd address issue
Browse files Browse the repository at this point in the history
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 Freescale#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 <han.xu@nxp.com>
Reviewed-by: Haibo Chen <haibo.chen@nxp.com>
  • Loading branch information
hanxu-nxp committed Oct 22, 2021
1 parent 5c9435f commit 84fc281
Showing 1 changed file with 46 additions and 9 deletions.
55 changes: 46 additions & 9 deletions drivers/spi/spi-nxp-fspi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 84fc281

Please sign in to comment.