nand
This commit is contained in:
parent
207b4c8202
commit
ead200eafe
@ -98,4 +98,12 @@
|
||||
reg = <0x80200000 0x1000>;
|
||||
interrupts = <22>;
|
||||
};
|
||||
|
||||
nand0: nand@a4000000 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
compatible = "cavium,cnc1800l-nand";
|
||||
reg = <0x80100000 0x48>, //SMC
|
||||
<0xa4000000 0x2000000>; //NAND
|
||||
};
|
||||
};
|
@ -38,6 +38,13 @@ config MTD_NAND_AMS_DELTA
|
||||
help
|
||||
Support for NAND flash on Amstrad E3 (Delta).
|
||||
|
||||
config MTD_NAND_CELESTIAL_CNC1800L
|
||||
tristate "Celestial CNC1800L NAND"
|
||||
depends on MACH_CELESTIAL || COMPILE_TEST
|
||||
default y
|
||||
help
|
||||
Support for NAND flash on Celestial CNC1800L.
|
||||
|
||||
config MTD_NAND_OMAP2
|
||||
tristate "OMAP2, OMAP3, OMAP4 and Keystone NAND controller"
|
||||
depends on ARCH_OMAP2PLUS || ARCH_KEYSTONE || COMPILE_TEST
|
||||
|
@ -5,6 +5,7 @@ obj-$(CONFIG_MTD_SM_COMMON) += sm_common.o
|
||||
|
||||
obj-$(CONFIG_MTD_NAND_CAFE) += cafe_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_AMS_DELTA) += ams-delta.o
|
||||
obj-$(CONFIG_MTD_NAND_CELESTIAL_CNC1800L) += cnc1800l-nand.o
|
||||
obj-$(CONFIG_MTD_NAND_DENALI) += denali.o
|
||||
obj-$(CONFIG_MTD_NAND_DENALI_PCI) += denali_pci.o
|
||||
obj-$(CONFIG_MTD_NAND_DENALI_DT) += denali_dt.o
|
||||
|
505
drivers/mtd/nand/raw/cnc1800l-nand.c
Normal file
505
drivers/mtd/nand/raw/cnc1800l-nand.c
Normal file
@ -0,0 +1,505 @@
|
||||
/*
|
||||
* drivers/mtd/nand/cnc_nand.c
|
||||
*
|
||||
* Copyrigth (C) 2010 Celestial Semiconductor
|
||||
* 2011 Cavium
|
||||
* Author: xiaodong fan <xiaodong.fan@caviumnetworks.com>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Overview:
|
||||
* This is a device driver for the NAND flash device found on the
|
||||
* Celestial CNC18xx SOC.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/rawnand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
|
||||
#define DRIVER_NAME "cnc1800l_nand"
|
||||
|
||||
#define CE0_CFG_0_OFFSET 0
|
||||
#define CE0_CFG_1_OFFSET 0x04
|
||||
#define CE0_CFG_2_OFFSET 0x08
|
||||
|
||||
#define CE1_CFG_0_OFFSET 0x0C
|
||||
#define CE1_CFG_1_OFFSET 0x10
|
||||
#define CE1_CFG_2_OFFSET 0x14
|
||||
|
||||
|
||||
#define CE2_CFG_0_OFFSET 0x18
|
||||
#define CE2_CFG_1_OFFSET 0x1C
|
||||
#define CE2_CFG_2_OFFSET 0x20
|
||||
|
||||
|
||||
#define CE3_CFG_0_OFFSET 0x24
|
||||
#define CE3_CFG_1_OFFSET 0x28
|
||||
#define CE3_CFG_2_OFFSET 0x2c
|
||||
|
||||
#define STATUS_OFFSET 0x30
|
||||
#define MODE_OFFSET 0x38
|
||||
|
||||
#define ECC_CODE_OFFSET 0x40
|
||||
#define ECC_CNT_OFFSET 0x44
|
||||
#define ECC_A_OFFSET 0x48
|
||||
|
||||
#define MASK_ALE 0x8
|
||||
#define MASK_CLE 0x4
|
||||
|
||||
#define MASK_CE_FORCE_LOW 0x200
|
||||
|
||||
#define PADDR 0x80100000
|
||||
|
||||
static struct mtd_partition cnc_nand_partitions[] = {
|
||||
{
|
||||
.name = "cavm_miniloader",
|
||||
.offset = 0,
|
||||
.size = SZ_128K,
|
||||
.mask_flags = MTD_WRITEABLE,/*Read only*/
|
||||
}, {
|
||||
.name = "cavm_uboot1",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_512K,
|
||||
.mask_flags = MTD_WRITEABLE,/*Read only*/
|
||||
}, {
|
||||
.name = "cavm_uboot1_pad",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_512K,
|
||||
.mask_flags = 0,
|
||||
}, {
|
||||
.name = "cavm_nvram_factory",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_128K,
|
||||
.mask_flags = MTD_WRITEABLE,/*Read only*/
|
||||
}, {
|
||||
.name = "cavm_nvram_factory_pad",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_128K,
|
||||
.mask_flags = MTD_WRITEABLE,/*Read only*/
|
||||
}, {
|
||||
.name = "cavm_nvram1b",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_128K,
|
||||
.mask_flags = MTD_WRITEABLE,/*Read only*/
|
||||
}, {
|
||||
.name = "cavm_nvram2",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_128K,
|
||||
.mask_flags = 0,
|
||||
}, {
|
||||
.name = "cavm_nvram2b",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_128K,
|
||||
.mask_flags = 0,
|
||||
}, {
|
||||
.name = "cavm_splash",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_2M,
|
||||
.mask_flags = 0,
|
||||
}, {
|
||||
.name = "cavm_all_img1_info",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_128K,
|
||||
.mask_flags = MTD_WRITEABLE,/*Read only*/
|
||||
}, {
|
||||
.name = "cavm_all_img1_info_pad",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_128K,
|
||||
.mask_flags = MTD_WRITEABLE,/*Read only*/
|
||||
}, {
|
||||
.name = "cavm_netHD_Image1",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_32M,
|
||||
.mask_flags = MTD_WRITEABLE,/*Read only*/
|
||||
}, {
|
||||
.name = "cavm_netHD_Image2",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_32M,
|
||||
.mask_flags = 0,
|
||||
}, {
|
||||
.name = "cavm_free1",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_16M + SZ_2M + SZ_1M + SZ_512K, /* 19.5M */
|
||||
.mask_flags = 0,
|
||||
}, {
|
||||
.name = "cavm_free1_pad",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_256K,
|
||||
.mask_flags = 0,
|
||||
}, {
|
||||
.name = "cavm_netHD_img2_info",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_256K,
|
||||
.mask_flags = 0,
|
||||
}, {
|
||||
.name = "cavm_blob",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_4M - SZ_1M,
|
||||
.mask_flags = MTD_WRITEABLE,/*Read only*/
|
||||
}, {
|
||||
.name = "cavm_uboot2",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_1M,
|
||||
// #ifdef UBOOT_UPGRADE_ALLOWED
|
||||
// .mask_flags = 0,
|
||||
// #else
|
||||
.mask_flags = MTD_WRITEABLE,/*Read only*/
|
||||
// #endif
|
||||
}, {
|
||||
.name = "cavm_ffs",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_32M,
|
||||
.mask_flags = 0,
|
||||
}, {
|
||||
.name = "customer_area",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = SZ_4M, //MTDPART_SIZ_FULL,
|
||||
.mask_flags = 0,
|
||||
}
|
||||
};
|
||||
|
||||
struct cnc_nand_info {
|
||||
struct nand_chip *chip;
|
||||
|
||||
struct device *dev;
|
||||
struct clk *clk;
|
||||
|
||||
void __iomem *smcbase;
|
||||
void __iomem *iobase;
|
||||
|
||||
uint32_t mask_ale;
|
||||
uint32_t mask_cle;
|
||||
|
||||
struct nand_controller controller;
|
||||
};
|
||||
|
||||
static inline unsigned int cnc_nand_readl(struct cnc_nand_info *info,
|
||||
int offset)
|
||||
{
|
||||
return __raw_readl(info->smcbase + offset);
|
||||
}
|
||||
|
||||
static inline void cnc_nand_writel(struct cnc_nand_info *info,
|
||||
int offset, unsigned long value)
|
||||
{
|
||||
__raw_writel(value, info->smcbase + offset);
|
||||
}
|
||||
|
||||
|
||||
static void cnc_nand_1bitecc_hwctl(struct nand_chip *chip, int mode)
|
||||
{
|
||||
struct cnc_nand_info *info = chip->priv;
|
||||
unsigned long ecc_addr;
|
||||
|
||||
|
||||
if (mode == NAND_ECC_READ){
|
||||
ecc_addr = (unsigned long)((u32)PADDR + ((u32)chip->legacy.IO_ADDR_R - (u32)info->iobase));
|
||||
cnc_nand_writel(info, ECC_A_OFFSET, ecc_addr);
|
||||
// CNC_DEBUG("HW Read ECC enable--0x%x\n",(u32)ecc_addr);
|
||||
}
|
||||
else if (mode == NAND_ECC_WRITE) {
|
||||
ecc_addr = (unsigned long)((u32)PADDR + ((u32)chip->legacy.IO_ADDR_W - (u32)info->iobase));
|
||||
cnc_nand_writel(info, ECC_A_OFFSET, ecc_addr);
|
||||
// CNC_DEBUG("HW Write ECC enable--0x%x\n",(u32)ecc_addr);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Read hardware ECC value and pack into three bytes
|
||||
*/
|
||||
static int cnc_nand_1bitecc_calculate(struct nand_chip *chip,
|
||||
const uint8_t *dat, uint8_t *ecc_code)
|
||||
{
|
||||
struct cnc_nand_info *info = chip->priv;
|
||||
unsigned int ecc24 = cnc_nand_readl(info, ECC_CODE_OFFSET);
|
||||
|
||||
ecc_code[0] = (uint8_t)(ecc24);
|
||||
ecc_code[1] = (uint8_t)(ecc24 >> 8);
|
||||
ecc_code[2] = (uint8_t)(ecc24 >> 16);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cnc_nand_1bitecc_correct(struct nand_chip *chip, uint8_t *dat,
|
||||
uint8_t *read_ecc, uint8_t *calc_ecc)
|
||||
{
|
||||
// struct nand_chip *chip = mtd->priv;
|
||||
uint32_t eccNand = read_ecc[0] | (read_ecc[1] << 8) |
|
||||
(read_ecc[2] << 16);
|
||||
uint32_t eccCalc = calc_ecc[0] | (calc_ecc[1] << 8) |
|
||||
(calc_ecc[2] << 16);
|
||||
uint32_t diff = eccCalc ^ eccNand;
|
||||
unsigned int bit, byte;
|
||||
|
||||
|
||||
// CNC_DEBUG("eccNand=0x%x, eccCalc=0x%x, diff=0x%x\n", eccNand, eccCalc, diff);
|
||||
|
||||
if (diff) {
|
||||
if ((((diff >> 1) ^ diff) & 0x555555) == 0x555555) {
|
||||
/* Correctable error */
|
||||
|
||||
/* calculate the bit position of the error */
|
||||
bit = ((diff >> 19) & 1) |
|
||||
((diff >> 20) & 2) |
|
||||
((diff >> 21) & 4);
|
||||
/* calculate the byte position of the error */
|
||||
|
||||
byte = ((diff >> 9) & 0x100) |
|
||||
(diff & 0x80) |
|
||||
((diff << 1) & 0x40) |
|
||||
((diff << 2) & 0x20) |
|
||||
((diff << 3) & 0x10) |
|
||||
((diff >> 12) & 0x08) |
|
||||
((diff >> 11) & 0x04) |
|
||||
((diff >> 10) & 0x02) |
|
||||
((diff >> 9) & 0x01);
|
||||
|
||||
// CNC_DEBUG("byte=%d, bit=%d, dat[byte]=0x%2x\n",byte,bit,dat[byte]);
|
||||
|
||||
dat[byte] ^= (1 << bit);
|
||||
|
||||
|
||||
// CNC_DEBUG("corrected dat[byte]=0x%x\n",dat[byte]);
|
||||
|
||||
return 1;
|
||||
} else if (!(diff & (diff - 1))) {
|
||||
/* Single bit ECC error in the ECC itself,
|
||||
* nothing to fix */
|
||||
return 1;
|
||||
} else {
|
||||
/* Uncorrectable error */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void cnc_nand_cmd_ctrl(struct nand_chip *chip, int cmd, unsigned int ctrl)
|
||||
{
|
||||
|
||||
struct cnc_nand_info *info = chip->priv;
|
||||
struct nand_chip *nand = chip;
|
||||
uint32_t addr = (uint32_t __force)info->iobase;
|
||||
|
||||
if (ctrl & NAND_CTRL_CHANGE) {
|
||||
if (ctrl & NAND_NCE){
|
||||
(* (unsigned int *) (info->smcbase)) |= MASK_CE_FORCE_LOW;
|
||||
}
|
||||
else {
|
||||
(* (unsigned int *) (info->smcbase)) &= ~(MASK_CE_FORCE_LOW);
|
||||
}
|
||||
|
||||
//command latch enable, address latch enable
|
||||
//these are controlled by address line
|
||||
if (ctrl & NAND_CLE){
|
||||
addr |= info->mask_cle;
|
||||
}
|
||||
else if (ctrl & NAND_ALE) {
|
||||
addr |= info->mask_ale;
|
||||
}
|
||||
nand->legacy.IO_ADDR_W = (void __iomem __force *)addr;
|
||||
}
|
||||
|
||||
|
||||
if (cmd != NAND_CMD_NONE)
|
||||
iowrite8(cmd, nand->legacy.IO_ADDR_W);
|
||||
}
|
||||
|
||||
static int cnc_nand_device_ready(struct nand_chip *chip)
|
||||
{
|
||||
struct cnc_nand_info *info = chip->priv;
|
||||
|
||||
return cnc_nand_readl(info, STATUS_OFFSET) & BIT(0);
|
||||
}
|
||||
|
||||
|
||||
static void cnc_nand_select_chip(struct nand_chip *chip, int cs)
|
||||
{
|
||||
struct cnc_nand_info *info = chip->priv;
|
||||
|
||||
//-1: ctrl change
|
||||
if (cs == -1) {
|
||||
chip->legacy.cmd_ctrl(chip, NAND_CMD_NONE, 0 | NAND_CTRL_CHANGE);
|
||||
}
|
||||
|
||||
chip->legacy.IO_ADDR_W = info->iobase;
|
||||
chip->legacy.IO_ADDR_R = chip->legacy.IO_ADDR_W;
|
||||
}
|
||||
|
||||
static void cnc_nand_read_buf(struct nand_chip *chip, u8 *buf, int len)
|
||||
{
|
||||
__raw_readsb(chip->legacy.IO_ADDR_R, buf, len);
|
||||
}
|
||||
|
||||
static void cnc_nand_write_buf(struct nand_chip *chip, const u8 *buf, int len)
|
||||
{
|
||||
__raw_writesb(chip->legacy.IO_ADDR_W, buf, len);
|
||||
}
|
||||
|
||||
static int cnc_nand_attach_chip(struct nand_chip *chip)
|
||||
{
|
||||
chip->ecc.calculate = cnc_nand_1bitecc_calculate;
|
||||
chip->ecc.correct = cnc_nand_1bitecc_correct;
|
||||
chip->ecc.hwctl = cnc_nand_1bitecc_hwctl;
|
||||
chip->ecc.strength = 1;
|
||||
chip->ecc.bytes = 3;
|
||||
chip->ecc.size = 512;
|
||||
chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct nand_controller_ops cnc_nand_controller_ops = {
|
||||
.attach_chip = cnc_nand_attach_chip,
|
||||
};
|
||||
|
||||
static int cnc1800l_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct nand_chip *this;
|
||||
struct cnc_nand_info *pdata;
|
||||
struct mtd_info *mtd;
|
||||
struct resource *res1;
|
||||
struct resource *res2;
|
||||
int err = 0;
|
||||
|
||||
this = devm_kzalloc(&pdev->dev, sizeof(struct nand_chip),
|
||||
GFP_KERNEL);
|
||||
if (!this){
|
||||
dev_err(&pdev->dev, "cnc-nand: allocation failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pdata = devm_kzalloc(&pdev->dev, sizeof(struct cnc_nand_info), GFP_KERNEL);
|
||||
|
||||
if(!pdata)
|
||||
goto nopdata;
|
||||
|
||||
this->priv = pdata;
|
||||
|
||||
pdata->chip = this;
|
||||
|
||||
pdata->dev = &pdev->dev;
|
||||
mtd = nand_to_mtd(this);
|
||||
|
||||
pdata->mask_ale = MASK_ALE;
|
||||
pdata->mask_cle = MASK_CLE;
|
||||
|
||||
mtd->dev.parent = &pdev->dev;
|
||||
mtd->name = DRIVER_NAME;
|
||||
mtd->priv = &pdata->chip;
|
||||
|
||||
this->legacy.chip_delay = 0;
|
||||
|
||||
nand_set_controller_data(this, pdata);
|
||||
nand_set_flash_node(this, pdev->dev.of_node);
|
||||
|
||||
this->options |= NAND_KEEP_TIMINGS | NAND_BBT_USE_FLASH | NAND_BBT_LASTBLOCK | NAND_BBT_WRITE;
|
||||
|
||||
res1 = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
pdata->smcbase = devm_ioremap_resource(&pdev->dev, res1);
|
||||
if(IS_ERR(pdata->smcbase)){
|
||||
err = PTR_ERR(pdata->smcbase);
|
||||
goto err_res1;
|
||||
}
|
||||
|
||||
res2 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
pdata->iobase = devm_ioremap_resource(&pdev->dev, res2);
|
||||
if (IS_ERR(pdata->iobase)){
|
||||
err = PTR_ERR(pdata->iobase);
|
||||
goto err_res2;
|
||||
}
|
||||
|
||||
this->controller = &pdata->controller;
|
||||
|
||||
this->legacy.IO_ADDR_R = pdata->iobase;
|
||||
this->legacy.IO_ADDR_W = pdata->iobase;
|
||||
|
||||
this->legacy.select_chip = cnc_nand_select_chip;
|
||||
this->legacy.cmd_ctrl = cnc_nand_cmd_ctrl;
|
||||
this->legacy.dev_ready = cnc_nand_device_ready;
|
||||
|
||||
|
||||
this->legacy.read_buf = cnc_nand_read_buf;
|
||||
this->legacy.write_buf = cnc_nand_write_buf;
|
||||
|
||||
this->controller->ops = &cnc_nand_controller_ops;
|
||||
err = nand_scan(this, 1);
|
||||
if (err){
|
||||
dev_err(&pdev->dev, "cnc-nand: failed to scan.\n");
|
||||
goto escan;
|
||||
}
|
||||
|
||||
err = mtd_device_register(mtd, cnc_nand_partitions, ARRAY_SIZE(cnc_nand_partitions));
|
||||
if (err){
|
||||
dev_err(&pdev->dev, "cnc-nand: failed to register partitions.\n");
|
||||
goto cleanup_nand;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, pdata);
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
escan:
|
||||
cleanup_nand:
|
||||
nand_cleanup(this);
|
||||
err_res2:
|
||||
err_res1:
|
||||
kfree(pdata);
|
||||
nopdata:
|
||||
kfree(this);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cnc1800l_nand__remove(struct platform_device *pdev)
|
||||
{
|
||||
struct cnc_nand_info *info = platform_get_drvdata(pdev);
|
||||
struct nand_chip *chip = info->chip;
|
||||
int ret;
|
||||
|
||||
ret = mtd_device_unregister(nand_to_mtd(chip));
|
||||
WARN_ON(ret);
|
||||
nand_cleanup(chip);
|
||||
kfree(chip->priv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id cnc1800l_nand_dt_ids[] = {
|
||||
{ .compatible = "cavium,cnc1800l-nand", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, cnc1800l_nand_dt_ids);
|
||||
|
||||
|
||||
static struct platform_driver cnc1800l_nand_driver = {
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.of_match_table = cnc1800l_nand_dt_ids,
|
||||
},
|
||||
.probe = cnc1800l_nand_probe,
|
||||
.remove = cnc1800l_nand__remove,
|
||||
};
|
||||
module_platform_driver(cnc1800l_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("xiaodong fan <xiaodong.fan@caviumnetworks.com>");
|
||||
MODULE_DESCRIPTION("NAND flash driver for Cavium celestial CNC18xx serials SOC");
|
||||
MODULE_ALIAS("platform:cnc1800l-nand");
|
||||
|
Loading…
x
Reference in New Issue
Block a user