| // 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, 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); |
| } |