linux-stable/drivers/mtd/nand/txx9ndfmc.c
Boris Brezillon d4092d76a4 mtd: nand: Rename nand.h into rawnand.h
We are planning to share more code between different NAND based
devices (SPI NAND, OneNAND and raw NANDs), but before doing that
we need to move the existing include/linux/mtd/nand.h file into
include/linux/mtd/rawnand.h so we can later create a nand.h header
containing all common structure and function prototypes.

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
Signed-off-by: Peter Pan <peterpandong@micron.com>
Acked-by: Vladimir Zapolskiy <vz@mleia.com>
Acked-by: Alexander Sverdlin <alexander.sverdlin@gmail.com>
Acked-by: Wenyou Yang <wenyou.yang@microchip.com>
Acked-by: Krzysztof Kozlowski <krzk@kernel.org>
Acked-by: Han Xu <han.xu@nxp.com>
Acked-by: H Hartley Sweeten <hsweeten@visionengravers.com>
Acked-by: Shawn Guo <shawnguo@kernel.org>
Acked-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
Acked-by: Neil Armstrong <narmstrong@baylibre.com>
Acked-by: Masahiro Yamada <yamada.masahiro@socionext.com>
Acked-By: Harvey Hunt <harveyhuntnexus@gmail.com>
Acked-by: Tony Lindgren <tony@atomide.com>
Acked-by: Krzysztof Halasa <khalasa@piap.pl>
2017-08-13 10:11:49 +02:00

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 <asm/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_hw_control hw_control;
};
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 mtd_info *mtd)
{
struct platform_device *dev = mtd_to_platdev(mtd);
return txx9ndfmc_read(dev, TXX9_NDFDTR);
}
static void txx9ndfmc_write_buf(struct mtd_info *mtd, const uint8_t *buf,
int len)
{
struct platform_device *dev = mtd_to_platdev(mtd);
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 mtd_info *mtd, uint8_t *buf, int len)
{
struct platform_device *dev = mtd_to_platdev(mtd);
void __iomem *ndfdtr = ndregaddr(dev, TXX9_NDFDTR);
while (len--)
*buf++ = __raw_readl(ndfdtr);
}
static void txx9ndfmc_cmd_ctrl(struct mtd_info *mtd, int cmd,
unsigned int ctrl)
{
struct nand_chip *chip = mtd_to_nand(mtd);
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 mtd_info *mtd)
{
struct platform_device *dev = mtd_to_platdev(mtd);
return !(txx9ndfmc_read(dev, TXX9_NDFSR) & TXX9_NDFSR_BUSY);
}
static int txx9ndfmc_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat,
uint8_t *ecc_code)
{
struct platform_device *dev = mtd_to_platdev(mtd);
struct nand_chip *chip = mtd_to_nand(mtd);
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 mtd_info *mtd, unsigned char *buf,
unsigned char *read_ecc, unsigned char *calc_ecc)
{
struct nand_chip *chip = mtd_to_nand(mtd);
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);
if (stat < 0)
return stat;
corrected += stat;
buf += 256;
read_ecc += 3;
calc_ecc += 3;
}
return corrected;
}
static void txx9ndfmc_enable_hwecc(struct mtd_info *mtd, int mode)
{
struct platform_device *dev = mtd_to_platdev(mtd);
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_nand_scan(struct mtd_info *mtd)
{
struct nand_chip *chip = mtd_to_nand(mtd);
int ret;
ret = nand_scan_ident(mtd, 1, NULL);
if (!ret) {
if (mtd->writesize >= 512) {
/* Hardware ECC 6 byte ECC per 512 Byte data */
chip->ecc.size = 512;
chip->ecc.bytes = 6;
}
ret = nand_scan_tail(mtd);
}
return ret;
}
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_hw_control_init(&drvdata->hw_control);
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->read_byte = txx9ndfmc_read_byte;
chip->read_buf = txx9ndfmc_read_buf;
chip->write_buf = txx9ndfmc_write_buf;
chip->cmd_ctrl = txx9ndfmc_cmd_ctrl;
chip->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;
/* txx9ndfmc_nand_scan will overwrite ecc.size and ecc.bytes */
chip->ecc.size = 256;
chip->ecc.bytes = 3;
chip->ecc.strength = 1;
chip->chip_delay = 100;
chip->controller = &drvdata->hw_control;
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 (txx9ndfmc_nand_scan(mtd)) {
kfree(txx9_priv->mtdname);
kfree(txx9_priv);
continue;
}
mtd->name = txx9_priv->mtdname;
mtd_device_parse_register(mtd, NULL, NULL, 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(mtd);
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");