1296 lines
34 KiB
C
1296 lines
34 KiB
C
|
/*
|
||
|
* ---------------------------------------------------------------------------
|
||
|
* stm_nand_flex.c STMicroelectronics NAND Flash driver: H/W FLEX mode
|
||
|
* ---------------------------------------------------------------------------
|
||
|
*
|
||
|
* Copyright (c) 2009 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.
|
||
|
* ---------------------------------------------------------------------------
|
||
|
*
|
||
|
* Notes:
|
||
|
*
|
||
|
* - Basic implementation initialises the NAND controller and overrides the
|
||
|
* MTD control layer (see flex_cmd_ctrl(), flex_read_byte(),
|
||
|
* flex_write_byte(), flex_rbn() and flex_select_chip()).
|
||
|
*
|
||
|
* - FDMA and NAND Controller in FLEX mode has not been validated. FDMA
|
||
|
* support diabled for the moment.
|
||
|
*
|
||
|
* - Support for BOOT mode partitions has been added. We maintain 2 sets of
|
||
|
* ECC-related parameters, and override the MTD read/write functions to
|
||
|
* switch ECC scheme depending on which partition we are in (see
|
||
|
* flex_setup_eccparam(), flex_select_eccparams(), nand_read(),
|
||
|
* nand_read_oob(), nand_write, nand_write_oob()). Need to force
|
||
|
* chip.options NAND_USE_FLASH_BBT, since BOOT mode ECC layout is
|
||
|
* incompatible with factory-written bad-block markers.
|
||
|
*
|
||
|
* TODO:
|
||
|
*
|
||
|
* - Test board with multiple NAND devices
|
||
|
*
|
||
|
* - Optimise nand_command and nand_command_lp to issue multibyte commands in
|
||
|
* one go.
|
||
|
*
|
||
|
* - Add support for x16 devices. Should just be a matter of configuring
|
||
|
* FLEX data register...
|
||
|
*
|
||
|
* Changelog:
|
||
|
* 2009-03-12 Angus Clark <angus.clark@st.com>
|
||
|
* - first version
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/list.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/mtd/mtd.h>
|
||
|
#include <linux/mtd/nand.h>
|
||
|
#include <linux/dma-mapping.h>
|
||
|
#include <asm/dma.h>
|
||
|
#include <linux/clk.h>
|
||
|
#include <linux/stm/stm-dma.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_nandc_regs.h"
|
||
|
|
||
|
#ifdef CONFIG_MTD_PARTITIONS
|
||
|
#include <linux/mtd/partitions.h>
|
||
|
#endif
|
||
|
|
||
|
#define NAME "stm-nand-flex"
|
||
|
|
||
|
#ifdef CONFIG_STM_NAND_FLEX_BOOTMODESUPPORT
|
||
|
#include "stm_nand_ecc.h"
|
||
|
/* NAND_BOOT uses a different ECC scheme to that of NAND_FLEX/NAND_AFM. In
|
||
|
* order to support the NAND_BOOT partition we need to maintain 2 sets of
|
||
|
* ECC-related paramters, and switch depending which partition we wish to
|
||
|
* access.
|
||
|
*/
|
||
|
struct ecc_params {
|
||
|
/* nand_chip params */
|
||
|
struct nand_ecc_ctrl ecc_ctrl;
|
||
|
int subpagesize;
|
||
|
|
||
|
/* mtd_info params */
|
||
|
u_int32_t subpage_sft;
|
||
|
};
|
||
|
#endif /* CONFIG_STM_NAND_FLEX_BOOTMODESUPPORT */
|
||
|
|
||
|
/* NAND device connected to STM NAND Controller operatring in FLEX mode. (There
|
||
|
* may be several NAND device connected to the NAND controller.)
|
||
|
*/
|
||
|
struct stm_nand_flex_device {
|
||
|
|
||
|
struct nand_chip chip;
|
||
|
struct mtd_info mtd;
|
||
|
int csn;
|
||
|
|
||
|
struct stm_nand_timing_data *timing_data;
|
||
|
|
||
|
#ifdef CONFIG_STM_NAND_FLEX_BOOTMODESUPPORT
|
||
|
unsigned long boot_start;
|
||
|
unsigned long boot_end;
|
||
|
struct ecc_params ecc_boot;
|
||
|
struct ecc_params ecc_flex;
|
||
|
#endif
|
||
|
|
||
|
#ifdef CONFIG_MTD_PARTITIONS
|
||
|
/* Partition Table */
|
||
|
int nr_parts;
|
||
|
struct mtd_partition *parts;
|
||
|
#endif
|
||
|
|
||
|
};
|
||
|
|
||
|
/* STM NAND Controller operating in FLEX mode */
|
||
|
struct stm_nand_flex_controller {
|
||
|
struct resource *mem_region;
|
||
|
void __iomem *base_addr;
|
||
|
|
||
|
int current_csn; /* Current chip */
|
||
|
|
||
|
struct nand_hw_control hwcontrol; /* Aribitrate access */
|
||
|
/* to FLEX devices */
|
||
|
|
||
|
int initialised;
|
||
|
|
||
|
uint8_t *buf; /* Bounce buffer for */
|
||
|
/* non-aligned xfer */
|
||
|
#ifdef CONFIG_STM_NAND_FLEX_CACHED
|
||
|
unsigned long data_phys; /* FLEX data IO */
|
||
|
void __iomem *data_cached;
|
||
|
spinlock_t lock;
|
||
|
#endif
|
||
|
|
||
|
struct stm_nand_flex_device *devices[0];
|
||
|
};
|
||
|
|
||
|
#ifdef CONFIG_STM_NAND_FLEX_BOOTMODESUPPORT
|
||
|
/* The command line passed to nboot_setup() */
|
||
|
__initdata static char *cmdline;
|
||
|
#endif
|
||
|
|
||
|
static struct stm_nand_flex_controller* mtd_to_flex(struct mtd_info *mtd)
|
||
|
{
|
||
|
struct nand_chip *chip = mtd->priv;
|
||
|
struct nand_hw_control *controller = chip->controller;
|
||
|
struct stm_nand_flex_controller *flex;
|
||
|
|
||
|
flex = container_of(controller, struct stm_nand_flex_controller,
|
||
|
hwcontrol);
|
||
|
return flex;
|
||
|
}
|
||
|
|
||
|
#define flex_writereg(val, reg) iowrite32(val, flex->base_addr + (reg))
|
||
|
#define flex_readreg(reg) ioread32(flex->base_addr + (reg))
|
||
|
|
||
|
#ifdef CONFIG_MTD_PARTITIONS
|
||
|
static const char *part_probes[] = { "cmdlinepart", NULL };
|
||
|
#endif
|
||
|
|
||
|
/*** FLEX mode control functions (cf nand_base.c) ***/
|
||
|
|
||
|
/* Assumes EMINAND_DATAREAD has been configured for 1-byte reads. */
|
||
|
static uint8_t flex_read_byte(struct mtd_info *mtd)
|
||
|
{
|
||
|
struct stm_nand_flex_controller *flex = mtd_to_flex(mtd);
|
||
|
uint32_t reg;
|
||
|
|
||
|
reg = flex_readreg(EMINAND_FLEX_DATA);
|
||
|
|
||
|
return (uint8_t)(reg & 0xff);
|
||
|
}
|
||
|
|
||
|
static void flex_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
|
||
|
{
|
||
|
struct stm_nand_flex_controller *flex = mtd_to_flex(mtd);
|
||
|
static unsigned int flex_ctrl;
|
||
|
uint32_t reg;
|
||
|
|
||
|
if (ctrl & NAND_CTRL_CHANGE)
|
||
|
flex_ctrl = ctrl;
|
||
|
|
||
|
if (cmd != NAND_CMD_NONE) {
|
||
|
if (flex_ctrl & NAND_CLE) {
|
||
|
reg = (cmd & 0xff) | FLX_CMD_REG_BEAT_1 |
|
||
|
FLX_CMD_REG_CSN_STATUS;
|
||
|
flex_writereg(reg, EMINAND_FLEX_COMMAND_REG);
|
||
|
} else if (flex_ctrl & NAND_ALE) {
|
||
|
reg = (cmd & 0xff) | FLX_ADDR_REG_ADD8_VALID |
|
||
|
FLX_ADDR_REG_BEAT_1 | FLX_ADDR_REG_CSN_STATUS;
|
||
|
flex_writereg(reg, EMINAND_FLEX_ADDRESS_REG);
|
||
|
} else {
|
||
|
printk(KERN_ERR NAME "%s: unknown ctrl 0x%02x!\n",
|
||
|
__FUNCTION__, flex_ctrl);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Override the default nand_wait() function. If we have access to
|
||
|
* RBn/dev_ready(), then we wait until RBn is asserted before issuing the
|
||
|
* NAND_CMD_STATUS command. If RBn is not available, then we revert to the
|
||
|
* default behaviour and accept a bus stall until the NAND device becomes ready.
|
||
|
*/
|
||
|
static int flex_nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
|
||
|
{
|
||
|
unsigned long timeo = jiffies;
|
||
|
int status, state = chip->state;
|
||
|
|
||
|
if (state == FL_ERASING)
|
||
|
timeo += (HZ * 400) / 1000;
|
||
|
else
|
||
|
timeo += (HZ * 20) / 1000;
|
||
|
|
||
|
/* Apply this short delay always to ensure that we do wait tWB in
|
||
|
* any case on any machine. */
|
||
|
ndelay(100);
|
||
|
|
||
|
if (chip->dev_ready) {
|
||
|
/* If we have access to RBn */
|
||
|
while (time_before(jiffies, timeo)) {
|
||
|
if (chip->dev_ready(mtd))
|
||
|
break;
|
||
|
cond_resched();
|
||
|
}
|
||
|
chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
|
||
|
} else {
|
||
|
/* Else read NAND status register
|
||
|
* (which will stall bus until NAND device is ready) */
|
||
|
chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
|
||
|
while (time_before(jiffies, timeo)) {
|
||
|
if (chip->read_byte(mtd) & NAND_STATUS_READY)
|
||
|
break;
|
||
|
cond_resched();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Get operation status */
|
||
|
status = (int)chip->read_byte(mtd);
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
static int flex_rbn(struct mtd_info *mtd)
|
||
|
{
|
||
|
struct stm_nand_flex_controller *flex = mtd_to_flex(mtd);
|
||
|
|
||
|
/* Apply a small delay before sampling RBn signal */
|
||
|
ndelay(100);
|
||
|
return (flex_readreg(EMINAND_RBN_STATUS) & (0x4)) ? 1 : 0;
|
||
|
}
|
||
|
|
||
|
/* FLEX mode ECC requires 4-byte read/writes. To maintain compatibility with
|
||
|
* MTD framework, the FLEX data register is, by default, configured for 1-byte
|
||
|
* read/writes. Therefore, we must switch temporarily to 4-byte read/writes.
|
||
|
* In addition, readsl/writesl requires buf to be 4-byte aligned. If necessary
|
||
|
* we use flex.buf as bounce buffer.
|
||
|
*/
|
||
|
#ifdef CONFIG_STM_NAND_FLEX_CACHED
|
||
|
static void flex_read_buf_cached(struct mtd_info *mtd, uint8_t *buf, int len)
|
||
|
{
|
||
|
struct stm_nand_flex_controller *flex = mtd_to_flex(mtd);
|
||
|
unsigned long irq_flags;
|
||
|
uint8_t *p;
|
||
|
int notaligned;
|
||
|
int lenaligned;
|
||
|
|
||
|
notaligned = ((uint32_t)buf & 0x3) || (len & 0x3);
|
||
|
|
||
|
/* Handle non-4-byte-aligned buffer */
|
||
|
if (notaligned) {
|
||
|
p = flex->buf;
|
||
|
lenaligned = (len + 0x3) & ~0x3;
|
||
|
} else {
|
||
|
p = buf;
|
||
|
lenaligned = len;
|
||
|
}
|
||
|
|
||
|
/* Switch to 4-byte reads */
|
||
|
flex_writereg(FLX_DATA_CFG_BEAT_4 | FLX_DATA_CFG_CSN_STATUS,
|
||
|
EMINAND_FLEX_DATAREAD_CONFIG);
|
||
|
|
||
|
while (lenaligned > 0) {
|
||
|
spin_lock_irqsave(&(flex->lock), irq_flags);
|
||
|
invalidate_ioremap_region(flex->data_phys,
|
||
|
flex->data_cached, 0, L1_CACHE_BYTES);
|
||
|
|
||
|
memcpy_fromio(p, flex->data_cached,
|
||
|
min(lenaligned, (int)L1_CACHE_BYTES));
|
||
|
spin_unlock_irqrestore(&(flex->lock), irq_flags);
|
||
|
|
||
|
p += 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 writes to FLEX_DATA.
|
||
|
*/
|
||
|
invalidate_ioremap_region(flex->data_phys,
|
||
|
flex->data_cached, 0, L1_CACHE_BYTES);
|
||
|
#endif
|
||
|
|
||
|
if (notaligned)
|
||
|
memcpy(buf, flex->buf, len);
|
||
|
|
||
|
/* Switch back to 1-byte reads */
|
||
|
flex_writereg(FLX_DATA_CFG_BEAT_1 | FLX_DATA_CFG_CSN_STATUS,
|
||
|
EMINAND_FLEX_DATAREAD_CONFIG);
|
||
|
}
|
||
|
#else
|
||
|
static void flex_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
||
|
{
|
||
|
struct stm_nand_flex_controller *flex = mtd_to_flex(mtd);
|
||
|
uint8_t *p;
|
||
|
int notaligned;
|
||
|
uint32_t lenaligned;
|
||
|
|
||
|
notaligned = ((uint32_t)buf & 0x3) || (len & 0x3);
|
||
|
|
||
|
/* Handle non-aligned buffer */
|
||
|
if (notaligned) {
|
||
|
p = flex->buf;
|
||
|
lenaligned = (len + 0x3) & ~0x3;
|
||
|
} else {
|
||
|
p = buf;
|
||
|
lenaligned = len;
|
||
|
}
|
||
|
|
||
|
/* Switch to 4-byte reads (required for ECC) */
|
||
|
flex_writereg(FLX_DATA_CFG_BEAT_4 | FLX_DATA_CFG_CSN_STATUS,
|
||
|
EMINAND_FLEX_DATAREAD_CONFIG);
|
||
|
|
||
|
readsl(flex->base_addr + EMINAND_FLEX_DATA, p, lenaligned/4);
|
||
|
|
||
|
if (notaligned)
|
||
|
memcpy(buf, p, len);
|
||
|
|
||
|
/* Switch back to 1-byte reads */
|
||
|
flex_writereg(FLX_DATA_CFG_BEAT_1 | FLX_DATA_CFG_CSN_STATUS,
|
||
|
EMINAND_FLEX_DATAREAD_CONFIG);
|
||
|
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static void flex_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
||
|
{
|
||
|
struct stm_nand_flex_controller *flex = mtd_to_flex(mtd);
|
||
|
const uint8_t *p;
|
||
|
|
||
|
BUG_ON(len & 0x3);
|
||
|
|
||
|
/* Handle non-aligned buffer */
|
||
|
if ((uint32_t)buf & 0x3) {
|
||
|
p = flex->buf;
|
||
|
memcpy(flex->buf, buf, len);
|
||
|
} else {
|
||
|
p = buf;
|
||
|
}
|
||
|
|
||
|
/* Switch to 4-byte reads (required for ECC) */
|
||
|
flex_writereg(FLX_DATA_CFG_BEAT_4 | FLX_DATA_CFG_CSN_STATUS,
|
||
|
EMINAND_FLEX_DATAWRITE_CONFIG);
|
||
|
|
||
|
writesl(flex->base_addr + EMINAND_FLEX_DATA, p, len/4);
|
||
|
|
||
|
/* Switch back to 1-byte writes */
|
||
|
flex_writereg(FLX_DATA_CFG_BEAT_1 | FLX_DATA_CFG_CSN_STATUS,
|
||
|
EMINAND_FLEX_DATAWRITE_CONFIG);
|
||
|
|
||
|
}
|
||
|
|
||
|
static int flex_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
||
|
{
|
||
|
struct stm_nand_flex_controller *flex = mtd_to_flex(mtd);
|
||
|
uint32_t *p = (uint32_t *)buf;
|
||
|
uint32_t d;
|
||
|
int ret = 0;
|
||
|
int i;
|
||
|
|
||
|
/* Switch to 4-byte reads */
|
||
|
flex_writereg(FLX_DATA_CFG_BEAT_4 | FLX_DATA_CFG_CSN_STATUS,
|
||
|
EMINAND_FLEX_DATAREAD_CONFIG);
|
||
|
|
||
|
for (i = 0; i < len/4; i++) {
|
||
|
d = readl(flex->base_addr + EMINAND_FLEX_DATA);
|
||
|
if (d != *p++) {
|
||
|
ret = -EFAULT;
|
||
|
goto out1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
out1:
|
||
|
/* Switch back to 1-byte reads */
|
||
|
flex_writereg(FLX_DATA_CFG_BEAT_1 | FLX_DATA_CFG_CSN_STATUS,
|
||
|
EMINAND_FLEX_DATAREAD_CONFIG);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_STM_NAND_FLEX_BOOTMODESUPPORT
|
||
|
/* The STMicroelectronics NAND boot-controller uses 3 bytes ECC per 128-byte
|
||
|
* data record. However, the ECC layout clashes with the factory-set bad-block
|
||
|
* markers. To help distinguish between boot-mode ECC blocks and factory-set
|
||
|
* bad-blocks, we use the 4th byte of each OOB record to store a boot-mode ECC
|
||
|
* marker.
|
||
|
*/
|
||
|
static struct nand_ecclayout boot_oob_16 = {
|
||
|
.eccbytes = 16,
|
||
|
.eccpos = {
|
||
|
0, 1, 2, 3, /* 1st 128-byte record: ECC0, ECC1, ECC2, B */
|
||
|
4, 5, 6, 7, /* 2st 128-byte record: 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, /* 1st 128-byte record: ECC0, ECC1, ECC2, B */
|
||
|
4, 5, 6, 7, /* 2st 128-byte record: 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 */
|
||
|
};
|
||
|
|
||
|
static uint8_t scan_ff_pattern[] = { 0xff, 0xff };
|
||
|
static struct nand_bbt_descr bbt_scan_sp = {
|
||
|
.options = NAND_BBT_SCAN2NDPAGE | NAND_BBT_SCANSTMBOOTECC,
|
||
|
.offs = 5,
|
||
|
.len = 1,
|
||
|
.pattern = scan_ff_pattern
|
||
|
};
|
||
|
|
||
|
static struct nand_bbt_descr bbt_scan_lp = {
|
||
|
.options = NAND_BBT_SCAN2NDPAGE | NAND_BBT_SCANSTMBOOTECC,
|
||
|
.offs = 0,
|
||
|
.len = 2,
|
||
|
.pattern = scan_ff_pattern
|
||
|
};
|
||
|
|
||
|
/* Update 'badblock_pattern' to handle STM boot-mode ECC prior to bad-block
|
||
|
* scanning */
|
||
|
static int scan_bbt_stmecc(struct mtd_info *mtd)
|
||
|
{
|
||
|
struct nand_chip *chip = mtd->priv;
|
||
|
|
||
|
chip->badblock_pattern = (mtd->writesize > 512) ?
|
||
|
&bbt_scan_lp : &bbt_scan_sp;
|
||
|
|
||
|
return nand_default_bbt(mtd);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* 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;
|
||
|
struct list_head list;
|
||
|
};
|
||
|
|
||
|
#define PART(x) ((struct mtd_part *)(x))
|
||
|
|
||
|
/* Boot mode ECC calc/correct function */
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/* Setup ECC params for NAND_BOOT and NAND_FLEX. The intial 'nand_scan()' sets
|
||
|
* up ECC parameters for NAND_FLEX mode. Here, we take a copy of the exisiting
|
||
|
* NAND_FLEX paramters, and derive a set of NAND_BOOT paramters.
|
||
|
*/
|
||
|
static void flex_setup_eccparams(struct mtd_info *mtd)
|
||
|
{
|
||
|
struct nand_chip *chip = mtd->priv;
|
||
|
struct stm_nand_flex_device *data = chip->priv;
|
||
|
|
||
|
/* Take a copy of ECC FLEX params, as set up during nand_scan() */
|
||
|
data->ecc_flex.ecc_ctrl = chip->ecc;
|
||
|
data->ecc_flex.subpagesize = chip->subpagesize;
|
||
|
data->ecc_flex.subpage_sft = mtd->subpage_sft;
|
||
|
|
||
|
/* Set ECC BOOT params */
|
||
|
data->ecc_boot.ecc_ctrl = data->ecc_flex.ecc_ctrl;
|
||
|
data->ecc_boot.ecc_ctrl.calculate = boot_calc_ecc;
|
||
|
data->ecc_boot.ecc_ctrl.correct = boot_correct_ecc;
|
||
|
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) {
|
||
|
printk(KERN_WARNING "Invalid ECC parameters\n");
|
||
|
BUG();
|
||
|
}
|
||
|
data->ecc_boot.ecc_ctrl.total = data->ecc_boot.ecc_ctrl.steps *
|
||
|
data->ecc_boot.ecc_ctrl.bytes;
|
||
|
|
||
|
if (!(chip->options & NAND_NO_SUBPAGE_WRITE) &&
|
||
|
!(chip->cellinfo & NAND_CI_CELLTYPE_MSK)) {
|
||
|
switch (data->ecc_boot.ecc_ctrl.steps) {
|
||
|
case 2:
|
||
|
data->ecc_boot.subpage_sft = 1;
|
||
|
break;
|
||
|
case 4:
|
||
|
case 8:
|
||
|
case 16:
|
||
|
data->ecc_boot.subpage_sft = 2;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
data->ecc_boot.subpagesize = mtd->writesize >>
|
||
|
data->ecc_boot.subpage_sft;
|
||
|
}
|
||
|
|
||
|
/* Set MTD to use ECC params */
|
||
|
static void flex_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 flex_select_eccparams(struct mtd_info *mtd, loff_t offs)
|
||
|
{
|
||
|
struct nand_chip *chip = mtd->priv;
|
||
|
struct stm_nand_flex_device *data = chip->priv;
|
||
|
|
||
|
if (offs >= data->boot_start &&
|
||
|
offs < data->boot_end) {
|
||
|
if (chip->ecc.layout != data->ecc_boot.ecc_ctrl.layout) {
|
||
|
DEBUG(MTD_DEBUG_LEVEL0, NAME
|
||
|
": Switching to BOOT mode ECC\n");
|
||
|
flex_set_eccparams(mtd, &data->ecc_boot);
|
||
|
}
|
||
|
} else {
|
||
|
if (chip->ecc.layout != data->ecc_flex.ecc_ctrl.layout) {
|
||
|
DEBUG(MTD_DEBUG_LEVEL0, NAME
|
||
|
": Switching to FLEX mode ECC\n");
|
||
|
flex_set_eccparams(mtd, &data->ecc_flex);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* nand_base.c functions required by MTD interface functions defined below */
|
||
|
int nand_get_device(struct nand_chip *chip, struct mtd_info *mtd,
|
||
|
int new_state);
|
||
|
void nand_release_device(struct mtd_info *mtd);
|
||
|
int nand_do_read_oob(struct mtd_info *mtd, loff_t from,
|
||
|
struct mtd_oob_ops *ops);
|
||
|
int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
|
||
|
struct mtd_oob_ops *ops);
|
||
|
int nand_do_write_oob(struct mtd_info *mtd, loff_t to,
|
||
|
struct mtd_oob_ops *ops);
|
||
|
int nand_do_write_ops(struct mtd_info *mtd, loff_t to,
|
||
|
struct mtd_oob_ops *ops);
|
||
|
|
||
|
/* Override the following functions (see nand_base.c), so that we can switch ECC
|
||
|
* scheme before starting xfer sequence.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* nand_read - [MTD Interface] MTD compability function for nand_do_read_ecc
|
||
|
* @mtd: MTD device structure
|
||
|
* @from: offset to read from
|
||
|
* @len: number of bytes to read
|
||
|
* @retlen: pointer to variable to store the number of read bytes
|
||
|
* @buf: the databuffer to put data
|
||
|
*
|
||
|
* Get hold of the chip and call nand_do_read
|
||
|
*/
|
||
|
static int nand_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);
|
||
|
|
||
|
/** Added to support switching ECC format **/
|
||
|
flex_select_eccparams(mtd, from);
|
||
|
|
||
|
chip->ops.len = len;
|
||
|
chip->ops.datbuf = buf;
|
||
|
chip->ops.oobbuf = NULL;
|
||
|
|
||
|
ret = nand_do_read_ops(mtd, from, &chip->ops);
|
||
|
|
||
|
*retlen = chip->ops.retlen;
|
||
|
|
||
|
nand_release_device(mtd);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* nand_read_oob - [MTD Interface] NAND read data and/or out-of-band
|
||
|
* @mtd: MTD device structure
|
||
|
* @from: offset to read from
|
||
|
* @ops: oob operation description structure
|
||
|
*
|
||
|
* NAND read data and/or out-of-band data
|
||
|
*/
|
||
|
static int nand_read_oob(struct mtd_info *mtd, loff_t from,
|
||
|
struct mtd_oob_ops *ops)
|
||
|
{
|
||
|
struct nand_chip *chip = mtd->priv;
|
||
|
int ret = -ENOTSUPP;
|
||
|
|
||
|
ops->retlen = 0;
|
||
|
|
||
|
/* 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);
|
||
|
|
||
|
/** Added to support switching ECC format **/
|
||
|
flex_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 = nand_do_read_oob(mtd, from, ops);
|
||
|
else
|
||
|
ret = nand_do_read_ops(mtd, from, ops);
|
||
|
|
||
|
out:
|
||
|
nand_release_device(mtd);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* nand_write - [MTD Interface] NAND write with ECC
|
||
|
* @mtd: MTD device structure
|
||
|
* @to: offset to write to
|
||
|
* @len: number of bytes to write
|
||
|
* @retlen: pointer to variable to store the number of written bytes
|
||
|
* @buf: the data to write
|
||
|
*
|
||
|
* NAND write with ECC
|
||
|
*/
|
||
|
static int nand_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);
|
||
|
|
||
|
/** Added to support switching ECC format **/
|
||
|
flex_select_eccparams(mtd, to);
|
||
|
|
||
|
chip->ops.len = len;
|
||
|
chip->ops.datbuf = (uint8_t *)buf;
|
||
|
chip->ops.oobbuf = NULL;
|
||
|
|
||
|
ret = nand_do_write_ops(mtd, to, &chip->ops);
|
||
|
|
||
|
*retlen = chip->ops.retlen;
|
||
|
|
||
|
nand_release_device(mtd);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* nand_write_oob - [MTD Interface] NAND write data and/or out-of-band
|
||
|
* @mtd: MTD device structure
|
||
|
* @to: offset to write to
|
||
|
* @ops: oob operation description structure
|
||
|
*/
|
||
|
static int nand_write_oob(struct mtd_info *mtd, loff_t to,
|
||
|
struct mtd_oob_ops *ops)
|
||
|
{
|
||
|
struct nand_chip *chip = mtd->priv;
|
||
|
int ret = -ENOTSUPP;
|
||
|
|
||
|
ops->retlen = 0;
|
||
|
|
||
|
/* 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);
|
||
|
|
||
|
flex_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 = nand_do_write_oob(mtd, to, ops);
|
||
|
else
|
||
|
ret = nand_do_write_ops(mtd, to, ops);
|
||
|
|
||
|
out:
|
||
|
nand_release_device(mtd);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#endif /* CONFIG_STM_NAND_FLEX_BOOTMODESUPPORT */
|
||
|
|
||
|
|
||
|
/* Configure NAND controller timing registers */
|
||
|
static void flex_set_timings(struct stm_nand_flex_controller *flex,
|
||
|
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;
|
||
|
|
||
|
DEBUG(MTD_DEBUG_LEVEL0, "%s: CONTROL_TIMING = 0x%08x\n", NAME, reg);
|
||
|
flex_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;
|
||
|
|
||
|
DEBUG(MTD_DEBUG_LEVEL0, "%s: WEN_TIMING = 0x%08x\n", NAME, reg);
|
||
|
flex_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;
|
||
|
|
||
|
DEBUG(MTD_DEBUG_LEVEL0, "%s: REN_TIMING = 0x%08x\n", NAME, reg);
|
||
|
flex_writereg(reg, EMINAND_REN_TIMING);
|
||
|
}
|
||
|
|
||
|
/* FLEX mode chip select: For now we only support 1 chip per
|
||
|
* 'stm_nand_flex_device' so chipnr will be 0 for select, -1 for deselect.
|
||
|
*
|
||
|
* So, if we change device:
|
||
|
* - Set bank in mux_control_reg to data->csn
|
||
|
* - Update read/write timings
|
||
|
*/
|
||
|
static void flex_select_chip(struct mtd_info *mtd, int chipnr)
|
||
|
{
|
||
|
struct stm_nand_flex_controller *flex = mtd_to_flex(mtd);
|
||
|
struct nand_chip *chip = mtd->priv;
|
||
|
struct stm_nand_flex_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 == flex->current_csn)
|
||
|
return;
|
||
|
|
||
|
/* Set CSn on FLEX controller */
|
||
|
flex->current_csn = data->csn;
|
||
|
flex_writereg(0x1 << data->csn, EMINAND_MUXCONTROL_REG);
|
||
|
|
||
|
/* Set up timing parameters */
|
||
|
flex_set_timings(flex, data->timing_data);
|
||
|
|
||
|
} else {
|
||
|
printk(KERN_ERR NAME ": attempt to select chipnr = %d\n",
|
||
|
chipnr);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_MTD_DEBUG
|
||
|
static void flex_print_regs(struct stm_nand_flex_controller *flex)
|
||
|
{
|
||
|
printk(NAME ": FLEX Registers:\n");
|
||
|
printk(KERN_INFO "\tbootbank_config = 0x%08x\n",
|
||
|
(unsigned int)flex_readreg(EMINAND_BOOTBANK_CONFIG));
|
||
|
printk(KERN_INFO "\trbn_status = 0x%08x\n",
|
||
|
(unsigned int)flex_readreg(EMINAND_RBN_STATUS));
|
||
|
printk(KERN_INFO "\tinterrupt_enable = 0x%08x\n",
|
||
|
(unsigned int)flex_readreg(EMINAND_INTERRUPT_ENABLE));
|
||
|
printk(KERN_INFO "\tinterrupt_status = 0x%08x\n",
|
||
|
(unsigned int)flex_readreg(EMINAND_INTERRUPT_STATUS));
|
||
|
printk(KERN_INFO "\tinterrupt_clear = 0x%08x\n",
|
||
|
(unsigned int)flex_readreg(EMINAND_INTERRUPT_CLEAR));
|
||
|
printk(KERN_INFO "\tinterrupt_edgeconfig = 0x%08x\n",
|
||
|
(unsigned int)flex_readreg(EMINAND_INTERRUPT_EDGECONFIG));
|
||
|
printk(KERN_INFO "\tcontrol_timing = 0x%08x\n",
|
||
|
(unsigned int)flex_readreg(EMINAND_CONTROL_TIMING));
|
||
|
printk(KERN_INFO "\twen_timing = 0x%08x\n",
|
||
|
(unsigned int)flex_readreg(EMINAND_WEN_TIMING));
|
||
|
printk(KERN_INFO "\tren_timing = 0x%08x\n",
|
||
|
(unsigned int)flex_readreg(EMINAND_REN_TIMING));
|
||
|
printk(KERN_INFO "\tflexmode_config = 0x%08x\n",
|
||
|
(unsigned int)flex_readreg(EMINAND_FLEXMODE_CONFIG));
|
||
|
printk(KERN_INFO "\tmuxcontrol_reg = 0x%08x\n",
|
||
|
(unsigned int)flex_readreg(EMINAND_MUXCONTROL_REG));
|
||
|
printk(KERN_INFO "\tcsn_alternate_reg = 0x%08x\n",
|
||
|
(unsigned int)flex_readreg(EMINAND_CSN_ALTERNATE));
|
||
|
printk(KERN_INFO "\tmulti_cs_config_reg = 0x%08x\n",
|
||
|
(unsigned int)flex_readreg(EMINAND_MULTI_CS_CONFIG_REG));
|
||
|
printk(KERN_INFO "\tversion_reg = 0x%08x\n",
|
||
|
(unsigned int)flex_readreg(EMINAND_VERSION_REG));
|
||
|
}
|
||
|
#endif /* CONFIG_MTD_DEBUG */
|
||
|
|
||
|
static struct stm_nand_flex_controller * __init
|
||
|
flex_init_controller(struct platform_device *pdev)
|
||
|
{
|
||
|
struct stm_plat_nand_flex_data *pdata = pdev->dev.platform_data;
|
||
|
struct resource *resource;
|
||
|
int res;
|
||
|
struct stm_nand_flex_controller *flex;
|
||
|
|
||
|
flex = kzalloc(sizeof(struct stm_nand_flex_controller) +
|
||
|
(sizeof(struct stm_nand_flex_device *) * pdata->nr_banks),
|
||
|
GFP_KERNEL);
|
||
|
if (!flex)
|
||
|
return ERR_PTR(-ENOMEM);
|
||
|
|
||
|
/* Request IO Memory */
|
||
|
resource = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||
|
"flex_mem");
|
||
|
if (!resource) {
|
||
|
printk(KERN_ERR NAME ": Failed to get FLEX IORESOURCE_MEM.\n");
|
||
|
res = -ENODEV;
|
||
|
goto out1;
|
||
|
}
|
||
|
flex->mem_region = request_mem_region(resource->start,
|
||
|
resource->end -
|
||
|
resource->start + 1,
|
||
|
pdev->name);
|
||
|
if (!flex->mem_region) {
|
||
|
printk(KERN_ERR NAME ": Failed request memory region 0x%08x.\n",
|
||
|
pdev->resource[0].start);
|
||
|
res = -EBUSY;
|
||
|
goto out1;
|
||
|
}
|
||
|
|
||
|
/* Map base address */
|
||
|
flex->base_addr = ioremap_nocache(resource->start,
|
||
|
resource->end - resource->start + 1);
|
||
|
if (!flex->base_addr) {
|
||
|
printk(KERN_ERR NAME " Failed tp map base address 0x%08x\n",
|
||
|
resource->start);
|
||
|
res = -EINVAL;
|
||
|
goto out2;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_STM_NAND_FLEX_CACHED
|
||
|
flex->data_phys = resource->start + EMINAND_FLEX_DATA;
|
||
|
flex->data_cached = ioremap_cache(flex->data_phys, L1_CACHE_BYTES);
|
||
|
if (!flex->data_cached) {
|
||
|
printk(KERN_ERR NAME " Failed to map data reg 0x%08x\n",
|
||
|
resource->start + EMINAND_FLEX_DATA);
|
||
|
res = -EINVAL;
|
||
|
goto out3;
|
||
|
}
|
||
|
spin_lock_init(&flex->lock);
|
||
|
#endif
|
||
|
|
||
|
flex->buf = kmalloc(NAND_MAX_PAGESIZE + NAND_MAX_OOBSIZE,
|
||
|
GFP_KERNEL | __GFP_DMA);
|
||
|
if (!flex->buf) {
|
||
|
printk(KERN_ERR NAME " Failed allocate bounce buffer\n");
|
||
|
res = -ENOMEM;
|
||
|
goto out4;
|
||
|
}
|
||
|
|
||
|
flex->current_csn = -1;
|
||
|
|
||
|
/* Initialise 'controller' structure */
|
||
|
spin_lock_init(&flex->hwcontrol.lock);
|
||
|
init_waitqueue_head(&flex->hwcontrol.wq);
|
||
|
|
||
|
/* Disable boot_not_flex */
|
||
|
flex_writereg(0x00000000, EMINAND_BOOTBANK_CONFIG);
|
||
|
|
||
|
/* Reset FLEX Controller */
|
||
|
flex_writereg((0x1 << 3), EMINAND_FLEXMODE_CONFIG);
|
||
|
udelay(1);
|
||
|
flex_writereg(0x00, EMINAND_FLEXMODE_CONFIG);
|
||
|
|
||
|
/* Set Controller to FLEX mode */
|
||
|
flex_writereg(0x00000001, EMINAND_FLEXMODE_CONFIG);
|
||
|
|
||
|
/* Not using interrupts in FLEX mode */
|
||
|
flex_writereg(0x00, EMINAND_INTERRUPT_ENABLE);
|
||
|
|
||
|
/* To fit with MTD framework, configure FLEX_DATA reg for 1-byte
|
||
|
* read/writes, and deassert CSn
|
||
|
*/
|
||
|
flex_writereg(FLX_DATA_CFG_BEAT_1 | FLX_DATA_CFG_CSN_STATUS,
|
||
|
EMINAND_FLEX_DATAWRITE_CONFIG);
|
||
|
flex_writereg(FLX_DATA_CFG_BEAT_1 | FLX_DATA_CFG_CSN_STATUS,
|
||
|
EMINAND_FLEX_DATAREAD_CONFIG);
|
||
|
|
||
|
#ifdef CONFIG_MTD_DEBUG
|
||
|
flex_print_regs(flex);
|
||
|
#endif
|
||
|
|
||
|
return flex;
|
||
|
out4:
|
||
|
#ifdef CONFIG_STM_NAND_FLEX_CACHED
|
||
|
iounmap(flex->data_cached);
|
||
|
out3:
|
||
|
#endif
|
||
|
iounmap(flex->base_addr);
|
||
|
out2:
|
||
|
release_resource(flex->mem_region);
|
||
|
out1:
|
||
|
return ERR_PTR(res);
|
||
|
}
|
||
|
|
||
|
static void __devexit flex_exit_controller(struct platform_device *pdev)
|
||
|
{
|
||
|
struct stm_nand_flex_controller *flex = platform_get_drvdata(pdev);
|
||
|
|
||
|
kfree(flex->buf);
|
||
|
iounmap(flex->base_addr);
|
||
|
#ifdef CONFIG_STM_NAND_FLEX_CACHED
|
||
|
iounmap(flex->data_cached);
|
||
|
#endif
|
||
|
release_resource(flex->mem_region);
|
||
|
}
|
||
|
|
||
|
static struct stm_nand_flex_device * __init
|
||
|
flex_init_bank(struct stm_nand_flex_controller *flex,
|
||
|
struct stm_nand_bank_data *bank,
|
||
|
int rbn_connected, const char *name)
|
||
|
{
|
||
|
struct stm_nand_flex_device *data;
|
||
|
int res;
|
||
|
|
||
|
#ifdef CONFIG_STM_NAND_FLEX_BOOTMODESUPPORT
|
||
|
struct mtd_info *slave;
|
||
|
struct mtd_part *part;
|
||
|
char *boot_part_name;
|
||
|
#endif
|
||
|
|
||
|
/* Allocate memory for the device structure (and zero it) */
|
||
|
data = kzalloc(sizeof(struct stm_nand_flex_device), GFP_KERNEL);
|
||
|
if (!data) {
|
||
|
printk(KERN_ERR NAME
|
||
|
": Failed to allocate device structure.\n");
|
||
|
res = -ENOMEM;
|
||
|
goto out1;
|
||
|
}
|
||
|
|
||
|
/* House keeping :-) */
|
||
|
data->chip.priv = data;
|
||
|
data->mtd.priv = &data->chip;
|
||
|
data->mtd.owner = THIS_MODULE;
|
||
|
|
||
|
/* Assign more sensible name (default is string from nand_ids.c!) */
|
||
|
data->mtd.name = name;
|
||
|
data->csn = bank->csn;
|
||
|
|
||
|
/* Use hwcontrol structure to manage access to FLEX Controller */
|
||
|
data->chip.controller = &flex->hwcontrol;
|
||
|
data->chip.state = FL_READY;
|
||
|
|
||
|
/* Get chip's timing data */
|
||
|
data->timing_data = bank->timing_data;
|
||
|
|
||
|
/* Copy over chip specific platform data */
|
||
|
data->chip.chip_delay = data->timing_data->chip_delay;
|
||
|
data->chip.options = bank->options;
|
||
|
data->chip.options |= NAND_NO_AUTOINCR; /* Not tested, disable */
|
||
|
|
||
|
#ifdef CONFIG_STM_NAND_FLEX_BOOTMODESUPPORT
|
||
|
/* Handle STM H/W ECC layouts when performing initial scan for
|
||
|
* bad-blocks */
|
||
|
data->chip.scan_bbt = scan_bbt_stmecc;
|
||
|
#endif
|
||
|
/* Callbacks for FLEX mode operation */
|
||
|
data->chip.cmd_ctrl = flex_cmd_ctrl;
|
||
|
data->chip.select_chip = flex_select_chip;
|
||
|
data->chip.read_byte = flex_read_byte;
|
||
|
#ifdef CONFIG_STM_NAND_FLEX_CACHED
|
||
|
data->chip.read_buf = flex_read_buf_cached;
|
||
|
#else
|
||
|
data->chip.read_buf = flex_read_buf;
|
||
|
#endif
|
||
|
data->chip.write_buf = flex_write_buf;
|
||
|
data->chip.verify_buf = flex_verify_buf;
|
||
|
data->chip.waitfunc = flex_nand_wait;
|
||
|
if (rbn_connected)
|
||
|
data->chip.dev_ready = flex_rbn;
|
||
|
|
||
|
/* For now, use NAND_ECC_SOFT. Callbacks filled in during scan() */
|
||
|
data->chip.ecc.mode = NAND_ECC_SOFT;
|
||
|
|
||
|
/* Data IO */
|
||
|
data->chip.IO_ADDR_R = flex->base_addr + EMINAND_FLEX_DATA;
|
||
|
data->chip.IO_ADDR_W = flex->base_addr + EMINAND_FLEX_DATA;
|
||
|
|
||
|
#if defined(CONFIG_CPU_SUBTYPE_STX7200)
|
||
|
/* Reset AFM program. Why!?! */
|
||
|
flex_readreg(EMINAND_AFM_SEQUENCE_STATUS_REG);
|
||
|
memset(flex->base_addr + EMINAND_AFM_SEQUENCE_REG_1, 0, 32);
|
||
|
#endif
|
||
|
|
||
|
/* Scan to find existance of the device */
|
||
|
if (nand_scan(&data->mtd, 1)) {
|
||
|
printk(KERN_ERR NAME ":nand_scan failed\n");
|
||
|
res = -ENXIO;
|
||
|
goto out2;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_STM_NAND_FLEX_BOOTMODESUPPORT
|
||
|
if (data->chip.ecc.mode == NAND_ECC_4BITONDIE) {
|
||
|
printk(KERN_ERR NAME ": boot-mode ECC not supported on "
|
||
|
"4-bit on-die ECC devices\n");
|
||
|
res = -ENXIO;
|
||
|
goto out2;
|
||
|
}
|
||
|
|
||
|
/* Setup ECC params, for NORMAL and BOOT operation */
|
||
|
flex_setup_eccparams(&data->mtd);
|
||
|
/* Override MTD NAND interface, to allow us to provide BOOT SUPPORT */
|
||
|
data->mtd.read = nand_read;
|
||
|
data->mtd.write = nand_write;
|
||
|
data->mtd.read_oob = nand_read_oob;
|
||
|
data->mtd.write_oob = nand_write_oob;
|
||
|
|
||
|
/* Set name of boot partition */
|
||
|
boot_part_name = cmdline ? cmdline : CONFIG_STM_NAND_FLEX_BOOTPARTITION;
|
||
|
printk(KERN_INFO NAME ": Using boot partition name [%s] (from %s)\n",
|
||
|
boot_part_name, cmdline ? "command line" : "kernel config");
|
||
|
|
||
|
#endif
|
||
|
|
||
|
#ifdef CONFIG_MTD_PARTITIONS
|
||
|
/* Try parsing commandline paritions */
|
||
|
data->nr_parts = parse_mtd_partitions(&data->mtd, part_probes,
|
||
|
&data->parts, 0);
|
||
|
|
||
|
/* Try platfotm 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) {
|
||
|
res = add_mtd_partitions(&data->mtd,
|
||
|
data->parts, data->nr_parts);
|
||
|
if (res)
|
||
|
goto out2;
|
||
|
|
||
|
#ifdef CONFIG_STM_NAND_FLEX_BOOTMODESUPPORT
|
||
|
/* Update boot-mode slave partition */
|
||
|
slave = get_mtd_partition_slave(&data->mtd, boot_part_name);
|
||
|
if (slave) {
|
||
|
printk(KERN_INFO NAME ": Found BOOT parition"
|
||
|
"[%s], updating ECC paramters\n",
|
||
|
slave->name);
|
||
|
|
||
|
part = PART(slave);
|
||
|
data->boot_start = part->offset;
|
||
|
data->boot_end = part->offset + slave->size;
|
||
|
|
||
|
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;
|
||
|
|
||
|
printk("boot-ECC 0x%08x->0x%08x\n",
|
||
|
(unsigned int)data->boot_start,
|
||
|
(unsigned int)data->boot_end);
|
||
|
|
||
|
} else {
|
||
|
printk(KERN_WARNING NAME ": Failed to find boot "
|
||
|
"partition [%s]\n", boot_part_name);
|
||
|
}
|
||
|
#endif
|
||
|
} else
|
||
|
#endif
|
||
|
res = add_mtd_device(&data->mtd);
|
||
|
|
||
|
if (!res)
|
||
|
return data;
|
||
|
|
||
|
out2:
|
||
|
#ifdef CONFIG_MTD_PARTITIONS
|
||
|
if (data->parts && data->parts != bank->partitions)
|
||
|
kfree(data->parts);
|
||
|
#endif
|
||
|
|
||
|
kfree(data);
|
||
|
out1:
|
||
|
return ERR_PTR(res);
|
||
|
}
|
||
|
|
||
|
static int __init stm_nand_flex_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct stm_plat_nand_flex_data *pdata = pdev->dev.platform_data;
|
||
|
int res;
|
||
|
int n;
|
||
|
struct stm_nand_bank_data *bank;
|
||
|
struct stm_nand_flex_controller *flex;
|
||
|
|
||
|
flex = flex_init_controller(pdev);
|
||
|
if (IS_ERR(flex)) {
|
||
|
dev_err(&pdev->dev, "Failed to initialise NAND Controller.\n");
|
||
|
res = PTR_ERR(flex);
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
bank = pdata->banks;
|
||
|
for (n=0; n<pdata->nr_banks; n++) {
|
||
|
flex->devices[n] = flex_init_bank(flex, bank,
|
||
|
pdata->flex_rbn_connected,
|
||
|
dev_name(&pdev->dev));
|
||
|
bank++;
|
||
|
}
|
||
|
|
||
|
platform_set_drvdata(pdev, flex);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int __devexit stm_nand_flex_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
struct stm_plat_nand_flex_data *pdata = pdev->dev.platform_data;
|
||
|
struct stm_nand_flex_controller *flex = platform_get_drvdata(pdev);
|
||
|
int n;
|
||
|
|
||
|
for (n=0; n<pdata->nr_banks; n++) {
|
||
|
struct stm_nand_flex_device *data = flex->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);
|
||
|
}
|
||
|
|
||
|
flex_exit_controller(pdev);
|
||
|
|
||
|
platform_set_drvdata(pdev, NULL);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct platform_driver stm_nand_flex_driver = {
|
||
|
.probe = stm_nand_flex_probe,
|
||
|
.remove = stm_nand_flex_remove,
|
||
|
.driver = {
|
||
|
.name = NAME,
|
||
|
.owner = THIS_MODULE,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
#ifdef CONFIG_STM_NAND_FLEX_BOOTMODESUPPORT
|
||
|
static int __init bootpart_setup(char *s)
|
||
|
{
|
||
|
cmdline = s;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
__setup("nbootpart=", bootpart_setup);
|
||
|
#endif
|
||
|
|
||
|
static int __init stm_nand_flex_init(void)
|
||
|
{
|
||
|
return platform_driver_register(&stm_nand_flex_driver);
|
||
|
}
|
||
|
|
||
|
static void __exit stm_nand_flex_exit(void)
|
||
|
{
|
||
|
platform_driver_unregister(&stm_nand_flex_driver);
|
||
|
}
|
||
|
|
||
|
module_init(stm_nand_flex_init);
|
||
|
module_exit(stm_nand_flex_exit);
|
||
|
|
||
|
MODULE_LICENSE("GPL");
|
||
|
MODULE_AUTHOR("Angus Clark");
|
||
|
MODULE_DESCRIPTION("STMicroelectronics NAND driver: H/W FLEX mode");
|