mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-22 11:13:07 +00:00
2635e3b50f
Add a mask parameter to control the lookup of the PCI region from which the mapping can be made. Signed-off-by: Andrew Scull <ascull@google.com> Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2259 lines
56 KiB
C
2259 lines
56 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2018 Marvell International Ltd.
|
|
*/
|
|
|
|
#include <dm.h>
|
|
#include <dm/device-internal.h>
|
|
#include <dm/devres.h>
|
|
#include <dm/of_access.h>
|
|
#include <malloc.h>
|
|
#include <memalign.h>
|
|
#include <nand.h>
|
|
#include <pci.h>
|
|
#include <time.h>
|
|
#include <linux/bitfield.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/err.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/libfdt.h>
|
|
#include <linux/mtd/mtd.h>
|
|
#include <linux/mtd/nand_bch.h>
|
|
#include <linux/mtd/nand_ecc.h>
|
|
#include <linux/mtd/rawnand.h>
|
|
#include <asm/global_data.h>
|
|
#include <asm/io.h>
|
|
#include <asm/types.h>
|
|
#include <asm/dma-mapping.h>
|
|
#include <asm/arch/clock.h>
|
|
#include "octeontx_bch.h"
|
|
|
|
#ifdef DEBUG
|
|
# undef CONFIG_LOGLEVEL
|
|
# define CONFIG_LOGLEVEL 8
|
|
#endif
|
|
|
|
/*
|
|
* The NDF_CMD queue takes commands between 16 - 128 bit.
|
|
* All commands must be 16 bit aligned and are little endian.
|
|
* WAIT_STATUS commands must be 64 bit aligned.
|
|
* Commands are selected by the 4 bit opcode.
|
|
*
|
|
* Available Commands:
|
|
*
|
|
* 16 Bit:
|
|
* NOP
|
|
* WAIT
|
|
* BUS_ACQ, BUS_REL
|
|
* CHIP_EN, CHIP_DIS
|
|
*
|
|
* 32 Bit:
|
|
* CLE_CMD
|
|
* RD_CMD, RD_EDO_CMD
|
|
* WR_CMD
|
|
*
|
|
* 64 Bit:
|
|
* SET_TM_PAR
|
|
*
|
|
* 96 Bit:
|
|
* ALE_CMD
|
|
*
|
|
* 128 Bit:
|
|
* WAIT_STATUS, WAIT_STATUS_ALE
|
|
*/
|
|
|
|
/* NDF Register offsets */
|
|
#define NDF_CMD 0x0
|
|
#define NDF_MISC 0x8
|
|
#define NDF_ECC_CNT 0x10
|
|
#define NDF_DRBELL 0x30
|
|
#define NDF_ST_REG 0x38 /* status */
|
|
#define NDF_INT 0x40
|
|
#define NDF_INT_W1S 0x48
|
|
#define NDF_DMA_CFG 0x50
|
|
#define NDF_DMA_ADR 0x58
|
|
#define NDF_INT_ENA_W1C 0x60
|
|
#define NDF_INT_ENA_W1S 0x68
|
|
|
|
/* NDF command opcodes */
|
|
#define NDF_OP_NOP 0x0
|
|
#define NDF_OP_SET_TM_PAR 0x1
|
|
#define NDF_OP_WAIT 0x2
|
|
#define NDF_OP_CHIP_EN_DIS 0x3
|
|
#define NDF_OP_CLE_CMD 0x4
|
|
#define NDF_OP_ALE_CMD 0x5
|
|
#define NDF_OP_WR_CMD 0x8
|
|
#define NDF_OP_RD_CMD 0x9
|
|
#define NDF_OP_RD_EDO_CMD 0xa
|
|
#define NDF_OP_WAIT_STATUS 0xb /* same opcode for WAIT_STATUS_ALE */
|
|
#define NDF_OP_BUS_ACQ_REL 0xf
|
|
|
|
#define NDF_BUS_ACQUIRE 1
|
|
#define NDF_BUS_RELEASE 0
|
|
|
|
#define DBGX_EDSCR(X) (0x87A008000088 + (X) * 0x80000)
|
|
|
|
struct ndf_nop_cmd {
|
|
u16 opcode: 4;
|
|
u16 nop: 12;
|
|
};
|
|
|
|
struct ndf_wait_cmd {
|
|
u16 opcode:4;
|
|
u16 r_b:1; /* wait for one cycle or PBUS_WAIT deassert */
|
|
u16:3;
|
|
u16 wlen:3; /* timing parameter select */
|
|
u16:5;
|
|
};
|
|
|
|
struct ndf_bus_cmd {
|
|
u16 opcode:4;
|
|
u16 direction:4; /* 1 = acquire, 0 = release */
|
|
u16:8;
|
|
};
|
|
|
|
struct ndf_chip_cmd {
|
|
u16 opcode:4;
|
|
u16 chip:3; /* select chip, 0 = disable */
|
|
u16 enable:1; /* 1 = enable, 0 = disable */
|
|
u16 bus_width:2; /* 10 = 16 bit, 01 = 8 bit */
|
|
u16:6;
|
|
};
|
|
|
|
struct ndf_cle_cmd {
|
|
u32 opcode:4;
|
|
u32:4;
|
|
u32 cmd_data:8; /* command sent to the PBUS AD pins */
|
|
u32 clen1:3; /* time between PBUS CLE and WE asserts */
|
|
u32 clen2:3; /* time WE remains asserted */
|
|
u32 clen3:3; /* time between WE deassert and CLE */
|
|
u32:7;
|
|
};
|
|
|
|
/* RD_EDO_CMD uses the same layout as RD_CMD */
|
|
struct ndf_rd_cmd {
|
|
u32 opcode:4;
|
|
u32 data:16; /* data bytes */
|
|
u32 rlen1:3;
|
|
u32 rlen2:3;
|
|
u32 rlen3:3;
|
|
u32 rlen4:3;
|
|
};
|
|
|
|
struct ndf_wr_cmd {
|
|
u32 opcode:4;
|
|
u32 data:16; /* data bytes */
|
|
u32:4;
|
|
u32 wlen1:3;
|
|
u32 wlen2:3;
|
|
u32:3;
|
|
};
|
|
|
|
struct ndf_set_tm_par_cmd {
|
|
u64 opcode:4;
|
|
u64 tim_mult:4; /* multiplier for the seven parameters */
|
|
u64 tm_par1:8; /* --> Following are the 7 timing parameters that */
|
|
u64 tm_par2:8; /* specify the number of coprocessor cycles. */
|
|
u64 tm_par3:8; /* A value of zero means one cycle. */
|
|
u64 tm_par4:8; /* All values are scaled by tim_mult */
|
|
u64 tm_par5:8; /* using tim_par * (2 ^ tim_mult). */
|
|
u64 tm_par6:8;
|
|
u64 tm_par7:8;
|
|
};
|
|
|
|
struct ndf_ale_cmd {
|
|
u32 opcode:4;
|
|
u32:4;
|
|
u32 adr_byte_num:4; /* number of address bytes to be sent */
|
|
u32:4;
|
|
u32 alen1:3;
|
|
u32 alen2:3;
|
|
u32 alen3:3;
|
|
u32 alen4:3;
|
|
u32:4;
|
|
u8 adr_byt1;
|
|
u8 adr_byt2;
|
|
u8 adr_byt3;
|
|
u8 adr_byt4;
|
|
u8 adr_byt5;
|
|
u8 adr_byt6;
|
|
u8 adr_byt7;
|
|
u8 adr_byt8;
|
|
};
|
|
|
|
struct ndf_wait_status_cmd {
|
|
u32 opcode:4;
|
|
u32:4;
|
|
u32 data:8; /** data */
|
|
u32 clen1:3;
|
|
u32 clen2:3;
|
|
u32 clen3:3;
|
|
u32:8;
|
|
/** set to 5 to select WAIT_STATUS_ALE command */
|
|
u32 ale_ind:8;
|
|
/** ALE only: number of address bytes to be sent */
|
|
u32 adr_byte_num:4;
|
|
u32:4;
|
|
u32 alen1:3; /* ALE only */
|
|
u32 alen2:3; /* ALE only */
|
|
u32 alen3:3; /* ALE only */
|
|
u32 alen4:3; /* ALE only */
|
|
u32:4;
|
|
u8 adr_byt[4]; /* ALE only */
|
|
u32 nine:4; /* set to 9 */
|
|
u32 and_mask:8;
|
|
u32 comp_byte:8;
|
|
u32 rlen1:3;
|
|
u32 rlen2:3;
|
|
u32 rlen3:3;
|
|
u32 rlen4:3;
|
|
};
|
|
|
|
union ndf_cmd {
|
|
u64 val[2];
|
|
union {
|
|
struct ndf_nop_cmd nop;
|
|
struct ndf_wait_cmd wait;
|
|
struct ndf_bus_cmd bus_acq_rel;
|
|
struct ndf_chip_cmd chip_en_dis;
|
|
struct ndf_cle_cmd cle_cmd;
|
|
struct ndf_rd_cmd rd_cmd;
|
|
struct ndf_wr_cmd wr_cmd;
|
|
struct ndf_set_tm_par_cmd set_tm_par;
|
|
struct ndf_ale_cmd ale_cmd;
|
|
struct ndf_wait_status_cmd wait_status;
|
|
} u;
|
|
};
|
|
|
|
/** Disable multi-bit error hangs */
|
|
#define NDF_MISC_MB_DIS BIT_ULL(27)
|
|
/** High watermark for NBR FIFO or load/store operations */
|
|
#define NDF_MISC_NBR_HWM GENMASK_ULL(26, 24)
|
|
/** Wait input filter count */
|
|
#define NDF_MISC_WAIT_CNT GENMASK_ULL(23, 18)
|
|
/** Unfilled NFD_CMD queue bytes */
|
|
#define NDF_MISC_FR_BYTE GENMASK_ULL(17, 7)
|
|
/** Set by HW when it reads the last 8 bytes of NDF_CMD */
|
|
#define NDF_MISC_RD_DONE BIT_ULL(6)
|
|
/** Set by HW when it reads. SW read of NDF_CMD clears it */
|
|
#define NDF_MISC_RD_VAL BIT_ULL(5)
|
|
/** Let HW read NDF_CMD queue. Cleared on SW NDF_CMD write */
|
|
#define NDF_MISC_RD_CMD BIT_ULL(4)
|
|
/** Boot disable */
|
|
#define NDF_MISC_BT_DIS BIT_ULL(2)
|
|
/** Stop command execution after completing command queue */
|
|
#define NDF_MISC_EX_DIS BIT_ULL(1)
|
|
/** Reset fifo */
|
|
#define NDF_MISC_RST_FF BIT_ULL(0)
|
|
|
|
/** DMA engine enable */
|
|
#define NDF_DMA_CFG_EN BIT_ULL(63)
|
|
/** Read or write */
|
|
#define NDF_DMA_CFG_RW BIT_ULL(62)
|
|
/** Terminates DMA and clears enable bit */
|
|
#define NDF_DMA_CFG_CLR BIT_ULL(61)
|
|
/** 32-bit swap enable */
|
|
#define NDF_DMA_CFG_SWAP32 BIT_ULL(59)
|
|
/** 16-bit swap enable */
|
|
#define NDF_DMA_CFG_SWAP16 BIT_ULL(58)
|
|
/** 8-bit swap enable */
|
|
#define NDF_DMA_CFG_SWAP8 BIT_ULL(57)
|
|
/** Endian mode */
|
|
#define NDF_DMA_CFG_CMD_BE BIT_ULL(56)
|
|
/** Number of 64 bit transfers */
|
|
#define NDF_DMA_CFG_SIZE GENMASK_ULL(55, 36)
|
|
|
|
/** Command execution status idle */
|
|
#define NDF_ST_REG_EXE_IDLE BIT_ULL(15)
|
|
/** Command execution SM states */
|
|
#define NDF_ST_REG_EXE_SM GENMASK_ULL(14, 11)
|
|
/** DMA and load SM states */
|
|
#define NDF_ST_REG_BT_SM GENMASK_ULL(10, 7)
|
|
/** Queue read-back SM bad state */
|
|
#define NDF_ST_REG_RD_FF_BAD BIT_ULL(6)
|
|
/** Queue read-back SM states */
|
|
#define NDF_ST_REG_RD_FF GENMASK_ULL(5, 4)
|
|
/** Main SM is in a bad state */
|
|
#define NDF_ST_REG_MAIN_BAD BIT_ULL(3)
|
|
/** Main SM states */
|
|
#define NDF_ST_REG_MAIN_SM GENMASK_ULL(2, 0)
|
|
|
|
#define MAX_NAND_NAME_LEN 64
|
|
#if (defined(NAND_MAX_PAGESIZE) && (NAND_MAX_PAGESIZE > 4096)) || \
|
|
!defined(NAND_MAX_PAGESIZE)
|
|
# undef NAND_MAX_PAGESIZE
|
|
# define NAND_MAX_PAGESIZE 4096
|
|
#endif
|
|
#if (defined(NAND_MAX_OOBSIZE) && (NAND_MAX_OOBSIZE > 256)) || \
|
|
!defined(NAND_MAX_OOBSIZE)
|
|
# undef NAND_MAX_OOBSIZE
|
|
# define NAND_MAX_OOBSIZE 256
|
|
#endif
|
|
|
|
#define OCTEONTX_NAND_DRIVER_NAME "octeontx_nand"
|
|
|
|
#define NDF_TIMEOUT 1000 /** Timeout in ms */
|
|
#define USEC_PER_SEC 1000000 /** Linux compatibility */
|
|
#ifndef NAND_MAX_CHIPS
|
|
# define NAND_MAX_CHIPS 8 /** Linux compatibility */
|
|
#endif
|
|
|
|
struct octeontx_nand_chip {
|
|
struct list_head node;
|
|
struct nand_chip nand;
|
|
struct ndf_set_tm_par_cmd timings;
|
|
int cs;
|
|
int selected_page;
|
|
int iface_mode;
|
|
int row_bytes;
|
|
int col_bytes;
|
|
bool oob_only;
|
|
bool iface_set;
|
|
};
|
|
|
|
struct octeontx_nand_buf {
|
|
u8 *dmabuf;
|
|
dma_addr_t dmaaddr;
|
|
int dmabuflen;
|
|
int data_len;
|
|
int data_index;
|
|
};
|
|
|
|
/** NAND flash controller (NDF) related information */
|
|
struct octeontx_nfc {
|
|
struct nand_hw_control controller;
|
|
struct udevice *dev;
|
|
void __iomem *base;
|
|
struct list_head chips;
|
|
int selected_chip; /* Currently selected NAND chip number */
|
|
|
|
/*
|
|
* Status is separate from octeontx_nand_buf because
|
|
* it can be used in parallel and during init.
|
|
*/
|
|
u8 *stat;
|
|
dma_addr_t stat_addr;
|
|
bool use_status;
|
|
|
|
struct octeontx_nand_buf buf;
|
|
union bch_resp *bch_resp;
|
|
dma_addr_t bch_rhandle;
|
|
|
|
/* BCH of all-0xff, so erased pages read as error-free */
|
|
unsigned char *eccmask;
|
|
};
|
|
|
|
/* settable timings - 0..7 select timing of alen1..4/clen1..3/etc */
|
|
enum tm_idx {
|
|
t0, /* fixed at 4<<mult cycles */
|
|
t1, t2, t3, t4, t5, t6, t7, /* settable per ONFI-timing mode */
|
|
};
|
|
|
|
struct octeontx_probe_device {
|
|
struct list_head list;
|
|
struct udevice *dev;
|
|
};
|
|
|
|
static struct bch_vf *bch_vf;
|
|
/** Deferred devices due to BCH not being ready */
|
|
LIST_HEAD(octeontx_pci_nand_deferred_devices);
|
|
|
|
/** default parameters used for probing chips */
|
|
#define MAX_ONFI_MODE 5
|
|
|
|
static int default_onfi_timing;
|
|
static int slew_ns = 2; /* default timing padding */
|
|
static int def_ecc_size = 512; /* 1024 best for sw_bch, <= 4095 for hw_bch */
|
|
static int default_width = 1; /* 8 bit */
|
|
static int default_page_size = 2048;
|
|
static struct ndf_set_tm_par_cmd default_timing_parms;
|
|
|
|
/** Port from Linux */
|
|
#define readq_poll_timeout(addr, val, cond, delay_us, timeout_us) \
|
|
({ \
|
|
ulong __start = get_timer(0); \
|
|
void *__addr = (addr); \
|
|
const ulong __timeout_ms = timeout_us / 1000; \
|
|
do { \
|
|
(val) = readq(__addr); \
|
|
if (cond) \
|
|
break; \
|
|
if (timeout_us && get_timer(__start) > __timeout_ms) { \
|
|
(val) = readq(__addr); \
|
|
break; \
|
|
} \
|
|
if (delay_us) \
|
|
udelay(delay_us); \
|
|
} while (1); \
|
|
(cond) ? 0 : -ETIMEDOUT; \
|
|
})
|
|
|
|
/** Ported from Linux 4.9.0 include/linux/of.h for compatibility */
|
|
static inline int of_get_child_count(const ofnode node)
|
|
{
|
|
return fdtdec_get_child_count(gd->fdt_blob, ofnode_to_offset(node));
|
|
}
|
|
|
|
/**
|
|
* Linux compatibility from Linux 4.9.0 drivers/mtd/nand/nand_base.c
|
|
*/
|
|
static int nand_ooblayout_ecc_lp(struct mtd_info *mtd, int section,
|
|
struct mtd_oob_region *oobregion)
|
|
{
|
|
struct nand_chip *chip = mtd_to_nand(mtd);
|
|
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
|
|
if (section || !ecc->total)
|
|
return -ERANGE;
|
|
|
|
oobregion->length = ecc->total;
|
|
oobregion->offset = mtd->oobsize - oobregion->length;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Linux compatibility from Linux 4.9.0 drivers/mtd/nand/nand_base.c
|
|
*/
|
|
static int nand_ooblayout_free_lp(struct mtd_info *mtd, int section,
|
|
struct mtd_oob_region *oobregion)
|
|
{
|
|
struct nand_chip *chip = mtd_to_nand(mtd);
|
|
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
|
|
if (section)
|
|
return -ERANGE;
|
|
|
|
oobregion->length = mtd->oobsize - ecc->total - 2;
|
|
oobregion->offset = 2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct mtd_ooblayout_ops nand_ooblayout_lp_ops = {
|
|
.ecc = nand_ooblayout_ecc_lp,
|
|
.rfree = nand_ooblayout_free_lp,
|
|
};
|
|
|
|
static inline struct octeontx_nand_chip *to_otx_nand(struct nand_chip *nand)
|
|
{
|
|
return container_of(nand, struct octeontx_nand_chip, nand);
|
|
}
|
|
|
|
static inline struct octeontx_nfc *to_otx_nfc(struct nand_hw_control *ctrl)
|
|
{
|
|
return container_of(ctrl, struct octeontx_nfc, controller);
|
|
}
|
|
|
|
static int octeontx_nand_calc_ecc_layout(struct nand_chip *nand)
|
|
{
|
|
struct nand_ecclayout *layout = nand->ecc.layout;
|
|
struct octeontx_nfc *tn = to_otx_nfc(nand->controller);
|
|
struct mtd_info *mtd = &nand->mtd;
|
|
int oobsize = mtd->oobsize;
|
|
int i;
|
|
bool layout_alloc = false;
|
|
|
|
if (!layout) {
|
|
layout = devm_kzalloc(tn->dev, sizeof(*layout), GFP_KERNEL);
|
|
if (!layout)
|
|
return -ENOMEM;
|
|
nand->ecc.layout = layout;
|
|
layout_alloc = true;
|
|
}
|
|
layout->eccbytes = nand->ecc.steps * nand->ecc.bytes;
|
|
/* Reserve 2 bytes for bad block marker */
|
|
if (layout->eccbytes + 2 > oobsize) {
|
|
pr_err("No suitable oob scheme available for oobsize %d eccbytes %u\n",
|
|
oobsize, layout->eccbytes);
|
|
goto fail;
|
|
}
|
|
/* put ecc bytes at oob tail */
|
|
for (i = 0; i < layout->eccbytes; i++)
|
|
layout->eccpos[i] = oobsize - layout->eccbytes + i;
|
|
layout->oobfree[0].offset = 2;
|
|
layout->oobfree[0].length = oobsize - 2 - layout->eccbytes;
|
|
nand->ecc.layout = layout;
|
|
return 0;
|
|
|
|
fail:
|
|
if (layout_alloc)
|
|
kfree(layout);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Read a single byte from the temporary buffer. Used after READID
|
|
* to get the NAND information and for STATUS.
|
|
*/
|
|
static u8 octeontx_nand_read_byte(struct mtd_info *mtd)
|
|
{
|
|
struct nand_chip *nand = mtd_to_nand(mtd);
|
|
struct octeontx_nfc *tn = to_otx_nfc(nand->controller);
|
|
|
|
if (tn->use_status) {
|
|
tn->use_status = false;
|
|
return *tn->stat;
|
|
}
|
|
|
|
if (tn->buf.data_index < tn->buf.data_len)
|
|
return tn->buf.dmabuf[tn->buf.data_index++];
|
|
|
|
dev_err(tn->dev, "No data to read, idx: 0x%x, len: 0x%x\n",
|
|
tn->buf.data_index, tn->buf.data_len);
|
|
|
|
return 0xff;
|
|
}
|
|
|
|
/*
|
|
* Read a number of pending bytes from the temporary buffer. Used
|
|
* to get page and OOB data.
|
|
*/
|
|
static void octeontx_nand_read_buf(struct mtd_info *mtd, u8 *buf, int len)
|
|
{
|
|
struct nand_chip *nand = mtd_to_nand(mtd);
|
|
struct octeontx_nfc *tn = to_otx_nfc(nand->controller);
|
|
|
|
if (len > tn->buf.data_len - tn->buf.data_index) {
|
|
dev_err(tn->dev, "Not enough data for read of %d bytes\n", len);
|
|
return;
|
|
}
|
|
|
|
memcpy(buf, tn->buf.dmabuf + tn->buf.data_index, len);
|
|
tn->buf.data_index += len;
|
|
}
|
|
|
|
static void octeontx_nand_write_buf(struct mtd_info *mtd,
|
|
const u8 *buf, int len)
|
|
{
|
|
struct nand_chip *nand = mtd_to_nand(mtd);
|
|
struct octeontx_nfc *tn = to_otx_nfc(nand->controller);
|
|
|
|
memcpy(tn->buf.dmabuf + tn->buf.data_len, buf, len);
|
|
tn->buf.data_len += len;
|
|
}
|
|
|
|
/* Overwrite default function to avoid sync abort on chip = -1. */
|
|
static void octeontx_nand_select_chip(struct mtd_info *mtd, int chip)
|
|
{
|
|
}
|
|
|
|
static inline int timing_to_cycle(u32 psec, unsigned long clock)
|
|
{
|
|
unsigned int ns;
|
|
int ticks;
|
|
|
|
ns = DIV_ROUND_UP(psec, 1000);
|
|
ns += slew_ns;
|
|
|
|
/* no rounding needed since clock is multiple of 1MHz */
|
|
clock /= 1000000;
|
|
ns *= clock;
|
|
|
|
ticks = DIV_ROUND_UP(ns, 1000);
|
|
|
|
/* actual delay is (tm_parX+1)<<tim_mult */
|
|
if (ticks)
|
|
ticks--;
|
|
|
|
return ticks;
|
|
}
|
|
|
|
static void set_timings(struct octeontx_nand_chip *chip,
|
|
struct ndf_set_tm_par_cmd *tp,
|
|
const struct nand_sdr_timings *timings,
|
|
unsigned long sclk)
|
|
{
|
|
/* scaled coprocessor-cycle values */
|
|
u32 s_wh, s_cls, s_clh, s_rp, s_wb, s_wc;
|
|
|
|
tp->tim_mult = 0;
|
|
s_wh = timing_to_cycle(timings->tWH_min, sclk);
|
|
s_cls = timing_to_cycle(timings->tCLS_min, sclk);
|
|
s_clh = timing_to_cycle(timings->tCLH_min, sclk);
|
|
s_rp = timing_to_cycle(timings->tRP_min, sclk);
|
|
s_wb = timing_to_cycle(timings->tWB_max, sclk);
|
|
s_wc = timing_to_cycle(timings->tWC_min, sclk);
|
|
|
|
tp->tm_par1 = s_wh;
|
|
tp->tm_par2 = s_clh;
|
|
tp->tm_par3 = s_rp + 1;
|
|
tp->tm_par4 = s_cls - s_wh;
|
|
tp->tm_par5 = s_wc - s_wh + 1;
|
|
tp->tm_par6 = s_wb;
|
|
tp->tm_par7 = 0;
|
|
tp->tim_mult++; /* overcompensate for bad math */
|
|
|
|
/* TODO: comment parameter re-use */
|
|
|
|
pr_debug("%s: tim_par: mult: %d p1: %d p2: %d p3: %d\n",
|
|
__func__, tp->tim_mult, tp->tm_par1, tp->tm_par2, tp->tm_par3);
|
|
pr_debug(" p4: %d p5: %d p6: %d p7: %d\n",
|
|
tp->tm_par4, tp->tm_par5, tp->tm_par6, tp->tm_par7);
|
|
}
|
|
|
|
static int set_default_timings(struct octeontx_nfc *tn,
|
|
const struct nand_sdr_timings *timings)
|
|
{
|
|
unsigned long sclk = octeontx_get_io_clock();
|
|
|
|
set_timings(NULL, &default_timing_parms, timings, sclk);
|
|
return 0;
|
|
}
|
|
|
|
static int octeontx_nfc_chip_set_timings(struct octeontx_nand_chip *chip,
|
|
const struct nand_sdr_timings *timings)
|
|
{
|
|
/*struct octeontx_nfc *tn = to_otx_nfc(chip->nand.controller);*/
|
|
unsigned long sclk = octeontx_get_io_clock();
|
|
|
|
set_timings(chip, &chip->timings, timings, sclk);
|
|
return 0;
|
|
}
|
|
|
|
/* How many bytes are free in the NFD_CMD queue? */
|
|
static int ndf_cmd_queue_free(struct octeontx_nfc *tn)
|
|
{
|
|
u64 ndf_misc;
|
|
|
|
ndf_misc = readq(tn->base + NDF_MISC);
|
|
return FIELD_GET(NDF_MISC_FR_BYTE, ndf_misc);
|
|
}
|
|
|
|
/* Submit a command to the NAND command queue. */
|
|
static int ndf_submit(struct octeontx_nfc *tn, union ndf_cmd *cmd)
|
|
{
|
|
int opcode = cmd->val[0] & 0xf;
|
|
|
|
switch (opcode) {
|
|
/* All these commands fit in one 64bit word */
|
|
case NDF_OP_NOP:
|
|
case NDF_OP_SET_TM_PAR:
|
|
case NDF_OP_WAIT:
|
|
case NDF_OP_CHIP_EN_DIS:
|
|
case NDF_OP_CLE_CMD:
|
|
case NDF_OP_WR_CMD:
|
|
case NDF_OP_RD_CMD:
|
|
case NDF_OP_RD_EDO_CMD:
|
|
case NDF_OP_BUS_ACQ_REL:
|
|
if (ndf_cmd_queue_free(tn) < 8)
|
|
goto full;
|
|
writeq(cmd->val[0], tn->base + NDF_CMD);
|
|
break;
|
|
case NDF_OP_ALE_CMD:
|
|
/* ALE commands take either one or two 64bit words */
|
|
if (cmd->u.ale_cmd.adr_byte_num < 5) {
|
|
if (ndf_cmd_queue_free(tn) < 8)
|
|
goto full;
|
|
writeq(cmd->val[0], tn->base + NDF_CMD);
|
|
} else {
|
|
if (ndf_cmd_queue_free(tn) < 16)
|
|
goto full;
|
|
writeq(cmd->val[0], tn->base + NDF_CMD);
|
|
writeq(cmd->val[1], tn->base + NDF_CMD);
|
|
}
|
|
break;
|
|
case NDF_OP_WAIT_STATUS: /* Wait status commands take two 64bit words */
|
|
if (ndf_cmd_queue_free(tn) < 16)
|
|
goto full;
|
|
writeq(cmd->val[0], tn->base + NDF_CMD);
|
|
writeq(cmd->val[1], tn->base + NDF_CMD);
|
|
break;
|
|
default:
|
|
dev_err(tn->dev, "%s: unknown command: %u\n", __func__, opcode);
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
|
|
full:
|
|
dev_err(tn->dev, "%s: no space left in command queue\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/**
|
|
* Wait for the ready/busy signal. First wait for busy to be valid,
|
|
* then wait for busy to de-assert.
|
|
*/
|
|
static int ndf_build_wait_busy(struct octeontx_nfc *tn)
|
|
{
|
|
union ndf_cmd cmd;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.u.wait.opcode = NDF_OP_WAIT;
|
|
cmd.u.wait.r_b = 1;
|
|
cmd.u.wait.wlen = t6;
|
|
|
|
if (ndf_submit(tn, &cmd))
|
|
return -ENOMEM;
|
|
return 0;
|
|
}
|
|
|
|
static bool ndf_dma_done(struct octeontx_nfc *tn)
|
|
{
|
|
u64 dma_cfg;
|
|
|
|
/* Enable bit should be clear after a transfer */
|
|
dma_cfg = readq(tn->base + NDF_DMA_CFG);
|
|
if (!(dma_cfg & NDF_DMA_CFG_EN))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static int ndf_wait(struct octeontx_nfc *tn)
|
|
{
|
|
ulong start = get_timer(0);
|
|
bool done;
|
|
|
|
while (!(done = ndf_dma_done(tn)) && get_timer(start) < NDF_TIMEOUT)
|
|
;
|
|
|
|
if (!done) {
|
|
dev_err(tn->dev, "%s: timeout error\n", __func__);
|
|
return -ETIMEDOUT;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ndf_wait_idle(struct octeontx_nfc *tn)
|
|
{
|
|
u64 val;
|
|
u64 dval = 0;
|
|
int rc;
|
|
int pause = 100;
|
|
u64 tot_us = USEC_PER_SEC / 10;
|
|
|
|
rc = readq_poll_timeout(tn->base + NDF_ST_REG,
|
|
val, val & NDF_ST_REG_EXE_IDLE, pause, tot_us);
|
|
if (!rc)
|
|
rc = readq_poll_timeout(tn->base + NDF_DMA_CFG,
|
|
dval, !(dval & NDF_DMA_CFG_EN),
|
|
pause, tot_us);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/** Issue set timing parameters */
|
|
static int ndf_queue_cmd_timing(struct octeontx_nfc *tn,
|
|
struct ndf_set_tm_par_cmd *timings)
|
|
{
|
|
union ndf_cmd cmd;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.u.set_tm_par.opcode = NDF_OP_SET_TM_PAR;
|
|
cmd.u.set_tm_par.tim_mult = timings->tim_mult;
|
|
cmd.u.set_tm_par.tm_par1 = timings->tm_par1;
|
|
cmd.u.set_tm_par.tm_par2 = timings->tm_par2;
|
|
cmd.u.set_tm_par.tm_par3 = timings->tm_par3;
|
|
cmd.u.set_tm_par.tm_par4 = timings->tm_par4;
|
|
cmd.u.set_tm_par.tm_par5 = timings->tm_par5;
|
|
cmd.u.set_tm_par.tm_par6 = timings->tm_par6;
|
|
cmd.u.set_tm_par.tm_par7 = timings->tm_par7;
|
|
return ndf_submit(tn, &cmd);
|
|
}
|
|
|
|
/** Issue bus acquire or release */
|
|
static int ndf_queue_cmd_bus(struct octeontx_nfc *tn, int direction)
|
|
{
|
|
union ndf_cmd cmd;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.u.bus_acq_rel.opcode = NDF_OP_BUS_ACQ_REL;
|
|
cmd.u.bus_acq_rel.direction = direction;
|
|
return ndf_submit(tn, &cmd);
|
|
}
|
|
|
|
/* Issue chip select or deselect */
|
|
static int ndf_queue_cmd_chip(struct octeontx_nfc *tn, int enable, int chip,
|
|
int width)
|
|
{
|
|
union ndf_cmd cmd;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.u.chip_en_dis.opcode = NDF_OP_CHIP_EN_DIS;
|
|
cmd.u.chip_en_dis.chip = chip;
|
|
cmd.u.chip_en_dis.enable = enable;
|
|
cmd.u.chip_en_dis.bus_width = width;
|
|
return ndf_submit(tn, &cmd);
|
|
}
|
|
|
|
static int ndf_queue_cmd_wait(struct octeontx_nfc *tn, int t_delay)
|
|
{
|
|
union ndf_cmd cmd;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.u.wait.opcode = NDF_OP_WAIT;
|
|
cmd.u.wait.wlen = t_delay;
|
|
return ndf_submit(tn, &cmd);
|
|
}
|
|
|
|
static int ndf_queue_cmd_cle(struct octeontx_nfc *tn, int command)
|
|
{
|
|
union ndf_cmd cmd;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.u.cle_cmd.opcode = NDF_OP_CLE_CMD;
|
|
cmd.u.cle_cmd.cmd_data = command;
|
|
cmd.u.cle_cmd.clen1 = t4;
|
|
cmd.u.cle_cmd.clen2 = t1;
|
|
cmd.u.cle_cmd.clen3 = t2;
|
|
return ndf_submit(tn, &cmd);
|
|
}
|
|
|
|
static int ndf_queue_cmd_ale(struct octeontx_nfc *tn, int addr_bytes,
|
|
struct nand_chip *nand, u64 page,
|
|
u32 col, int page_size)
|
|
{
|
|
struct octeontx_nand_chip *octeontx_nand = (nand) ?
|
|
to_otx_nand(nand) : NULL;
|
|
union ndf_cmd cmd;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.u.ale_cmd.opcode = NDF_OP_ALE_CMD;
|
|
cmd.u.ale_cmd.adr_byte_num = addr_bytes;
|
|
|
|
/* set column bit for OOB area, assume OOB follows page */
|
|
if (octeontx_nand && octeontx_nand->oob_only)
|
|
col += page_size;
|
|
|
|
/* page is u64 for this generality, even if cmdfunc() passes int */
|
|
switch (addr_bytes) {
|
|
/* 4-8 bytes: page, then 2-byte col */
|
|
case 8:
|
|
cmd.u.ale_cmd.adr_byt8 = (page >> 40) & 0xff;
|
|
fallthrough;
|
|
case 7:
|
|
cmd.u.ale_cmd.adr_byt7 = (page >> 32) & 0xff;
|
|
fallthrough;
|
|
case 6:
|
|
cmd.u.ale_cmd.adr_byt6 = (page >> 24) & 0xff;
|
|
fallthrough;
|
|
case 5:
|
|
cmd.u.ale_cmd.adr_byt5 = (page >> 16) & 0xff;
|
|
fallthrough;
|
|
case 4:
|
|
cmd.u.ale_cmd.adr_byt4 = (page >> 8) & 0xff;
|
|
cmd.u.ale_cmd.adr_byt3 = page & 0xff;
|
|
cmd.u.ale_cmd.adr_byt2 = (col >> 8) & 0xff;
|
|
cmd.u.ale_cmd.adr_byt1 = col & 0xff;
|
|
break;
|
|
/* 1-3 bytes: just the page address */
|
|
case 3:
|
|
cmd.u.ale_cmd.adr_byt3 = (page >> 16) & 0xff;
|
|
fallthrough;
|
|
case 2:
|
|
cmd.u.ale_cmd.adr_byt2 = (page >> 8) & 0xff;
|
|
fallthrough;
|
|
case 1:
|
|
cmd.u.ale_cmd.adr_byt1 = page & 0xff;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
cmd.u.ale_cmd.alen1 = t3;
|
|
cmd.u.ale_cmd.alen2 = t1;
|
|
cmd.u.ale_cmd.alen3 = t5;
|
|
cmd.u.ale_cmd.alen4 = t2;
|
|
return ndf_submit(tn, &cmd);
|
|
}
|
|
|
|
static int ndf_queue_cmd_write(struct octeontx_nfc *tn, int len)
|
|
{
|
|
union ndf_cmd cmd;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.u.wr_cmd.opcode = NDF_OP_WR_CMD;
|
|
cmd.u.wr_cmd.data = len;
|
|
cmd.u.wr_cmd.wlen1 = t3;
|
|
cmd.u.wr_cmd.wlen2 = t1;
|
|
return ndf_submit(tn, &cmd);
|
|
}
|
|
|
|
static int ndf_build_pre_cmd(struct octeontx_nfc *tn, int cmd1,
|
|
int addr_bytes, u64 page, u32 col, int cmd2)
|
|
{
|
|
struct nand_chip *nand = tn->controller.active;
|
|
struct octeontx_nand_chip *octeontx_nand;
|
|
struct ndf_set_tm_par_cmd *timings;
|
|
int width, page_size, rc;
|
|
|
|
/* Also called before chip probing is finished */
|
|
if (!nand) {
|
|
timings = &default_timing_parms;
|
|
page_size = default_page_size;
|
|
width = default_width;
|
|
} else {
|
|
octeontx_nand = to_otx_nand(nand);
|
|
timings = &octeontx_nand->timings;
|
|
page_size = nand->mtd.writesize;
|
|
if (nand->options & NAND_BUSWIDTH_16)
|
|
width = 2;
|
|
else
|
|
width = 1;
|
|
}
|
|
rc = ndf_queue_cmd_timing(tn, timings);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ndf_queue_cmd_bus(tn, NDF_BUS_ACQUIRE);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ndf_queue_cmd_chip(tn, 1, tn->selected_chip, width);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ndf_queue_cmd_wait(tn, t1);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ndf_queue_cmd_cle(tn, cmd1);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (addr_bytes) {
|
|
rc = ndf_build_wait_busy(tn);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ndf_queue_cmd_ale(tn, addr_bytes, nand,
|
|
page, col, page_size);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
/* CLE 2 */
|
|
if (cmd2) {
|
|
rc = ndf_build_wait_busy(tn);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ndf_queue_cmd_cle(tn, cmd2);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ndf_build_post_cmd(struct octeontx_nfc *tn, int hold_time)
|
|
{
|
|
int rc;
|
|
|
|
/* Deselect chip */
|
|
rc = ndf_queue_cmd_chip(tn, 0, 0, 0);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ndf_queue_cmd_wait(tn, t2);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Release bus */
|
|
rc = ndf_queue_cmd_bus(tn, 0);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ndf_queue_cmd_wait(tn, hold_time);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/*
|
|
* Last action is ringing the doorbell with number of bus
|
|
* acquire-releases cycles (currently 1).
|
|
*/
|
|
writeq(1, tn->base + NDF_DRBELL);
|
|
return 0;
|
|
}
|
|
|
|
/* Setup the NAND DMA engine for a transfer. */
|
|
static void ndf_setup_dma(struct octeontx_nfc *tn, int is_write,
|
|
dma_addr_t bus_addr, int len)
|
|
{
|
|
u64 dma_cfg;
|
|
|
|
dma_cfg = FIELD_PREP(NDF_DMA_CFG_RW, is_write) |
|
|
FIELD_PREP(NDF_DMA_CFG_SIZE, (len >> 3) - 1);
|
|
dma_cfg |= NDF_DMA_CFG_EN;
|
|
writeq(bus_addr, tn->base + NDF_DMA_ADR);
|
|
writeq(dma_cfg, tn->base + NDF_DMA_CFG);
|
|
}
|
|
|
|
static int octeontx_nand_reset(struct octeontx_nfc *tn)
|
|
{
|
|
int rc;
|
|
|
|
rc = ndf_build_pre_cmd(tn, NAND_CMD_RESET, 0, 0, 0, 0);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ndf_build_wait_busy(tn);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ndf_build_post_cmd(tn, t2);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ndf_read(struct octeontx_nfc *tn, int cmd1, int addr_bytes,
|
|
u64 page, u32 col, int cmd2, int len)
|
|
{
|
|
dma_addr_t bus_addr = tn->use_status ? tn->stat_addr : tn->buf.dmaaddr;
|
|
struct nand_chip *nand = tn->controller.active;
|
|
int timing_mode, bytes, rc;
|
|
union ndf_cmd cmd;
|
|
u64 start, end;
|
|
|
|
pr_debug("%s(%p, 0x%x, 0x%x, 0x%llx, 0x%x, 0x%x, 0x%x)\n", __func__,
|
|
tn, cmd1, addr_bytes, page, col, cmd2, len);
|
|
if (!nand)
|
|
timing_mode = default_onfi_timing;
|
|
else
|
|
timing_mode = nand->onfi_timing_mode_default;
|
|
|
|
/* Build the command and address cycles */
|
|
rc = ndf_build_pre_cmd(tn, cmd1, addr_bytes, page, col, cmd2);
|
|
if (rc) {
|
|
dev_err(tn->dev, "Build pre command failed\n");
|
|
return rc;
|
|
}
|
|
|
|
/* This waits for some time, then waits for busy to be de-asserted. */
|
|
rc = ndf_build_wait_busy(tn);
|
|
if (rc) {
|
|
dev_err(tn->dev, "Wait timeout\n");
|
|
return rc;
|
|
}
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
|
|
if (timing_mode < 4)
|
|
cmd.u.rd_cmd.opcode = NDF_OP_RD_CMD;
|
|
else
|
|
cmd.u.rd_cmd.opcode = NDF_OP_RD_EDO_CMD;
|
|
|
|
cmd.u.rd_cmd.data = len;
|
|
cmd.u.rd_cmd.rlen1 = t7;
|
|
cmd.u.rd_cmd.rlen2 = t3;
|
|
cmd.u.rd_cmd.rlen3 = t1;
|
|
cmd.u.rd_cmd.rlen4 = t7;
|
|
rc = ndf_submit(tn, &cmd);
|
|
if (rc) {
|
|
dev_err(tn->dev, "Error submitting command\n");
|
|
return rc;
|
|
}
|
|
|
|
start = (u64)bus_addr;
|
|
ndf_setup_dma(tn, 0, bus_addr, len);
|
|
|
|
rc = ndf_build_post_cmd(tn, t2);
|
|
if (rc) {
|
|
dev_err(tn->dev, "Build post command failed\n");
|
|
return rc;
|
|
}
|
|
|
|
/* Wait for the DMA to complete */
|
|
rc = ndf_wait(tn);
|
|
if (rc) {
|
|
dev_err(tn->dev, "DMA timed out\n");
|
|
return rc;
|
|
}
|
|
|
|
end = readq(tn->base + NDF_DMA_ADR);
|
|
bytes = end - start;
|
|
|
|
/* Make sure NDF is really done */
|
|
rc = ndf_wait_idle(tn);
|
|
if (rc) {
|
|
dev_err(tn->dev, "poll idle failed\n");
|
|
return rc;
|
|
}
|
|
|
|
pr_debug("%s: Read %d bytes\n", __func__, bytes);
|
|
return bytes;
|
|
}
|
|
|
|
static int octeontx_nand_get_features(struct mtd_info *mtd,
|
|
struct nand_chip *chip, int feature_addr,
|
|
u8 *subfeature_para)
|
|
{
|
|
struct nand_chip *nand = chip;
|
|
struct octeontx_nfc *tn = to_otx_nfc(nand->controller);
|
|
int len = 8;
|
|
int rc;
|
|
|
|
pr_debug("%s: feature addr: 0x%x\n", __func__, feature_addr);
|
|
memset(tn->buf.dmabuf, 0xff, len);
|
|
tn->buf.data_index = 0;
|
|
tn->buf.data_len = 0;
|
|
rc = ndf_read(tn, NAND_CMD_GET_FEATURES, 1, feature_addr, 0, 0, len);
|
|
if (rc)
|
|
return rc;
|
|
|
|
memcpy(subfeature_para, tn->buf.dmabuf, ONFI_SUBFEATURE_PARAM_LEN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int octeontx_nand_set_features(struct mtd_info *mtd,
|
|
struct nand_chip *chip, int feature_addr,
|
|
u8 *subfeature_para)
|
|
{
|
|
struct nand_chip *nand = chip;
|
|
struct octeontx_nfc *tn = to_otx_nfc(nand->controller);
|
|
const int len = ONFI_SUBFEATURE_PARAM_LEN;
|
|
int rc;
|
|
|
|
rc = ndf_build_pre_cmd(tn, NAND_CMD_SET_FEATURES,
|
|
1, feature_addr, 0, 0);
|
|
if (rc)
|
|
return rc;
|
|
|
|
memcpy(tn->buf.dmabuf, subfeature_para, len);
|
|
memset(tn->buf.dmabuf + len, 0, 8 - len);
|
|
|
|
ndf_setup_dma(tn, 1, tn->buf.dmaaddr, 8);
|
|
|
|
rc = ndf_queue_cmd_write(tn, 8);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ndf_build_wait_busy(tn);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ndf_build_post_cmd(tn, t2);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Read a page from NAND. If the buffer has room, the out of band
|
|
* data will be included.
|
|
*/
|
|
static int ndf_page_read(struct octeontx_nfc *tn, u64 page, int col, int len)
|
|
{
|
|
debug("%s(%p, 0x%llx, 0x%x, 0x%x) active: %p\n", __func__,
|
|
tn, page, col, len, tn->controller.active);
|
|
struct nand_chip *nand = tn->controller.active;
|
|
struct octeontx_nand_chip *chip = to_otx_nand(nand);
|
|
int addr_bytes = chip->row_bytes + chip->col_bytes;
|
|
|
|
memset(tn->buf.dmabuf, 0xff, len);
|
|
return ndf_read(tn, NAND_CMD_READ0, addr_bytes,
|
|
page, col, NAND_CMD_READSTART, len);
|
|
}
|
|
|
|
/* Erase a NAND block */
|
|
static int ndf_block_erase(struct octeontx_nfc *tn, u64 page_addr)
|
|
{
|
|
struct nand_chip *nand = tn->controller.active;
|
|
struct octeontx_nand_chip *chip = to_otx_nand(nand);
|
|
int addr_bytes = chip->row_bytes;
|
|
int rc;
|
|
|
|
rc = ndf_build_pre_cmd(tn, NAND_CMD_ERASE1, addr_bytes,
|
|
page_addr, 0, NAND_CMD_ERASE2);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Wait for R_B to signal erase is complete */
|
|
rc = ndf_build_wait_busy(tn);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ndf_build_post_cmd(tn, t2);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Wait until the command queue is idle */
|
|
return ndf_wait_idle(tn);
|
|
}
|
|
|
|
/*
|
|
* Write a page (or less) to NAND.
|
|
*/
|
|
static int ndf_page_write(struct octeontx_nfc *tn, int page)
|
|
{
|
|
int len, rc;
|
|
struct nand_chip *nand = tn->controller.active;
|
|
struct octeontx_nand_chip *chip = to_otx_nand(nand);
|
|
int addr_bytes = chip->row_bytes + chip->col_bytes;
|
|
|
|
len = tn->buf.data_len - tn->buf.data_index;
|
|
chip->oob_only = (tn->buf.data_index >= nand->mtd.writesize);
|
|
WARN_ON_ONCE(len & 0x7);
|
|
|
|
ndf_setup_dma(tn, 1, tn->buf.dmaaddr + tn->buf.data_index, len);
|
|
rc = ndf_build_pre_cmd(tn, NAND_CMD_SEQIN, addr_bytes, page, 0, 0);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ndf_queue_cmd_write(tn, len);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ndf_queue_cmd_cle(tn, NAND_CMD_PAGEPROG);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Wait for R_B to signal program is complete */
|
|
rc = ndf_build_wait_busy(tn);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ndf_build_post_cmd(tn, t2);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Wait for the DMA to complete */
|
|
rc = ndf_wait(tn);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Data transfer is done but NDF is not, it is waiting for R/B# */
|
|
return ndf_wait_idle(tn);
|
|
}
|
|
|
|
static void octeontx_nand_cmdfunc(struct mtd_info *mtd, unsigned int command,
|
|
int column, int page_addr)
|
|
{
|
|
struct nand_chip *nand = mtd_to_nand(mtd);
|
|
struct octeontx_nand_chip *octeontx_nand = to_otx_nand(nand);
|
|
struct octeontx_nfc *tn = to_otx_nfc(nand->controller);
|
|
int rc;
|
|
|
|
tn->selected_chip = octeontx_nand->cs;
|
|
if (tn->selected_chip < 0 || tn->selected_chip >= NAND_MAX_CHIPS) {
|
|
dev_err(tn->dev, "invalid chip select\n");
|
|
return;
|
|
}
|
|
|
|
tn->use_status = false;
|
|
|
|
pr_debug("%s(%p, 0x%x, 0x%x, 0x%x) cs: %d\n", __func__, mtd, command,
|
|
column, page_addr, tn->selected_chip);
|
|
switch (command) {
|
|
case NAND_CMD_READID:
|
|
tn->buf.data_index = 0;
|
|
octeontx_nand->oob_only = false;
|
|
rc = ndf_read(tn, command, 1, column, 0, 0, 8);
|
|
if (rc < 0)
|
|
dev_err(tn->dev, "READID failed with %d\n", rc);
|
|
else
|
|
tn->buf.data_len = rc;
|
|
break;
|
|
|
|
case NAND_CMD_READOOB:
|
|
octeontx_nand->oob_only = true;
|
|
tn->buf.data_index = 0;
|
|
tn->buf.data_len = 0;
|
|
rc = ndf_page_read(tn, page_addr, column, mtd->oobsize);
|
|
if (rc < mtd->oobsize)
|
|
dev_err(tn->dev, "READOOB failed with %d\n",
|
|
tn->buf.data_len);
|
|
else
|
|
tn->buf.data_len = rc;
|
|
break;
|
|
|
|
case NAND_CMD_READ0:
|
|
octeontx_nand->oob_only = false;
|
|
tn->buf.data_index = 0;
|
|
tn->buf.data_len = 0;
|
|
rc = ndf_page_read(tn, page_addr, column,
|
|
mtd->writesize + mtd->oobsize);
|
|
|
|
if (rc < mtd->writesize + mtd->oobsize)
|
|
dev_err(tn->dev, "READ0 failed with %d\n", rc);
|
|
else
|
|
tn->buf.data_len = rc;
|
|
break;
|
|
|
|
case NAND_CMD_STATUS:
|
|
/* used in oob/not states */
|
|
tn->use_status = true;
|
|
rc = ndf_read(tn, command, 0, 0, 0, 0, 8);
|
|
if (rc < 0)
|
|
dev_err(tn->dev, "STATUS failed with %d\n", rc);
|
|
break;
|
|
|
|
case NAND_CMD_RESET:
|
|
/* used in oob/not states */
|
|
rc = octeontx_nand_reset(tn);
|
|
if (rc < 0)
|
|
dev_err(tn->dev, "RESET failed with %d\n", rc);
|
|
break;
|
|
|
|
case NAND_CMD_PARAM:
|
|
octeontx_nand->oob_only = false;
|
|
tn->buf.data_index = 0;
|
|
rc = ndf_read(tn, command, 1, 0, 0, 0,
|
|
min(tn->buf.dmabuflen, 3 * 512));
|
|
if (rc < 0)
|
|
dev_err(tn->dev, "PARAM failed with %d\n", rc);
|
|
else
|
|
tn->buf.data_len = rc;
|
|
break;
|
|
|
|
case NAND_CMD_RNDOUT:
|
|
tn->buf.data_index = column;
|
|
break;
|
|
|
|
case NAND_CMD_ERASE1:
|
|
if (ndf_block_erase(tn, page_addr))
|
|
dev_err(tn->dev, "ERASE1 failed\n");
|
|
break;
|
|
|
|
case NAND_CMD_ERASE2:
|
|
/* We do all erase processing in the first command, so ignore
|
|
* this one.
|
|
*/
|
|
break;
|
|
|
|
case NAND_CMD_SEQIN:
|
|
octeontx_nand->oob_only = (column >= mtd->writesize);
|
|
tn->buf.data_index = column;
|
|
tn->buf.data_len = column;
|
|
|
|
octeontx_nand->selected_page = page_addr;
|
|
break;
|
|
|
|
case NAND_CMD_PAGEPROG:
|
|
rc = ndf_page_write(tn, octeontx_nand->selected_page);
|
|
if (rc)
|
|
dev_err(tn->dev, "PAGEPROG failed with %d\n", rc);
|
|
break;
|
|
|
|
case NAND_CMD_SET_FEATURES:
|
|
octeontx_nand->oob_only = false;
|
|
/* assume tn->buf.data_len == 4 of data has been set there */
|
|
rc = octeontx_nand_set_features(mtd, nand,
|
|
page_addr, tn->buf.dmabuf);
|
|
if (rc)
|
|
dev_err(tn->dev, "SET_FEATURES failed with %d\n", rc);
|
|
break;
|
|
|
|
case NAND_CMD_GET_FEATURES:
|
|
octeontx_nand->oob_only = false;
|
|
rc = octeontx_nand_get_features(mtd, nand,
|
|
page_addr, tn->buf.dmabuf);
|
|
if (!rc) {
|
|
tn->buf.data_index = 0;
|
|
tn->buf.data_len = 4;
|
|
} else {
|
|
dev_err(tn->dev, "GET_FEATURES failed with %d\n", rc);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
WARN_ON_ONCE(1);
|
|
dev_err(tn->dev, "unhandled nand cmd: %x\n", command);
|
|
}
|
|
}
|
|
|
|
static int octeontx_nand_waitfunc(struct mtd_info *mtd, struct nand_chip *chip)
|
|
{
|
|
struct octeontx_nfc *tn = to_otx_nfc(chip->controller);
|
|
int ret;
|
|
|
|
ret = ndf_wait_idle(tn);
|
|
return (ret < 0) ? -EIO : 0;
|
|
}
|
|
|
|
/* check compatibility with ONFI timing mode#N, and optionally apply */
|
|
/* TODO: Implement chipnr support? */
|
|
static int octeontx_nand_setup_dat_intf(struct mtd_info *mtd, int chipnr,
|
|
const struct nand_data_interface *conf)
|
|
{
|
|
static const bool check_only;
|
|
struct nand_chip *nand = mtd_to_nand(mtd);
|
|
struct octeontx_nand_chip *chip = to_otx_nand(nand);
|
|
static u64 t_wc_n[MAX_ONFI_MODE + 2]; /* cache a mode signature */
|
|
int mode; /* deduced mode number, for reporting and restricting */
|
|
int rc;
|
|
|
|
/*
|
|
* Cache timing modes for reporting, and reducing needless change.
|
|
*
|
|
* Challenge: caller does not pass ONFI mode#, but reporting the mode
|
|
* and restricting to a maximum, or a list, are useful for diagnosing
|
|
* new hardware. So use tWC_min, distinct and monotonic across modes,
|
|
* to discover the requested/accepted mode number
|
|
*/
|
|
for (mode = MAX_ONFI_MODE; mode >= 0 && !t_wc_n[0]; mode--) {
|
|
const struct nand_sdr_timings *t;
|
|
|
|
t = onfi_async_timing_mode_to_sdr_timings(mode);
|
|
if (!t)
|
|
continue;
|
|
t_wc_n[mode] = t->tWC_min;
|
|
}
|
|
|
|
if (!conf) {
|
|
rc = -EINVAL;
|
|
} else if (check_only) {
|
|
rc = 0;
|
|
} else if (nand->data_interface &&
|
|
chip->iface_set && chip->iface_mode == mode) {
|
|
/*
|
|
* Cases:
|
|
* - called from nand_reset, which clears DDR timing
|
|
* mode back to SDR. BUT if we're already in SDR,
|
|
* timing mode persists over resets.
|
|
* While mtd/nand layer only supports SDR,
|
|
* this is always safe. And this driver only supports SDR.
|
|
*
|
|
* - called from post-power-event nand_reset (maybe
|
|
* NFC+flash power down, or system hibernate.
|
|
* Address this when CONFIG_PM support added
|
|
*/
|
|
rc = 0;
|
|
} else {
|
|
rc = octeontx_nfc_chip_set_timings(chip, &conf->timings.sdr);
|
|
if (!rc) {
|
|
chip->iface_mode = mode;
|
|
chip->iface_set = true;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static void octeontx_bch_reset(void)
|
|
{
|
|
}
|
|
|
|
/*
|
|
* Given a page, calculate the ECC code
|
|
*
|
|
* chip: Pointer to NAND chip data structure
|
|
* buf: Buffer to calculate ECC on
|
|
* code: Buffer to hold ECC data
|
|
*
|
|
* Return 0 on success or -1 on failure
|
|
*/
|
|
static int octeontx_nand_bch_calculate_ecc_internal(struct mtd_info *mtd,
|
|
dma_addr_t ihandle,
|
|
u8 *code)
|
|
{
|
|
struct nand_chip *nand = mtd_to_nand(mtd);
|
|
struct octeontx_nfc *tn = to_otx_nfc(nand->controller);
|
|
int rc;
|
|
int i;
|
|
static u8 *ecc_buffer;
|
|
static int ecc_size;
|
|
static unsigned long ecc_handle;
|
|
union bch_resp *r = tn->bch_resp;
|
|
|
|
if (!ecc_buffer || ecc_size < nand->ecc.size) {
|
|
ecc_size = nand->ecc.size;
|
|
ecc_buffer = dma_alloc_coherent(ecc_size,
|
|
(unsigned long *)&ecc_handle);
|
|
}
|
|
|
|
memset(ecc_buffer, 0, nand->ecc.bytes);
|
|
|
|
r->u16 = 0;
|
|
__iowmb(); /* flush done=0 before making request */
|
|
|
|
rc = octeontx_bch_encode(bch_vf, ihandle, nand->ecc.size,
|
|
nand->ecc.strength,
|
|
(dma_addr_t)ecc_handle, tn->bch_rhandle);
|
|
|
|
if (!rc) {
|
|
octeontx_bch_wait(bch_vf, r, tn->bch_rhandle);
|
|
} else {
|
|
dev_err(tn->dev, "octeontx_bch_encode failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if (!r->s.done || r->s.uncorrectable) {
|
|
dev_err(tn->dev,
|
|
"%s timeout, done:%d uncorr:%d corr:%d erased:%d\n",
|
|
__func__, r->s.done, r->s.uncorrectable,
|
|
r->s.num_errors, r->s.erased);
|
|
octeontx_bch_reset();
|
|
return -1;
|
|
}
|
|
|
|
memcpy(code, ecc_buffer, nand->ecc.bytes);
|
|
|
|
for (i = 0; i < nand->ecc.bytes; i++)
|
|
code[i] ^= tn->eccmask[i];
|
|
|
|
return tn->bch_resp->s.num_errors;
|
|
}
|
|
|
|
/*
|
|
* Given a page, calculate the ECC code
|
|
*
|
|
* mtd: MTD block structure
|
|
* dat: raw data (unused)
|
|
* ecc_code: buffer for ECC
|
|
*/
|
|
static int octeontx_nand_bch_calculate(struct mtd_info *mtd,
|
|
const u8 *dat, u8 *ecc_code)
|
|
{
|
|
struct nand_chip *nand = mtd_to_nand(mtd);
|
|
dma_addr_t handle = dma_map_single((u8 *)dat,
|
|
nand->ecc.size, DMA_TO_DEVICE);
|
|
int ret;
|
|
|
|
ret = octeontx_nand_bch_calculate_ecc_internal(mtd, handle,
|
|
(void *)ecc_code);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Detect and correct multi-bit ECC for a page
|
|
*
|
|
* mtd: MTD block structure
|
|
* dat: raw data read from the chip
|
|
* read_ecc: ECC from the chip (unused)
|
|
* isnull: unused
|
|
*
|
|
* Returns number of bits corrected or -1 if unrecoverable
|
|
*/
|
|
static int octeontx_nand_bch_correct(struct mtd_info *mtd, u_char *dat,
|
|
u_char *read_ecc, u_char *isnull)
|
|
{
|
|
struct nand_chip *nand = mtd_to_nand(mtd);
|
|
struct octeontx_nfc *tn = to_otx_nfc(nand->controller);
|
|
int i = nand->ecc.size + nand->ecc.bytes;
|
|
static u8 *data_buffer;
|
|
static dma_addr_t ihandle;
|
|
static int buffer_size;
|
|
dma_addr_t ohandle;
|
|
union bch_resp *r = tn->bch_resp;
|
|
int rc;
|
|
|
|
if (i > buffer_size) {
|
|
if (buffer_size)
|
|
free(data_buffer);
|
|
data_buffer = dma_alloc_coherent(i,
|
|
(unsigned long *)&ihandle);
|
|
if (!data_buffer) {
|
|
dev_err(tn->dev,
|
|
"%s: Could not allocate %d bytes for buffer\n",
|
|
__func__, i);
|
|
goto error;
|
|
}
|
|
buffer_size = i;
|
|
}
|
|
|
|
memcpy(data_buffer, dat, nand->ecc.size);
|
|
memcpy(data_buffer + nand->ecc.size, read_ecc, nand->ecc.bytes);
|
|
|
|
for (i = 0; i < nand->ecc.bytes; i++)
|
|
data_buffer[nand->ecc.size + i] ^= tn->eccmask[i];
|
|
|
|
r->u16 = 0;
|
|
__iowmb(); /* flush done=0 before making request */
|
|
|
|
ohandle = dma_map_single(dat, nand->ecc.size, DMA_FROM_DEVICE);
|
|
rc = octeontx_bch_decode(bch_vf, ihandle, nand->ecc.size,
|
|
nand->ecc.strength, ohandle, tn->bch_rhandle);
|
|
|
|
if (!rc)
|
|
octeontx_bch_wait(bch_vf, r, tn->bch_rhandle);
|
|
|
|
if (rc) {
|
|
dev_err(tn->dev, "octeontx_bch_decode failed\n");
|
|
goto error;
|
|
}
|
|
|
|
if (!r->s.done) {
|
|
dev_err(tn->dev, "Error: BCH engine timeout\n");
|
|
octeontx_bch_reset();
|
|
goto error;
|
|
}
|
|
|
|
if (r->s.erased) {
|
|
debug("Info: BCH block is erased\n");
|
|
return 0;
|
|
}
|
|
|
|
if (r->s.uncorrectable) {
|
|
debug("Cannot correct NAND block, response: 0x%x\n",
|
|
r->u16);
|
|
goto error;
|
|
}
|
|
|
|
return r->s.num_errors;
|
|
|
|
error:
|
|
debug("Error performing bch correction\n");
|
|
return -1;
|
|
}
|
|
|
|
void octeontx_nand_bch_hwctl(struct mtd_info *mtd, int mode)
|
|
{
|
|
/* Do nothing. */
|
|
}
|
|
|
|
static int octeontx_nand_hw_bch_read_page(struct mtd_info *mtd,
|
|
struct nand_chip *chip, u8 *buf,
|
|
int oob_required, int page)
|
|
{
|
|
struct nand_chip *nand = mtd_to_nand(mtd);
|
|
struct octeontx_nfc *tn = to_otx_nfc(nand->controller);
|
|
int i, eccsize = chip->ecc.size, ret;
|
|
int eccbytes = chip->ecc.bytes;
|
|
int eccsteps = chip->ecc.steps;
|
|
u8 *p;
|
|
u8 *ecc_code = chip->buffers->ecccode;
|
|
unsigned int max_bitflips = 0;
|
|
|
|
/* chip->read_buf() insists on sequential order, we do OOB first */
|
|
memcpy(chip->oob_poi, tn->buf.dmabuf + mtd->writesize, mtd->oobsize);
|
|
|
|
/* Use private buffer as input for ECC correction */
|
|
p = tn->buf.dmabuf;
|
|
|
|
ret = mtd_ooblayout_get_eccbytes(mtd, ecc_code, chip->oob_poi, 0,
|
|
chip->ecc.total);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
|
|
int stat;
|
|
|
|
debug("Correcting block offset %lx, ecc offset %x\n",
|
|
p - buf, i);
|
|
stat = chip->ecc.correct(mtd, p, &ecc_code[i], NULL);
|
|
|
|
if (stat < 0) {
|
|
mtd->ecc_stats.failed++;
|
|
debug("Cannot correct NAND page %d\n", page);
|
|
} else {
|
|
mtd->ecc_stats.corrected += stat;
|
|
max_bitflips = max_t(unsigned int, max_bitflips, stat);
|
|
}
|
|
}
|
|
|
|
/* Copy corrected data to caller's buffer now */
|
|
memcpy(buf, tn->buf.dmabuf, mtd->writesize);
|
|
|
|
return max_bitflips;
|
|
}
|
|
|
|
static int octeontx_nand_hw_bch_write_page(struct mtd_info *mtd,
|
|
struct nand_chip *chip,
|
|
const u8 *buf, int oob_required,
|
|
int page)
|
|
{
|
|
struct octeontx_nfc *tn = to_otx_nfc(chip->controller);
|
|
int i, eccsize = chip->ecc.size, ret;
|
|
int eccbytes = chip->ecc.bytes;
|
|
int eccsteps = chip->ecc.steps;
|
|
const u8 *p;
|
|
u8 *ecc_calc = chip->buffers->ecccalc;
|
|
|
|
debug("%s(buf?%p, oob%d p%x)\n",
|
|
__func__, buf, oob_required, page);
|
|
for (i = 0; i < chip->ecc.total; i++)
|
|
ecc_calc[i] = 0xFF;
|
|
|
|
/* Copy the page data from caller's buffers to private buffer */
|
|
chip->write_buf(mtd, buf, mtd->writesize);
|
|
/* Use private date as source for ECC calculation */
|
|
p = tn->buf.dmabuf;
|
|
|
|
/* Hardware ECC calculation */
|
|
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
|
|
int ret;
|
|
|
|
ret = chip->ecc.calculate(mtd, p, &ecc_calc[i]);
|
|
|
|
if (ret < 0)
|
|
debug("calculate(mtd, p?%p, &ecc_calc[%d]?%p) returned %d\n",
|
|
p, i, &ecc_calc[i], ret);
|
|
|
|
debug("block offset %lx, ecc offset %x\n", p - buf, i);
|
|
}
|
|
|
|
ret = mtd_ooblayout_set_eccbytes(mtd, ecc_calc, chip->oob_poi, 0,
|
|
chip->ecc.total);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Store resulting OOB into private buffer, will be sent to HW */
|
|
chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* nand_write_page_raw - [INTERN] raw page write function
|
|
* @mtd: mtd info structure
|
|
* @chip: nand chip info structure
|
|
* @buf: data buffer
|
|
* @oob_required: must write chip->oob_poi to OOB
|
|
* @page: page number to write
|
|
*
|
|
* Not for syndrome calculating ECC controllers, which use a special oob layout.
|
|
*/
|
|
static int octeontx_nand_write_page_raw(struct mtd_info *mtd,
|
|
struct nand_chip *chip,
|
|
const u8 *buf, int oob_required,
|
|
int page)
|
|
{
|
|
chip->write_buf(mtd, buf, mtd->writesize);
|
|
if (oob_required)
|
|
chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* octeontx_nand_write_oob_std - [REPLACEABLE] the most common OOB data write
|
|
* function
|
|
* @mtd: mtd info structure
|
|
* @chip: nand chip info structure
|
|
* @page: page number to write
|
|
*/
|
|
static int octeontx_nand_write_oob_std(struct mtd_info *mtd,
|
|
struct nand_chip *chip,
|
|
int page)
|
|
{
|
|
int status = 0;
|
|
const u8 *buf = chip->oob_poi;
|
|
int length = mtd->oobsize;
|
|
|
|
chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page);
|
|
chip->write_buf(mtd, buf, length);
|
|
/* Send command to program the OOB data */
|
|
chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
|
|
|
|
status = chip->waitfunc(mtd, chip);
|
|
|
|
return status & NAND_STATUS_FAIL ? -EIO : 0;
|
|
}
|
|
|
|
/**
|
|
* octeontx_nand_read_page_raw - [INTERN] read raw page data without ecc
|
|
* @mtd: mtd info structure
|
|
* @chip: nand chip info structure
|
|
* @buf: buffer to store read data
|
|
* @oob_required: caller requires OOB data read to chip->oob_poi
|
|
* @page: page number to read
|
|
*
|
|
* Not for syndrome calculating ECC controllers, which use a special oob layout.
|
|
*/
|
|
static int octeontx_nand_read_page_raw(struct mtd_info *mtd,
|
|
struct nand_chip *chip,
|
|
u8 *buf, int oob_required, int page)
|
|
{
|
|
chip->read_buf(mtd, buf, mtd->writesize);
|
|
if (oob_required)
|
|
chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
|
|
return 0;
|
|
}
|
|
|
|
static int octeontx_nand_read_oob_std(struct mtd_info *mtd,
|
|
struct nand_chip *chip,
|
|
int page)
|
|
|
|
{
|
|
chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
|
|
chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
|
|
return 0;
|
|
}
|
|
|
|
static int octeontx_nand_calc_bch_ecc_strength(struct nand_chip *nand)
|
|
{
|
|
struct mtd_info *mtd = nand_to_mtd(nand);
|
|
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
|
struct octeontx_nfc *tn = to_otx_nfc(nand->controller);
|
|
int nsteps = mtd->writesize / ecc->size;
|
|
int oobchunk = mtd->oobsize / nsteps;
|
|
|
|
/* ecc->strength determines ecc_level and OOB's ecc_bytes. */
|
|
const u8 strengths[] = {4, 8, 16, 24, 32, 40, 48, 56, 60, 64};
|
|
/* first set the desired ecc_level to match strengths[] */
|
|
int index = ARRAY_SIZE(strengths) - 1;
|
|
int need;
|
|
|
|
while (index > 0 && !(ecc->options & NAND_ECC_MAXIMIZE) &&
|
|
strengths[index - 1] >= ecc->strength)
|
|
index--;
|
|
|
|
do {
|
|
need = DIV_ROUND_UP(15 * strengths[index], 8);
|
|
if (need <= oobchunk - 2)
|
|
break;
|
|
} while (index > 0);
|
|
|
|
debug("%s: steps ds: %d, strength ds: %d\n", __func__,
|
|
nand->ecc_step_ds, nand->ecc_strength_ds);
|
|
ecc->strength = strengths[index];
|
|
ecc->bytes = need;
|
|
debug("%s: strength: %d, bytes: %d\n", __func__, ecc->strength,
|
|
ecc->bytes);
|
|
|
|
if (!tn->eccmask)
|
|
tn->eccmask = devm_kzalloc(tn->dev, ecc->bytes, GFP_KERNEL);
|
|
if (!tn->eccmask)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* sample the BCH signature of an erased (all 0xff) page,
|
|
* to XOR into all page traffic, so erased pages have no ECC errors
|
|
*/
|
|
static int octeontx_bch_save_empty_eccmask(struct nand_chip *nand)
|
|
{
|
|
struct mtd_info *mtd = nand_to_mtd(nand);
|
|
struct octeontx_nfc *tn = to_otx_nfc(nand->controller);
|
|
unsigned int eccsize = nand->ecc.size;
|
|
unsigned int eccbytes = nand->ecc.bytes;
|
|
u8 erased_ecc[eccbytes];
|
|
unsigned long erased_handle;
|
|
unsigned char *erased_page = dma_alloc_coherent(eccsize,
|
|
&erased_handle);
|
|
int i;
|
|
int rc = 0;
|
|
|
|
if (!erased_page)
|
|
return -ENOMEM;
|
|
|
|
memset(erased_page, 0xff, eccsize);
|
|
memset(erased_ecc, 0, eccbytes);
|
|
|
|
rc = octeontx_nand_bch_calculate_ecc_internal(mtd,
|
|
(dma_addr_t)erased_handle,
|
|
erased_ecc);
|
|
|
|
free(erased_page);
|
|
|
|
for (i = 0; i < eccbytes; i++)
|
|
tn->eccmask[i] = erased_ecc[i] ^ 0xff;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void octeontx_nfc_chip_sizing(struct nand_chip *nand)
|
|
{
|
|
struct octeontx_nand_chip *chip = to_otx_nand(nand);
|
|
struct mtd_info *mtd = nand_to_mtd(nand);
|
|
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
|
|
|
chip->row_bytes = nand->onfi_params.addr_cycles & 0xf;
|
|
chip->col_bytes = nand->onfi_params.addr_cycles >> 4;
|
|
debug("%s(%p) row bytes: %d, col bytes: %d, ecc mode: %d\n",
|
|
__func__, nand, chip->row_bytes, chip->col_bytes, ecc->mode);
|
|
|
|
/*
|
|
* HW_BCH using OcteonTX BCH engine, or SOFT_BCH laid out in
|
|
* HW_BCH-compatible fashion, depending on devtree advice
|
|
* and kernel config.
|
|
* BCH/NFC hardware capable of subpage ops, not implemented.
|
|
*/
|
|
mtd_set_ooblayout(mtd, &nand_ooblayout_lp_ops);
|
|
nand->options |= NAND_NO_SUBPAGE_WRITE;
|
|
debug("%s: start steps: %d, size: %d, bytes: %d\n",
|
|
__func__, ecc->steps, ecc->size, ecc->bytes);
|
|
debug("%s: step ds: %d, strength ds: %d\n", __func__,
|
|
nand->ecc_step_ds, nand->ecc_strength_ds);
|
|
|
|
if (ecc->mode != NAND_ECC_NONE) {
|
|
int nsteps = ecc->steps ? ecc->steps : 1;
|
|
|
|
if (ecc->size && ecc->size != mtd->writesize)
|
|
nsteps = mtd->writesize / ecc->size;
|
|
else if (mtd->writesize > def_ecc_size &&
|
|
!(mtd->writesize & (def_ecc_size - 1)))
|
|
nsteps = mtd->writesize / def_ecc_size;
|
|
ecc->steps = nsteps;
|
|
ecc->size = mtd->writesize / nsteps;
|
|
ecc->bytes = mtd->oobsize / nsteps;
|
|
|
|
if (nand->ecc_strength_ds)
|
|
ecc->strength = nand->ecc_strength_ds;
|
|
if (nand->ecc_step_ds)
|
|
ecc->size = nand->ecc_step_ds;
|
|
/*
|
|
* no subpage ops, but set subpage-shift to match ecc->steps
|
|
* so mtd_nandbiterrs tests appropriate boundaries
|
|
*/
|
|
if (!mtd->subpage_sft && !(ecc->steps & (ecc->steps - 1)))
|
|
mtd->subpage_sft = fls(ecc->steps) - 1;
|
|
|
|
if (IS_ENABLED(CONFIG_NAND_OCTEONTX_HW_ECC)) {
|
|
debug("%s: ecc mode: %d\n", __func__, ecc->mode);
|
|
if (ecc->mode != NAND_ECC_SOFT &&
|
|
!octeontx_nand_calc_bch_ecc_strength(nand)) {
|
|
struct octeontx_nfc *tn =
|
|
to_otx_nfc(nand->controller);
|
|
|
|
debug("Using hardware BCH engine support\n");
|
|
ecc->mode = NAND_ECC_HW_SYNDROME;
|
|
ecc->read_page = octeontx_nand_hw_bch_read_page;
|
|
ecc->write_page =
|
|
octeontx_nand_hw_bch_write_page;
|
|
ecc->read_page_raw =
|
|
octeontx_nand_read_page_raw;
|
|
ecc->write_page_raw =
|
|
octeontx_nand_write_page_raw;
|
|
ecc->read_oob = octeontx_nand_read_oob_std;
|
|
ecc->write_oob = octeontx_nand_write_oob_std;
|
|
|
|
ecc->calculate = octeontx_nand_bch_calculate;
|
|
ecc->correct = octeontx_nand_bch_correct;
|
|
ecc->hwctl = octeontx_nand_bch_hwctl;
|
|
|
|
debug("NAND chip %d using hw_bch\n",
|
|
tn->selected_chip);
|
|
debug(" %d bytes ECC per %d byte block\n",
|
|
ecc->bytes, ecc->size);
|
|
debug(" for %d bits of correction per block.",
|
|
ecc->strength);
|
|
octeontx_nand_calc_ecc_layout(nand);
|
|
octeontx_bch_save_empty_eccmask(nand);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int octeontx_nfc_chip_init(struct octeontx_nfc *tn, struct udevice *dev,
|
|
ofnode node)
|
|
{
|
|
struct octeontx_nand_chip *chip;
|
|
struct nand_chip *nand;
|
|
struct mtd_info *mtd;
|
|
int ret;
|
|
|
|
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
|
|
if (!chip)
|
|
return -ENOMEM;
|
|
|
|
debug("%s: Getting chip select\n", __func__);
|
|
ret = ofnode_read_s32(node, "reg", &chip->cs);
|
|
if (ret) {
|
|
dev_err(dev, "could not retrieve reg property: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (chip->cs >= NAND_MAX_CHIPS) {
|
|
dev_err(dev, "invalid reg value: %u (max CS = 7)\n", chip->cs);
|
|
return -EINVAL;
|
|
}
|
|
debug("%s: chip select: %d\n", __func__, chip->cs);
|
|
nand = &chip->nand;
|
|
nand->controller = &tn->controller;
|
|
if (!tn->controller.active)
|
|
tn->controller.active = nand;
|
|
|
|
debug("%s: Setting flash node\n", __func__);
|
|
nand_set_flash_node(nand, node);
|
|
|
|
nand->options = 0;
|
|
nand->select_chip = octeontx_nand_select_chip;
|
|
nand->cmdfunc = octeontx_nand_cmdfunc;
|
|
nand->waitfunc = octeontx_nand_waitfunc;
|
|
nand->read_byte = octeontx_nand_read_byte;
|
|
nand->read_buf = octeontx_nand_read_buf;
|
|
nand->write_buf = octeontx_nand_write_buf;
|
|
nand->onfi_set_features = octeontx_nand_set_features;
|
|
nand->onfi_get_features = octeontx_nand_get_features;
|
|
nand->setup_data_interface = octeontx_nand_setup_dat_intf;
|
|
|
|
mtd = nand_to_mtd(nand);
|
|
debug("%s: mtd: %p\n", __func__, mtd);
|
|
mtd->dev->parent = dev;
|
|
|
|
debug("%s: NDF_MISC: 0x%llx\n", __func__,
|
|
readq(tn->base + NDF_MISC));
|
|
|
|
/* TODO: support more then 1 chip */
|
|
debug("%s: Scanning identification\n", __func__);
|
|
ret = nand_scan_ident(mtd, 1, NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
debug("%s: Sizing chip\n", __func__);
|
|
octeontx_nfc_chip_sizing(nand);
|
|
|
|
debug("%s: Scanning tail\n", __func__);
|
|
ret = nand_scan_tail(mtd);
|
|
if (ret) {
|
|
dev_err(dev, "nand_scan_tail failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
debug("%s: Registering mtd\n", __func__);
|
|
ret = nand_register(0, mtd);
|
|
|
|
debug("%s: Adding tail\n", __func__);
|
|
list_add_tail(&chip->node, &tn->chips);
|
|
return 0;
|
|
}
|
|
|
|
static int octeontx_nfc_chips_init(struct octeontx_nfc *tn)
|
|
{
|
|
struct udevice *dev = tn->dev;
|
|
ofnode node = dev_ofnode(dev);
|
|
ofnode nand_node;
|
|
int nr_chips = of_get_child_count(node);
|
|
int ret;
|
|
|
|
debug("%s: node: %s\n", __func__, ofnode_get_name(node));
|
|
debug("%s: %d chips\n", __func__, nr_chips);
|
|
if (nr_chips > NAND_MAX_CHIPS) {
|
|
dev_err(dev, "too many NAND chips: %d\n", nr_chips);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!nr_chips) {
|
|
debug("no DT NAND chips found\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
pr_info("%s: scanning %d chips DTs\n", __func__, nr_chips);
|
|
|
|
ofnode_for_each_subnode(nand_node, node) {
|
|
debug("%s: Calling octeontx_nfc_chip_init(%p, %s, %ld)\n",
|
|
__func__, tn, dev->name, nand_node.of_offset);
|
|
ret = octeontx_nfc_chip_init(tn, dev, nand_node);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Reset NFC and initialize registers. */
|
|
static int octeontx_nfc_init(struct octeontx_nfc *tn)
|
|
{
|
|
const struct nand_sdr_timings *timings;
|
|
u64 ndf_misc;
|
|
int rc;
|
|
|
|
/* Initialize values and reset the fifo */
|
|
ndf_misc = readq(tn->base + NDF_MISC);
|
|
|
|
ndf_misc &= ~NDF_MISC_EX_DIS;
|
|
ndf_misc |= (NDF_MISC_BT_DIS | NDF_MISC_RST_FF);
|
|
writeq(ndf_misc, tn->base + NDF_MISC);
|
|
debug("%s: NDF_MISC: 0x%llx\n", __func__, readq(tn->base + NDF_MISC));
|
|
|
|
/* Bring the fifo out of reset */
|
|
ndf_misc &= ~(NDF_MISC_RST_FF);
|
|
|
|
/* Maximum of co-processor cycles for glitch filtering */
|
|
ndf_misc |= FIELD_PREP(NDF_MISC_WAIT_CNT, 0x3f);
|
|
|
|
writeq(ndf_misc, tn->base + NDF_MISC);
|
|
|
|
/* Set timing parameters to onfi mode 0 for probing */
|
|
timings = onfi_async_timing_mode_to_sdr_timings(0);
|
|
if (IS_ERR(timings))
|
|
return PTR_ERR(timings);
|
|
rc = set_default_timings(tn, timings);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int octeontx_pci_nand_probe(struct udevice *dev)
|
|
{
|
|
struct octeontx_nfc *tn = dev_get_priv(dev);
|
|
int ret;
|
|
static bool probe_done;
|
|
|
|
debug("%s(%s) tn: %p\n", __func__, dev->name, tn);
|
|
if (probe_done)
|
|
return 0;
|
|
|
|
if (IS_ENABLED(CONFIG_NAND_OCTEONTX_HW_ECC)) {
|
|
bch_vf = octeontx_bch_getv();
|
|
if (!bch_vf) {
|
|
struct octeontx_probe_device *probe_dev;
|
|
|
|
debug("%s: bch not yet initialized\n", __func__);
|
|
probe_dev = calloc(sizeof(*probe_dev), 1);
|
|
if (!probe_dev) {
|
|
printf("%s: Out of memory\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
probe_dev->dev = dev;
|
|
INIT_LIST_HEAD(&probe_dev->list);
|
|
list_add_tail(&probe_dev->list,
|
|
&octeontx_pci_nand_deferred_devices);
|
|
debug("%s: Defering probe until after BCH initialization\n",
|
|
__func__);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
tn->dev = dev;
|
|
INIT_LIST_HEAD(&tn->chips);
|
|
|
|
tn->base = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0, 0, 0, PCI_REGION_TYPE, PCI_REGION_MEM);
|
|
if (!tn->base) {
|
|
ret = -EINVAL;
|
|
goto release;
|
|
}
|
|
debug("%s: bar at %p\n", __func__, tn->base);
|
|
tn->buf.dmabuflen = NAND_MAX_PAGESIZE + NAND_MAX_OOBSIZE;
|
|
tn->buf.dmabuf = dma_alloc_coherent(tn->buf.dmabuflen,
|
|
(unsigned long *)&tn->buf.dmaaddr);
|
|
if (!tn->buf.dmabuf) {
|
|
ret = -ENOMEM;
|
|
debug("%s: Could not allocate DMA buffer\n", __func__);
|
|
goto unclk;
|
|
}
|
|
|
|
/* one hw-bch response, for one outstanding transaction */
|
|
tn->bch_resp = dma_alloc_coherent(sizeof(*tn->bch_resp),
|
|
(unsigned long *)&tn->bch_rhandle);
|
|
|
|
tn->stat = dma_alloc_coherent(8, (unsigned long *)&tn->stat_addr);
|
|
if (!tn->stat || !tn->bch_resp) {
|
|
debug("%s: Could not allocate bch status or response\n",
|
|
__func__);
|
|
ret = -ENOMEM;
|
|
goto unclk;
|
|
}
|
|
|
|
debug("%s: Calling octeontx_nfc_init()\n", __func__);
|
|
octeontx_nfc_init(tn);
|
|
debug("%s: Initializing chips\n", __func__);
|
|
ret = octeontx_nfc_chips_init(tn);
|
|
debug("%s: init chips ret: %d\n", __func__, ret);
|
|
if (ret) {
|
|
if (ret != -ENODEV)
|
|
dev_err(dev, "failed to init nand chips\n");
|
|
goto unclk;
|
|
}
|
|
dev_info(dev, "probed\n");
|
|
return 0;
|
|
|
|
unclk:
|
|
release:
|
|
return ret;
|
|
}
|
|
|
|
int octeontx_pci_nand_disable(struct udevice *dev)
|
|
{
|
|
struct octeontx_nfc *tn = dev_get_priv(dev);
|
|
u64 dma_cfg;
|
|
u64 ndf_misc;
|
|
|
|
debug("%s: Disabling NAND device %s\n", __func__, dev->name);
|
|
dma_cfg = readq(tn->base + NDF_DMA_CFG);
|
|
dma_cfg &= ~NDF_DMA_CFG_EN;
|
|
dma_cfg |= NDF_DMA_CFG_CLR;
|
|
writeq(dma_cfg, tn->base + NDF_DMA_CFG);
|
|
|
|
/* Disable execution and put FIFO in reset mode */
|
|
ndf_misc = readq(tn->base + NDF_MISC);
|
|
ndf_misc |= NDF_MISC_EX_DIS | NDF_MISC_RST_FF;
|
|
writeq(ndf_misc, tn->base + NDF_MISC);
|
|
ndf_misc &= ~NDF_MISC_RST_FF;
|
|
writeq(ndf_misc, tn->base + NDF_MISC);
|
|
#ifdef DEBUG
|
|
printf("%s: NDF_MISC: 0x%llx\n", __func__, readq(tn->base + NDF_MISC));
|
|
#endif
|
|
/* Clear any interrupts and enable bits */
|
|
writeq(~0ull, tn->base + NDF_INT_ENA_W1C);
|
|
writeq(~0ull, tn->base + NDF_INT);
|
|
debug("%s: NDF_ST_REG: 0x%llx\n", __func__,
|
|
readq(tn->base + NDF_ST_REG));
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Since it's possible (and even likely) that the NAND device will be probed
|
|
* before the BCH device has been probed, we may need to defer the probing.
|
|
*
|
|
* In this case, the initial probe returns success but the actual probing
|
|
* is deferred until the BCH VF has been probed.
|
|
*
|
|
* Return: 0 for success, otherwise error
|
|
*/
|
|
int octeontx_pci_nand_deferred_probe(void)
|
|
{
|
|
int rc = 0;
|
|
struct octeontx_probe_device *pdev;
|
|
|
|
debug("%s: Performing deferred probing\n", __func__);
|
|
list_for_each_entry(pdev, &octeontx_pci_nand_deferred_devices, list) {
|
|
debug("%s: Probing %s\n", __func__, pdev->dev->name);
|
|
dev_get_flags(pdev->dev) &= ~DM_FLAG_ACTIVATED;
|
|
rc = device_probe(pdev->dev);
|
|
if (rc && rc != -ENODEV) {
|
|
printf("%s: Error %d with deferred probe of %s\n",
|
|
__func__, rc, pdev->dev->name);
|
|
break;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static const struct pci_device_id octeontx_nfc_pci_id_table[] = {
|
|
{ PCI_VDEVICE(CAVIUM, 0xA04F) },
|
|
{}
|
|
};
|
|
|
|
static int octeontx_nand_of_to_plat(struct udevice *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct udevice_id octeontx_nand_ids[] = {
|
|
{ .compatible = "cavium,cn8130-nand" },
|
|
{ },
|
|
};
|
|
|
|
U_BOOT_DRIVER(octeontx_pci_nand) = {
|
|
.name = OCTEONTX_NAND_DRIVER_NAME,
|
|
.id = UCLASS_MTD,
|
|
.of_match = of_match_ptr(octeontx_nand_ids),
|
|
.of_to_plat = octeontx_nand_of_to_plat,
|
|
.probe = octeontx_pci_nand_probe,
|
|
.priv_auto = sizeof(struct octeontx_nfc),
|
|
.remove = octeontx_pci_nand_disable,
|
|
.flags = DM_FLAG_OS_PREPARE,
|
|
};
|
|
|
|
U_BOOT_PCI_DEVICE(octeontx_pci_nand, octeontx_nfc_pci_id_table);
|
|
|
|
void board_nand_init(void)
|
|
{
|
|
struct udevice *dev;
|
|
int ret;
|
|
|
|
if (IS_ENABLED(CONFIG_NAND_OCTEONTX_HW_ECC)) {
|
|
ret = uclass_get_device_by_driver(UCLASS_MISC,
|
|
DM_DRIVER_GET(octeontx_pci_bchpf),
|
|
&dev);
|
|
if (ret && ret != -ENODEV) {
|
|
pr_err("Failed to initialize OcteonTX BCH PF controller. (error %d)\n",
|
|
ret);
|
|
}
|
|
ret = uclass_get_device_by_driver(UCLASS_MISC,
|
|
DM_DRIVER_GET(octeontx_pci_bchvf),
|
|
&dev);
|
|
if (ret && ret != -ENODEV) {
|
|
pr_err("Failed to initialize OcteonTX BCH VF controller. (error %d)\n",
|
|
ret);
|
|
}
|
|
}
|
|
|
|
ret = uclass_get_device_by_driver(UCLASS_MTD,
|
|
DM_DRIVER_GET(octeontx_pci_nand),
|
|
&dev);
|
|
if (ret && ret != -ENODEV)
|
|
pr_err("Failed to initialize OcteonTX NAND controller. (error %d)\n",
|
|
ret);
|
|
}
|