mirror of
https://github.com/torvalds/linux.git
synced 2024-12-05 18:41:23 +00:00
309600c14e
Currently, the selection of ECC byte ordering for software hamming is done at compilation time, which doesn't make sense when ECC byte calculation is done in hardware and byte ordering is forced by the hardware engine. In this case, only the correction is done in software and we want to force the byte-ordering no matter the value of CONFIG_MTD_NAND_ECC_SMC. This is typically the case for the FSMC (Smart Media ordering), TMIO and TXX9NDFMC (regular byte ordering) blocks. For all other use cases (pure software implementation, SM FTL and nandecctest), we keep selecting the byte ordering based on the CONFIG_MTD_NAND_ECC_SMC value. It might not be ideal for SM FTL (I'd expect Smart Media ordering to be employed by the Smart Media FTL), but this option doesn't seem to be enabled in the existing _defconfig, so I can't tell setting sm_order to true is the right choice. Signed-off-by: Boris Brezillon <boris.brezillon@bootlin.com> Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
423 lines
11 KiB
C
423 lines
11 KiB
C
/*
|
|
* TXx9 NAND flash memory controller driver
|
|
* Based on RBTX49xx patch from CELF patch archive.
|
|
*
|
|
* 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.
|
|
*
|
|
* (C) Copyright TOSHIBA CORPORATION 2004-2007
|
|
* All Rights Reserved.
|
|
*/
|
|
#include <linux/err.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/mtd/mtd.h>
|
|
#include <linux/mtd/rawnand.h>
|
|
#include <linux/mtd/nand_ecc.h>
|
|
#include <linux/mtd/partitions.h>
|
|
#include <linux/io.h>
|
|
#include <linux/platform_data/txx9/ndfmc.h>
|
|
|
|
/* TXX9 NDFMC Registers */
|
|
#define TXX9_NDFDTR 0x00
|
|
#define TXX9_NDFMCR 0x04
|
|
#define TXX9_NDFSR 0x08
|
|
#define TXX9_NDFISR 0x0c
|
|
#define TXX9_NDFIMR 0x10
|
|
#define TXX9_NDFSPR 0x14
|
|
#define TXX9_NDFRSTR 0x18 /* not TX4939 */
|
|
|
|
/* NDFMCR : NDFMC Mode Control */
|
|
#define TXX9_NDFMCR_WE 0x80
|
|
#define TXX9_NDFMCR_ECC_ALL 0x60
|
|
#define TXX9_NDFMCR_ECC_RESET 0x60
|
|
#define TXX9_NDFMCR_ECC_READ 0x40
|
|
#define TXX9_NDFMCR_ECC_ON 0x20
|
|
#define TXX9_NDFMCR_ECC_OFF 0x00
|
|
#define TXX9_NDFMCR_CE 0x10
|
|
#define TXX9_NDFMCR_BSPRT 0x04 /* TX4925/TX4926 only */
|
|
#define TXX9_NDFMCR_ALE 0x02
|
|
#define TXX9_NDFMCR_CLE 0x01
|
|
/* TX4939 only */
|
|
#define TXX9_NDFMCR_X16 0x0400
|
|
#define TXX9_NDFMCR_DMAREQ_MASK 0x0300
|
|
#define TXX9_NDFMCR_DMAREQ_NODMA 0x0000
|
|
#define TXX9_NDFMCR_DMAREQ_128 0x0100
|
|
#define TXX9_NDFMCR_DMAREQ_256 0x0200
|
|
#define TXX9_NDFMCR_DMAREQ_512 0x0300
|
|
#define TXX9_NDFMCR_CS_MASK 0x0c
|
|
#define TXX9_NDFMCR_CS(ch) ((ch) << 2)
|
|
|
|
/* NDFMCR : NDFMC Status */
|
|
#define TXX9_NDFSR_BUSY 0x80
|
|
/* TX4939 only */
|
|
#define TXX9_NDFSR_DMARUN 0x40
|
|
|
|
/* NDFMCR : NDFMC Reset */
|
|
#define TXX9_NDFRSTR_RST 0x01
|
|
|
|
struct txx9ndfmc_priv {
|
|
struct platform_device *dev;
|
|
struct nand_chip chip;
|
|
int cs;
|
|
const char *mtdname;
|
|
};
|
|
|
|
#define MAX_TXX9NDFMC_DEV 4
|
|
struct txx9ndfmc_drvdata {
|
|
struct mtd_info *mtds[MAX_TXX9NDFMC_DEV];
|
|
void __iomem *base;
|
|
unsigned char hold; /* in gbusclock */
|
|
unsigned char spw; /* in gbusclock */
|
|
struct nand_controller controller;
|
|
};
|
|
|
|
static struct platform_device *mtd_to_platdev(struct mtd_info *mtd)
|
|
{
|
|
struct nand_chip *chip = mtd_to_nand(mtd);
|
|
struct txx9ndfmc_priv *txx9_priv = nand_get_controller_data(chip);
|
|
return txx9_priv->dev;
|
|
}
|
|
|
|
static void __iomem *ndregaddr(struct platform_device *dev, unsigned int reg)
|
|
{
|
|
struct txx9ndfmc_drvdata *drvdata = platform_get_drvdata(dev);
|
|
struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev);
|
|
|
|
return drvdata->base + (reg << plat->shift);
|
|
}
|
|
|
|
static u32 txx9ndfmc_read(struct platform_device *dev, unsigned int reg)
|
|
{
|
|
return __raw_readl(ndregaddr(dev, reg));
|
|
}
|
|
|
|
static void txx9ndfmc_write(struct platform_device *dev,
|
|
u32 val, unsigned int reg)
|
|
{
|
|
__raw_writel(val, ndregaddr(dev, reg));
|
|
}
|
|
|
|
static uint8_t txx9ndfmc_read_byte(struct nand_chip *chip)
|
|
{
|
|
struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip));
|
|
|
|
return txx9ndfmc_read(dev, TXX9_NDFDTR);
|
|
}
|
|
|
|
static void txx9ndfmc_write_buf(struct nand_chip *chip, const uint8_t *buf,
|
|
int len)
|
|
{
|
|
struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip));
|
|
void __iomem *ndfdtr = ndregaddr(dev, TXX9_NDFDTR);
|
|
u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR);
|
|
|
|
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_WE, TXX9_NDFMCR);
|
|
while (len--)
|
|
__raw_writel(*buf++, ndfdtr);
|
|
txx9ndfmc_write(dev, mcr, TXX9_NDFMCR);
|
|
}
|
|
|
|
static void txx9ndfmc_read_buf(struct nand_chip *chip, uint8_t *buf, int len)
|
|
{
|
|
struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip));
|
|
void __iomem *ndfdtr = ndregaddr(dev, TXX9_NDFDTR);
|
|
|
|
while (len--)
|
|
*buf++ = __raw_readl(ndfdtr);
|
|
}
|
|
|
|
static void txx9ndfmc_cmd_ctrl(struct nand_chip *chip, int cmd,
|
|
unsigned int ctrl)
|
|
{
|
|
struct txx9ndfmc_priv *txx9_priv = nand_get_controller_data(chip);
|
|
struct platform_device *dev = txx9_priv->dev;
|
|
struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev);
|
|
|
|
if (ctrl & NAND_CTRL_CHANGE) {
|
|
u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR);
|
|
|
|
mcr &= ~(TXX9_NDFMCR_CLE | TXX9_NDFMCR_ALE | TXX9_NDFMCR_CE);
|
|
mcr |= ctrl & NAND_CLE ? TXX9_NDFMCR_CLE : 0;
|
|
mcr |= ctrl & NAND_ALE ? TXX9_NDFMCR_ALE : 0;
|
|
/* TXX9_NDFMCR_CE bit is 0:high 1:low */
|
|
mcr |= ctrl & NAND_NCE ? TXX9_NDFMCR_CE : 0;
|
|
if (txx9_priv->cs >= 0 && (ctrl & NAND_NCE)) {
|
|
mcr &= ~TXX9_NDFMCR_CS_MASK;
|
|
mcr |= TXX9_NDFMCR_CS(txx9_priv->cs);
|
|
}
|
|
txx9ndfmc_write(dev, mcr, TXX9_NDFMCR);
|
|
}
|
|
if (cmd != NAND_CMD_NONE)
|
|
txx9ndfmc_write(dev, cmd & 0xff, TXX9_NDFDTR);
|
|
if (plat->flags & NDFMC_PLAT_FLAG_DUMMYWRITE) {
|
|
/* dummy write to update external latch */
|
|
if ((ctrl & NAND_CTRL_CHANGE) && cmd == NAND_CMD_NONE)
|
|
txx9ndfmc_write(dev, 0, TXX9_NDFDTR);
|
|
}
|
|
mmiowb();
|
|
}
|
|
|
|
static int txx9ndfmc_dev_ready(struct nand_chip *chip)
|
|
{
|
|
struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip));
|
|
|
|
return !(txx9ndfmc_read(dev, TXX9_NDFSR) & TXX9_NDFSR_BUSY);
|
|
}
|
|
|
|
static int txx9ndfmc_calculate_ecc(struct nand_chip *chip, const uint8_t *dat,
|
|
uint8_t *ecc_code)
|
|
{
|
|
struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip));
|
|
int eccbytes;
|
|
u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR);
|
|
|
|
mcr &= ~TXX9_NDFMCR_ECC_ALL;
|
|
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR);
|
|
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_READ, TXX9_NDFMCR);
|
|
for (eccbytes = chip->ecc.bytes; eccbytes > 0; eccbytes -= 3) {
|
|
ecc_code[1] = txx9ndfmc_read(dev, TXX9_NDFDTR);
|
|
ecc_code[0] = txx9ndfmc_read(dev, TXX9_NDFDTR);
|
|
ecc_code[2] = txx9ndfmc_read(dev, TXX9_NDFDTR);
|
|
ecc_code += 3;
|
|
}
|
|
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR);
|
|
return 0;
|
|
}
|
|
|
|
static int txx9ndfmc_correct_data(struct nand_chip *chip, unsigned char *buf,
|
|
unsigned char *read_ecc,
|
|
unsigned char *calc_ecc)
|
|
{
|
|
int eccsize;
|
|
int corrected = 0;
|
|
int stat;
|
|
|
|
for (eccsize = chip->ecc.size; eccsize > 0; eccsize -= 256) {
|
|
stat = __nand_correct_data(buf, read_ecc, calc_ecc, 256,
|
|
false);
|
|
if (stat < 0)
|
|
return stat;
|
|
corrected += stat;
|
|
buf += 256;
|
|
read_ecc += 3;
|
|
calc_ecc += 3;
|
|
}
|
|
return corrected;
|
|
}
|
|
|
|
static void txx9ndfmc_enable_hwecc(struct nand_chip *chip, int mode)
|
|
{
|
|
struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip));
|
|
u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR);
|
|
|
|
mcr &= ~TXX9_NDFMCR_ECC_ALL;
|
|
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_RESET, TXX9_NDFMCR);
|
|
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR);
|
|
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_ON, TXX9_NDFMCR);
|
|
}
|
|
|
|
static void txx9ndfmc_initialize(struct platform_device *dev)
|
|
{
|
|
struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev);
|
|
struct txx9ndfmc_drvdata *drvdata = platform_get_drvdata(dev);
|
|
int tmout = 100;
|
|
|
|
if (plat->flags & NDFMC_PLAT_FLAG_NO_RSTR)
|
|
; /* no NDFRSTR. Write to NDFSPR resets the NDFMC. */
|
|
else {
|
|
/* reset NDFMC */
|
|
txx9ndfmc_write(dev,
|
|
txx9ndfmc_read(dev, TXX9_NDFRSTR) |
|
|
TXX9_NDFRSTR_RST,
|
|
TXX9_NDFRSTR);
|
|
while (txx9ndfmc_read(dev, TXX9_NDFRSTR) & TXX9_NDFRSTR_RST) {
|
|
if (--tmout == 0) {
|
|
dev_err(&dev->dev, "reset failed.\n");
|
|
break;
|
|
}
|
|
udelay(1);
|
|
}
|
|
}
|
|
/* setup Hold Time, Strobe Pulse Width */
|
|
txx9ndfmc_write(dev, (drvdata->hold << 4) | drvdata->spw, TXX9_NDFSPR);
|
|
txx9ndfmc_write(dev,
|
|
(plat->flags & NDFMC_PLAT_FLAG_USE_BSPRT) ?
|
|
TXX9_NDFMCR_BSPRT : 0, TXX9_NDFMCR);
|
|
}
|
|
|
|
#define TXX9NDFMC_NS_TO_CYC(gbusclk, ns) \
|
|
DIV_ROUND_UP((ns) * DIV_ROUND_UP(gbusclk, 1000), 1000000)
|
|
|
|
static int txx9ndfmc_attach_chip(struct nand_chip *chip)
|
|
{
|
|
struct mtd_info *mtd = nand_to_mtd(chip);
|
|
|
|
if (mtd->writesize >= 512) {
|
|
chip->ecc.size = 512;
|
|
chip->ecc.bytes = 6;
|
|
} else {
|
|
chip->ecc.size = 256;
|
|
chip->ecc.bytes = 3;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct nand_controller_ops txx9ndfmc_controller_ops = {
|
|
.attach_chip = txx9ndfmc_attach_chip,
|
|
};
|
|
|
|
static int __init txx9ndfmc_probe(struct platform_device *dev)
|
|
{
|
|
struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev);
|
|
int hold, spw;
|
|
int i;
|
|
struct txx9ndfmc_drvdata *drvdata;
|
|
unsigned long gbusclk = plat->gbus_clock;
|
|
struct resource *res;
|
|
|
|
drvdata = devm_kzalloc(&dev->dev, sizeof(*drvdata), GFP_KERNEL);
|
|
if (!drvdata)
|
|
return -ENOMEM;
|
|
res = platform_get_resource(dev, IORESOURCE_MEM, 0);
|
|
drvdata->base = devm_ioremap_resource(&dev->dev, res);
|
|
if (IS_ERR(drvdata->base))
|
|
return PTR_ERR(drvdata->base);
|
|
|
|
hold = plat->hold ?: 20; /* tDH */
|
|
spw = plat->spw ?: 90; /* max(tREADID, tWP, tRP) */
|
|
|
|
hold = TXX9NDFMC_NS_TO_CYC(gbusclk, hold);
|
|
spw = TXX9NDFMC_NS_TO_CYC(gbusclk, spw);
|
|
if (plat->flags & NDFMC_PLAT_FLAG_HOLDADD)
|
|
hold -= 2; /* actual hold time : (HOLD + 2) BUSCLK */
|
|
spw -= 1; /* actual wait time : (SPW + 1) BUSCLK */
|
|
hold = clamp(hold, 1, 15);
|
|
drvdata->hold = hold;
|
|
spw = clamp(spw, 1, 15);
|
|
drvdata->spw = spw;
|
|
dev_info(&dev->dev, "CLK:%ldMHz HOLD:%d SPW:%d\n",
|
|
(gbusclk + 500000) / 1000000, hold, spw);
|
|
|
|
nand_controller_init(&drvdata->controller);
|
|
drvdata->controller.ops = &txx9ndfmc_controller_ops;
|
|
|
|
platform_set_drvdata(dev, drvdata);
|
|
txx9ndfmc_initialize(dev);
|
|
|
|
for (i = 0; i < MAX_TXX9NDFMC_DEV; i++) {
|
|
struct txx9ndfmc_priv *txx9_priv;
|
|
struct nand_chip *chip;
|
|
struct mtd_info *mtd;
|
|
|
|
if (!(plat->ch_mask & (1 << i)))
|
|
continue;
|
|
txx9_priv = kzalloc(sizeof(struct txx9ndfmc_priv),
|
|
GFP_KERNEL);
|
|
if (!txx9_priv)
|
|
continue;
|
|
chip = &txx9_priv->chip;
|
|
mtd = nand_to_mtd(chip);
|
|
mtd->dev.parent = &dev->dev;
|
|
|
|
chip->legacy.read_byte = txx9ndfmc_read_byte;
|
|
chip->legacy.read_buf = txx9ndfmc_read_buf;
|
|
chip->legacy.write_buf = txx9ndfmc_write_buf;
|
|
chip->legacy.cmd_ctrl = txx9ndfmc_cmd_ctrl;
|
|
chip->legacy.dev_ready = txx9ndfmc_dev_ready;
|
|
chip->ecc.calculate = txx9ndfmc_calculate_ecc;
|
|
chip->ecc.correct = txx9ndfmc_correct_data;
|
|
chip->ecc.hwctl = txx9ndfmc_enable_hwecc;
|
|
chip->ecc.mode = NAND_ECC_HW;
|
|
chip->ecc.strength = 1;
|
|
chip->legacy.chip_delay = 100;
|
|
chip->controller = &drvdata->controller;
|
|
|
|
nand_set_controller_data(chip, txx9_priv);
|
|
txx9_priv->dev = dev;
|
|
|
|
if (plat->ch_mask != 1) {
|
|
txx9_priv->cs = i;
|
|
txx9_priv->mtdname = kasprintf(GFP_KERNEL, "%s.%u",
|
|
dev_name(&dev->dev), i);
|
|
} else {
|
|
txx9_priv->cs = -1;
|
|
txx9_priv->mtdname = kstrdup(dev_name(&dev->dev),
|
|
GFP_KERNEL);
|
|
}
|
|
if (!txx9_priv->mtdname) {
|
|
kfree(txx9_priv);
|
|
dev_err(&dev->dev, "Unable to allocate MTD name.\n");
|
|
continue;
|
|
}
|
|
if (plat->wide_mask & (1 << i))
|
|
chip->options |= NAND_BUSWIDTH_16;
|
|
|
|
if (nand_scan(chip, 1)) {
|
|
kfree(txx9_priv->mtdname);
|
|
kfree(txx9_priv);
|
|
continue;
|
|
}
|
|
mtd->name = txx9_priv->mtdname;
|
|
|
|
mtd_device_register(mtd, NULL, 0);
|
|
drvdata->mtds[i] = mtd;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __exit txx9ndfmc_remove(struct platform_device *dev)
|
|
{
|
|
struct txx9ndfmc_drvdata *drvdata = platform_get_drvdata(dev);
|
|
int i;
|
|
|
|
if (!drvdata)
|
|
return 0;
|
|
for (i = 0; i < MAX_TXX9NDFMC_DEV; i++) {
|
|
struct mtd_info *mtd = drvdata->mtds[i];
|
|
struct nand_chip *chip;
|
|
struct txx9ndfmc_priv *txx9_priv;
|
|
|
|
if (!mtd)
|
|
continue;
|
|
chip = mtd_to_nand(mtd);
|
|
txx9_priv = nand_get_controller_data(chip);
|
|
|
|
nand_release(chip);
|
|
kfree(txx9_priv->mtdname);
|
|
kfree(txx9_priv);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int txx9ndfmc_resume(struct platform_device *dev)
|
|
{
|
|
if (platform_get_drvdata(dev))
|
|
txx9ndfmc_initialize(dev);
|
|
return 0;
|
|
}
|
|
#else
|
|
#define txx9ndfmc_resume NULL
|
|
#endif
|
|
|
|
static struct platform_driver txx9ndfmc_driver = {
|
|
.remove = __exit_p(txx9ndfmc_remove),
|
|
.resume = txx9ndfmc_resume,
|
|
.driver = {
|
|
.name = "txx9ndfmc",
|
|
},
|
|
};
|
|
|
|
module_platform_driver_probe(txx9ndfmc_driver, txx9ndfmc_probe);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("TXx9 SoC NAND flash controller driver");
|
|
MODULE_ALIAS("platform:txx9ndfmc");
|