3148 lines
79 KiB
C
3148 lines
79 KiB
C
/*
|
|
* ------------------------------------------------------------------------
|
|
* stm_afm_nand.c STMicroelectronics Advanced Flex Mode NAND Flash driver
|
|
* for SoC's with the Hamming NAND Controller.
|
|
* ------------------------------------------------------------------------
|
|
*
|
|
* Copyright (c) 2010 STMicroelectronics Limited
|
|
* Author: Angus Clark <Angus.Clark@st.com>
|
|
*
|
|
* ------------------------------------------------------------------------
|
|
* May be copied or modified under the terms of the GNU General Public
|
|
* License Version 2.0 only. See linux/COPYING for more information.
|
|
* ------------------------------------------------------------------------
|
|
*
|
|
*/
|
|
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mtd/mtd.h>
|
|
#include <linux/mtd/nand.h>
|
|
#include <linux/err.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/mtd/nand_ecc.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/stm/platform.h>
|
|
#include <linux/stm/nand.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include "stm_nand_ecc.h"
|
|
#include "stm_nandc_regs.h"
|
|
|
|
#define NAME "stm-nand-afm"
|
|
|
|
|
|
#ifdef CONFIG_MTD_PARTITIONS
|
|
#include <linux/mtd/partitions.h>
|
|
static const char *part_probes[] = { "cmdlinepart", NULL };
|
|
#endif
|
|
|
|
#ifdef CONFIG_STM_NAND_AFM_BOOTMODESUPPORT
|
|
/*
|
|
* Support for STM boot-mode ECC: The STM NAND Boot Controller uses a different
|
|
* ECC scheme to the AFM controller. In order to support boot-mode ECC data, we
|
|
* maintian two sets of ECC parameters and switch depending on which partition
|
|
* we are about to read from.
|
|
*/
|
|
struct ecc_params {
|
|
/* nand_chip params */
|
|
struct nand_ecc_ctrl ecc_ctrl;
|
|
int subpagesize;
|
|
|
|
/* mtd_info params */
|
|
u_int32_t subpage_sft;
|
|
};
|
|
|
|
static void afm_select_eccparams(struct mtd_info *mtd, loff_t offs);
|
|
static void afm_write_page_boot(struct mtd_info *mtd,
|
|
struct nand_chip *chip,
|
|
const uint8_t *buf);
|
|
static int afm_read_page_boot(struct mtd_info *mtd,
|
|
struct nand_chip *chip,
|
|
uint8_t *buf, int page);
|
|
|
|
/* Module parameter for specifying name of boot partition */
|
|
static char *nbootpart;
|
|
module_param(nbootpart, charp, 0000);
|
|
MODULE_PARM_DESC(nbootpart, "MTD name of NAND boot-mode ECC partition");
|
|
|
|
#else
|
|
#define afm_select_eccparams(x, y) do {} while (0);
|
|
#endif /* CONFIG_STM_NAND_AFM_BOOTMODESUPPORT */
|
|
|
|
|
|
/* External functions */
|
|
|
|
/* AFM 32-byte program block */
|
|
struct afm_prog {
|
|
uint8_t instr[16];
|
|
uint32_t addr_reg;
|
|
uint32_t extra_reg;
|
|
uint8_t cmds[4];
|
|
uint32_t seq_cfg;
|
|
} __attribute__((__packed__));
|
|
|
|
/* NAND device connected to STM NAND Controller */
|
|
struct stm_nand_afm_device {
|
|
struct nand_chip chip;
|
|
struct mtd_info mtd;
|
|
|
|
int csn;
|
|
struct stm_nand_timing_data *timing_data;
|
|
|
|
struct device *dev;
|
|
|
|
#ifdef CONFIG_STM_NAND_AFM_BOOTMODESUPPORT
|
|
unsigned long boot_start;
|
|
unsigned long boot_end;
|
|
struct ecc_params ecc_boot;
|
|
struct ecc_params ecc_afm;
|
|
#endif
|
|
|
|
#ifdef CONFIG_MTD_PARTITIONS
|
|
int nr_parts;
|
|
struct mtd_partition *parts;
|
|
#endif
|
|
|
|
};
|
|
|
|
/* STM NAND AFM Controller */
|
|
struct stm_nand_afm_controller {
|
|
void __iomem *base;
|
|
void __iomem *fifo_cached;
|
|
unsigned long fifo_phys;
|
|
|
|
/* resources */
|
|
struct device *dev;
|
|
unsigned int map_base;
|
|
unsigned int map_size;
|
|
unsigned int irq;
|
|
|
|
spinlock_t lock; /* save/restore IRQ flags */
|
|
|
|
int current_csn;
|
|
|
|
/* access control */
|
|
struct nand_hw_control hwcontrol;
|
|
|
|
/* irq completions */
|
|
struct completion rbn_completed;
|
|
struct completion seq_completed;
|
|
|
|
/* emulate MTD nand_base.c implementation */
|
|
uint32_t status;
|
|
uint32_t col;
|
|
uint32_t page;
|
|
|
|
/* devices connected to controller */
|
|
struct stm_nand_afm_device *devices[0];
|
|
};
|
|
|
|
#define afm_writereg(val, reg) iowrite32(val, afm->base + (reg))
|
|
#define afm_readreg(reg) ioread32(afm->base + (reg))
|
|
|
|
static struct stm_nand_afm_controller *mtd_to_afm(struct mtd_info *mtd)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
struct nand_hw_control *controller = chip->controller;
|
|
struct stm_nand_afm_controller *afm;
|
|
|
|
afm = container_of(controller, struct stm_nand_afm_controller,
|
|
hwcontrol);
|
|
return afm;
|
|
}
|
|
|
|
|
|
/*
|
|
* Define some template AFM programs
|
|
*/
|
|
#define AFM_INSTR(cmd, op) ((cmd) | ((op) << 4))
|
|
|
|
/* AFM Prog: Block Erase */
|
|
struct afm_prog afm_prog_erase = {
|
|
.cmds = {
|
|
NAND_CMD_ERASE1,
|
|
NAND_CMD_ERASE1,
|
|
NAND_CMD_ERASE2,
|
|
NAND_CMD_STATUS
|
|
},
|
|
.instr = {
|
|
AFM_INSTR(AFM_CMD, 1),
|
|
AFM_INSTR(AFM_ADDR, 0),
|
|
AFM_INSTR(AFM_ADDR, 1),
|
|
AFM_INSTR(AFM_CMD, 2),
|
|
AFM_INSTR(AFM_CMD, 3),
|
|
AFM_INSTR(AFM_CHECK, 0),
|
|
AFM_INSTR(AFM_STOP, 0)
|
|
},
|
|
.seq_cfg = AFM_SEQ_CFG_GO,
|
|
};
|
|
|
|
/* AFM Prog: Block Erase, extra address cycle */
|
|
struct afm_prog afm_prog_erase_ext = {
|
|
.cmds = {
|
|
NAND_CMD_ERASE1,
|
|
NAND_CMD_ERASE1,
|
|
NAND_CMD_ERASE2,
|
|
NAND_CMD_STATUS
|
|
},
|
|
.instr = {
|
|
AFM_INSTR(AFM_CMD, 1),
|
|
AFM_INSTR(AFM_ADDR, 0),
|
|
AFM_INSTR(AFM_ADDR, 1),
|
|
AFM_INSTR(AFM_ADDR, 2),
|
|
AFM_INSTR(AFM_CMD, 2),
|
|
AFM_INSTR(AFM_CMD, 3),
|
|
AFM_INSTR(AFM_CHECK, 0),
|
|
AFM_INSTR(AFM_STOP, 0)
|
|
},
|
|
.seq_cfg = AFM_SEQ_CFG_GO,
|
|
};
|
|
|
|
/* AFM Prog: Read OOB [SmallPage] */
|
|
struct afm_prog afm_prog_read_oob_sp = {
|
|
.cmds = {
|
|
NAND_CMD_READOOB
|
|
},
|
|
.instr = {
|
|
AFM_INSTR(AFM_CMD, 0),
|
|
AFM_INSTR(AFM_DATA, 0),
|
|
AFM_INSTR(AFM_STOP, 0)
|
|
},
|
|
.seq_cfg = AFM_SEQ_CFG_GO,
|
|
};
|
|
|
|
/* AFM Prog: Read OOB [LargePage] */
|
|
struct afm_prog afm_prog_read_oob_lp = {
|
|
.cmds = {
|
|
NAND_CMD_READ0,
|
|
NAND_CMD_READSTART
|
|
},
|
|
.instr = {
|
|
AFM_INSTR(AFM_CMD, 0),
|
|
AFM_INSTR(AFM_CMD, 1),
|
|
AFM_INSTR(AFM_DATA, 0),
|
|
AFM_INSTR(AFM_STOP, 0)
|
|
},
|
|
.seq_cfg = AFM_SEQ_CFG_GO,
|
|
};
|
|
|
|
/* AFM Prog: Read Raw Page and OOB Data [SmallPage]
|
|
*
|
|
* Note, "SPARE3" would normally be used to read the OOB data. However, SPARE3
|
|
* has been observed to cause problems with EMI Arbitration resulting in system
|
|
* deadlock. To avoid such problems, we use the DATA0 command here. This will
|
|
* have the effect of over-reading 48 bytes from the end of OOB area but should
|
|
* be benign on current NAND devices.
|
|
*/
|
|
struct afm_prog afm_prog_read_raw_sp = {
|
|
.cmds = {
|
|
NAND_CMD_READ0
|
|
},
|
|
.instr = {
|
|
AFM_INSTR(AFM_CMD, 0),
|
|
AFM_INSTR(AFM_DATA, 1),
|
|
AFM_INSTR(AFM_DATA, 0),
|
|
AFM_INSTR(AFM_STOP, 0)
|
|
},
|
|
.seq_cfg = AFM_SEQ_CFG_GO,
|
|
};
|
|
|
|
/* AFM Prog: Read Raw Page and OOB Data [LargePage] */
|
|
struct afm_prog afm_prog_read_raw_lp = {
|
|
.cmds = {
|
|
NAND_CMD_READ0,
|
|
NAND_CMD_READSTART
|
|
},
|
|
.instr = {
|
|
AFM_INSTR(AFM_CMD, 0),
|
|
AFM_INSTR(AFM_CMD, 1),
|
|
AFM_INSTR(AFM_DATA, 1),
|
|
AFM_INSTR(AFM_DATA, 1),
|
|
AFM_INSTR(AFM_DATA, 1),
|
|
AFM_INSTR(AFM_DATA, 1),
|
|
AFM_INSTR(AFM_DATA, 0),
|
|
AFM_INSTR(AFM_STOP, 0)
|
|
},
|
|
.seq_cfg = AFM_SEQ_CFG_GO,
|
|
};
|
|
|
|
/* AFM Prog: Write Raw Page [SmallPage]
|
|
* OOB written with FLEX mode.
|
|
*/
|
|
struct afm_prog afm_prog_write_raw_sp_a = {
|
|
.cmds = {
|
|
NAND_CMD_SEQIN,
|
|
NAND_CMD_READ0,
|
|
NAND_CMD_PAGEPROG,
|
|
NAND_CMD_STATUS,
|
|
},
|
|
.instr = {
|
|
AFM_INSTR(AFM_CMD, 1),
|
|
AFM_INSTR(AFM_CMD, 0),
|
|
AFM_INSTR(AFM_DATA, 1),
|
|
AFM_INSTR(AFM_CMD, 2),
|
|
AFM_INSTR(AFM_CMD, 3),
|
|
AFM_INSTR(AFM_CHECK, 0),
|
|
AFM_INSTR(AFM_STOP, 0)
|
|
},
|
|
.seq_cfg = AFM_SEQ_CFG_GO | AFM_SEQ_CFG_DIR_WRITE,
|
|
};
|
|
|
|
/* AFM Prog: Write Raw Page and OOB Data [LargePage] */
|
|
struct afm_prog afm_prog_write_raw_lp = {
|
|
.cmds = {
|
|
NAND_CMD_SEQIN,
|
|
NAND_CMD_PAGEPROG,
|
|
NAND_CMD_STATUS,
|
|
},
|
|
.instr = {
|
|
AFM_INSTR(AFM_CMD, 0),
|
|
AFM_INSTR(AFM_DATA, 1),
|
|
AFM_INSTR(AFM_DATA, 1),
|
|
AFM_INSTR(AFM_DATA, 1),
|
|
AFM_INSTR(AFM_DATA, 1),
|
|
AFM_INSTR(AFM_DATA, 0),
|
|
AFM_INSTR(AFM_CMD, 1),
|
|
AFM_INSTR(AFM_CMD, 2),
|
|
AFM_INSTR(AFM_CHECK, 0),
|
|
AFM_INSTR(AFM_STOP, 0)
|
|
},
|
|
.seq_cfg = AFM_SEQ_CFG_GO | AFM_SEQ_CFG_DIR_WRITE,
|
|
};
|
|
|
|
/* AFM Prog: Write Page and OOB Data, with ECC support [SmallPage]
|
|
*
|
|
* Note, the AFM command, "SPARE3", would normally be used to write the OOB
|
|
* data, with ECC inserted on the fly by the NAND controller. However, the
|
|
* SPARE3 command has been observed to cause EMI Arbitration issues resulting in
|
|
* a bus stall and system deadlock. To avoid this problem, we switch to FLEX
|
|
* mode to write the OOB data, extracting and inserting the ECC bytes from the
|
|
* NAND ECC checkcode regiter (see afm_write_page_ecc_sp())
|
|
*/
|
|
struct afm_prog afm_prog_write_ecc_sp_a = {
|
|
.cmds = {
|
|
NAND_CMD_SEQIN,
|
|
NAND_CMD_READ0,
|
|
NAND_CMD_PAGEPROG,
|
|
NAND_CMD_STATUS,
|
|
},
|
|
.instr = {
|
|
AFM_INSTR(AFM_CMD, 1),
|
|
AFM_INSTR(AFM_CMD, 0),
|
|
AFM_INSTR(AFM_DATA, 1),
|
|
AFM_INSTR(AFM_STOP, 0)
|
|
},
|
|
.seq_cfg = AFM_SEQ_CFG_GO | AFM_SEQ_CFG_DIR_WRITE,
|
|
};
|
|
|
|
/* AFM Prog: Write Page and OOB Data, with ECC support [LargePage] */
|
|
struct afm_prog afm_prog_write_ecc_lp = {
|
|
.cmds = {
|
|
NAND_CMD_SEQIN,
|
|
NAND_CMD_PAGEPROG,
|
|
NAND_CMD_STATUS,
|
|
},
|
|
.instr = {
|
|
AFM_INSTR(AFM_CMD, 0),
|
|
AFM_INSTR(AFM_DATA, 2),
|
|
AFM_INSTR(AFM_CMD, 1),
|
|
AFM_INSTR(AFM_CMD, 2),
|
|
AFM_INSTR(AFM_CHECK, 0),
|
|
AFM_INSTR(AFM_STOP, 0)
|
|
},
|
|
.seq_cfg = AFM_SEQ_CFG_GO | AFM_SEQ_CFG_DIR_WRITE,
|
|
};
|
|
|
|
/* AFM Prog: Write OOB Data [LargePage] */
|
|
struct afm_prog afm_prog_write_oob_lp = {
|
|
.cmds = {
|
|
NAND_CMD_SEQIN,
|
|
NAND_CMD_PAGEPROG,
|
|
NAND_CMD_STATUS
|
|
},
|
|
.instr = {
|
|
AFM_INSTR(AFM_CMD, 0),
|
|
AFM_INSTR(AFM_DATA, 0),
|
|
AFM_INSTR(AFM_CMD, 1),
|
|
AFM_INSTR(AFM_CMD, 2),
|
|
AFM_INSTR(AFM_CHECK, 0),
|
|
AFM_INSTR(AFM_STOP, 0)
|
|
},
|
|
.seq_cfg = AFM_SEQ_CFG_GO | AFM_SEQ_CFG_DIR_WRITE,
|
|
};
|
|
|
|
|
|
/*
|
|
* STMicroeclectronics H/W ECC layouts
|
|
*
|
|
* AFM4 ECC: 512-byte data records, 3 bytes H/W ECC, 1 byte S/W ECC, 3
|
|
* bytes marker ({'A', 'F', 'M'})
|
|
* Boot-mode ECC: 128-byte data records, 3 bytes ECC + 1 byte marker ('B')
|
|
*
|
|
*/
|
|
static struct nand_ecclayout afm_oob_16 = {
|
|
.eccbytes = 7,
|
|
.eccpos = {
|
|
/* { HW_ECC0, HW_ECC1, HW_ECC2, 'A', 'F', 'M', SW_ECC } */
|
|
0, 1, 2, 3, 4, 5, 6},
|
|
.oobfree = {
|
|
{.offset = 7,
|
|
. length = 9},
|
|
}
|
|
};
|
|
|
|
static struct nand_ecclayout afm_oob_64 = {
|
|
.eccbytes = 28,
|
|
.eccpos = {
|
|
/* { HW_ECC0, HW_ECC1, HW_ECC2, 'A', 'F', 'M', SW_ECC } */
|
|
0, 1, 2, 3, 4, 5, 6, /* Record 0 */
|
|
16, 17, 18, 19, 20, 21, 22, /* Record 1 */
|
|
32, 33, 34, 35, 36, 37, 38, /* Record 2 */
|
|
48, 49, 50, 51, 52, 53, 54, /* Record 3 */
|
|
},
|
|
.oobfree = {
|
|
{.offset = 7,
|
|
.length = 9
|
|
},
|
|
{.offset = 23,
|
|
.length = 9
|
|
},
|
|
{.offset = 39,
|
|
.length = 9
|
|
},
|
|
{.offset = 55,
|
|
.length = 9
|
|
},
|
|
}
|
|
};
|
|
|
|
#ifdef CONFIG_STM_NAND_AFM_BOOTMODESUPPORT
|
|
static struct nand_ecclayout boot_oob_16 = {
|
|
.eccbytes = 16,
|
|
.eccpos = {
|
|
0, 1, 2, 3, /* Record 0: ECC0, ECC1, ECC2, 'B' */
|
|
4, 5, 6, 7, /* Record 1: ECC0, ECC1, ECC2, 'B' */
|
|
8, 9, 10, 11, /* ... */
|
|
12, 13, 14, 15
|
|
},
|
|
.oobfree = {{0, 0} }, /* No free space in OOB */
|
|
};
|
|
|
|
static struct nand_ecclayout boot_oob_64 = {
|
|
.eccbytes = 64,
|
|
.eccpos = {
|
|
0, 1, 2, 3, /* Record 0: ECC0, ECC1, ECC2, B */
|
|
4, 5, 6, 7, /* Record 1: ECC0, ECC1, ECC2, B */
|
|
8, 9, 10, 11, /* ... */
|
|
12, 13, 14, 15,
|
|
16, 17, 18, 19,
|
|
20, 21, 22, 23,
|
|
24, 25, 26, 27,
|
|
28, 29, 30, 31,
|
|
32, 33, 34, 35,
|
|
36, 37, 38, 39,
|
|
40, 41, 42, 43,
|
|
44, 45, 46, 47,
|
|
48, 49, 50, 51,
|
|
52, 53, 54, 55,
|
|
56, 57, 58, 59,
|
|
60, 61, 62, 63
|
|
},
|
|
.oobfree = {{0, 0} }, /* No free OOB bytes */
|
|
};
|
|
#endif
|
|
|
|
/* Pattern descriptors for scanning bad-block scanning - add support for AFM ECC
|
|
* scheme */
|
|
static uint8_t scan_ff_pattern[] = { 0xff, 0xff };
|
|
static struct nand_bbt_descr bbt_scan_sp = {
|
|
|
|
.options = (NAND_BBT_SCAN2NDPAGE | NAND_BBT_SCANSTMAFMECC),
|
|
.offs = 5,
|
|
.len = 1,
|
|
.pattern = scan_ff_pattern
|
|
};
|
|
static struct nand_bbt_descr bbt_scan_lp = {
|
|
.options = (NAND_BBT_SCAN2NDPAGE | NAND_BBT_SCANSTMAFMECC),
|
|
.offs = 0,
|
|
.len = 2,
|
|
.pattern = scan_ff_pattern
|
|
};
|
|
|
|
|
|
/*
|
|
* AFM Interrupts
|
|
*/
|
|
|
|
#define AFM_IRQ (1 << 0)
|
|
#define AFM_IRQ_RBN (1 << 2)
|
|
#define AFM_IRQ_DATA_DREQ (1 << 3)
|
|
#define AFM_IRQ_SEQ_DREQ (1 << 4)
|
|
#define AFM_IRQ_CHECK (1 << 5)
|
|
#define AFM_IRQ_ECC_FIX (1 << 6)
|
|
|
|
static void afm_enable_interrupts(struct stm_nand_afm_controller *afm,
|
|
uint32_t irqs)
|
|
{
|
|
uint32_t reg;
|
|
|
|
reg = afm_readreg(EMINAND_INTERRUPT_ENABLE);
|
|
reg |= irqs;
|
|
afm_writereg(reg, EMINAND_INTERRUPT_ENABLE);
|
|
|
|
}
|
|
|
|
static void afm_disable_interrupts(struct stm_nand_afm_controller *afm,
|
|
uint32_t irqs)
|
|
{
|
|
uint32_t reg;
|
|
|
|
reg = afm_readreg(EMINAND_INTERRUPT_ENABLE);
|
|
reg &= ~irqs;
|
|
afm_writereg(reg, EMINAND_INTERRUPT_ENABLE);
|
|
}
|
|
|
|
static irqreturn_t afm_irq_handler(int irq, void *dev)
|
|
{
|
|
struct stm_nand_afm_controller *afm =
|
|
(struct stm_nand_afm_controller *)dev;
|
|
unsigned int irq_status;
|
|
|
|
irq_status = afm_readreg(EMINAND_INTERRUPT_STATUS);
|
|
|
|
/* RBn */
|
|
if (irq_status & 0x4) {
|
|
afm_writereg(0x04, EMINAND_INTERRUPT_CLEAR);
|
|
complete(&afm->rbn_completed);
|
|
}
|
|
|
|
/* ECC_fix */
|
|
if (irq_status & 0x40)
|
|
afm_writereg(0x10, EMINAND_INTERRUPT_CLEAR);
|
|
|
|
/* Seq_req */
|
|
if (irq_status & AFM_IRQ_SEQ_DREQ) {
|
|
afm_writereg(0x40, EMINAND_INTERRUPT_CLEAR);
|
|
complete(&afm->seq_completed);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* AFM Initialisation
|
|
*/
|
|
|
|
/* AFM set generic config register */
|
|
static void afm_generic_config(struct stm_nand_afm_controller *afm,
|
|
uint32_t busw, uint32_t page_size,
|
|
uint32_t chip_size)
|
|
{
|
|
uint32_t reg;
|
|
|
|
reg = 0x00;
|
|
if ((busw & NAND_BUSWIDTH_16) == 0)
|
|
reg |= 0x1 << 16; /* data_8_not_16 */
|
|
|
|
if (page_size > 512) {
|
|
/* large page */
|
|
reg |= 0x1 << 17; /* page size */
|
|
if (chip_size > (128 << 20))
|
|
reg |= 0x1 << 18; /* extra addr cycle */
|
|
} else if (chip_size > (32 << 20)) {
|
|
/* small page */
|
|
reg |= 0x1 << 18; /* extra addr cycle */
|
|
|
|
}
|
|
|
|
dev_dbg(afm->dev, "setting AFM_GENERIC_CONFIG_REG = 0x%08x\n", reg);
|
|
|
|
afm_writereg(reg, EMINAND_AFM_GENERIC_CONFIG_REG);
|
|
}
|
|
|
|
/* AFM configure timing parameters */
|
|
static void afm_set_timings(struct stm_nand_afm_controller *afm,
|
|
struct stm_nand_timing_data *tm)
|
|
{
|
|
uint32_t n;
|
|
uint32_t reg;
|
|
|
|
struct clk *emi_clk;
|
|
uint32_t emi_t_ns;
|
|
|
|
/* Timings set in terms of EMI clock... */
|
|
emi_clk = clk_get(NULL, "emi_clk");
|
|
|
|
if (!emi_clk || IS_ERR(emi_clk)) {
|
|
printk(KERN_WARNING NAME ": Failed to find EMI clock. "
|
|
"Using default 100MHz.\n");
|
|
emi_t_ns = 10;
|
|
} else {
|
|
emi_t_ns = 1000000000UL / clk_get_rate(emi_clk);
|
|
}
|
|
|
|
/* CONTROL_TIMING */
|
|
n = (tm->sig_setup + emi_t_ns - 1)/emi_t_ns;
|
|
reg = (n & 0xff) << 0;
|
|
|
|
n = (tm->sig_hold + emi_t_ns - 1)/emi_t_ns;
|
|
reg |= (n & 0xff) << 8;
|
|
|
|
n = (tm->CE_deassert + emi_t_ns - 1)/emi_t_ns;
|
|
reg |= (n & 0xff) << 16;
|
|
|
|
n = (tm->WE_to_RBn + emi_t_ns - 1)/emi_t_ns;
|
|
reg |= (n & 0xff) << 24;
|
|
|
|
dev_dbg(afm->dev, "setting CONTROL_TIMING = 0x%08x\n", reg);
|
|
afm_writereg(reg, EMINAND_CONTROL_TIMING);
|
|
|
|
/* WEN_TIMING */
|
|
n = (tm->wr_on + emi_t_ns - 1)/emi_t_ns;
|
|
reg = (n & 0xff) << 0;
|
|
|
|
n = (tm->wr_off + emi_t_ns - 1)/emi_t_ns;
|
|
reg |= (n & 0xff) << 8;
|
|
|
|
dev_dbg(afm->dev, "setting WEN_TIMING = 0x%08x\n", reg);
|
|
afm_writereg(reg, EMINAND_WEN_TIMING);
|
|
|
|
/* REN_TIMING */
|
|
n = (tm->rd_on + emi_t_ns - 1)/emi_t_ns;
|
|
reg = (n & 0xff) << 0;
|
|
|
|
n = (tm->rd_off + emi_t_ns - 1)/emi_t_ns;
|
|
reg |= (n & 0xff) << 8;
|
|
|
|
dev_dbg(afm->dev, "setting REN_TIMING = 0x%08x\n", reg);
|
|
afm_writereg(reg, EMINAND_REN_TIMING);
|
|
}
|
|
|
|
/* Initialise the AFM NAND controller */
|
|
static struct stm_nand_afm_controller * __init
|
|
afm_init_controller(struct platform_device *pdev)
|
|
{
|
|
struct stm_plat_nand_flex_data *pdata = pdev->dev.platform_data;
|
|
struct stm_nand_afm_controller *afm;
|
|
struct resource *resource;
|
|
int err = 0;
|
|
|
|
afm = kzalloc(sizeof(struct stm_nand_afm_controller) +
|
|
(sizeof(struct stm_nand_afm_device *) * pdata->nr_banks),
|
|
GFP_KERNEL);
|
|
if (!afm) {
|
|
dev_err(&pdev->dev, "failed to allocate afm controller data\n");
|
|
err = -ENOMEM;
|
|
goto err0;
|
|
}
|
|
|
|
afm->dev = &pdev->dev;
|
|
|
|
/* Request IO Memory */
|
|
resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!resource) {
|
|
dev_err(&pdev->dev, "failed to find IOMEM resource\n");
|
|
err = -ENOENT;
|
|
goto err1;
|
|
}
|
|
afm->map_base = resource->start;
|
|
afm->map_size = resource->end - resource->start + 1;
|
|
|
|
if (!request_mem_region(afm->map_base, afm->map_size, pdev->name)) {
|
|
dev_err(&pdev->dev, "request memory region failed [0x%08x]\n",
|
|
afm->map_base);
|
|
err = -EBUSY;
|
|
goto err1;
|
|
}
|
|
|
|
/* Map base address */
|
|
afm->base = ioremap_nocache(afm->map_base, afm->map_size);
|
|
if (!afm->base) {
|
|
dev_err(&pdev->dev, "base ioremap failed [0x%08x]\n",
|
|
afm->map_base);
|
|
err = -ENXIO;
|
|
goto err2;
|
|
}
|
|
|
|
#ifdef CONFIG_STM_NAND_AFM_CACHED
|
|
/* Setup cached mapping to the AFM data FIFO */
|
|
afm->fifo_phys = (afm->map_base + EMINAND_AFM_DATA_FIFO);
|
|
#ifndef CONFIG_32BIT
|
|
/* 29-bit uses 'Area 7' address. [Should this be done in ioremap?] */
|
|
afm->fifo_phys &= 0x1fffffff;
|
|
#endif
|
|
afm->fifo_cached = ioremap_cache(afm->fifo_phys, 512);
|
|
if (!afm->fifo_cached) {
|
|
dev_err(&pdev->dev, "fifo ioremap failed [0x%08x]\n",
|
|
afm->map_base + EMINAND_AFM_DATA_FIFO);
|
|
err = -ENXIO;
|
|
goto err3;
|
|
}
|
|
|
|
spin_lock_init(&afm->lock);
|
|
#endif
|
|
|
|
/* Setup IRQ handler */
|
|
resource = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
if (!resource) {
|
|
dev_err(&pdev->dev, "failed to find IRQ resource\n");
|
|
err = -ENOENT;
|
|
goto err4;
|
|
}
|
|
|
|
afm->irq = resource->start;
|
|
if (request_irq(afm->irq, afm_irq_handler, IRQF_DISABLED,
|
|
dev_name(&pdev->dev), afm)) {
|
|
dev_err(&pdev->dev, "irq request failed\n");
|
|
err = -EBUSY;
|
|
goto err4;
|
|
}
|
|
|
|
/* Initialise 'controller' structure */
|
|
spin_lock_init(&afm->hwcontrol.lock);
|
|
init_waitqueue_head(&afm->hwcontrol.wq);
|
|
|
|
init_completion(&afm->rbn_completed);
|
|
init_completion(&afm->seq_completed);
|
|
afm->current_csn = -1;
|
|
|
|
/* Stop AFM Controller, in case it's still running! */
|
|
afm_writereg(0x00000000, EMINAND_AFM_SEQUENCE_CONFIG_REG);
|
|
memset(afm->base + EMINAND_AFM_SEQUENCE_REG_1, 0, 32);
|
|
|
|
/* Reset AFM Controller */
|
|
afm_writereg((0x1 << 3), EMINAND_FLEXMODE_CONFIG);
|
|
udelay(1);
|
|
afm_writereg(0x00, EMINAND_FLEXMODE_CONFIG);
|
|
|
|
/* Disable boot_not_flex */
|
|
afm_writereg(0x00000000, EMINAND_BOOTBANK_CONFIG);
|
|
|
|
/* Set Controller to AFM */
|
|
afm_writereg(0x00000002, EMINAND_FLEXMODE_CONFIG);
|
|
|
|
/* Enable Interrupts: individual interrupts enabled when needed! */
|
|
afm_writereg(0x0000007C, EMINAND_INTERRUPT_CLEAR);
|
|
afm_writereg(0x00000001, EMINAND_INTERRUPT_EDGECONFIG);
|
|
afm_writereg(AFM_IRQ, EMINAND_INTERRUPT_ENABLE);
|
|
|
|
/* Success :-) */
|
|
return afm;
|
|
|
|
|
|
/* Error path */
|
|
err4:
|
|
#ifdef CONFIG_STM_NAND_AFM_CACHED
|
|
iounmap(afm->fifo_cached);
|
|
err3:
|
|
#endif
|
|
iounmap(afm->base);
|
|
err2:
|
|
release_mem_region(afm->map_base, afm->map_size);
|
|
err1:
|
|
kfree(afm);
|
|
err0:
|
|
return ERR_PTR(err);
|
|
|
|
}
|
|
|
|
static void __devexit afm_exit_controller(struct platform_device *pdev)
|
|
{
|
|
struct stm_nand_afm_controller *afm = platform_get_drvdata(pdev);
|
|
|
|
free_irq(afm->irq, afm);
|
|
#ifdef CONFIG_STM_NAND_AFM_CACHED
|
|
iounmap(afm->fifo_cached);
|
|
#endif
|
|
iounmap(afm->base);
|
|
release_mem_region(afm->map_base, afm->map_size);
|
|
kfree(afm);
|
|
}
|
|
|
|
#ifdef CONFIG_STM_NAND_AFM_BOOTMODESUPPORT
|
|
static void afm_setup_eccparams(struct mtd_info *mtd)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
struct stm_nand_afm_device *data = chip->priv;
|
|
|
|
/* Take a copy of ECC AFM params, as set up during nand_scan() */
|
|
data->ecc_afm.ecc_ctrl = chip->ecc;
|
|
data->ecc_afm.subpagesize = chip->subpagesize;
|
|
data->ecc_afm.subpage_sft = mtd->subpage_sft;
|
|
|
|
/* Set ECC BOOT params */
|
|
data->ecc_boot.ecc_ctrl = data->ecc_afm.ecc_ctrl;
|
|
data->ecc_boot.ecc_ctrl.write_page = afm_write_page_boot;
|
|
data->ecc_boot.ecc_ctrl.read_page = afm_read_page_boot;
|
|
data->ecc_boot.ecc_ctrl.size = 128;
|
|
data->ecc_boot.ecc_ctrl.bytes = 4;
|
|
|
|
if (mtd->oobsize == 16)
|
|
data->ecc_boot.ecc_ctrl.layout = &boot_oob_16;
|
|
else
|
|
data->ecc_boot.ecc_ctrl.layout = &boot_oob_64;
|
|
|
|
data->ecc_boot.ecc_ctrl.layout->oobavail = 0;
|
|
data->ecc_boot.ecc_ctrl.steps = mtd->writesize /
|
|
data->ecc_boot.ecc_ctrl.size;
|
|
|
|
if (data->ecc_boot.ecc_ctrl.steps * data->ecc_boot.ecc_ctrl.size !=
|
|
mtd->writesize) {
|
|
dev_err(data->dev, "invalid ECC parameters\n");
|
|
BUG();
|
|
}
|
|
data->ecc_boot.ecc_ctrl.total = data->ecc_boot.ecc_ctrl.steps *
|
|
data->ecc_boot.ecc_ctrl.bytes;
|
|
|
|
/* Disable subpage writes */
|
|
data->ecc_boot.subpage_sft = 0;
|
|
data->ecc_boot.subpagesize = mtd->writesize;
|
|
}
|
|
|
|
/* Switch ECC parameters */
|
|
static void afm_set_eccparams(struct mtd_info *mtd, struct ecc_params *params)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
|
|
chip->ecc = params->ecc_ctrl;
|
|
chip->subpagesize = params->subpagesize;
|
|
|
|
mtd->oobavail = params->ecc_ctrl.layout->oobavail;
|
|
mtd->subpage_sft = params->subpage_sft;
|
|
}
|
|
|
|
static void afm_select_eccparams(struct mtd_info *mtd, loff_t offs)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
struct stm_nand_afm_device *data = chip->priv;
|
|
|
|
if (offs >= data->boot_start &&
|
|
offs < data->boot_end) {
|
|
if (chip->ecc.layout != data->ecc_boot.ecc_ctrl.layout) {
|
|
dev_dbg(data->dev, "switching to boot-mode ECC\n");
|
|
afm_set_eccparams(mtd, &data->ecc_boot);
|
|
}
|
|
} else {
|
|
if (chip->ecc.layout != data->ecc_afm.ecc_ctrl.layout) {
|
|
dev_dbg(data->dev, "switching to AFM4 ECC\n");
|
|
afm_set_eccparams(mtd, &data->ecc_afm);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Replicated from ../mtdpart.c: required here to get slave MTD offsets and
|
|
* determine which ECC mode to use.
|
|
*/
|
|
struct mtd_part {
|
|
struct mtd_info mtd;
|
|
struct mtd_info *master;
|
|
u_int32_t offset;
|
|
int index;
|
|
struct list_head list;
|
|
int registered;
|
|
};
|
|
|
|
#define PART(x) ((struct mtd_part *)(x))
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
* Internal helper-functions for MTD Interface callbacks
|
|
*/
|
|
static uint8_t *afm_transfer_oob(struct nand_chip *chip, uint8_t *oob,
|
|
struct mtd_oob_ops *ops, size_t len)
|
|
{
|
|
switch (ops->mode) {
|
|
|
|
case MTD_OOB_PLACE:
|
|
case MTD_OOB_RAW:
|
|
memcpy(oob, chip->oob_poi + ops->ooboffs, len);
|
|
return oob + len;
|
|
|
|
case MTD_OOB_AUTO: {
|
|
struct nand_oobfree *free = chip->ecc.layout->oobfree;
|
|
uint32_t boffs = 0, roffs = ops->ooboffs;
|
|
size_t bytes = 0;
|
|
|
|
for (; free->length && len; free++, len -= bytes) {
|
|
/* Read request not from offset 0 ? */
|
|
if (unlikely(roffs)) {
|
|
if (roffs >= free->length) {
|
|
roffs -= free->length;
|
|
continue;
|
|
}
|
|
boffs = free->offset + roffs;
|
|
bytes = min_t(size_t, len,
|
|
(free->length - roffs));
|
|
roffs = 0;
|
|
} else {
|
|
bytes = min_t(size_t, len, free->length);
|
|
boffs = free->offset;
|
|
}
|
|
memcpy(oob, chip->oob_poi + boffs, bytes);
|
|
oob += bytes;
|
|
}
|
|
|
|
return oob;
|
|
}
|
|
default:
|
|
BUG();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int afm_do_read_oob(struct mtd_info *mtd, loff_t from,
|
|
struct mtd_oob_ops *ops)
|
|
{
|
|
int page, realpage, chipnr, sndcmd = 1;
|
|
struct nand_chip *chip = mtd->priv;
|
|
int blkcheck = (1 << (chip->phys_erase_shift - chip->page_shift)) - 1;
|
|
int readlen = ops->ooblen;
|
|
int len;
|
|
uint8_t *buf = ops->oobbuf;
|
|
|
|
DEBUG(MTD_DEBUG_LEVEL3, "afm_do_read_oob: from = 0x%08Lx, len = %i\n",
|
|
(unsigned long long)from, readlen);
|
|
|
|
|
|
if (ops->mode == MTD_OOB_AUTO)
|
|
len = chip->ecc.layout->oobavail;
|
|
else
|
|
len = mtd->oobsize;
|
|
|
|
if (unlikely(ops->ooboffs >= len)) {
|
|
DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: "
|
|
"Attempt to start read outside oob\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Do not allow reads past end of device */
|
|
if (unlikely(from >= mtd->size ||
|
|
ops->ooboffs + readlen >
|
|
((mtd->size >> chip->page_shift) -
|
|
(from >> chip->page_shift)) * len)) {
|
|
DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: "
|
|
"Attempt read beyond end of device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
chipnr = (int)(from >> chip->chip_shift);
|
|
chip->select_chip(mtd, chipnr);
|
|
|
|
/* Shift to get page */
|
|
realpage = (int)(from >> chip->page_shift);
|
|
page = realpage & chip->pagemask;
|
|
|
|
while (1) {
|
|
sndcmd = chip->ecc.read_oob(mtd, chip, page, sndcmd);
|
|
|
|
len = min(len, readlen);
|
|
buf = afm_transfer_oob(chip, buf, ops, len);
|
|
|
|
if (!(chip->options & NAND_NO_READRDY)) {
|
|
/*
|
|
* Apply delay or wait for ready/busy pin. Do this
|
|
* before the AUTOINCR check, so no problems arise if a
|
|
* chip which does auto increment is marked as
|
|
* NOAUTOINCR by the board driver.
|
|
*/
|
|
if (!chip->dev_ready)
|
|
udelay(chip->chip_delay);
|
|
else
|
|
nand_wait_ready(mtd);
|
|
}
|
|
|
|
readlen -= len;
|
|
if (!readlen)
|
|
break;
|
|
|
|
/* Increment page address */
|
|
realpage++;
|
|
|
|
page = realpage & chip->pagemask;
|
|
/* Check, if we cross a chip boundary */
|
|
if (!page) {
|
|
chipnr++;
|
|
chip->select_chip(mtd, -1);
|
|
chip->select_chip(mtd, chipnr);
|
|
}
|
|
|
|
/* Check, if the chip supports auto page increment
|
|
* or if we have hit a block boundary.
|
|
*/
|
|
if (!NAND_CANAUTOINCR(chip) || !(page & blkcheck))
|
|
sndcmd = 1;
|
|
}
|
|
|
|
ops->oobretlen = ops->ooblen;
|
|
return 0;
|
|
}
|
|
|
|
static int afm_do_read_ops(struct mtd_info *mtd, loff_t from,
|
|
struct mtd_oob_ops *ops)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
int chipnr, page, realpage, col, bytes, aligned;
|
|
struct nand_chip *chip = mtd->priv;
|
|
struct mtd_ecc_stats stats;
|
|
int blkcheck = (1 << (chip->phys_erase_shift - chip->page_shift)) - 1;
|
|
int sndcmd = 1;
|
|
int ret = 0;
|
|
uint32_t readlen = ops->len;
|
|
uint32_t oobreadlen = ops->ooblen;
|
|
uint8_t *bufpoi, *oob, *buf;
|
|
|
|
DEBUG(MTD_DEBUG_LEVEL3, "afm_do_read_ops: from = 0x%08Lx, len = %i\n",
|
|
(unsigned long long)from, readlen);
|
|
|
|
stats = mtd->ecc_stats;
|
|
|
|
chipnr = (int)(from >> chip->chip_shift);
|
|
chip->select_chip(mtd, chipnr);
|
|
|
|
realpage = (int)(from >> chip->page_shift); /* actual page no. */
|
|
page = realpage & chip->pagemask; /* within-chip page */
|
|
|
|
col = (int)(from & (mtd->writesize - 1)); /* col within page */
|
|
|
|
buf = ops->datbuf; /* pointer to data buf */
|
|
oob = ops->oobbuf; /* pointer to oob buf */
|
|
|
|
while (1) {
|
|
/* #bytes in next transfer */
|
|
bytes = min(mtd->writesize - col, readlen);
|
|
/* tranfer aligned to page? */
|
|
aligned = (bytes == mtd->writesize);
|
|
|
|
/* Add test for for alignment */
|
|
if ((uint32_t)buf & 0x3)
|
|
aligned = 0;
|
|
|
|
/* Is the current page in the buffer ? */
|
|
if (realpage != chip->pagebuf || oob) {
|
|
bufpoi = aligned ? buf : chip->buffers->databuf;
|
|
|
|
afm->page = page;
|
|
afm->col = 0;
|
|
/* Now read the page into the buffer, without ECC if
|
|
* MTD_OOB_RAW */
|
|
if (unlikely(ops->mode == MTD_OOB_RAW))
|
|
ret = chip->ecc.read_page_raw(mtd, chip,
|
|
bufpoi, page);
|
|
else
|
|
ret = chip->ecc.read_page(mtd, chip,
|
|
bufpoi, page);
|
|
if (ret < 0)
|
|
break;
|
|
|
|
/* Transfer not aligned data */
|
|
if (!aligned) {
|
|
chip->pagebuf = realpage;
|
|
memcpy(buf, chip->buffers->databuf + col,
|
|
bytes);
|
|
}
|
|
|
|
buf += bytes;
|
|
|
|
/* Done page data, now look at OOB... */
|
|
if (unlikely(oob)) {
|
|
/* Raw mode does data:oob:data:oob */
|
|
if (ops->mode != MTD_OOB_RAW) {
|
|
int toread = min(oobreadlen,
|
|
chip->ecc.layout->oobavail);
|
|
if (toread) {
|
|
oob = afm_transfer_oob(chip,
|
|
oob,
|
|
ops,
|
|
toread);
|
|
oobreadlen -= toread;
|
|
}
|
|
} else
|
|
buf = afm_transfer_oob(chip,
|
|
buf, ops,
|
|
mtd->oobsize);
|
|
}
|
|
} else {
|
|
memcpy(buf, chip->buffers->databuf + col, bytes);
|
|
buf += bytes;
|
|
}
|
|
|
|
readlen -= bytes;
|
|
|
|
if (!readlen)
|
|
break;
|
|
|
|
/* For subsequent reads align to page boundary. */
|
|
col = 0;
|
|
/* Increment page address */
|
|
realpage++;
|
|
|
|
page = realpage & chip->pagemask;
|
|
/* Check, if we cross a chip boundary */
|
|
if (!page) {
|
|
chipnr++;
|
|
chip->select_chip(mtd, -1);
|
|
chip->select_chip(mtd, chipnr);
|
|
}
|
|
|
|
/* Check, if the chip supports auto page increment
|
|
* or if we have hit a block boundary.
|
|
*/
|
|
if (!NAND_CANAUTOINCR(chip) || !(page & blkcheck))
|
|
sndcmd = 1;
|
|
}
|
|
|
|
ops->retlen = ops->len - (size_t) readlen;
|
|
if (oob)
|
|
ops->oobretlen = ops->ooblen - oobreadlen;
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (mtd->ecc_stats.failed - stats.failed)
|
|
return -EBADMSG;
|
|
|
|
return mtd->ecc_stats.corrected - stats.corrected ? -EUCLEAN : 0;
|
|
|
|
}
|
|
|
|
static int afm_check_wp(struct mtd_info *mtd)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
uint32_t status;
|
|
|
|
/* Switch to Flex Mode */
|
|
afm_writereg(0x00000001, EMINAND_FLEXMODE_CONFIG);
|
|
|
|
/* Read status register */
|
|
afm_writereg(FLX_CMD(NAND_CMD_STATUS), EMINAND_FLEX_COMMAND_REG);
|
|
status = afm_readreg(EMINAND_FLEX_DATA);
|
|
|
|
/* Switch back to AFM */
|
|
afm_writereg(0x00000002, EMINAND_FLEXMODE_CONFIG);
|
|
|
|
return (status & NAND_STATUS_WP) ? 0 : 1;
|
|
}
|
|
|
|
static uint8_t *afm_fill_oob(struct nand_chip *chip, uint8_t *oob,
|
|
struct mtd_oob_ops *ops)
|
|
{
|
|
size_t len = ops->ooblen;
|
|
|
|
switch (ops->mode) {
|
|
|
|
case MTD_OOB_PLACE:
|
|
case MTD_OOB_RAW:
|
|
memcpy(chip->oob_poi + ops->ooboffs, oob, len);
|
|
return oob + len;
|
|
|
|
case MTD_OOB_AUTO: {
|
|
struct nand_oobfree *free = chip->ecc.layout->oobfree;
|
|
uint32_t boffs = 0, woffs = ops->ooboffs;
|
|
size_t bytes = 0;
|
|
|
|
for (; free->length && len; free++, len -= bytes) {
|
|
/* Write request not from offset 0 ? */
|
|
if (unlikely(woffs)) {
|
|
if (woffs >= free->length) {
|
|
woffs -= free->length;
|
|
continue;
|
|
}
|
|
boffs = free->offset + woffs;
|
|
bytes = min_t(size_t, len,
|
|
(free->length - woffs));
|
|
woffs = 0;
|
|
} else {
|
|
bytes = min_t(size_t, len, free->length);
|
|
boffs = free->offset;
|
|
}
|
|
memcpy(chip->oob_poi + boffs, oob, bytes);
|
|
oob += bytes;
|
|
}
|
|
return oob;
|
|
}
|
|
default:
|
|
BUG();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int afm_do_write_oob(struct mtd_info *mtd, loff_t to,
|
|
struct mtd_oob_ops *ops)
|
|
{
|
|
int chipnr, page, status, len;
|
|
struct nand_chip *chip = mtd->priv;
|
|
|
|
DEBUG(MTD_DEBUG_LEVEL3, "nand_write_oob: to = 0x%08x, len = %i\n",
|
|
(unsigned int)to, (int)ops->ooblen);
|
|
|
|
if (ops->mode == MTD_OOB_AUTO)
|
|
len = chip->ecc.layout->oobavail;
|
|
else
|
|
len = mtd->oobsize;
|
|
|
|
/* Do not allow write past end of page */
|
|
if ((ops->ooboffs + ops->ooblen) > len) {
|
|
DEBUG(MTD_DEBUG_LEVEL0, "nand_write_oob: "
|
|
"Attempt to write past end of page\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (unlikely(ops->ooboffs >= len)) {
|
|
DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: "
|
|
"Attempt to start write outside oob\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Do not allow reads past end of device */
|
|
if (unlikely(to >= mtd->size ||
|
|
ops->ooboffs + ops->ooblen >
|
|
((mtd->size >> chip->page_shift) -
|
|
(to >> chip->page_shift)) * len)) {
|
|
DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: "
|
|
"Attempt write beyond end of device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
chipnr = (int)(to >> chip->chip_shift);
|
|
chip->select_chip(mtd, chipnr);
|
|
|
|
/* Shift to get page */
|
|
page = (int)(to >> chip->page_shift);
|
|
|
|
/* Check, if it is write protected */
|
|
if (afm_check_wp(mtd))
|
|
return -EROFS;
|
|
|
|
/* Invalidate the page cache, if we write to the cached page */
|
|
if (page == chip->pagebuf)
|
|
chip->pagebuf = -1;
|
|
|
|
memset(chip->oob_poi, 0xff, mtd->oobsize);
|
|
afm_fill_oob(chip, ops->oobbuf, ops);
|
|
status = chip->ecc.write_oob(mtd, chip, page & chip->pagemask);
|
|
memset(chip->oob_poi, 0xff, mtd->oobsize);
|
|
|
|
if (status)
|
|
return status;
|
|
|
|
ops->oobretlen = ops->ooblen;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int afm_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
|
const uint8_t *buf, int page, int cached, int raw)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
|
|
afm->page = page;
|
|
afm->col = 0;
|
|
|
|
if (unlikely(raw))
|
|
chip->ecc.write_page_raw(mtd, chip, buf);
|
|
else
|
|
chip->ecc.write_page(mtd, chip, buf);
|
|
|
|
/* Check status information */
|
|
if (afm->status & NAND_STATUS_FAIL)
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define NOTALIGNED(x) ((x & (chip->subpagesize - 1)) != 0)
|
|
static int afm_do_write_ops(struct mtd_info *mtd, loff_t to,
|
|
struct mtd_oob_ops *ops)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
|
|
int chipnr, realpage, page, blockmask, column;
|
|
struct nand_chip *chip = mtd->priv;
|
|
uint32_t writelen = ops->len;
|
|
uint8_t *oob = ops->oobbuf;
|
|
uint8_t *buf = ops->datbuf;
|
|
int ret, subpage;
|
|
|
|
ops->retlen = 0;
|
|
if (!writelen)
|
|
return 0;
|
|
|
|
/* reject writes, which are not page aligned */
|
|
if (NOTALIGNED(to) || NOTALIGNED(ops->len)) {
|
|
dev_err(afm->dev, "attempt to write not page aligned data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
column = to & (mtd->writesize - 1);
|
|
subpage = column || (writelen & (mtd->writesize - 1));
|
|
|
|
if (subpage && oob)
|
|
return -EINVAL;
|
|
|
|
chipnr = (int)(to >> chip->chip_shift);
|
|
chip->select_chip(mtd, chipnr);
|
|
|
|
/* Check, if it is write protected */
|
|
if (afm_check_wp(mtd))
|
|
return -EIO;
|
|
|
|
realpage = (int)(to >> chip->page_shift);
|
|
page = realpage & chip->pagemask;
|
|
blockmask = (1 << (chip->phys_erase_shift - chip->page_shift)) - 1;
|
|
|
|
/* Invalidate the page cache, when we write to the cached page */
|
|
if (to <= (chip->pagebuf << chip->page_shift) &&
|
|
(chip->pagebuf << chip->page_shift) < (to + ops->len))
|
|
chip->pagebuf = -1;
|
|
|
|
/* If we're not given explicit OOB data, let it be 0xFF */
|
|
if (likely(!oob))
|
|
memset(chip->oob_poi, 0xff, mtd->oobsize);
|
|
|
|
while (1) {
|
|
int bytes = mtd->writesize;
|
|
int cached = writelen > bytes && page != blockmask;
|
|
uint8_t *wbuf = buf;
|
|
|
|
/* Partial page write ? */
|
|
/* TODO: change alignment constraints for DMA transfer! */
|
|
if (unlikely(column || writelen < (mtd->writesize - 1) ||
|
|
((uint32_t)wbuf & 31))) {
|
|
cached = 0;
|
|
bytes = min_t(int, bytes - column, (int) writelen);
|
|
chip->pagebuf = -1;
|
|
memset(chip->buffers->databuf, 0xff, mtd->writesize);
|
|
memcpy(&chip->buffers->databuf[column], buf, bytes);
|
|
wbuf = chip->buffers->databuf;
|
|
}
|
|
|
|
if (unlikely(oob))
|
|
oob = afm_fill_oob(chip, oob, ops);
|
|
|
|
ret = chip->write_page(mtd, chip, wbuf, page, cached,
|
|
(ops->mode == MTD_OOB_RAW));
|
|
if (ret)
|
|
break;
|
|
|
|
writelen -= bytes;
|
|
if (!writelen)
|
|
break;
|
|
|
|
column = 0;
|
|
buf += bytes;
|
|
realpage++;
|
|
|
|
page = realpage & chip->pagemask;
|
|
/* Check, if we cross a chip boundary */
|
|
if (!page) {
|
|
chipnr++;
|
|
chip->select_chip(mtd, -1);
|
|
chip->select_chip(mtd, chipnr);
|
|
}
|
|
}
|
|
|
|
ops->retlen = ops->len - writelen;
|
|
if (unlikely(oob))
|
|
ops->oobretlen = ops->ooblen;
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* MTD Interface callbacks (replacing equivalent functions in nand_base.c)
|
|
*/
|
|
|
|
|
|
/* MTD Interface - erase block(s) */
|
|
static int afm_erase(struct mtd_info *mtd, struct erase_info *instr)
|
|
{
|
|
return nand_erase_nand(mtd, instr, 0);
|
|
}
|
|
|
|
/* MTD Interface - Check if block at offset is bad */
|
|
static int afm_block_isbad(struct mtd_info *mtd, loff_t offs)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
|
|
/* Check for invalid offset */
|
|
if (offs > mtd->size)
|
|
return -EINVAL;
|
|
|
|
/* We should always have a memory-resident BBT */
|
|
BUG_ON(!chip->bbt);
|
|
|
|
return nand_isbad_bbt(mtd, offs, 0);
|
|
}
|
|
|
|
/* MTD Interface - Mark block at the given offset as bad */
|
|
static int afm_block_markbad(struct mtd_info *mtd, loff_t offs)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
uint8_t buf[16];
|
|
int block, ret;
|
|
|
|
/* Is is already marked bad? */
|
|
ret = afm_block_isbad(mtd, offs);
|
|
if (ret)
|
|
return (ret > 0) ? 0 : ret;
|
|
|
|
/* Update memory-resident BBT */
|
|
BUG_ON(!chip->bbt);
|
|
block = (int)(offs >> chip->bbt_erase_shift);
|
|
chip->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1);
|
|
|
|
/* Update non-volatile marker... */
|
|
if (chip->options & NAND_USE_FLASH_BBT) {
|
|
/* Update flash-resident BBT */
|
|
ret = nand_update_bbt(mtd, offs);
|
|
} else {
|
|
/*
|
|
* Write the bad-block marker to OOB. We also need to wipe the
|
|
* first 'AFM' marker (just in case it survived the preceding
|
|
* failed ERASE) to prevent subsequent on-boot scans from
|
|
* recognising an AFM block, instead of a marked-bad block.
|
|
*/
|
|
memset(buf, 0, 16);
|
|
nand_get_device(chip, mtd, FL_WRITING);
|
|
offs += mtd->oobsize;
|
|
chip->ops.mode = MTD_OOB_PLACE;
|
|
chip->ops.len = 16;
|
|
chip->ops.ooblen = 16;
|
|
chip->ops.datbuf = NULL;
|
|
chip->ops.oobbuf = buf;
|
|
chip->ops.ooboffs = chip->badblockpos & ~0x01;
|
|
|
|
ret = afm_do_write_oob(mtd, offs, &chip->ops);
|
|
nand_release_device(mtd);
|
|
}
|
|
if (ret == 0)
|
|
mtd->ecc_stats.badblocks++;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* MTD Interface - Read chunk of page data */
|
|
static int afm_read(struct mtd_info *mtd, loff_t from, size_t len,
|
|
size_t *retlen, uint8_t *buf)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
int ret;
|
|
|
|
/* Do not allow reads past end of device */
|
|
if ((from + len) > mtd->size)
|
|
return -EINVAL;
|
|
if (!len)
|
|
return 0;
|
|
|
|
nand_get_device(chip, mtd, FL_READING);
|
|
afm_select_eccparams(mtd, from);
|
|
|
|
chip->ops.len = len;
|
|
chip->ops.datbuf = buf;
|
|
chip->ops.oobbuf = NULL;
|
|
|
|
ret = afm_do_read_ops(mtd, from, &chip->ops);
|
|
|
|
*retlen = chip->ops.retlen;
|
|
|
|
nand_release_device(mtd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* MTD Interface - read page data and/or OOB */
|
|
static int afm_read_oob(struct mtd_info *mtd, loff_t from,
|
|
struct mtd_oob_ops *ops)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
int ret = -ENOTSUPP;
|
|
|
|
/* Do not allow reads past end of device */
|
|
if (ops->datbuf && (from + ops->len) > mtd->size) {
|
|
DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: "
|
|
"Attempt read beyond end of device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
nand_get_device(chip, mtd, FL_READING);
|
|
afm_select_eccparams(mtd, from);
|
|
|
|
switch (ops->mode) {
|
|
case MTD_OOB_PLACE:
|
|
case MTD_OOB_AUTO:
|
|
case MTD_OOB_RAW:
|
|
break;
|
|
|
|
default:
|
|
goto out;
|
|
}
|
|
|
|
if (!ops->datbuf)
|
|
ret = afm_do_read_oob(mtd, from, ops);
|
|
else
|
|
ret = afm_do_read_ops(mtd, from, ops);
|
|
|
|
out:
|
|
nand_release_device(mtd);
|
|
return ret;
|
|
}
|
|
|
|
/* MTD Interface - write page data */
|
|
static int afm_write(struct mtd_info *mtd, loff_t to, size_t len,
|
|
size_t *retlen, const uint8_t *buf)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
int ret;
|
|
|
|
|
|
/* Do not allow reads past end of device */
|
|
if ((to + len) > mtd->size)
|
|
return -EINVAL;
|
|
if (!len)
|
|
return 0;
|
|
|
|
nand_get_device(chip, mtd, FL_WRITING);
|
|
afm_select_eccparams(mtd, to);
|
|
|
|
chip->ops.len = len;
|
|
chip->ops.datbuf = (uint8_t *)buf;
|
|
chip->ops.oobbuf = NULL;
|
|
|
|
ret = afm_do_write_ops(mtd, to, &chip->ops);
|
|
|
|
*retlen = chip->ops.retlen;
|
|
|
|
nand_release_device(mtd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* MTD Interface - write page data and/or OOB */
|
|
static int afm_write_oob(struct mtd_info *mtd, loff_t to,
|
|
struct mtd_oob_ops *ops)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
int ret = -ENOTSUPP;
|
|
|
|
/* Do not allow writes past end of device */
|
|
if (ops->datbuf && (to + ops->len) > mtd->size) {
|
|
DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: "
|
|
"Attempt read beyond end of device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
nand_get_device(chip, mtd, FL_WRITING);
|
|
afm_select_eccparams(mtd, to);
|
|
|
|
switch (ops->mode) {
|
|
case MTD_OOB_PLACE:
|
|
case MTD_OOB_AUTO:
|
|
case MTD_OOB_RAW:
|
|
break;
|
|
|
|
default:
|
|
goto out;
|
|
}
|
|
|
|
if (!ops->datbuf)
|
|
ret = afm_do_write_oob(mtd, to, ops);
|
|
else
|
|
ret = afm_do_write_ops(mtd, to, ops);
|
|
|
|
out:
|
|
nand_release_device(mtd);
|
|
return ret;
|
|
}
|
|
|
|
/* MTD Interface - Sync (just wait for chip ready, then release) */
|
|
static void afm_sync(struct mtd_info *mtd)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
|
|
DEBUG(MTD_DEBUG_LEVEL3, "nand_sync: called\n");
|
|
|
|
/* Grab the lock and see if the device is available */
|
|
nand_get_device(chip, mtd, FL_SYNCING);
|
|
/* Release it and go back */
|
|
nand_release_device(mtd);
|
|
}
|
|
|
|
/* MTD Interface - Suspend (wait for chip ready, then suspend) */
|
|
static int afm_suspend(struct mtd_info *mtd)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
|
|
return nand_get_device(chip, mtd, FL_PM_SUSPENDED);
|
|
}
|
|
|
|
/* MTD Interface - Resume (release device) */
|
|
static void afm_resume(struct mtd_info *mtd)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
|
|
if (chip->state == FL_PM_SUSPENDED)
|
|
nand_release_device(mtd);
|
|
else
|
|
printk(KERN_ERR "afm_resume() called for a chip which is not "
|
|
"in suspended state\n");
|
|
}
|
|
|
|
/*
|
|
* AFM data transfer routines
|
|
*/
|
|
|
|
#ifdef CONFIG_STM_NAND_AFM_CACHED
|
|
static void afm_read_buf_cached(struct mtd_info *mtd, uint8_t *buf, int len)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
|
|
unsigned long irq_flags;
|
|
|
|
int lenaligned = len & ~(L1_CACHE_BYTES-1);
|
|
int lenleft = len & (L1_CACHE_BYTES-1);
|
|
|
|
while (lenaligned > 0) {
|
|
spin_lock_irqsave(&(afm->lock), irq_flags);
|
|
invalidate_ioremap_region(afm->fifo_phys, afm->fifo_cached,
|
|
0, L1_CACHE_BYTES);
|
|
memcpy_fromio(buf, afm->fifo_cached, L1_CACHE_BYTES);
|
|
spin_unlock_irqrestore(&(afm->lock), irq_flags);
|
|
|
|
buf += L1_CACHE_BYTES;
|
|
lenaligned -= L1_CACHE_BYTES;
|
|
}
|
|
|
|
#ifdef CONFIG_STM_L2_CACHE
|
|
/* If L2 cache is enabled, we must ensure the cacheline is evicted prior
|
|
* to non-cached accesses..
|
|
*/
|
|
invalidate_ioremap_region(afm->fifo_phys, afm->fifo_cached,
|
|
0, L1_CACHE_BYTES);
|
|
#endif
|
|
/* Mop up any remaining bytes */
|
|
while (lenleft > 0) {
|
|
*buf++ = readb(afm->base + EMINAND_AFM_DATA_FIFO);
|
|
lenleft--;
|
|
}
|
|
}
|
|
#else
|
|
/* Default read buffer command */
|
|
static void afm_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
|
|
readsl(afm->base + EMINAND_AFM_DATA_FIFO, buf, len/4);
|
|
}
|
|
#endif
|
|
|
|
/* Default write buffer command */
|
|
static void afm_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
|
|
writesl(afm->base + EMINAND_AFM_DATA_FIFO, buf, len/4);
|
|
}
|
|
|
|
|
|
/*
|
|
* AFM low-level chip operations
|
|
*/
|
|
|
|
/* AFM: Block Erase */
|
|
static void afm_erase_cmd(struct mtd_info *mtd, int page)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
|
|
struct afm_prog *prog;
|
|
struct nand_chip *chip = mtd->priv;
|
|
uint32_t reg;
|
|
int ret;
|
|
|
|
afm->status = 0;
|
|
|
|
/* Initialise Seq interrupts */
|
|
INIT_COMPLETION(afm->seq_completed);
|
|
afm_enable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
|
|
/* Select AFM program */
|
|
if ((mtd->writesize == 512 && chip->chipsize > (32 << 20)) ||
|
|
(mtd->writesize == 2048 && chip->chipsize > (128 << 20)))
|
|
/* For 'large' chips, we need an extra address cycle */
|
|
prog = &afm_prog_erase_ext;
|
|
else
|
|
prog = &afm_prog_erase;
|
|
|
|
/* Set page address */
|
|
prog->extra_reg = page;
|
|
|
|
/* Copy program to controller, and start sequence */
|
|
memcpy_toio(afm->base + EMINAND_AFM_SEQUENCE_REG_1, prog, 32);
|
|
|
|
/* Wait for sequence to finish */
|
|
ret = wait_for_completion_timeout(&afm->seq_completed, 2*HZ);
|
|
if (!ret) {
|
|
dev_warn(afm->dev, "sequence timeout, force exit!\n");
|
|
afm_writereg(0x00000000, EMINAND_AFM_SEQUENCE_CONFIG_REG);
|
|
afm->status = NAND_STATUS_FAIL;
|
|
afm_disable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
return;
|
|
}
|
|
|
|
/* Get status */
|
|
reg = afm_readreg(EMINAND_AFM_SEQUENCE_STATUS_REG);
|
|
afm->status = NAND_STATUS_READY | ((reg & 0x60) >> 5);
|
|
|
|
/* Disable interrupts */
|
|
afm_disable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
}
|
|
|
|
static int afm_correct_ecc(struct mtd_info *mtd, unsigned char *buf,
|
|
unsigned char *read_ecc, unsigned char *calc_ecc)
|
|
{
|
|
/* No error */
|
|
if ((read_ecc[0] ^ calc_ecc[0]) == 0 &&
|
|
(read_ecc[1] ^ calc_ecc[1]) == 0 &&
|
|
(read_ecc[2] ^ calc_ecc[2]) == 0)
|
|
return 0;
|
|
|
|
/* Special test for freshly erased page */
|
|
if (read_ecc[0] == 0xff && calc_ecc[0] == 0x00 &&
|
|
read_ecc[1] == 0xff && calc_ecc[1] == 0x00 &&
|
|
read_ecc[2] == 0xff && calc_ecc[2] == 0x00)
|
|
return 0;
|
|
|
|
/* Use nand_ecc.c:nand_correct_data() function */
|
|
return nand_correct_data(mtd, buf, read_ecc, calc_ecc);
|
|
|
|
}
|
|
|
|
/* AFM: Read Page and OOB Data with ECC */
|
|
static int afm_read_page_ecc(struct mtd_info *mtd, struct nand_chip *chip,
|
|
uint8_t *buf, int page)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
int i, j;
|
|
int eccsize = chip->ecc.size;
|
|
int eccbytes = chip->ecc.bytes;
|
|
int eccsteps = chip->ecc.steps;
|
|
|
|
uint8_t *p = buf;
|
|
uint8_t *ecc_code = chip->buffers->ecccode;
|
|
uint8_t *ecc_calc = chip->buffers->ecccalc;
|
|
uint32_t *eccpos = chip->ecc.layout->eccpos;
|
|
uint8_t *e1, *e2, t;
|
|
|
|
/* Read raw page+oob data */
|
|
chip->ecc.read_page_raw(mtd, chip, buf, page);
|
|
|
|
/* Recover ECC from OOB: H/W ECC + S/W LP16,17 from device */
|
|
e1 = ecc_code;
|
|
for (i = 0; i < chip->ecc.total; i++)
|
|
e1[i] = chip->oob_poi[eccpos[i]];
|
|
|
|
/* Generate linux-style 'ecc_code' and 'ecc_calc' */
|
|
e1 = ecc_code;
|
|
e2 = ecc_calc;
|
|
p = buf;
|
|
for (i = 0; i < eccsteps; i++) {
|
|
uint32_t ecc_afm;
|
|
uint8_t lp1617;
|
|
uint32_t chkcode_offs;
|
|
|
|
/* Get H/W ECC */
|
|
chkcode_offs = (eccsteps == 1) ? (3 << 2) : (i << 2);
|
|
ecc_afm = afm_readreg(EMINAND_AFM_ECC_CHECKCODE_REG_0 +
|
|
chkcode_offs);
|
|
|
|
/* Extract ecc_code and ecc_calc */
|
|
for (j = 0; j < 3; j++) {
|
|
e2[j] = (unsigned char)(ecc_afm & 0xff);
|
|
ecc_afm >>= 8;
|
|
}
|
|
|
|
/* Swap e[0] and e[1] */
|
|
t = e1[0]; e1[0] = e1[1]; e1[1] = t;
|
|
t = e2[0]; e2[0] = e2[1]; e2[1] = t;
|
|
|
|
/* Generate S/W LP1617 ecc_calc */
|
|
lp1617 = stm_afm_lp1617(p);
|
|
|
|
/* Copy S/W LP1617 bits to standard location */
|
|
e1[2] = (e1[2] & 0xfc) | (e1[6] & 0x3);
|
|
e2[2] = (e2[2] & 0xfc) | (lp1617 & 0x3);
|
|
|
|
/* Move on to next ECC block */
|
|
e1 += eccbytes;
|
|
e2 += eccbytes;
|
|
p += eccsize;
|
|
}
|
|
|
|
/* Detect/Correct ECC errors */
|
|
p = buf;
|
|
for (i = 0 ; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
|
|
int stat;
|
|
|
|
stat = afm_correct_ecc(mtd, p, &ecc_code[i], &ecc_calc[i]);
|
|
|
|
if (stat == -1) {
|
|
mtd->ecc_stats.failed++;
|
|
printk(KERN_CONT "step %d, page %d]\n",
|
|
i / eccbytes, page);
|
|
} else if (stat == 1) {
|
|
printk(KERN_CONT "step %d, page = %d]\n",
|
|
i / eccbytes, page);
|
|
mtd->ecc_stats.corrected++;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* AFM: Read Raw Page and OOB Data */
|
|
static int afm_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
|
uint8_t *buf, int page)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
|
|
struct afm_prog *prog;
|
|
uint32_t reg;
|
|
int ret;
|
|
|
|
/* Select AFM program */
|
|
prog = (mtd->writesize == 512) ?
|
|
&afm_prog_read_raw_sp :
|
|
&afm_prog_read_raw_lp;
|
|
|
|
/* Initialise RBn interrupt */
|
|
INIT_COMPLETION(afm->rbn_completed);
|
|
afm_enable_interrupts(afm, AFM_IRQ_RBN);
|
|
|
|
if (mtd->writesize == 512) {
|
|
/* SmallPage: Reset ECC counters */
|
|
reg = afm_readreg(EMINAND_FLEXMODE_CONFIG);
|
|
reg |= (1 << 6);
|
|
afm_writereg(reg, EMINAND_FLEXMODE_CONFIG);
|
|
}
|
|
|
|
/* Set page address */
|
|
prog->addr_reg = afm->page << 8;
|
|
|
|
/* Copy program to controller, and start sequence */
|
|
memcpy_toio(afm->base + EMINAND_AFM_SEQUENCE_REG_1, prog, 32);
|
|
|
|
/* Wait for data to become available */
|
|
ret = wait_for_completion_timeout(&afm->rbn_completed, HZ/2);
|
|
if (!ret) {
|
|
dev_err(afm->dev, "RBn timeout, force exit\n");
|
|
afm_writereg(0x00000000, EMINAND_AFM_SEQUENCE_CONFIG_REG);
|
|
afm_disable_interrupts(afm, AFM_IRQ_RBN);
|
|
return 1;
|
|
}
|
|
/* Read page data and OOB (SmallPage: +48 bytes dummy data) */
|
|
chip->read_buf(mtd, buf, mtd->writesize);
|
|
chip->read_buf(mtd, chip->oob_poi, 64);
|
|
|
|
/* Disable RBn interrupts */
|
|
afm_disable_interrupts(afm, AFM_IRQ_RBN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* AFM: Read OOB data */
|
|
static int afm_read_oob_chip(struct mtd_info *mtd, struct nand_chip *chip,
|
|
int page, int sndcmd)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
struct afm_prog *prog;
|
|
uint32_t reg;
|
|
int ret;
|
|
|
|
/* Select AFM program */
|
|
prog = (mtd->writesize == 512) ?
|
|
&afm_prog_read_oob_sp :
|
|
&afm_prog_read_oob_lp;
|
|
|
|
/* Initialise RBn interrupt */
|
|
INIT_COMPLETION(afm->rbn_completed);
|
|
afm_enable_interrupts(afm, AFM_IRQ_RBN);
|
|
|
|
if (mtd->writesize == 512) {
|
|
/* SmallPage: Reset ECC counters and set page address */
|
|
reg = afm_readreg(EMINAND_FLEXMODE_CONFIG);
|
|
reg |= (1 << 6);
|
|
afm_writereg(reg, EMINAND_FLEXMODE_CONFIG);
|
|
|
|
prog->addr_reg = page << 8;
|
|
} else {
|
|
/* LargePage: Set page address */
|
|
prog->addr_reg = (page << 8) | (2048 >> 8);
|
|
}
|
|
|
|
/* Copy program to controller, and start sequence */
|
|
memcpy_toio(afm->base + EMINAND_AFM_SEQUENCE_REG_1, prog, 32);
|
|
|
|
/* Wait for data to become available */
|
|
ret = wait_for_completion_timeout(&afm->rbn_completed, HZ/2);
|
|
if (!ret) {
|
|
dev_err(afm->dev, "RBn timeout, force exit\n");
|
|
afm_writereg(0x00000000, EMINAND_AFM_SEQUENCE_CONFIG_REG);
|
|
afm_disable_interrupts(afm, AFM_IRQ_RBN);
|
|
return 1;
|
|
}
|
|
|
|
/* Read OOB data to chip->oob_poi buffer */
|
|
chip->read_buf(mtd, chip->oob_poi, 64);
|
|
|
|
/* Disable RBn Interrupts */
|
|
afm_disable_interrupts(afm, AFM_IRQ_RBN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* AFM: Write Page and OOB Data, with ECC support [SmallPage] */
|
|
static void afm_write_page_ecc_sp(struct mtd_info *mtd,
|
|
struct nand_chip *chip,
|
|
const uint8_t *buf)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
struct afm_prog *prog = &afm_prog_write_ecc_sp_a;
|
|
uint32_t ecc_afm;
|
|
uint32_t reg;
|
|
int ret;
|
|
|
|
afm->status = 0;
|
|
|
|
/* 1. Write page data to chip's page buffer */
|
|
|
|
/* Initialise Seq interrupt */
|
|
INIT_COMPLETION(afm->seq_completed);
|
|
afm_enable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
|
|
/* Reset ECC counters */
|
|
reg = afm_readreg(EMINAND_FLEXMODE_CONFIG);
|
|
reg |= (1 << 6);
|
|
afm_writereg(reg, EMINAND_FLEXMODE_CONFIG);
|
|
|
|
/* Set page address */
|
|
prog->addr_reg = afm->page << 8;
|
|
|
|
/* Copy program to controller, and start sequence */
|
|
memcpy_toio(afm->base + EMINAND_AFM_SEQUENCE_REG_1, prog, 32);
|
|
|
|
/* Write page data */
|
|
chip->write_buf(mtd, buf, mtd->writesize);
|
|
|
|
/* Wait for the sequence to terminate */
|
|
ret = wait_for_completion_timeout(&afm->seq_completed, HZ/2);
|
|
if (!ret) {
|
|
dev_err(afm->dev, "seq timeout, force exit\n");
|
|
afm_writereg(0x00000000, EMINAND_AFM_SEQUENCE_CONFIG_REG);
|
|
afm->status = NAND_STATUS_FAIL;
|
|
afm_disable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
return;
|
|
}
|
|
|
|
/* Disable Seq interrupt */
|
|
afm_disable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
|
|
/* 2. Write OOB data */
|
|
/* Populate OOB with ECC data */
|
|
ecc_afm = afm_readreg(EMINAND_AFM_ECC_CHECKCODE_REG_3);
|
|
chip->oob_poi[0] = ecc_afm & 0xff; ecc_afm >>= 8;
|
|
chip->oob_poi[1] = ecc_afm & 0xff; ecc_afm >>= 8;
|
|
chip->oob_poi[2] = ecc_afm & 0xff;
|
|
chip->oob_poi[3] = 'A';
|
|
chip->oob_poi[4] = 'F';
|
|
chip->oob_poi[5] = 'M';
|
|
chip->oob_poi[6] = stm_afm_lp1617(buf);
|
|
|
|
/* Switch to FLEX mode for writing OOB */
|
|
INIT_COMPLETION(afm->rbn_completed);
|
|
afm_enable_interrupts(afm, AFM_IRQ_RBN);
|
|
afm_writereg(0x00000001, EMINAND_FLEXMODE_CONFIG);
|
|
|
|
/* Write OOB data */
|
|
afm_writereg(FLX_DATA_CFG_BEAT_4 | FLX_DATA_CFG_CSN_STATUS,
|
|
EMINAND_FLEX_DATAWRITE_CONFIG);
|
|
writesl(afm->base + EMINAND_FLEX_DATA, chip->oob_poi, 4);
|
|
afm_writereg(FLX_DATA_CFG_BEAT_1 | FLX_DATA_CFG_CSN_STATUS,
|
|
EMINAND_FLEX_DATAWRITE_CONFIG);
|
|
|
|
/* Issue page program command */
|
|
afm_writereg(FLX_CMD(NAND_CMD_PAGEPROG), EMINAND_FLEX_COMMAND_REG);
|
|
|
|
/* Wait for page program operation to complete */
|
|
ret = wait_for_completion_timeout(&afm->rbn_completed, HZ/2);
|
|
if (!ret)
|
|
dev_err(afm->dev, "RBn timeout, force exit\n");
|
|
|
|
/* Get status */
|
|
afm_writereg(FLX_CMD(NAND_CMD_STATUS), EMINAND_FLEX_COMMAND_REG);
|
|
afm->status = afm_readreg(EMINAND_FLEX_DATA);
|
|
|
|
afm_disable_interrupts(afm, AFM_IRQ_RBN);
|
|
|
|
/* 3. Switch back to AFM */
|
|
afm_writereg(0x00000002, EMINAND_FLEXMODE_CONFIG);
|
|
}
|
|
|
|
|
|
/* AFM: Write Page and OOB Data, with ECC support [LargePage] */
|
|
static void afm_write_page_ecc_lp(struct mtd_info *mtd,
|
|
struct nand_chip *chip,
|
|
const uint8_t *buf)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
int eccsize = chip->ecc.size;
|
|
int eccbytes = chip->ecc.bytes;
|
|
int eccsteps = chip->ecc.steps;
|
|
uint32_t *eccpos = chip->ecc.layout->eccpos;
|
|
const uint8_t *p;
|
|
|
|
struct afm_prog *prog = &afm_prog_write_ecc_lp;
|
|
|
|
uint32_t reg;
|
|
int ret;
|
|
int i;
|
|
|
|
afm->status = 0;
|
|
|
|
/* Calc S/W ECC LP1617, insert into OOB area with 'AFM' signature */
|
|
p = buf;
|
|
for (i = 0; i < eccsteps; i++) {
|
|
chip->oob_poi[eccpos[3+i*eccbytes]] = 'A';
|
|
chip->oob_poi[eccpos[4+i*eccbytes]] = 'F';
|
|
chip->oob_poi[eccpos[5+i*eccbytes]] = 'M';
|
|
chip->oob_poi[eccpos[6+i*eccbytes]] = stm_afm_lp1617(p);
|
|
p += eccsize;
|
|
}
|
|
|
|
/* Initialise Seq interrupt */
|
|
INIT_COMPLETION(afm->seq_completed);
|
|
afm_enable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
|
|
/* Reset ECC counters */
|
|
reg = afm_readreg(EMINAND_FLEXMODE_CONFIG);
|
|
reg |= (1 << 6);
|
|
afm_writereg(reg, EMINAND_FLEXMODE_CONFIG);
|
|
|
|
/* Set page address */
|
|
prog->addr_reg = afm->page << 8;
|
|
|
|
/* Copy program to controller, and start sequence */
|
|
memcpy_toio(afm->base + EMINAND_AFM_SEQUENCE_REG_1, prog, 32);
|
|
|
|
/* Write page and oob data */
|
|
chip->write_buf(mtd, buf, mtd->writesize);
|
|
chip->write_buf(mtd, chip->oob_poi, 64);
|
|
|
|
/* Wait for sequence to complete */
|
|
ret = wait_for_completion_timeout(&afm->seq_completed, HZ/2);
|
|
if (!ret) {
|
|
dev_err(afm->dev, "seq timeout, force exit\n");
|
|
afm_writereg(0x00000000, EMINAND_AFM_SEQUENCE_CONFIG_REG);
|
|
afm->status = NAND_STATUS_FAIL;
|
|
afm_disable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
return;
|
|
}
|
|
|
|
/* Get status */
|
|
reg = afm_readreg(EMINAND_AFM_SEQUENCE_STATUS_REG);
|
|
afm->status = NAND_STATUS_READY | ((reg & 0x60) >> 5);
|
|
|
|
/* Disable Seq interrupt */
|
|
afm_disable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
}
|
|
|
|
/* AFM: Write Raw Page and OOB Data [LargePage] */
|
|
static void afm_write_page_raw_lp(struct mtd_info *mtd, struct nand_chip *chip,
|
|
const uint8_t *buf)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
struct afm_prog *prog = &afm_prog_write_raw_lp;
|
|
uint32_t reg;
|
|
int ret;
|
|
|
|
afm->status = 0;
|
|
|
|
/* Initialise Seq Interrupts */
|
|
INIT_COMPLETION(afm->seq_completed);
|
|
afm_enable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
|
|
/* Set page address */
|
|
prog->addr_reg = afm->page << 8;
|
|
|
|
/* Copy program to controller, and start sequence */
|
|
memcpy_toio(afm->base + EMINAND_AFM_SEQUENCE_REG_1, prog, 32);
|
|
|
|
/* Write page and OOB data */
|
|
chip->write_buf(mtd, buf, 2048);
|
|
chip->write_buf(mtd, chip->oob_poi, 64);
|
|
|
|
/* Wait for sequence to complete */
|
|
ret = wait_for_completion_timeout(&afm->seq_completed, HZ/2);
|
|
if (!ret) {
|
|
dev_err(afm->dev, "seq timeout, force exit\n");
|
|
afm_writereg(0x00000000, EMINAND_AFM_SEQUENCE_CONFIG_REG);
|
|
afm->status = NAND_STATUS_FAIL;
|
|
afm_disable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
return;
|
|
}
|
|
|
|
/* Get status */
|
|
reg = afm_readreg(EMINAND_AFM_SEQUENCE_STATUS_REG);
|
|
afm->status = NAND_STATUS_READY | ((reg & 0x60) >> 5);
|
|
|
|
/* Disable Seq Interrupts */
|
|
afm_disable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
}
|
|
|
|
/* AFM: Write Raw Page and OOB Data [SmallPage] */
|
|
static void afm_write_page_raw_sp(struct mtd_info *mtd, struct nand_chip *chip,
|
|
const uint8_t *buf)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
struct afm_prog *prog = &afm_prog_write_raw_sp_a;
|
|
uint32_t reg;
|
|
int ret;
|
|
|
|
afm->status = 0;
|
|
|
|
/* Enable sequence interrupts */
|
|
INIT_COMPLETION(afm->seq_completed);
|
|
afm_enable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
|
|
/* 1. Write page data to chip's page buffer */
|
|
prog->addr_reg = afm->page << 8;
|
|
memcpy_toio(afm->base + EMINAND_AFM_SEQUENCE_REG_1, prog, 32);
|
|
|
|
chip->write_buf(mtd, buf, mtd->writesize);
|
|
|
|
ret = wait_for_completion_timeout(&afm->seq_completed, HZ/2);
|
|
if (!ret) {
|
|
dev_err(afm->dev, "seq timeout, force exit\n");
|
|
afm_writereg(0x00000000, EMINAND_AFM_SEQUENCE_CONFIG_REG);
|
|
afm->status = NAND_STATUS_FAIL;
|
|
afm_disable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
return;
|
|
}
|
|
afm_disable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
|
|
reg = afm_readreg(EMINAND_AFM_SEQUENCE_STATUS_REG);
|
|
if ((reg & 0x60) != 0) {
|
|
dev_err(afm->dev, "error programming page data\n");
|
|
afm->status = NAND_STATUS_FAIL;
|
|
return;
|
|
}
|
|
|
|
/* 2. Switch to FLEX mode and write OOB data */
|
|
INIT_COMPLETION(afm->rbn_completed);
|
|
afm_enable_interrupts(afm, AFM_IRQ_RBN);
|
|
afm_writereg(0x00000001, EMINAND_FLEXMODE_CONFIG);
|
|
|
|
/* Send OOB pointer operation */
|
|
afm_writereg(FLX_CMD(NAND_CMD_READOOB), EMINAND_FLEX_COMMAND_REG);
|
|
|
|
/* Send SEQIN command */
|
|
afm_writereg(FLX_CMD(NAND_CMD_SEQIN), EMINAND_FLEX_COMMAND_REG);
|
|
|
|
/* Send page address */
|
|
reg = afm->page << 8 | FLX_ADDR_REG_ADD8_VALID |
|
|
FLX_ADDR_REG_CSN_STATUS;
|
|
if (chip->chipsize > (32 << 20))
|
|
reg |= FLX_ADDR_REG_BEAT_4;
|
|
else
|
|
reg |= FLX_ADDR_REG_BEAT_3;
|
|
afm_writereg(reg, EMINAND_FLEX_ADDRESS_REG);
|
|
|
|
/* Send OOB data */
|
|
afm_writereg(FLX_DATA_CFG_BEAT_4 | FLX_DATA_CFG_CSN_STATUS,
|
|
EMINAND_FLEX_DATAWRITE_CONFIG);
|
|
writesl(afm->base + EMINAND_FLEX_DATA, chip->oob_poi, 4);
|
|
afm_writereg(FLX_DATA_CFG_BEAT_1 | FLX_DATA_CFG_CSN_STATUS,
|
|
EMINAND_FLEX_DATAWRITE_CONFIG);
|
|
|
|
/* Send page program command */
|
|
afm_writereg(FLX_CMD(NAND_CMD_PAGEPROG), EMINAND_FLEX_COMMAND_REG);
|
|
|
|
/* Wait for page program operation to complete */
|
|
ret = wait_for_completion_timeout(&afm->rbn_completed, HZ/2);
|
|
if (!ret)
|
|
dev_err(afm->dev, "RBn timeout!\n");
|
|
|
|
/* Get status */
|
|
afm_writereg(FLX_CMD(NAND_CMD_STATUS), EMINAND_FLEX_COMMAND_REG);
|
|
afm->status = afm_readreg(EMINAND_FLEX_DATA);
|
|
|
|
afm_disable_interrupts(afm, AFM_IRQ_RBN);
|
|
|
|
/* 3. Switch back to AFM */
|
|
afm_writereg(0x00000002, EMINAND_FLEXMODE_CONFIG);
|
|
}
|
|
|
|
/* AFM: Write OOB Data [LargePage] */
|
|
static int afm_write_oob_chip_lp(struct mtd_info *mtd, struct nand_chip *chip,
|
|
int page)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
struct afm_prog *prog = &afm_prog_write_oob_lp;
|
|
uint32_t reg;
|
|
int ret;
|
|
|
|
afm->status = 0;
|
|
|
|
/* Enable sequence interrupts */
|
|
INIT_COMPLETION(afm->seq_completed);
|
|
afm_enable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
|
|
prog->addr_reg = (page << 8) | (2048 >> 8);
|
|
|
|
memcpy_toio(afm->base + EMINAND_AFM_SEQUENCE_REG_1, prog, 32);
|
|
|
|
/* Write OOB */
|
|
chip->write_buf(mtd, chip->oob_poi, 64);
|
|
|
|
/* Wait for sequence to complete */
|
|
ret = wait_for_completion_timeout(&afm->seq_completed, HZ);
|
|
if (!ret) {
|
|
dev_err(afm->dev, "seq timeout, force exit\n");
|
|
afm_writereg(0x00000000, EMINAND_AFM_SEQUENCE_CONFIG_REG);
|
|
afm->status = NAND_STATUS_FAIL;
|
|
afm_disable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
return 1;
|
|
}
|
|
|
|
/* Get status */
|
|
reg = afm_readreg(EMINAND_AFM_SEQUENCE_STATUS_REG);
|
|
afm->status = NAND_STATUS_READY | ((reg & 0x60) >> 5);
|
|
|
|
/* Disable sequence interrupts */
|
|
afm_disable_interrupts(afm, AFM_IRQ_SEQ_DREQ);
|
|
|
|
return afm->status & NAND_STATUS_FAIL ? -EIO : 0;
|
|
}
|
|
|
|
/* AFM: Write OOB Data [SmallPage] */
|
|
static int afm_write_oob_chip_sp(struct mtd_info *mtd, struct nand_chip *chip,
|
|
int page)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
int ret;
|
|
uint32_t status;
|
|
uint32_t reg;
|
|
|
|
afm->status = 0;
|
|
|
|
/* Initialise interrupts */
|
|
INIT_COMPLETION(afm->rbn_completed);
|
|
afm_enable_interrupts(afm, AFM_IRQ_RBN);
|
|
|
|
/* Switch to Flex Mode */
|
|
afm_writereg(0x00000001, EMINAND_FLEXMODE_CONFIG);
|
|
|
|
/* Pointer Operation */
|
|
afm_writereg(FLX_CMD(NAND_CMD_READOOB), EMINAND_FLEX_COMMAND_REG);
|
|
|
|
/* Send SEQIN command */
|
|
afm_writereg(FLX_CMD(NAND_CMD_SEQIN), EMINAND_FLEX_COMMAND_REG);
|
|
|
|
/* Send Col/Page Addr */
|
|
reg = (page << 8 |
|
|
FLX_ADDR_REG_RBN |
|
|
FLX_ADDR_REG_ADD8_VALID |
|
|
FLX_ADDR_REG_CSN_STATUS);
|
|
|
|
if (chip->chipsize > (32 << 20))
|
|
/* Need extra address cycle for 'large' devices */
|
|
reg |= FLX_ADDR_REG_BEAT_4;
|
|
else
|
|
reg |= FLX_ADDR_REG_BEAT_3;
|
|
afm_writereg(reg, EMINAND_FLEX_ADDRESS_REG);
|
|
|
|
/* Write OOB data */
|
|
afm_writereg(FLX_DATA_CFG_BEAT_4 | FLX_DATA_CFG_CSN_STATUS,
|
|
EMINAND_FLEX_DATAWRITE_CONFIG);
|
|
writesl(afm->base + EMINAND_FLEX_DATA, chip->oob_poi, 4);
|
|
afm_writereg(FLX_DATA_CFG_BEAT_1 | FLX_DATA_CFG_CSN_STATUS,
|
|
EMINAND_FLEX_DATAWRITE_CONFIG);
|
|
|
|
/* Issue Page Program command */
|
|
afm_writereg(FLX_CMD(NAND_CMD_PAGEPROG), EMINAND_FLEX_COMMAND_REG);
|
|
|
|
/* Wait for page program operation to complete */
|
|
ret = wait_for_completion_timeout(&afm->rbn_completed, HZ/2);
|
|
if (!ret)
|
|
dev_err(afm->dev, "RBn timeout\n");
|
|
|
|
/* Get status */
|
|
afm_writereg(FLX_CMD(NAND_CMD_STATUS), EMINAND_FLEX_COMMAND_REG);
|
|
status = afm_readreg(EMINAND_FLEX_DATA);
|
|
|
|
afm->status = 0xff & status;
|
|
|
|
/* Switch back to AFM */
|
|
afm_writereg(0x00000002, EMINAND_FLEXMODE_CONFIG);
|
|
|
|
/* Disable RBn interrupts */
|
|
afm_disable_interrupts(afm, AFM_IRQ_RBN);
|
|
|
|
return status & NAND_STATUS_FAIL ? -EIO : 0;
|
|
}
|
|
|
|
/* Read the device electronic signature */
|
|
static int afm_read_sig(struct mtd_info *mtd,
|
|
int *maf_id, int *dev_id,
|
|
uint8_t *cellinfo, uint8_t *extid)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
uint32_t ret, reg;
|
|
|
|
/* Enable RBn interrupts */
|
|
INIT_COMPLETION(afm->rbn_completed);
|
|
afm_enable_interrupts(afm, AFM_IRQ_RBN);
|
|
|
|
/* Switch to Flex Mode */
|
|
afm_writereg(0x00000001, EMINAND_FLEXMODE_CONFIG);
|
|
|
|
/* Issue NAND reset */
|
|
afm_writereg(FLX_CMD(NAND_CMD_RESET), EMINAND_FLEX_COMMAND_REG);
|
|
|
|
/* Wait for reset to complete */
|
|
ret = wait_for_completion_timeout(&afm->rbn_completed, HZ/2);
|
|
if (!ret)
|
|
dev_err(afm->dev, "RBn timeout\n");
|
|
|
|
/* Read electronic signature */
|
|
afm_writereg(FLX_CMD(NAND_CMD_READID), EMINAND_FLEX_COMMAND_REG);
|
|
|
|
reg = (0x00 |
|
|
FLX_ADDR_REG_BEAT_1 |
|
|
FLX_ADDR_REG_RBN |
|
|
FLX_ADDR_REG_CSN_STATUS);
|
|
afm_writereg(reg, EMINAND_FLEX_ADDRESS_REG);
|
|
|
|
afm_writereg(FLX_DATA_CFG_BEAT_4 | FLX_DATA_CFG_CSN_STATUS,
|
|
EMINAND_FLEX_DATAREAD_CONFIG);
|
|
reg = afm_readreg(EMINAND_FLEX_DATA);
|
|
afm_writereg(FLX_DATA_CFG_BEAT_1 | FLX_DATA_CFG_CSN_STATUS,
|
|
EMINAND_FLEX_DATAREAD_CONFIG);
|
|
|
|
/* Extract manufacturer and device ID */
|
|
*maf_id = reg & 0xff;
|
|
*dev_id = (reg >> 8) & 0xff;
|
|
|
|
/* Newer devices have all the information in additional id bytes */
|
|
*cellinfo = (reg >> 16) & 0xff;
|
|
*extid = (reg >> 24) & 0xff;
|
|
|
|
/* Switch back to AFM Mode */
|
|
afm_writereg(0x00000002, EMINAND_FLEXMODE_CONFIG);
|
|
|
|
/* Disable RBn interrupts */
|
|
afm_disable_interrupts(afm, AFM_IRQ_RBN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_STM_NAND_AFM_BOOTMODESUPPORT
|
|
/* For boot-mode, we use software ECC with AFM raw read/write commands */
|
|
static int boot_calc_ecc(struct mtd_info *mtd, const unsigned char *buf,
|
|
unsigned char *ecc)
|
|
{
|
|
stm_ecc_gen(buf, ecc, ECC_128);
|
|
ecc[3] = 'B';
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int boot_correct_ecc(struct mtd_info *mtd, unsigned char *buf,
|
|
unsigned char *read_ecc, unsigned char *calc_ecc)
|
|
{
|
|
int status;
|
|
|
|
status = stm_ecc_correct(buf, read_ecc, calc_ecc, ECC_128);
|
|
|
|
/* convert to MTD-compatible status */
|
|
if (status == E_NO_CHK)
|
|
return 0;
|
|
if (status == E_D1_CHK || status == E_C1_CHK)
|
|
return 1;
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int afm_read_page_boot(struct mtd_info *mtd, struct nand_chip *chip,
|
|
uint8_t *buf, int page)
|
|
{
|
|
int i, eccsize = chip->ecc.size;
|
|
int eccbytes = chip->ecc.bytes;
|
|
int eccsteps = chip->ecc.steps;
|
|
uint8_t *p = buf;
|
|
uint8_t *ecc_calc = chip->buffers->ecccalc;
|
|
uint8_t *ecc_code = chip->buffers->ecccode;
|
|
uint32_t *eccpos = chip->ecc.layout->eccpos;
|
|
|
|
chip->ecc.read_page_raw(mtd, chip, buf, page);
|
|
|
|
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)
|
|
boot_calc_ecc(mtd, p, &ecc_calc[i]);
|
|
|
|
for (i = 0; i < chip->ecc.total; i++)
|
|
ecc_code[i] = chip->oob_poi[eccpos[i]];
|
|
|
|
eccsteps = chip->ecc.steps;
|
|
p = buf;
|
|
|
|
for (i = 0 ; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
|
|
int stat;
|
|
|
|
stat = boot_correct_ecc(mtd, p, &ecc_code[i], &ecc_calc[i]);
|
|
if (stat == -1) {
|
|
printk(KERN_CONT "step %d, page %d]\n",
|
|
i / eccbytes, page);
|
|
mtd->ecc_stats.failed++;
|
|
} else if (stat == 1) {
|
|
printk(KERN_CONT "step %d, page = %d]\n",
|
|
i / eccbytes, page);
|
|
mtd->ecc_stats.corrected += stat;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void afm_write_page_boot(struct mtd_info *mtd,
|
|
struct nand_chip *chip,
|
|
const uint8_t *buf)
|
|
|
|
{
|
|
int i, eccsize = chip->ecc.size;
|
|
int eccbytes = chip->ecc.bytes;
|
|
int eccsteps = chip->ecc.steps;
|
|
uint8_t *ecc_calc = chip->buffers->ecccalc;
|
|
const uint8_t *p = buf;
|
|
uint32_t *eccpos = chip->ecc.layout->eccpos;
|
|
|
|
/* Software ecc calculation */
|
|
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)
|
|
boot_calc_ecc(mtd, p, &ecc_calc[i]);
|
|
|
|
for (i = 0; i < chip->ecc.total; i++)
|
|
chip->oob_poi[eccpos[i]] = ecc_calc[i];
|
|
|
|
chip->ecc.write_page_raw(mtd, chip, buf);
|
|
|
|
}
|
|
#endif /* CONFIG_STM_NAND_AFM_BOOTMODESUPPORT */
|
|
|
|
|
|
/* AFM : Select Chip
|
|
* For now we only support 1 chip per 'stm_nand_afm_device' so chipnr will be 0
|
|
* for select, -1 for deselect.
|
|
*/
|
|
static void afm_select_chip(struct mtd_info *mtd, int chipnr)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
struct nand_chip *chip = mtd->priv;
|
|
struct stm_nand_afm_device *data = chip->priv;
|
|
|
|
/* Deselect, do nothing */
|
|
if (chipnr == -1) {
|
|
return;
|
|
} else if (chipnr == 0) {
|
|
/* If same chip as last time, no need to change anything */
|
|
if (data->csn == afm->current_csn)
|
|
return;
|
|
|
|
/* Set CSn on AFM controller */
|
|
afm->current_csn = data->csn;
|
|
afm_writereg(0x1 << data->csn, EMINAND_MUXCONTROL_REG);
|
|
|
|
/* Set up timing parameters */
|
|
afm_set_timings(afm, data->timing_data);
|
|
} else {
|
|
dev_err(afm->dev, "attempt to select chipnr = %d\n", chipnr);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* The low-level AFM chip operations wait internally, and set the status field.
|
|
* All that is left to do here is return the status */
|
|
static int afm_wait(struct mtd_info *mtd, struct nand_chip *chip)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
|
|
return afm->status;
|
|
}
|
|
|
|
/*
|
|
* afm_command() and afm_read_byte() are treated as special cases here. We need
|
|
* to support nand_base.c:nand_erase_nand() because this is called directly by
|
|
* nand_bbt.c (why is it not a callback?). However, nand_erase_nand() calls
|
|
* nand_check_wp() which in turn calls chip->cmdfunc() and chip->read_byte() in
|
|
* order to check the status register. Since nand_check_wp() is the only
|
|
* function that uses these callbacks, we can implement specialised versions
|
|
* such that afm_command() is empty, and afm_read_byte() queries and returns the
|
|
* status register.
|
|
*
|
|
* Need to track updates to nand_base.c to ensure these assumptions remain valid
|
|
* in the future!
|
|
*/
|
|
static void afm_command(struct mtd_info *mtd, unsigned int command,
|
|
int column, int page_addr)
|
|
{
|
|
|
|
}
|
|
|
|
static uint8_t afm_read_byte(struct mtd_info *mtd)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
uint32_t reg;
|
|
|
|
/* Switch to Flex Mode */
|
|
afm_writereg(0x00000001, EMINAND_FLEXMODE_CONFIG);
|
|
|
|
/* Write STATUS command (do not set FLX_CMD_REG_RBN!) */
|
|
reg = (NAND_CMD_STATUS | FLX_CMD_REG_BEAT_1 | FLX_CMD_REG_CSN_STATUS);
|
|
afm_writereg(reg, EMINAND_FLEX_COMMAND_REG);
|
|
|
|
reg = afm_readreg(EMINAND_FLEX_DATA);
|
|
|
|
/* Switch back to AFM */
|
|
afm_writereg(0x00000002, EMINAND_FLEXMODE_CONFIG);
|
|
|
|
return reg & 0xff;
|
|
}
|
|
|
|
static int afm_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
|
{
|
|
printk(KERN_ERR NAME ": %s Unsupported callback", __func__);
|
|
BUG();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u16 afm_read_word(struct mtd_info *mtd)
|
|
{
|
|
printk(KERN_ERR NAME ": %s Unsupported callback", __func__);
|
|
BUG();
|
|
|
|
return 0xff;
|
|
}
|
|
|
|
/*
|
|
* AFM scan/probe NAND routines
|
|
*/
|
|
|
|
/* Set AFM generic call-backs (not chip-specific) */
|
|
static void afm_set_defaults(struct nand_chip *chip, int busw)
|
|
{
|
|
chip->chip_delay = 0;
|
|
chip->cmdfunc = afm_command;
|
|
chip->waitfunc = afm_wait;
|
|
chip->select_chip = afm_select_chip;
|
|
chip->read_byte = afm_read_byte;
|
|
chip->read_word = afm_read_word;
|
|
chip->block_bad = NULL;
|
|
chip->block_markbad = NULL;
|
|
chip->write_buf = afm_write_buf;
|
|
chip->verify_buf = afm_verify_buf;
|
|
chip->scan_bbt = nand_default_bbt;
|
|
#ifdef CONFIG_STM_NAND_AFM_CACHED
|
|
chip->read_buf = afm_read_buf_cached;
|
|
#else
|
|
chip->read_buf = afm_read_buf;
|
|
#endif
|
|
}
|
|
|
|
|
|
/* Determine the NAND device paramters
|
|
* [cf nand_base.c:nand_get_flash_type()] */
|
|
static struct nand_flash_dev *afm_get_flash_type(struct mtd_info *mtd,
|
|
struct nand_chip *chip,
|
|
int busw, int *maf_id)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
struct nand_flash_dev *type = NULL;
|
|
int i, dev_id, maf_idx;
|
|
uint8_t cellinfo;
|
|
uint8_t extid;
|
|
|
|
/* Select the device */
|
|
chip->select_chip(mtd, 0);
|
|
|
|
/* Read the electronic signature */
|
|
afm_read_sig(mtd, maf_id, &dev_id, &cellinfo, &extid);
|
|
|
|
/* Lookup device */
|
|
for (i = 0; nand_flash_ids[i].name != NULL; i++) {
|
|
if (dev_id == nand_flash_ids[i].id) {
|
|
type = &nand_flash_ids[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!type)
|
|
return ERR_PTR(-ENODEV);
|
|
|
|
if (!mtd->name)
|
|
mtd->name = type->name;
|
|
|
|
chip->chipsize = type->chipsize << 20;
|
|
|
|
if (!type->pagesize) {
|
|
/* New devices use the extended chip info... */
|
|
chip->cellinfo = cellinfo;
|
|
|
|
/* Calc pagesize */
|
|
mtd->writesize = 1024 << (extid & 0x3);
|
|
extid >>= 2;
|
|
|
|
/* Calc oobsize */
|
|
mtd->oobsize = (8 << (extid & 0x01)) * (mtd->writesize >> 9);
|
|
extid >>= 2;
|
|
|
|
/* Calc blocksize. Blocksize is multiples of 64KiB */
|
|
mtd->erasesize = (64 * 1024) << (extid & 0x03);
|
|
extid >>= 2;
|
|
|
|
/* Get buswidth information */
|
|
busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0;
|
|
} else {
|
|
/* Old devices have chip data hardcoded in device id table */
|
|
mtd->erasesize = type->erasesize;
|
|
mtd->writesize = type->pagesize;
|
|
mtd->oobsize = mtd->writesize / 32;
|
|
busw = type->options & NAND_BUSWIDTH_16;
|
|
}
|
|
|
|
afm_generic_config(afm, chip->options & NAND_BUSWIDTH_16,
|
|
mtd->writesize, chip->chipsize);
|
|
|
|
/* Try to identify manufacturer */
|
|
for (maf_idx = 0; nand_manuf_ids[maf_idx].id != 0x0; maf_idx++) {
|
|
if (nand_manuf_ids[maf_idx].id == *maf_id)
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Check, if buswidth is correct. Hardware drivers should set
|
|
* chip correct !
|
|
*/
|
|
if (busw != (chip->options & NAND_BUSWIDTH_16)) {
|
|
dev_info(afm->dev, "NAND device: Manufacturer ID:"
|
|
" 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id,
|
|
dev_id, nand_manuf_ids[maf_idx].name, mtd->name);
|
|
dev_info(afm->dev, "NAND bus width %d instead %d bit\n",
|
|
(chip->options & NAND_BUSWIDTH_16) ? 16 : 8,
|
|
busw ? 16 : 8);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
/* Calculate the address shift from the page size */
|
|
chip->page_shift = ffs(mtd->writesize) - 1;
|
|
|
|
/* Convert chipsize to number of pages per chip -1. */
|
|
chip->pagemask = (chip->chipsize >> chip->page_shift) - 1;
|
|
|
|
chip->bbt_erase_shift = ffs(mtd->erasesize) - 1;
|
|
chip->phys_erase_shift = chip->bbt_erase_shift;
|
|
|
|
chip->chip_shift = ffs(chip->chipsize) - 1;
|
|
|
|
/* Set the bad block position [AAC: Now obsolete?] */
|
|
chip->badblockpos = mtd->writesize > 512 ?
|
|
NAND_LARGE_BADBLOCK_POS : NAND_SMALL_BADBLOCK_POS;
|
|
|
|
/* Get chip options, preserve non chip based options */
|
|
chip->options &= ~NAND_CHIPOPTIONS_MSK;
|
|
chip->options |= type->options & NAND_CHIPOPTIONS_MSK;
|
|
|
|
/*
|
|
* Set chip as a default. Board drivers can override it, if necessary
|
|
*/
|
|
chip->options |= NAND_NO_AUTOINCR;
|
|
|
|
/* Check if chip is a not a samsung device. Do not clear the
|
|
* options for chips which are not having an extended id.
|
|
*/
|
|
if (*maf_id != NAND_MFR_SAMSUNG && !type->pagesize)
|
|
chip->options &= ~NAND_SAMSUNG_LP_OPTIONS;
|
|
|
|
|
|
chip->erase_cmd = afm_erase_cmd;
|
|
|
|
dev_info(afm->dev, "NAND device: Manufacturer ID:"
|
|
" 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id, dev_id,
|
|
nand_manuf_ids[maf_idx].name, type->name);
|
|
|
|
return type;
|
|
}
|
|
|
|
|
|
/* Scan device, part 1 : Determine device paramters */
|
|
int afm_scan_ident(struct mtd_info *mtd, int maxchips)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
int busw, maf_id;
|
|
struct nand_chip *chip = mtd->priv;
|
|
struct nand_flash_dev *type = NULL;
|
|
|
|
if (maxchips != 1) {
|
|
dev_err(afm->dev, "Only chip per MTD device allowed\n");
|
|
return 1;
|
|
}
|
|
|
|
busw = chip->options & NAND_BUSWIDTH_16;
|
|
afm_set_defaults(chip, busw);
|
|
|
|
type = afm_get_flash_type(mtd, chip, busw, &maf_id);
|
|
|
|
if (IS_ERR(type)) {
|
|
dev_err(afm->dev, "No NAND device found\n");
|
|
chip->select_chip(mtd, -1);
|
|
return PTR_ERR(type);
|
|
}
|
|
|
|
/* Not dealing with multichip support just yet! */
|
|
chip->numchips = 1;
|
|
mtd->size = chip->chipsize;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* Scan device, part 2: Configure AFM according to device paramters */
|
|
static int afm_scan_tail(struct mtd_info *mtd)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
int i;
|
|
struct nand_chip *chip = mtd->priv;
|
|
|
|
int ret;
|
|
|
|
if (!(chip->options & NAND_OWN_BUFFERS))
|
|
chip->buffers = kmalloc(sizeof(*chip->buffers), GFP_KERNEL);
|
|
if (!chip->buffers)
|
|
return -ENOMEM;
|
|
|
|
/* Set the internal oob buffer location, just after the page data */
|
|
chip->oob_poi = chip->buffers->databuf + mtd->writesize;
|
|
|
|
if (mtd->writesize == 512 && mtd->oobsize == 16) {
|
|
chip->ecc.layout = &afm_oob_16;
|
|
chip->badblock_pattern = &bbt_scan_sp;
|
|
} else if (mtd->writesize == 2048 && mtd->oobsize == 64) {
|
|
chip->ecc.layout = &afm_oob_64;
|
|
chip->badblock_pattern = &bbt_scan_lp;
|
|
} else {
|
|
dev_err(afm->dev, "Unsupported chip type "
|
|
"[pagesize = %d, oobsize = %d]\n",
|
|
mtd->writesize, mtd->oobsize);
|
|
return 1;
|
|
}
|
|
|
|
#ifdef CONFIG_STM_NAND_AFM_BOOTMODESUPPORT
|
|
/* Handle boot-mode ECC when scanning for bad blocks */
|
|
chip->badblock_pattern->options |= NAND_BBT_SCANSTMBOOTECC;
|
|
#endif
|
|
|
|
/* Set ECC parameters and call-backs */
|
|
chip->ecc.mode = NAND_ECC_HW;
|
|
chip->ecc.size = 512;
|
|
chip->ecc.bytes = 7;
|
|
chip->write_page = afm_write_page;
|
|
chip->ecc.read_page = afm_read_page_ecc;
|
|
chip->ecc.read_page_raw = afm_read_page_raw;
|
|
chip->ecc.read_oob = afm_read_oob_chip;
|
|
if (mtd->writesize == 512) {
|
|
chip->ecc.write_page = afm_write_page_ecc_sp;
|
|
chip->ecc.write_page_raw = afm_write_page_raw_sp;
|
|
chip->ecc.write_oob = afm_write_oob_chip_sp;
|
|
} else {
|
|
chip->ecc.write_page = afm_write_page_ecc_lp;
|
|
chip->ecc.write_page_raw = afm_write_page_raw_lp;
|
|
chip->ecc.write_oob = afm_write_oob_chip_lp;
|
|
}
|
|
chip->ecc.steps = mtd->writesize / chip->ecc.size;
|
|
if (chip->ecc.steps * chip->ecc.size != mtd->writesize) {
|
|
dev_err(afm->dev, "Invalid ecc parameters\n");
|
|
return 1;
|
|
}
|
|
chip->ecc.total = chip->ecc.steps * chip->ecc.bytes;
|
|
chip->ecc.calculate = NULL; /* Hard-coded in call-backs */
|
|
chip->ecc.correct = NULL;
|
|
|
|
/* Count the number of OOB bytes available for client data */
|
|
chip->ecc.layout->oobavail = 0;
|
|
for (i = 0; chip->ecc.layout->oobfree[i].length
|
|
&& i < MTD_MAX_OOBFREE_ENTRIES; i++)
|
|
chip->ecc.layout->oobavail +=
|
|
chip->ecc.layout->oobfree[i].length;
|
|
mtd->oobavail = chip->ecc.layout->oobavail;
|
|
|
|
/* Disable subpage writes for now */
|
|
mtd->subpage_sft = 0;
|
|
chip->subpagesize = mtd->writesize >> mtd->subpage_sft;
|
|
|
|
/* Initialize state */
|
|
chip->state = FL_READY;
|
|
|
|
/* Invalidate the pagebuffer reference */
|
|
chip->pagebuf = -1;
|
|
|
|
/* Fill in remaining MTD driver data */
|
|
mtd->type = MTD_NANDFLASH;
|
|
mtd->flags = MTD_CAP_NANDFLASH;
|
|
mtd->erase = afm_erase; /* uses chip->erase_cmd */
|
|
mtd->point = NULL;
|
|
mtd->unpoint = NULL;
|
|
mtd->read = afm_read;
|
|
mtd->write = afm_write;
|
|
mtd->read_oob = afm_read_oob;
|
|
mtd->write_oob = afm_write_oob;
|
|
mtd->sync = afm_sync;
|
|
mtd->lock = NULL;
|
|
mtd->unlock = NULL;
|
|
mtd->suspend = afm_suspend;
|
|
mtd->resume = afm_resume;
|
|
mtd->block_isbad = afm_block_isbad;
|
|
mtd->block_markbad = afm_block_markbad;
|
|
|
|
/* Propagate ecc.layout to mtd_info */
|
|
mtd->ecclayout = chip->ecc.layout;
|
|
|
|
#ifdef CONFIG_STM_NAND_AFM_BOOTMODESUPPORT
|
|
/* Setup ECC params, for AFM and BOOT operation */
|
|
afm_setup_eccparams(mtd);
|
|
#endif
|
|
|
|
/* Check, if we should skip the bad block table scan */
|
|
if (chip->options & NAND_SKIP_BBTSCAN)
|
|
return 0;
|
|
|
|
/* Build bad block table */
|
|
ret = chip->scan_bbt(mtd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Scan for the NAND device */
|
|
int afm_scan(struct mtd_info *mtd, int maxchips)
|
|
{
|
|
int ret;
|
|
|
|
ret = afm_scan_ident(mtd, maxchips);
|
|
if (!ret)
|
|
ret = afm_scan_tail(mtd);
|
|
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_STM_NAND_AFM_PBLBOOTBOUNDARY
|
|
/* The NAND_BLOCK_ZERO_REMAP_REG is not implemented on current versions of the
|
|
* NAND controller. We must therefore scan for the logical block 0 pattern.
|
|
*/
|
|
static int check_block_zero_pattern(uint8_t *buf)
|
|
{
|
|
uint8_t i;
|
|
uint8_t *b;
|
|
|
|
for (i = 0, b = buf + 128; i < 64; i++, b++)
|
|
if (*b != i)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pbl_boot_boundary(struct mtd_info *mtd, uint32_t *_boundary)
|
|
{
|
|
struct stm_nand_afm_controller *afm = mtd_to_afm(mtd);
|
|
struct nand_chip *chip = mtd->priv;
|
|
struct stm_nand_afm_device *data = chip->priv;
|
|
uint8_t *buf = chip->buffers->databuf;
|
|
int block;
|
|
int block_zero_phys = -1;
|
|
int pages_per_block;
|
|
uint32_t boundary_log = 0;
|
|
uint32_t boundary_phys;
|
|
|
|
/* Select boot-mode ECC */
|
|
afm_select_eccparams(mtd, data->boot_start);
|
|
|
|
/* Find logical block zero */
|
|
pages_per_block = 1 << (chip->phys_erase_shift - chip->page_shift);
|
|
afm->col = 0;
|
|
afm->page = 0;
|
|
for (block = 0; block < 512; block++) {
|
|
if (nand_isbad_bbt(mtd, block << chip->phys_erase_shift, 0)
|
|
== 0) {
|
|
afm_read_page_boot(mtd, chip, buf, afm->page);
|
|
|
|
if (check_block_zero_pattern(buf) == 0) {
|
|
block_zero_phys = block;
|
|
boundary_log = *((uint32_t *)(buf + 0x0034));
|
|
break;
|
|
}
|
|
}
|
|
afm->page += pages_per_block;
|
|
}
|
|
|
|
/* Return if we didn't find logical block zero */
|
|
if (block_zero_phys == -1)
|
|
return 1;
|
|
|
|
/* For bootmode_boundary, map logical offset to physical offset */
|
|
boundary_phys = block_zero_phys << chip->phys_erase_shift;
|
|
while (boundary_log >= mtd->erasesize) {
|
|
boundary_phys += mtd->erasesize;
|
|
if (!afm_block_isbad(mtd, boundary_phys))
|
|
boundary_log -= mtd->erasesize;
|
|
}
|
|
boundary_phys += boundary_log; /* Add residual offset */
|
|
|
|
dev_info(afm->dev, "boot-mode boundary = 0x%08x (physical)\n",
|
|
boundary_phys);
|
|
|
|
*_boundary = boundary_phys;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
|
|
static struct stm_nand_afm_device * __init
|
|
afm_init_bank(struct stm_nand_afm_controller *afm,
|
|
struct stm_nand_bank_data *bank,
|
|
const char *name)
|
|
{
|
|
struct stm_nand_afm_device *data;
|
|
int err;
|
|
|
|
#ifdef CONFIG_STM_NAND_AFM_BOOTMODESUPPORT
|
|
struct mtd_info *slave;
|
|
struct mtd_part *part;
|
|
char *boot_part_name;
|
|
#ifdef CONFIG_STM_NAND_AFM_PBLBOOTBOUNDARY
|
|
uint32_t boundary;
|
|
#endif
|
|
#endif
|
|
|
|
/* Allocate memory for the driver structure (and zero it) */
|
|
data = kzalloc(sizeof(struct stm_nand_afm_device), GFP_KERNEL);
|
|
if (!data) {
|
|
dev_err(afm->dev, "failed to allocate device structure\n");
|
|
err = -ENOMEM;
|
|
goto err1;
|
|
}
|
|
|
|
data->chip.priv = data;
|
|
data->mtd.priv = &data->chip;
|
|
data->mtd.owner = THIS_MODULE;
|
|
data->dev = afm->dev;
|
|
|
|
/* Use hwcontrol structure to manage access to AFM Controller */
|
|
data->chip.controller = &afm->hwcontrol;
|
|
data->chip.state = FL_READY;
|
|
|
|
/* Assign more sensible name (default is string from nand_ids.c!) */
|
|
data->mtd.name = name;
|
|
data->csn = bank->csn;
|
|
|
|
data->timing_data = bank->timing_data;
|
|
|
|
data->chip.options = bank->options;
|
|
data->chip.options |= NAND_NO_AUTOINCR;
|
|
|
|
/* Scan to find existance of device */
|
|
if (afm_scan(&data->mtd, 1)) {
|
|
dev_err(afm->dev, "device scan failed\n");
|
|
err = -ENXIO;
|
|
goto err2;
|
|
}
|
|
|
|
#ifdef CONFIG_STM_NAND_AFM_BOOTMODESUPPORT
|
|
/* Set name of boot partition */
|
|
boot_part_name = nbootpart ? nbootpart :
|
|
CONFIG_STM_NAND_AFM_BOOTPARTITION;
|
|
dev_info(afm->dev, "using boot partition name [%s] (from %s)\n",
|
|
boot_part_name, nbootpart ? "command line" : "kernel config");
|
|
#endif
|
|
|
|
#ifdef CONFIG_MTD_PARTITIONS
|
|
/* Try probing for MTD partitions */
|
|
data->nr_parts = parse_mtd_partitions(&data->mtd,
|
|
part_probes,
|
|
&data->parts, 0);
|
|
|
|
/* Try platform data */
|
|
if (!data->nr_parts && bank->partitions) {
|
|
data->parts = bank->partitions;
|
|
data->nr_parts = bank->nr_partitions;
|
|
}
|
|
|
|
/* Add any partitions that were found */
|
|
if (data->nr_parts) {
|
|
err = add_mtd_partitions(&data->mtd, data->parts,
|
|
data->nr_parts);
|
|
if (err)
|
|
goto err3;
|
|
|
|
#ifdef CONFIG_STM_NAND_AFM_BOOTMODESUPPORT
|
|
/* Update boot-mode slave partition */
|
|
slave = get_mtd_partition_slave(&data->mtd, boot_part_name);
|
|
if (slave) {
|
|
dev_info(afm->dev, "found boot-mode partition "
|
|
"updating ECC paramters\n");
|
|
|
|
part = PART(slave);
|
|
data->boot_start = part->offset;
|
|
data->boot_end = part->offset + slave->size;
|
|
|
|
#ifdef CONFIG_STM_NAND_AFM_PBLBOOTBOUNDARY
|
|
/* Update 'boot_end' with value in PBL image */
|
|
if (pbl_boot_boundary(&data->mtd,
|
|
&boundary) != 0) {
|
|
dev_info(afm->dev, "failed to get boot-mode "
|
|
"boundary from PBL\n");
|
|
} else {
|
|
if (boundary < data->boot_start ||
|
|
boundary > data->boot_end) {
|
|
dev_info(afm->dev,
|
|
"PBL boot-mode "
|
|
"boundary lies outside "
|
|
"boot partition\n");
|
|
} else {
|
|
dev_info(afm->dev,
|
|
"Updating boot-mode "
|
|
"ECC boundary from PBL");
|
|
data->boot_end = boundary;
|
|
}
|
|
}
|
|
#endif
|
|
slave->oobavail =
|
|
data->ecc_boot.ecc_ctrl.layout->oobavail;
|
|
slave->subpage_sft =
|
|
data->ecc_boot.subpage_sft;
|
|
slave->ecclayout =
|
|
data->ecc_boot.ecc_ctrl.layout;
|
|
|
|
dev_info(afm->dev, "boot-mode ECC: 0x%08x->0x%08x\n",
|
|
(unsigned int)data->boot_start,
|
|
(unsigned int)data->boot_end);
|
|
|
|
} else {
|
|
dev_info(afm->dev, "failed to find boot "
|
|
"partition [%s]\n", boot_part_name);
|
|
}
|
|
#endif
|
|
} else
|
|
#endif
|
|
|
|
err = add_mtd_device(&data->mtd);
|
|
|
|
/* Success! */
|
|
if (!err)
|
|
return data;
|
|
|
|
#ifdef CONFIG_MTD_PARTITIONS
|
|
err3:
|
|
if (data->parts && data->parts != bank->partitions)
|
|
kfree(data->parts);
|
|
#endif
|
|
err2:
|
|
kfree(data);
|
|
err1:
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
/*
|
|
* stm-nand-afm device probe
|
|
*/
|
|
static int __init stm_afm_probe(struct platform_device *pdev)
|
|
{
|
|
struct stm_plat_nand_flex_data *pdata = pdev->dev.platform_data;
|
|
struct stm_nand_bank_data *bank;
|
|
struct stm_nand_afm_controller *afm;
|
|
int n;
|
|
int res;
|
|
|
|
afm = afm_init_controller(pdev);
|
|
if (IS_ERR(afm)) {
|
|
dev_err(&pdev->dev, "failed to initialise NAND Controller.\n");
|
|
res = PTR_ERR(afm);
|
|
return res;
|
|
}
|
|
|
|
bank = pdata->banks;
|
|
for (n = 0; n < pdata->nr_banks; n++) {
|
|
afm->devices[n] = afm_init_bank(afm, bank,
|
|
dev_name(&pdev->dev));
|
|
bank++;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, afm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int __devexit stm_afm_remove(struct platform_device *pdev)
|
|
{
|
|
struct stm_plat_nand_flex_data *pdata = pdev->dev.platform_data;
|
|
struct stm_nand_afm_controller *afm = platform_get_drvdata(pdev);
|
|
int n;
|
|
|
|
for (n = 0; n < pdata->nr_banks; n++) {
|
|
struct stm_nand_afm_device *data = afm->devices[n];
|
|
nand_release(&data->mtd);
|
|
|
|
#ifdef CONFIG_MTD_PARTITIONS
|
|
if (data->parts && data->parts != pdata->banks[n].partitions)
|
|
kfree(data->parts);
|
|
#endif
|
|
kfree(data);
|
|
}
|
|
|
|
afm_exit_controller(pdev);
|
|
|
|
platform_set_drvdata(pdev, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver stm_afm_nand_driver = {
|
|
.probe = stm_afm_probe,
|
|
.remove = stm_afm_remove,
|
|
.driver = {
|
|
.name = NAME,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
static int __init stm_afm_nand_init(void)
|
|
{
|
|
return platform_driver_register(&stm_afm_nand_driver);
|
|
}
|
|
|
|
static void __exit stm_afm_nand_exit(void)
|
|
{
|
|
platform_driver_unregister(&stm_afm_nand_driver);
|
|
}
|
|
|
|
module_init(stm_afm_nand_init);
|
|
module_exit(stm_afm_nand_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Angus Clark");
|
|
MODULE_DESCRIPTION("STM AFM-based NAND Flash driver");
|