![Stefan Roese](/assets/img/avatar_default.png)
This patch adds 405 platform support to the 440 NDFC driver. The new AMCC 405EZ PPC is equipped with the same NDFC core as the 440EP(x) and other will follow soon. Signed-off-by: Stefan Roese <sr@denx.de> Signed-off-by: David Woodhouse <dwmw2@infradead.org>
320 lines
7.7 KiB
C
320 lines
7.7 KiB
C
/*
|
|
* drivers/mtd/ndfc.c
|
|
*
|
|
* Overview:
|
|
* Platform independend driver for NDFC (NanD Flash Controller)
|
|
* integrated into EP440 cores
|
|
*
|
|
* Author: Thomas Gleixner
|
|
*
|
|
* Copyright 2006 IBM
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
#include <linux/module.h>
|
|
#include <linux/mtd/nand.h>
|
|
#include <linux/mtd/nand_ecc.h>
|
|
#include <linux/mtd/partitions.h>
|
|
#include <linux/mtd/ndfc.h>
|
|
#include <linux/mtd/mtd.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <asm/io.h>
|
|
#ifdef CONFIG_40x
|
|
#include <asm/ibm405.h>
|
|
#else
|
|
#include <asm/ibm44x.h>
|
|
#endif
|
|
|
|
struct ndfc_nand_mtd {
|
|
struct mtd_info mtd;
|
|
struct nand_chip chip;
|
|
struct platform_nand_chip *pl_chip;
|
|
};
|
|
|
|
static struct ndfc_nand_mtd ndfc_mtd[NDFC_MAX_BANKS];
|
|
|
|
struct ndfc_controller {
|
|
void __iomem *ndfcbase;
|
|
struct nand_hw_control ndfc_control;
|
|
atomic_t childs_active;
|
|
};
|
|
|
|
static struct ndfc_controller ndfc_ctrl;
|
|
|
|
static void ndfc_select_chip(struct mtd_info *mtd, int chip)
|
|
{
|
|
uint32_t ccr;
|
|
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
|
struct nand_chip *nandchip = mtd->priv;
|
|
struct ndfc_nand_mtd *nandmtd = nandchip->priv;
|
|
struct platform_nand_chip *pchip = nandmtd->pl_chip;
|
|
|
|
ccr = __raw_readl(ndfc->ndfcbase + NDFC_CCR);
|
|
if (chip >= 0) {
|
|
ccr &= ~NDFC_CCR_BS_MASK;
|
|
ccr |= NDFC_CCR_BS(chip + pchip->chip_offset);
|
|
} else
|
|
ccr |= NDFC_CCR_RESET_CE;
|
|
__raw_writel(ccr, ndfc->ndfcbase + NDFC_CCR);
|
|
}
|
|
|
|
static void ndfc_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl)
|
|
{
|
|
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
|
|
|
if (cmd == NAND_CMD_NONE)
|
|
return;
|
|
|
|
if (ctrl & NAND_CLE)
|
|
writel(cmd & 0xFF, ndfc->ndfcbase + NDFC_CMD);
|
|
else
|
|
writel(cmd & 0xFF, ndfc->ndfcbase + NDFC_ALE);
|
|
}
|
|
|
|
static int ndfc_ready(struct mtd_info *mtd)
|
|
{
|
|
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
|
|
|
return __raw_readl(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY;
|
|
}
|
|
|
|
static void ndfc_enable_hwecc(struct mtd_info *mtd, int mode)
|
|
{
|
|
uint32_t ccr;
|
|
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
|
|
|
ccr = __raw_readl(ndfc->ndfcbase + NDFC_CCR);
|
|
ccr |= NDFC_CCR_RESET_ECC;
|
|
__raw_writel(ccr, ndfc->ndfcbase + NDFC_CCR);
|
|
wmb();
|
|
}
|
|
|
|
static int ndfc_calculate_ecc(struct mtd_info *mtd,
|
|
const u_char *dat, u_char *ecc_code)
|
|
{
|
|
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
|
uint32_t ecc;
|
|
uint8_t *p = (uint8_t *)&ecc;
|
|
|
|
wmb();
|
|
ecc = __raw_readl(ndfc->ndfcbase + NDFC_ECC);
|
|
ecc_code[0] = p[1];
|
|
ecc_code[1] = p[2];
|
|
ecc_code[2] = p[3];
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Speedups for buffer read/write/verify
|
|
*
|
|
* NDFC allows 32bit read/write of data. So we can speed up the buffer
|
|
* functions. No further checking, as nand_base will always read/write
|
|
* page aligned.
|
|
*/
|
|
static void ndfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
|
{
|
|
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
|
uint32_t *p = (uint32_t *) buf;
|
|
|
|
for(;len > 0; len -= 4)
|
|
*p++ = __raw_readl(ndfc->ndfcbase + NDFC_DATA);
|
|
}
|
|
|
|
static void ndfc_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
|
{
|
|
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
|
uint32_t *p = (uint32_t *) buf;
|
|
|
|
for(;len > 0; len -= 4)
|
|
__raw_writel(*p++, ndfc->ndfcbase + NDFC_DATA);
|
|
}
|
|
|
|
static int ndfc_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
|
{
|
|
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
|
uint32_t *p = (uint32_t *) buf;
|
|
|
|
for(;len > 0; len -= 4)
|
|
if (*p++ != __raw_readl(ndfc->ndfcbase + NDFC_DATA))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Initialize chip structure
|
|
*/
|
|
static void ndfc_chip_init(struct ndfc_nand_mtd *mtd)
|
|
{
|
|
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
|
struct nand_chip *chip = &mtd->chip;
|
|
|
|
chip->IO_ADDR_R = ndfc->ndfcbase + NDFC_DATA;
|
|
chip->IO_ADDR_W = ndfc->ndfcbase + NDFC_DATA;
|
|
chip->cmd_ctrl = ndfc_hwcontrol;
|
|
chip->dev_ready = ndfc_ready;
|
|
chip->select_chip = ndfc_select_chip;
|
|
chip->chip_delay = 50;
|
|
chip->priv = mtd;
|
|
chip->options = mtd->pl_chip->options;
|
|
chip->controller = &ndfc->ndfc_control;
|
|
chip->read_buf = ndfc_read_buf;
|
|
chip->write_buf = ndfc_write_buf;
|
|
chip->verify_buf = ndfc_verify_buf;
|
|
chip->ecc.correct = nand_correct_data;
|
|
chip->ecc.hwctl = ndfc_enable_hwecc;
|
|
chip->ecc.calculate = ndfc_calculate_ecc;
|
|
chip->ecc.mode = NAND_ECC_HW;
|
|
chip->ecc.size = 256;
|
|
chip->ecc.bytes = 3;
|
|
chip->ecclayout = chip->ecc.layout = mtd->pl_chip->ecclayout;
|
|
mtd->mtd.priv = chip;
|
|
mtd->mtd.owner = THIS_MODULE;
|
|
}
|
|
|
|
static int ndfc_chip_probe(struct platform_device *pdev)
|
|
{
|
|
struct platform_nand_chip *nc = pdev->dev.platform_data;
|
|
struct ndfc_chip_settings *settings = nc->priv;
|
|
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
|
struct ndfc_nand_mtd *nandmtd;
|
|
|
|
if (nc->chip_offset >= NDFC_MAX_BANKS || nc->nr_chips > NDFC_MAX_BANKS)
|
|
return -EINVAL;
|
|
|
|
/* Set the bank settings */
|
|
__raw_writel(settings->bank_settings,
|
|
ndfc->ndfcbase + NDFC_BCFG0 + (nc->chip_offset << 2));
|
|
|
|
nandmtd = &ndfc_mtd[pdev->id];
|
|
if (nandmtd->pl_chip)
|
|
return -EBUSY;
|
|
|
|
nandmtd->pl_chip = nc;
|
|
ndfc_chip_init(nandmtd);
|
|
|
|
/* Scan for chips */
|
|
if (nand_scan(&nandmtd->mtd, nc->nr_chips)) {
|
|
nandmtd->pl_chip = NULL;
|
|
return -ENODEV;
|
|
}
|
|
|
|
#ifdef CONFIG_MTD_PARTITIONS
|
|
printk("Number of partitions %d\n", nc->nr_partitions);
|
|
if (nc->nr_partitions) {
|
|
/* Add the full device, so complete dumps can be made */
|
|
add_mtd_device(&nandmtd->mtd);
|
|
add_mtd_partitions(&nandmtd->mtd, nc->partitions,
|
|
nc->nr_partitions);
|
|
|
|
} else
|
|
#else
|
|
add_mtd_device(&nandmtd->mtd);
|
|
#endif
|
|
|
|
atomic_inc(&ndfc->childs_active);
|
|
return 0;
|
|
}
|
|
|
|
static int ndfc_chip_remove(struct platform_device *pdev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int ndfc_nand_probe(struct platform_device *pdev)
|
|
{
|
|
struct platform_nand_ctrl *nc = pdev->dev.platform_data;
|
|
struct ndfc_controller_settings *settings = nc->priv;
|
|
struct resource *res = pdev->resource;
|
|
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
|
unsigned long long phys = settings->ndfc_erpn | res->start;
|
|
|
|
#ifndef CONFIG_PHYS_64BIT
|
|
ndfc->ndfcbase = ioremap((phys_addr_t)phys, res->end - res->start + 1);
|
|
#else
|
|
ndfc->ndfcbase = ioremap64(phys, res->end - res->start + 1);
|
|
#endif
|
|
if (!ndfc->ndfcbase) {
|
|
printk(KERN_ERR "NDFC: ioremap failed\n");
|
|
return -EIO;
|
|
}
|
|
|
|
__raw_writel(settings->ccr_settings, ndfc->ndfcbase + NDFC_CCR);
|
|
|
|
spin_lock_init(&ndfc->ndfc_control.lock);
|
|
init_waitqueue_head(&ndfc->ndfc_control.wq);
|
|
|
|
platform_set_drvdata(pdev, ndfc);
|
|
|
|
printk("NDFC NAND Driver initialized. Chip-Rev: 0x%08x\n",
|
|
__raw_readl(ndfc->ndfcbase + NDFC_REVID));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ndfc_nand_remove(struct platform_device *pdev)
|
|
{
|
|
struct ndfc_controller *ndfc = platform_get_drvdata(pdev);
|
|
|
|
if (atomic_read(&ndfc->childs_active))
|
|
return -EBUSY;
|
|
|
|
if (ndfc) {
|
|
platform_set_drvdata(pdev, NULL);
|
|
iounmap(ndfc_ctrl.ndfcbase);
|
|
ndfc_ctrl.ndfcbase = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* driver device registration */
|
|
|
|
static struct platform_driver ndfc_chip_driver = {
|
|
.probe = ndfc_chip_probe,
|
|
.remove = ndfc_chip_remove,
|
|
.driver = {
|
|
.name = "ndfc-chip",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
static struct platform_driver ndfc_nand_driver = {
|
|
.probe = ndfc_nand_probe,
|
|
.remove = ndfc_nand_remove,
|
|
.driver = {
|
|
.name = "ndfc-nand",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
static int __init ndfc_nand_init(void)
|
|
{
|
|
int ret;
|
|
|
|
spin_lock_init(&ndfc_ctrl.ndfc_control.lock);
|
|
init_waitqueue_head(&ndfc_ctrl.ndfc_control.wq);
|
|
|
|
ret = platform_driver_register(&ndfc_nand_driver);
|
|
if (!ret)
|
|
ret = platform_driver_register(&ndfc_chip_driver);
|
|
return ret;
|
|
}
|
|
|
|
static void __exit ndfc_nand_exit(void)
|
|
{
|
|
platform_driver_unregister(&ndfc_chip_driver);
|
|
platform_driver_unregister(&ndfc_nand_driver);
|
|
}
|
|
|
|
module_init(ndfc_nand_init);
|
|
module_exit(ndfc_nand_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>");
|
|
MODULE_DESCRIPTION("Platform driver for NDFC");
|