![Mike Frysinger](/assets/img/avatar_default.png)
The bbt structure isn't actually used, just the badblockpos. This lets the driver correctly handle badblocks with the different OOB layout with certain sized flashes. Previously, the blocks would all be reported as bad and be completely unusable. Signed-off-by: Mike Frysinger <vapier@gentoo.org> Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
898 lines
20 KiB
C
898 lines
20 KiB
C
/* linux/drivers/mtd/nand/bf5xx_nand.c
|
|
*
|
|
* Copyright 2006-2008 Analog Devices Inc.
|
|
* http://blackfin.uclinux.org/
|
|
* Bryan Wu <bryan.wu@analog.com>
|
|
*
|
|
* Blackfin BF5xx on-chip NAND flash controller driver
|
|
*
|
|
* Derived from drivers/mtd/nand/s3c2410.c
|
|
* Copyright (c) 2007 Ben Dooks <ben@simtec.co.uk>
|
|
*
|
|
* Derived from drivers/mtd/nand/cafe.c
|
|
* Copyright © 2006 Red Hat, Inc.
|
|
* Copyright © 2006 David Woodhouse <dwmw2@infradead.org>
|
|
*
|
|
* Changelog:
|
|
* 12-Jun-2007 Bryan Wu: Initial version
|
|
* 18-Jul-2007 Bryan Wu:
|
|
* - ECC_HW and ECC_SW supported
|
|
* - DMA supported in ECC_HW
|
|
* - YAFFS tested as rootfs in both ECC_HW and ECC_SW
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/types.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/string.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/err.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/io.h>
|
|
#include <linux/bitops.h>
|
|
|
|
#include <linux/mtd/mtd.h>
|
|
#include <linux/mtd/nand.h>
|
|
#include <linux/mtd/nand_ecc.h>
|
|
#include <linux/mtd/partitions.h>
|
|
|
|
#include <asm/blackfin.h>
|
|
#include <asm/dma.h>
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/nand.h>
|
|
#include <asm/portmux.h>
|
|
|
|
#define DRV_NAME "bf5xx-nand"
|
|
#define DRV_VERSION "1.2"
|
|
#define DRV_AUTHOR "Bryan Wu <bryan.wu@analog.com>"
|
|
#define DRV_DESC "BF5xx on-chip NAND FLash Controller Driver"
|
|
|
|
/* NFC_STAT Masks */
|
|
#define NBUSY 0x01 /* Not Busy */
|
|
#define WB_FULL 0x02 /* Write Buffer Full */
|
|
#define PG_WR_STAT 0x04 /* Page Write Pending */
|
|
#define PG_RD_STAT 0x08 /* Page Read Pending */
|
|
#define WB_EMPTY 0x10 /* Write Buffer Empty */
|
|
|
|
/* NFC_IRQSTAT Masks */
|
|
#define NBUSYIRQ 0x01 /* Not Busy IRQ */
|
|
#define WB_OVF 0x02 /* Write Buffer Overflow */
|
|
#define WB_EDGE 0x04 /* Write Buffer Edge Detect */
|
|
#define RD_RDY 0x08 /* Read Data Ready */
|
|
#define WR_DONE 0x10 /* Page Write Done */
|
|
|
|
/* NFC_RST Masks */
|
|
#define ECC_RST 0x01 /* ECC (and NFC counters) Reset */
|
|
|
|
/* NFC_PGCTL Masks */
|
|
#define PG_RD_START 0x01 /* Page Read Start */
|
|
#define PG_WR_START 0x02 /* Page Write Start */
|
|
|
|
#ifdef CONFIG_MTD_NAND_BF5XX_HWECC
|
|
static int hardware_ecc = 1;
|
|
#else
|
|
static int hardware_ecc;
|
|
#endif
|
|
|
|
static const unsigned short bfin_nfc_pin_req[] =
|
|
{P_NAND_CE,
|
|
P_NAND_RB,
|
|
P_NAND_D0,
|
|
P_NAND_D1,
|
|
P_NAND_D2,
|
|
P_NAND_D3,
|
|
P_NAND_D4,
|
|
P_NAND_D5,
|
|
P_NAND_D6,
|
|
P_NAND_D7,
|
|
P_NAND_WE,
|
|
P_NAND_RE,
|
|
P_NAND_CLE,
|
|
P_NAND_ALE,
|
|
0};
|
|
|
|
#ifdef CONFIG_MTD_NAND_BF5XX_BOOTROM_ECC
|
|
static struct nand_ecclayout bootrom_ecclayout = {
|
|
.eccbytes = 24,
|
|
.eccpos = {
|
|
0x8 * 0, 0x8 * 0 + 1, 0x8 * 0 + 2,
|
|
0x8 * 1, 0x8 * 1 + 1, 0x8 * 1 + 2,
|
|
0x8 * 2, 0x8 * 2 + 1, 0x8 * 2 + 2,
|
|
0x8 * 3, 0x8 * 3 + 1, 0x8 * 3 + 2,
|
|
0x8 * 4, 0x8 * 4 + 1, 0x8 * 4 + 2,
|
|
0x8 * 5, 0x8 * 5 + 1, 0x8 * 5 + 2,
|
|
0x8 * 6, 0x8 * 6 + 1, 0x8 * 6 + 2,
|
|
0x8 * 7, 0x8 * 7 + 1, 0x8 * 7 + 2
|
|
},
|
|
.oobfree = {
|
|
{ 0x8 * 0 + 3, 5 },
|
|
{ 0x8 * 1 + 3, 5 },
|
|
{ 0x8 * 2 + 3, 5 },
|
|
{ 0x8 * 3 + 3, 5 },
|
|
{ 0x8 * 4 + 3, 5 },
|
|
{ 0x8 * 5 + 3, 5 },
|
|
{ 0x8 * 6 + 3, 5 },
|
|
{ 0x8 * 7 + 3, 5 },
|
|
}
|
|
};
|
|
#endif
|
|
|
|
/*
|
|
* Data structures for bf5xx nand flash controller driver
|
|
*/
|
|
|
|
/* bf5xx nand info */
|
|
struct bf5xx_nand_info {
|
|
/* mtd info */
|
|
struct nand_hw_control controller;
|
|
struct mtd_info mtd;
|
|
struct nand_chip chip;
|
|
|
|
/* platform info */
|
|
struct bf5xx_nand_platform *platform;
|
|
|
|
/* device info */
|
|
struct device *device;
|
|
|
|
/* DMA stuff */
|
|
struct completion dma_completion;
|
|
};
|
|
|
|
/*
|
|
* Conversion functions
|
|
*/
|
|
static struct bf5xx_nand_info *mtd_to_nand_info(struct mtd_info *mtd)
|
|
{
|
|
return container_of(mtd, struct bf5xx_nand_info, mtd);
|
|
}
|
|
|
|
static struct bf5xx_nand_info *to_nand_info(struct platform_device *pdev)
|
|
{
|
|
return platform_get_drvdata(pdev);
|
|
}
|
|
|
|
static struct bf5xx_nand_platform *to_nand_plat(struct platform_device *pdev)
|
|
{
|
|
return pdev->dev.platform_data;
|
|
}
|
|
|
|
/*
|
|
* struct nand_chip interface function pointers
|
|
*/
|
|
|
|
/*
|
|
* bf5xx_nand_hwcontrol
|
|
*
|
|
* Issue command and address cycles to the chip
|
|
*/
|
|
static void bf5xx_nand_hwcontrol(struct mtd_info *mtd, int cmd,
|
|
unsigned int ctrl)
|
|
{
|
|
if (cmd == NAND_CMD_NONE)
|
|
return;
|
|
|
|
while (bfin_read_NFC_STAT() & WB_FULL)
|
|
cpu_relax();
|
|
|
|
if (ctrl & NAND_CLE)
|
|
bfin_write_NFC_CMD(cmd);
|
|
else if (ctrl & NAND_ALE)
|
|
bfin_write_NFC_ADDR(cmd);
|
|
SSYNC();
|
|
}
|
|
|
|
/*
|
|
* bf5xx_nand_devready()
|
|
*
|
|
* returns 0 if the nand is busy, 1 if it is ready
|
|
*/
|
|
static int bf5xx_nand_devready(struct mtd_info *mtd)
|
|
{
|
|
unsigned short val = bfin_read_NFC_STAT();
|
|
|
|
if ((val & NBUSY) == NBUSY)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ECC functions
|
|
* These allow the bf5xx to use the controller's ECC
|
|
* generator block to ECC the data as it passes through
|
|
*/
|
|
|
|
/*
|
|
* ECC error correction function
|
|
*/
|
|
static int bf5xx_nand_correct_data_256(struct mtd_info *mtd, u_char *dat,
|
|
u_char *read_ecc, u_char *calc_ecc)
|
|
{
|
|
struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
|
|
u32 syndrome[5];
|
|
u32 calced, stored;
|
|
int i;
|
|
unsigned short failing_bit, failing_byte;
|
|
u_char data;
|
|
|
|
calced = calc_ecc[0] | (calc_ecc[1] << 8) | (calc_ecc[2] << 16);
|
|
stored = read_ecc[0] | (read_ecc[1] << 8) | (read_ecc[2] << 16);
|
|
|
|
syndrome[0] = (calced ^ stored);
|
|
|
|
/*
|
|
* syndrome 0: all zero
|
|
* No error in data
|
|
* No action
|
|
*/
|
|
if (!syndrome[0] || !calced || !stored)
|
|
return 0;
|
|
|
|
/*
|
|
* sysdrome 0: only one bit is one
|
|
* ECC data was incorrect
|
|
* No action
|
|
*/
|
|
if (hweight32(syndrome[0]) == 1) {
|
|
dev_err(info->device, "ECC data was incorrect!\n");
|
|
return 1;
|
|
}
|
|
|
|
syndrome[1] = (calced & 0x7FF) ^ (stored & 0x7FF);
|
|
syndrome[2] = (calced & 0x7FF) ^ ((calced >> 11) & 0x7FF);
|
|
syndrome[3] = (stored & 0x7FF) ^ ((stored >> 11) & 0x7FF);
|
|
syndrome[4] = syndrome[2] ^ syndrome[3];
|
|
|
|
for (i = 0; i < 5; i++)
|
|
dev_info(info->device, "syndrome[%d] 0x%08x\n", i, syndrome[i]);
|
|
|
|
dev_info(info->device,
|
|
"calced[0x%08x], stored[0x%08x]\n",
|
|
calced, stored);
|
|
|
|
/*
|
|
* sysdrome 0: exactly 11 bits are one, each parity
|
|
* and parity' pair is 1 & 0 or 0 & 1.
|
|
* 1-bit correctable error
|
|
* Correct the error
|
|
*/
|
|
if (hweight32(syndrome[0]) == 11 && syndrome[4] == 0x7FF) {
|
|
dev_info(info->device,
|
|
"1-bit correctable error, correct it.\n");
|
|
dev_info(info->device,
|
|
"syndrome[1] 0x%08x\n", syndrome[1]);
|
|
|
|
failing_bit = syndrome[1] & 0x7;
|
|
failing_byte = syndrome[1] >> 0x3;
|
|
data = *(dat + failing_byte);
|
|
data = data ^ (0x1 << failing_bit);
|
|
*(dat + failing_byte) = data;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sysdrome 0: random data
|
|
* More than 1-bit error, non-correctable error
|
|
* Discard data, mark bad block
|
|
*/
|
|
dev_err(info->device,
|
|
"More than 1-bit error, non-correctable error.\n");
|
|
dev_err(info->device,
|
|
"Please discard data, mark bad block\n");
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int bf5xx_nand_correct_data(struct mtd_info *mtd, u_char *dat,
|
|
u_char *read_ecc, u_char *calc_ecc)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
int ret;
|
|
|
|
ret = bf5xx_nand_correct_data_256(mtd, dat, read_ecc, calc_ecc);
|
|
|
|
/* If ecc size is 512, correct second 256 bytes */
|
|
if (chip->ecc.size == 512) {
|
|
dat += 256;
|
|
read_ecc += 3;
|
|
calc_ecc += 3;
|
|
ret |= bf5xx_nand_correct_data_256(mtd, dat, read_ecc, calc_ecc);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void bf5xx_nand_enable_hwecc(struct mtd_info *mtd, int mode)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static int bf5xx_nand_calculate_ecc(struct mtd_info *mtd,
|
|
const u_char *dat, u_char *ecc_code)
|
|
{
|
|
struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
|
|
struct nand_chip *chip = mtd->priv;
|
|
u16 ecc0, ecc1;
|
|
u32 code[2];
|
|
u8 *p;
|
|
|
|
/* first 3 bytes ECC code for 256 page size */
|
|
ecc0 = bfin_read_NFC_ECC0();
|
|
ecc1 = bfin_read_NFC_ECC1();
|
|
|
|
code[0] = (ecc0 & 0x7ff) | ((ecc1 & 0x7ff) << 11);
|
|
|
|
dev_dbg(info->device, "returning ecc 0x%08x\n", code[0]);
|
|
|
|
p = (u8 *) code;
|
|
memcpy(ecc_code, p, 3);
|
|
|
|
/* second 3 bytes ECC code for 512 ecc size */
|
|
if (chip->ecc.size == 512) {
|
|
ecc0 = bfin_read_NFC_ECC2();
|
|
ecc1 = bfin_read_NFC_ECC3();
|
|
code[1] = (ecc0 & 0x7ff) | ((ecc1 & 0x7ff) << 11);
|
|
|
|
/* second 3 bytes in ecc_code for second 256
|
|
* bytes of 512 page size
|
|
*/
|
|
p = (u8 *) (code + 1);
|
|
memcpy((ecc_code + 3), p, 3);
|
|
dev_dbg(info->device, "returning ecc 0x%08x\n", code[1]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* PIO mode for buffer writing and reading
|
|
*/
|
|
static void bf5xx_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
|
{
|
|
int i;
|
|
unsigned short val;
|
|
|
|
/*
|
|
* Data reads are requested by first writing to NFC_DATA_RD
|
|
* and then reading back from NFC_READ.
|
|
*/
|
|
for (i = 0; i < len; i++) {
|
|
while (bfin_read_NFC_STAT() & WB_FULL)
|
|
cpu_relax();
|
|
|
|
/* Contents do not matter */
|
|
bfin_write_NFC_DATA_RD(0x0000);
|
|
SSYNC();
|
|
|
|
while ((bfin_read_NFC_IRQSTAT() & RD_RDY) != RD_RDY)
|
|
cpu_relax();
|
|
|
|
buf[i] = bfin_read_NFC_READ();
|
|
|
|
val = bfin_read_NFC_IRQSTAT();
|
|
val |= RD_RDY;
|
|
bfin_write_NFC_IRQSTAT(val);
|
|
SSYNC();
|
|
}
|
|
}
|
|
|
|
static uint8_t bf5xx_nand_read_byte(struct mtd_info *mtd)
|
|
{
|
|
uint8_t val;
|
|
|
|
bf5xx_nand_read_buf(mtd, &val, 1);
|
|
|
|
return val;
|
|
}
|
|
|
|
static void bf5xx_nand_write_buf(struct mtd_info *mtd,
|
|
const uint8_t *buf, int len)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
while (bfin_read_NFC_STAT() & WB_FULL)
|
|
cpu_relax();
|
|
|
|
bfin_write_NFC_DATA_WR(buf[i]);
|
|
SSYNC();
|
|
}
|
|
}
|
|
|
|
static void bf5xx_nand_read_buf16(struct mtd_info *mtd, uint8_t *buf, int len)
|
|
{
|
|
int i;
|
|
u16 *p = (u16 *) buf;
|
|
len >>= 1;
|
|
|
|
/*
|
|
* Data reads are requested by first writing to NFC_DATA_RD
|
|
* and then reading back from NFC_READ.
|
|
*/
|
|
bfin_write_NFC_DATA_RD(0x5555);
|
|
|
|
SSYNC();
|
|
|
|
for (i = 0; i < len; i++)
|
|
p[i] = bfin_read_NFC_READ();
|
|
}
|
|
|
|
static void bf5xx_nand_write_buf16(struct mtd_info *mtd,
|
|
const uint8_t *buf, int len)
|
|
{
|
|
int i;
|
|
u16 *p = (u16 *) buf;
|
|
len >>= 1;
|
|
|
|
for (i = 0; i < len; i++)
|
|
bfin_write_NFC_DATA_WR(p[i]);
|
|
|
|
SSYNC();
|
|
}
|
|
|
|
/*
|
|
* DMA functions for buffer writing and reading
|
|
*/
|
|
static irqreturn_t bf5xx_nand_dma_irq(int irq, void *dev_id)
|
|
{
|
|
struct bf5xx_nand_info *info = dev_id;
|
|
|
|
clear_dma_irqstat(CH_NFC);
|
|
disable_dma(CH_NFC);
|
|
complete(&info->dma_completion);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void bf5xx_nand_dma_rw(struct mtd_info *mtd,
|
|
uint8_t *buf, int is_read)
|
|
{
|
|
struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
|
|
struct nand_chip *chip = mtd->priv;
|
|
unsigned short val;
|
|
|
|
dev_dbg(info->device, " mtd->%p, buf->%p, is_read %d\n",
|
|
mtd, buf, is_read);
|
|
|
|
/*
|
|
* Before starting a dma transfer, be sure to invalidate/flush
|
|
* the cache over the address range of your DMA buffer to
|
|
* prevent cache coherency problems. Otherwise very subtle bugs
|
|
* can be introduced to your driver.
|
|
*/
|
|
if (is_read)
|
|
invalidate_dcache_range((unsigned int)buf,
|
|
(unsigned int)(buf + chip->ecc.size));
|
|
else
|
|
flush_dcache_range((unsigned int)buf,
|
|
(unsigned int)(buf + chip->ecc.size));
|
|
|
|
/*
|
|
* This register must be written before each page is
|
|
* transferred to generate the correct ECC register
|
|
* values.
|
|
*/
|
|
bfin_write_NFC_RST(ECC_RST);
|
|
SSYNC();
|
|
while (bfin_read_NFC_RST() & ECC_RST)
|
|
cpu_relax();
|
|
|
|
disable_dma(CH_NFC);
|
|
clear_dma_irqstat(CH_NFC);
|
|
|
|
/* setup DMA register with Blackfin DMA API */
|
|
set_dma_config(CH_NFC, 0x0);
|
|
set_dma_start_addr(CH_NFC, (unsigned long) buf);
|
|
|
|
/* The DMAs have different size on BF52x and BF54x */
|
|
#ifdef CONFIG_BF52x
|
|
set_dma_x_count(CH_NFC, (chip->ecc.size >> 1));
|
|
set_dma_x_modify(CH_NFC, 2);
|
|
val = DI_EN | WDSIZE_16;
|
|
#endif
|
|
|
|
#ifdef CONFIG_BF54x
|
|
set_dma_x_count(CH_NFC, (chip->ecc.size >> 2));
|
|
set_dma_x_modify(CH_NFC, 4);
|
|
val = DI_EN | WDSIZE_32;
|
|
#endif
|
|
/* setup write or read operation */
|
|
if (is_read)
|
|
val |= WNR;
|
|
set_dma_config(CH_NFC, val);
|
|
enable_dma(CH_NFC);
|
|
|
|
/* Start PAGE read/write operation */
|
|
if (is_read)
|
|
bfin_write_NFC_PGCTL(PG_RD_START);
|
|
else
|
|
bfin_write_NFC_PGCTL(PG_WR_START);
|
|
wait_for_completion(&info->dma_completion);
|
|
}
|
|
|
|
static void bf5xx_nand_dma_read_buf(struct mtd_info *mtd,
|
|
uint8_t *buf, int len)
|
|
{
|
|
struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
|
|
struct nand_chip *chip = mtd->priv;
|
|
|
|
dev_dbg(info->device, "mtd->%p, buf->%p, int %d\n", mtd, buf, len);
|
|
|
|
if (len == chip->ecc.size)
|
|
bf5xx_nand_dma_rw(mtd, buf, 1);
|
|
else
|
|
bf5xx_nand_read_buf(mtd, buf, len);
|
|
}
|
|
|
|
static void bf5xx_nand_dma_write_buf(struct mtd_info *mtd,
|
|
const uint8_t *buf, int len)
|
|
{
|
|
struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
|
|
struct nand_chip *chip = mtd->priv;
|
|
|
|
dev_dbg(info->device, "mtd->%p, buf->%p, len %d\n", mtd, buf, len);
|
|
|
|
if (len == chip->ecc.size)
|
|
bf5xx_nand_dma_rw(mtd, (uint8_t *)buf, 0);
|
|
else
|
|
bf5xx_nand_write_buf(mtd, buf, len);
|
|
}
|
|
|
|
static int bf5xx_nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
|
uint8_t *buf, int page)
|
|
{
|
|
bf5xx_nand_read_buf(mtd, buf, mtd->writesize);
|
|
bf5xx_nand_read_buf(mtd, chip->oob_poi, mtd->oobsize);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void bf5xx_nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
|
const uint8_t *buf)
|
|
{
|
|
bf5xx_nand_write_buf(mtd, buf, mtd->writesize);
|
|
bf5xx_nand_write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
|
}
|
|
|
|
/*
|
|
* System initialization functions
|
|
*/
|
|
static int bf5xx_nand_dma_init(struct bf5xx_nand_info *info)
|
|
{
|
|
int ret;
|
|
|
|
/* Do not use dma */
|
|
if (!hardware_ecc)
|
|
return 0;
|
|
|
|
init_completion(&info->dma_completion);
|
|
|
|
/* Request NFC DMA channel */
|
|
ret = request_dma(CH_NFC, "BF5XX NFC driver");
|
|
if (ret < 0) {
|
|
dev_err(info->device, " unable to get DMA channel\n");
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_BF54x
|
|
/* Setup DMAC1 channel mux for NFC which shared with SDH */
|
|
bfin_write_DMAC1_PERIMUX(bfin_read_DMAC1_PERIMUX() & ~1);
|
|
SSYNC();
|
|
#endif
|
|
|
|
set_dma_callback(CH_NFC, bf5xx_nand_dma_irq, info);
|
|
|
|
/* Turn off the DMA channel first */
|
|
disable_dma(CH_NFC);
|
|
return 0;
|
|
}
|
|
|
|
static void bf5xx_nand_dma_remove(struct bf5xx_nand_info *info)
|
|
{
|
|
/* Free NFC DMA channel */
|
|
if (hardware_ecc)
|
|
free_dma(CH_NFC);
|
|
}
|
|
|
|
/*
|
|
* BF5XX NFC hardware initialization
|
|
* - pin mux setup
|
|
* - clear interrupt status
|
|
*/
|
|
static int bf5xx_nand_hw_init(struct bf5xx_nand_info *info)
|
|
{
|
|
int err = 0;
|
|
unsigned short val;
|
|
struct bf5xx_nand_platform *plat = info->platform;
|
|
|
|
/* setup NFC_CTL register */
|
|
dev_info(info->device,
|
|
"data_width=%d, wr_dly=%d, rd_dly=%d\n",
|
|
(plat->data_width ? 16 : 8),
|
|
plat->wr_dly, plat->rd_dly);
|
|
|
|
val = (1 << NFC_PG_SIZE_OFFSET) |
|
|
(plat->data_width << NFC_NWIDTH_OFFSET) |
|
|
(plat->rd_dly << NFC_RDDLY_OFFSET) |
|
|
(plat->wr_dly << NFC_WRDLY_OFFSET);
|
|
dev_dbg(info->device, "NFC_CTL is 0x%04x\n", val);
|
|
|
|
bfin_write_NFC_CTL(val);
|
|
SSYNC();
|
|
|
|
/* clear interrupt status */
|
|
bfin_write_NFC_IRQMASK(0x0);
|
|
SSYNC();
|
|
val = bfin_read_NFC_IRQSTAT();
|
|
bfin_write_NFC_IRQSTAT(val);
|
|
SSYNC();
|
|
|
|
/* DMA initialization */
|
|
if (bf5xx_nand_dma_init(info))
|
|
err = -ENXIO;
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Device management interface
|
|
*/
|
|
static int __devinit bf5xx_nand_add_partition(struct bf5xx_nand_info *info)
|
|
{
|
|
struct mtd_info *mtd = &info->mtd;
|
|
|
|
#ifdef CONFIG_MTD_PARTITIONS
|
|
struct mtd_partition *parts = info->platform->partitions;
|
|
int nr = info->platform->nr_partitions;
|
|
|
|
return add_mtd_partitions(mtd, parts, nr);
|
|
#else
|
|
return add_mtd_device(mtd);
|
|
#endif
|
|
}
|
|
|
|
static int __devexit bf5xx_nand_remove(struct platform_device *pdev)
|
|
{
|
|
struct bf5xx_nand_info *info = to_nand_info(pdev);
|
|
|
|
platform_set_drvdata(pdev, NULL);
|
|
|
|
/* first thing we need to do is release all our mtds
|
|
* and their partitions, then go through freeing the
|
|
* resources used
|
|
*/
|
|
nand_release(&info->mtd);
|
|
|
|
peripheral_free_list(bfin_nfc_pin_req);
|
|
bf5xx_nand_dma_remove(info);
|
|
|
|
/* free the common resources */
|
|
kfree(info);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bf5xx_nand_scan(struct mtd_info *mtd)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
int ret;
|
|
|
|
ret = nand_scan_ident(mtd, 1, NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (hardware_ecc) {
|
|
/*
|
|
* for nand with page size > 512B, think it as several sections with 512B
|
|
*/
|
|
if (likely(mtd->writesize >= 512)) {
|
|
chip->ecc.size = 512;
|
|
chip->ecc.bytes = 6;
|
|
} else {
|
|
chip->ecc.size = 256;
|
|
chip->ecc.bytes = 3;
|
|
bfin_write_NFC_CTL(bfin_read_NFC_CTL() & ~(1 << NFC_PG_SIZE_OFFSET));
|
|
SSYNC();
|
|
}
|
|
}
|
|
|
|
return nand_scan_tail(mtd);
|
|
}
|
|
|
|
/*
|
|
* bf5xx_nand_probe
|
|
*
|
|
* called by device layer when it finds a device matching
|
|
* one our driver can handled. This code checks to see if
|
|
* it can allocate all necessary resources then calls the
|
|
* nand layer to look for devices
|
|
*/
|
|
static int __devinit bf5xx_nand_probe(struct platform_device *pdev)
|
|
{
|
|
struct bf5xx_nand_platform *plat = to_nand_plat(pdev);
|
|
struct bf5xx_nand_info *info = NULL;
|
|
struct nand_chip *chip = NULL;
|
|
struct mtd_info *mtd = NULL;
|
|
int err = 0;
|
|
|
|
dev_dbg(&pdev->dev, "(%p)\n", pdev);
|
|
|
|
if (!plat) {
|
|
dev_err(&pdev->dev, "no platform specific information\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (peripheral_request_list(bfin_nfc_pin_req, DRV_NAME)) {
|
|
dev_err(&pdev->dev, "requesting Peripherals failed\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
|
if (info == NULL) {
|
|
dev_err(&pdev->dev, "no memory for flash info\n");
|
|
err = -ENOMEM;
|
|
goto out_err_kzalloc;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, info);
|
|
|
|
spin_lock_init(&info->controller.lock);
|
|
init_waitqueue_head(&info->controller.wq);
|
|
|
|
info->device = &pdev->dev;
|
|
info->platform = plat;
|
|
|
|
/* initialise chip data struct */
|
|
chip = &info->chip;
|
|
|
|
if (plat->data_width)
|
|
chip->options |= NAND_BUSWIDTH_16;
|
|
|
|
chip->options |= NAND_CACHEPRG | NAND_SKIP_BBTSCAN;
|
|
|
|
chip->read_buf = (plat->data_width) ?
|
|
bf5xx_nand_read_buf16 : bf5xx_nand_read_buf;
|
|
chip->write_buf = (plat->data_width) ?
|
|
bf5xx_nand_write_buf16 : bf5xx_nand_write_buf;
|
|
|
|
chip->read_byte = bf5xx_nand_read_byte;
|
|
|
|
chip->cmd_ctrl = bf5xx_nand_hwcontrol;
|
|
chip->dev_ready = bf5xx_nand_devready;
|
|
|
|
chip->priv = &info->mtd;
|
|
chip->controller = &info->controller;
|
|
|
|
chip->IO_ADDR_R = (void __iomem *) NFC_READ;
|
|
chip->IO_ADDR_W = (void __iomem *) NFC_DATA_WR;
|
|
|
|
chip->chip_delay = 0;
|
|
|
|
/* initialise mtd info data struct */
|
|
mtd = &info->mtd;
|
|
mtd->priv = chip;
|
|
mtd->owner = THIS_MODULE;
|
|
|
|
/* initialise the hardware */
|
|
err = bf5xx_nand_hw_init(info);
|
|
if (err)
|
|
goto out_err_hw_init;
|
|
|
|
/* setup hardware ECC data struct */
|
|
if (hardware_ecc) {
|
|
#ifdef CONFIG_MTD_NAND_BF5XX_BOOTROM_ECC
|
|
chip->ecc.layout = &bootrom_ecclayout;
|
|
#endif
|
|
chip->read_buf = bf5xx_nand_dma_read_buf;
|
|
chip->write_buf = bf5xx_nand_dma_write_buf;
|
|
chip->ecc.calculate = bf5xx_nand_calculate_ecc;
|
|
chip->ecc.correct = bf5xx_nand_correct_data;
|
|
chip->ecc.mode = NAND_ECC_HW;
|
|
chip->ecc.hwctl = bf5xx_nand_enable_hwecc;
|
|
chip->ecc.read_page_raw = bf5xx_nand_read_page_raw;
|
|
chip->ecc.write_page_raw = bf5xx_nand_write_page_raw;
|
|
} else {
|
|
chip->ecc.mode = NAND_ECC_SOFT;
|
|
}
|
|
|
|
/* scan hardware nand chip and setup mtd info data struct */
|
|
if (bf5xx_nand_scan(mtd)) {
|
|
err = -ENXIO;
|
|
goto out_err_nand_scan;
|
|
}
|
|
|
|
#ifdef CONFIG_MTD_NAND_BF5XX_BOOTROM_ECC
|
|
chip->badblockpos = 63;
|
|
#endif
|
|
|
|
/* add NAND partition */
|
|
bf5xx_nand_add_partition(info);
|
|
|
|
dev_dbg(&pdev->dev, "initialised ok\n");
|
|
return 0;
|
|
|
|
out_err_nand_scan:
|
|
bf5xx_nand_dma_remove(info);
|
|
out_err_hw_init:
|
|
platform_set_drvdata(pdev, NULL);
|
|
kfree(info);
|
|
out_err_kzalloc:
|
|
peripheral_free_list(bfin_nfc_pin_req);
|
|
|
|
return err;
|
|
}
|
|
|
|
/* PM Support */
|
|
#ifdef CONFIG_PM
|
|
|
|
static int bf5xx_nand_suspend(struct platform_device *dev, pm_message_t pm)
|
|
{
|
|
struct bf5xx_nand_info *info = platform_get_drvdata(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bf5xx_nand_resume(struct platform_device *dev)
|
|
{
|
|
struct bf5xx_nand_info *info = platform_get_drvdata(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
#define bf5xx_nand_suspend NULL
|
|
#define bf5xx_nand_resume NULL
|
|
#endif
|
|
|
|
/* driver device registration */
|
|
static struct platform_driver bf5xx_nand_driver = {
|
|
.probe = bf5xx_nand_probe,
|
|
.remove = __devexit_p(bf5xx_nand_remove),
|
|
.suspend = bf5xx_nand_suspend,
|
|
.resume = bf5xx_nand_resume,
|
|
.driver = {
|
|
.name = DRV_NAME,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
static int __init bf5xx_nand_init(void)
|
|
{
|
|
printk(KERN_INFO "%s, Version %s (c) 2007 Analog Devices, Inc.\n",
|
|
DRV_DESC, DRV_VERSION);
|
|
|
|
return platform_driver_register(&bf5xx_nand_driver);
|
|
}
|
|
|
|
static void __exit bf5xx_nand_exit(void)
|
|
{
|
|
platform_driver_unregister(&bf5xx_nand_driver);
|
|
}
|
|
|
|
module_init(bf5xx_nand_init);
|
|
module_exit(bf5xx_nand_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR(DRV_AUTHOR);
|
|
MODULE_DESCRIPTION(DRV_DESC);
|
|
MODULE_ALIAS("platform:" DRV_NAME);
|