summaryrefslogtreecommitdiffstats
path: root/drivers/mtd
diff options
context:
space:
mode:
authorMiquel Raynal <miquel.raynal@bootlin.com>2020-03-30 10:14:19 +0200
committerMiquel Raynal <miquel.raynal@bootlin.com>2020-03-30 10:14:19 +0200
commit699274b1a1272601eaa379973d10a503a60bef5d (patch)
treea9a409356f65274898ca7aa4b959e37705e59e0c /drivers/mtd
parent245bbe80e01b15655a1b75dff90ec304f9992190 (diff)
parentfca88925d76978b7f20de42d8ead34fb91500003 (diff)
Merge tag 'nand/for-5.7' into mtd/next
Raw NAND core changes: * Add support for manufacturer specific suspend/resume operation * Add support for manufacturer specific lock/unlock operation * Replace zero-length array with flexible-array member * Fix a typo ("manufecturer") * Ensure nand_soft_waitrdy wait period is enough Raw NAND controller driver changes: * Brcmnand: Add support for flash-edu for dma transfers (+ bindings) * Cadence: Reinit completion before executing a new command Change bad block marker size Fix the calculation of the avaialble OOB size Get meta data size from registers * Qualcom: Use dma_request_chan() instead dma_request_slave_channel() Release resources on failure within qcom_nandc_alloc() * Allwinner: Use dma_request_chan() instead dma_request_slave_channel() * Marvell: Use dma_request_chan() instead dma_request_slave_channel() Release DMA channel on error * Freescale: Use dma_request_chan() instead dma_request_slave_channel() * Macronix: Add support for Macronix NAND randomizer (+ bindings) * Ams-delta: Rename structures and functions to gpio_nand* Make the driver custom I/O ready Drop useless local variable Support custom driver initialisation Add module device tables Handle more GPIO pins as optional Make read pulses optional Don't hardcode read/write pulse widths Push inversion handling to gpiolib Enable OF partition info support Drop board specific partition info Use struct gpio_nand_platdata Write protect device during probe * Ingenic: Use devm_platform_ioremap_resource() Add dependency on MIPS || COMPILE_TEST * Denali: Deassert write protect pin * ST: Use dma_request_chan() instead dma_request_slave_channel() Raw NAND chip driver changes: * Toshiba: Support reading the number of bitflips for BENAND (Built-in ECC NAND) * Macronix: Add support for deep power down mode Add support for block protection SPI-NAND core changes: * Do not erase the block before writing a bad block marker * Explicitly use MTD_OPS_RAW to write the bad block marker to OOB * Stop using spinand->oobbuf for buffering bad block markers * Rework detect procedure for different READ_ID operation SPI-NAND driver changes: * Toshiba: Support for new Kioxia Serial NAND Rename function name to change suffix and prefix (8Gbit) Add comment about Kioxia ID * Micron: Add new Micron SPI NAND devices with multiple dies Add M70A series Micron SPI NAND devices identify SPI NAND device with Continuous Read mode Add new Micron SPI NAND devices Describe the SPI NAND device MT29F2G01ABAGD Generalize the OOB layout structure and function names
Diffstat (limited to 'drivers/mtd')
-rw-r--r--drivers/mtd/nand/raw/ams-delta.c237
-rw-r--r--drivers/mtd/nand/raw/brcmnand/brcmnand.c293
-rw-r--r--drivers/mtd/nand/raw/cadence-nand-controller.c34
-rw-r--r--drivers/mtd/nand/raw/denali.c1
-rw-r--r--drivers/mtd/nand/raw/denali.h2
-rw-r--r--drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c21
-rw-r--r--drivers/mtd/nand/raw/ingenic/Kconfig1
-rw-r--r--drivers/mtd/nand/raw/ingenic/ingenic_ecc.c4
-rw-r--r--drivers/mtd/nand/raw/internals.h1
-rw-r--r--drivers/mtd/nand/raw/marvell_nand.c40
-rw-r--r--drivers/mtd/nand/raw/meson_nand.c2
-rw-r--r--drivers/mtd/nand/raw/mtk_nand.c2
-rw-r--r--drivers/mtd/nand/raw/nand_base.c62
-rw-r--r--drivers/mtd/nand/raw/nand_hynix.c2
-rw-r--r--drivers/mtd/nand/raw/nand_macronix.c227
-rw-r--r--drivers/mtd/nand/raw/nand_toshiba.c58
-rw-r--r--drivers/mtd/nand/raw/qcom_nandc.c105
-rw-r--r--drivers/mtd/nand/raw/stm32_fmc2_nand.c44
-rw-r--r--drivers/mtd/nand/raw/sunxi_nand.c17
-rw-r--r--drivers/mtd/nand/spi/core.c104
-rw-r--r--drivers/mtd/nand/spi/gigadevice.c45
-rw-r--r--drivers/mtd/nand/spi/macronix.c30
-rw-r--r--drivers/mtd/nand/spi/micron.c172
-rw-r--r--drivers/mtd/nand/spi/paragon.c28
-rw-r--r--drivers/mtd/nand/spi/toshiba.c208
-rw-r--r--drivers/mtd/nand/spi/winbond.c34
26 files changed, 1347 insertions, 427 deletions
diff --git a/drivers/mtd/nand/raw/ams-delta.c b/drivers/mtd/nand/raw/ams-delta.c
index 8312182088c1..d66dab25df20 100644
--- a/drivers/mtd/nand/raw/ams-delta.c
+++ b/drivers/mtd/nand/raw/ams-delta.c
@@ -19,15 +19,17 @@
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand-gpio.h>
#include <linux/mtd/rawnand.h>
#include <linux/mtd/partitions.h>
+#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/sizes.h>
/*
* MTD structure for E3 (Delta)
*/
-struct ams_delta_nand {
+struct gpio_nand {
struct nand_controller base;
struct nand_chip nand_chip;
struct gpio_desc *gpiod_rdy;
@@ -39,41 +41,20 @@ struct ams_delta_nand {
struct gpio_desc *gpiod_cle;
struct gpio_descs *data_gpiods;
bool data_in;
+ unsigned int tRP;
+ unsigned int tWP;
+ u8 (*io_read)(struct gpio_nand *this);
+ void (*io_write)(struct gpio_nand *this, u8 byte);
};
-/*
- * Define partitions for flash devices
- */
-
-static const struct mtd_partition partition_info[] = {
- { .name = "Kernel",
- .offset = 0,
- .size = 3 * SZ_1M + SZ_512K },
- { .name = "u-boot",
- .offset = 3 * SZ_1M + SZ_512K,
- .size = SZ_256K },
- { .name = "u-boot params",
- .offset = 3 * SZ_1M + SZ_512K + SZ_256K,
- .size = SZ_256K },
- { .name = "Amstrad LDR",
- .offset = 4 * SZ_1M,
- .size = SZ_256K },
- { .name = "File system",
- .offset = 4 * SZ_1M + 1 * SZ_256K,
- .size = 27 * SZ_1M },
- { .name = "PBL reserved",
- .offset = 32 * SZ_1M - 3 * SZ_256K,
- .size = 3 * SZ_256K },
-};
-
-static void ams_delta_write_commit(struct ams_delta_nand *priv)
+static void gpio_nand_write_commit(struct gpio_nand *priv)
{
- gpiod_set_value(priv->gpiod_nwe, 0);
- ndelay(40);
gpiod_set_value(priv->gpiod_nwe, 1);
+ ndelay(priv->tWP);
+ gpiod_set_value(priv->gpiod_nwe, 0);
}
-static void ams_delta_io_write(struct ams_delta_nand *priv, u8 byte)
+static void gpio_nand_io_write(struct gpio_nand *priv, u8 byte)
{
struct gpio_descs *data_gpiods = priv->data_gpiods;
DECLARE_BITMAP(values, BITS_PER_TYPE(byte)) = { byte, };
@@ -81,10 +62,10 @@ static void ams_delta_io_write(struct ams_delta_nand *priv, u8 byte)
gpiod_set_raw_array_value(data_gpiods->ndescs, data_gpiods->desc,
data_gpiods->info, values);
- ams_delta_write_commit(priv);
+ gpio_nand_write_commit(priv);
}
-static void ams_delta_dir_output(struct ams_delta_nand *priv, u8 byte)
+static void gpio_nand_dir_output(struct gpio_nand *priv, u8 byte)
{
struct gpio_descs *data_gpiods = priv->data_gpiods;
DECLARE_BITMAP(values, BITS_PER_TYPE(byte)) = { byte, };
@@ -94,30 +75,30 @@ static void ams_delta_dir_output(struct ams_delta_nand *priv, u8 byte)
gpiod_direction_output_raw(data_gpiods->desc[i],
test_bit(i, values));
- ams_delta_write_commit(priv);
+ gpio_nand_write_commit(priv);
priv->data_in = false;
}
-static u8 ams_delta_io_read(struct ams_delta_nand *priv)
+static u8 gpio_nand_io_read(struct gpio_nand *priv)
{
u8 res;
struct gpio_descs *data_gpiods = priv->data_gpiods;
DECLARE_BITMAP(values, BITS_PER_TYPE(res)) = { 0, };
- gpiod_set_value(priv->gpiod_nre, 0);
- ndelay(40);
+ gpiod_set_value(priv->gpiod_nre, 1);
+ ndelay(priv->tRP);
gpiod_get_raw_array_value(data_gpiods->ndescs, data_gpiods->desc,
data_gpiods->info, values);
- gpiod_set_value(priv->gpiod_nre, 1);
+ gpiod_set_value(priv->gpiod_nre, 0);
res = values[0];
return res;
}
-static void ams_delta_dir_input(struct ams_delta_nand *priv)
+static void gpio_nand_dir_input(struct gpio_nand *priv)
{
struct gpio_descs *data_gpiods = priv->data_gpiods;
int i;
@@ -128,68 +109,67 @@ static void ams_delta_dir_input(struct ams_delta_nand *priv)
priv->data_in = true;
}
-static void ams_delta_write_buf(struct ams_delta_nand *priv, const u8 *buf,
- int len)
+static void gpio_nand_write_buf(struct gpio_nand *priv, const u8 *buf, int len)
{
int i = 0;
if (len > 0 && priv->data_in)
- ams_delta_dir_output(priv, buf[i++]);
+ gpio_nand_dir_output(priv, buf[i++]);
while (i < len)
- ams_delta_io_write(priv, buf[i++]);
+ priv->io_write(priv, buf[i++]);
}
-static void ams_delta_read_buf(struct ams_delta_nand *priv, u8 *buf, int len)
+static void gpio_nand_read_buf(struct gpio_nand *priv, u8 *buf, int len)
{
int i;
- if (!priv->data_in)
- ams_delta_dir_input(priv);
+ if (priv->data_gpiods && !priv->data_in)
+ gpio_nand_dir_input(priv);
for (i = 0; i < len; i++)
- buf[i] = ams_delta_io_read(priv);
+ buf[i] = priv->io_read(priv);
}
-static void ams_delta_ctrl_cs(struct ams_delta_nand *priv, bool assert)
+static void gpio_nand_ctrl_cs(struct gpio_nand *priv, bool assert)
{
- gpiod_set_value(priv->gpiod_nce, assert ? 0 : 1);
+ gpiod_set_value(priv->gpiod_nce, assert);
}
-static int ams_delta_exec_op(struct nand_chip *this,
+static int gpio_nand_exec_op(struct nand_chip *this,
const struct nand_operation *op, bool check_only)
{
- struct ams_delta_nand *priv = nand_get_controller_data(this);
+ struct gpio_nand *priv = nand_get_controller_data(this);
const struct nand_op_instr *instr;
int ret = 0;
if (check_only)
return 0;
- ams_delta_ctrl_cs(priv, 1);
+ gpio_nand_ctrl_cs(priv, 1);
for (instr = op->instrs; instr < op->instrs + op->ninstrs; instr++) {
switch (instr->type) {
case NAND_OP_CMD_INSTR:
gpiod_set_value(priv->gpiod_cle, 1);
- ams_delta_write_buf(priv, &instr->ctx.cmd.opcode, 1);
+ gpio_nand_write_buf(priv, &instr->ctx.cmd.opcode, 1);
gpiod_set_value(priv->gpiod_cle, 0);
break;
case NAND_OP_ADDR_INSTR:
gpiod_set_value(priv->gpiod_ale, 1);
- ams_delta_write_buf(priv, instr->ctx.addr.addrs,
+ gpio_nand_write_buf(priv, instr->ctx.addr.addrs,
instr->ctx.addr.naddrs);
gpiod_set_value(priv->gpiod_ale, 0);
break;
case NAND_OP_DATA_IN_INSTR:
- ams_delta_read_buf(priv, instr->ctx.data.buf.in,
+ gpio_nand_read_buf(priv, instr->ctx.data.buf.in,
instr->ctx.data.len);
break;
case NAND_OP_DATA_OUT_INSTR:
- ams_delta_write_buf(priv, instr->ctx.data.buf.out,
+ gpio_nand_write_buf(priv, instr->ctx.data.buf.out,
instr->ctx.data.len);
break;
@@ -206,28 +186,61 @@ static int ams_delta_exec_op(struct nand_chip *this,
break;
}
- ams_delta_ctrl_cs(priv, 0);
+ gpio_nand_ctrl_cs(priv, 0);
return ret;
}
-static const struct nand_controller_ops ams_delta_ops = {
- .exec_op = ams_delta_exec_op,
+static int gpio_nand_setup_data_interface(struct nand_chip *this, int csline,
+ const struct nand_data_interface *cf)
+{
+ struct gpio_nand *priv = nand_get_controller_data(this);
+ const struct nand_sdr_timings *sdr = nand_get_sdr_timings(cf);
+ struct device *dev = &nand_to_mtd(this)->dev;
+
+ if (IS_ERR(sdr))
+ return PTR_ERR(sdr);
+
+ if (csline == NAND_DATA_IFACE_CHECK_ONLY)
+ return 0;
+
+ if (priv->gpiod_nre) {
+ priv->tRP = DIV_ROUND_UP(sdr->tRP_min, 1000);
+ dev_dbg(dev, "using %u ns read pulse width\n", priv->tRP);
+ }
+
+ priv->tWP = DIV_ROUND_UP(sdr->tWP_min, 1000);
+ dev_dbg(dev, "using %u ns write pulse width\n", priv->tWP);
+
+ return 0;
+}
+
+static const struct nand_controller_ops gpio_nand_ops = {
+ .exec_op = gpio_nand_exec_op,
+ .setup_data_interface = gpio_nand_setup_data_interface,
};
/*
* Main initialization routine
*/
-static int ams_delta_init(struct platform_device *pdev)
+static int gpio_nand_probe(struct platform_device *pdev)
{
- struct ams_delta_nand *priv;
+ struct gpio_nand_platdata *pdata = dev_get_platdata(&pdev->dev);
+ const struct mtd_partition *partitions = NULL;
+ int num_partitions = 0;
+ struct gpio_nand *priv;
struct nand_chip *this;
struct mtd_info *mtd;
- struct gpio_descs *data_gpiods;
+ int (*probe)(struct platform_device *pdev, struct gpio_nand *priv);
int err = 0;
+ if (pdata) {
+ partitions = pdata->parts;
+ num_partitions = pdata->num_parts;
+ }
+
/* Allocate memory for MTD device structure and private data */
- priv = devm_kzalloc(&pdev->dev, sizeof(struct ams_delta_nand),
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct gpio_nand),
GFP_KERNEL);
if (!priv)
return -ENOMEM;
@@ -238,6 +251,7 @@ static int ams_delta_init(struct platform_device *pdev)
mtd->dev.parent = &pdev->dev;
nand_set_controller_data(this, priv);
+ nand_set_flash_node(this, pdev->dev.of_node);
priv->gpiod_rdy = devm_gpiod_get_optional(&pdev->dev, "rdy", GPIOD_IN);
if (IS_ERR(priv->gpiod_rdy)) {
@@ -251,29 +265,33 @@ static int ams_delta_init(struct platform_device *pdev)
platform_set_drvdata(pdev, priv);
- /* Set chip enabled, but */
- priv->gpiod_nwp = devm_gpiod_get(&pdev->dev, "nwp", GPIOD_OUT_HIGH);
+ /* Set chip enabled but write protected */
+ priv->gpiod_nwp = devm_gpiod_get_optional(&pdev->dev, "nwp",
+ GPIOD_OUT_HIGH);
if (IS_ERR(priv->gpiod_nwp)) {
err = PTR_ERR(priv->gpiod_nwp);
dev_err(&pdev->dev, "NWP GPIO request failed (%d)\n", err);
return err;
}
- priv->gpiod_nce = devm_gpiod_get(&pdev->dev, "nce", GPIOD_OUT_HIGH);
+ priv->gpiod_nce = devm_gpiod_get_optional(&pdev->dev, "nce",
+ GPIOD_OUT_LOW);
if (IS_ERR(priv->gpiod_nce)) {
err = PTR_ERR(priv->gpiod_nce);
dev_err(&pdev->dev, "NCE GPIO request failed (%d)\n", err);
return err;
}
- priv->gpiod_nre = devm_gpiod_get(&pdev->dev, "nre", GPIOD_OUT_HIGH);
+ priv->gpiod_nre = devm_gpiod_get_optional(&pdev->dev, "nre",
+ GPIOD_OUT_LOW);
if (IS_ERR(priv->gpiod_nre)) {
err = PTR_ERR(priv->gpiod_nre);
dev_err(&pdev->dev, "NRE GPIO request failed (%d)\n", err);
return err;
}
- priv->gpiod_nwe = devm_gpiod_get(&pdev->dev, "nwe", GPIOD_OUT_HIGH);
+ priv->gpiod_nwe = devm_gpiod_get_optional(&pdev->dev, "nwe",
+ GPIOD_OUT_LOW);
if (IS_ERR(priv->gpiod_nwe)) {
err = PTR_ERR(priv->gpiod_nwe);
dev_err(&pdev->dev, "NWE GPIO request failed (%d)\n", err);
@@ -295,28 +313,62 @@ static int ams_delta_init(struct platform_device *pdev)
}
/* Request array of data pins, initialize them as input */
- data_gpiods = devm_gpiod_get_array(&pdev->dev, "data", GPIOD_IN);
- if (IS_ERR(data_gpiods)) {
- err = PTR_ERR(data_gpiods);
+ priv->data_gpiods = devm_gpiod_get_array_optional(&pdev->dev, "data",
+ GPIOD_IN);
+ if (IS_ERR(priv->data_gpiods)) {
+ err = PTR_ERR(priv->data_gpiods);
dev_err(&pdev->dev, "data GPIO request failed: %d\n", err);
return err;
}
- priv->data_gpiods = data_gpiods;
- priv->data_in = true;
+ if (priv->data_gpiods) {
+ if (!priv->gpiod_nwe) {
+ dev_err(&pdev->dev,
+ "mandatory NWE pin not provided by platform\n");
+ return -ENODEV;
+ }
- /* Initialize the NAND controller object embedded in ams_delta_nand. */
- priv->base.ops = &ams_delta_ops;
+ priv->io_read = gpio_nand_io_read;
+ priv->io_write = gpio_nand_io_write;
+ priv->data_in = true;
+ }
+
+ if (pdev->id_entry)
+ probe = (void *) pdev->id_entry->driver_data;
+ else
+ probe = of_device_get_match_data(&pdev->dev);
+ if (probe)
+ err = probe(pdev, priv);
+ if (err)
+ return err;
+
+ if (!priv->io_read || !priv->io_write) {
+ dev_err(&pdev->dev, "incomplete device configuration\n");
+ return -ENODEV;
+ }
+
+ /* Initialize the NAND controller object embedded in gpio_nand. */
+ priv->base.ops = &gpio_nand_ops;
nand_controller_init(&priv->base);
this->controller = &priv->base;
+ /*
+ * FIXME: We should release write protection only after nand_scan() to
+ * be on the safe side but we can't do that until we have a generic way
+ * to assert/deassert WP from the core. Even if the core shouldn't
+ * write things in the nand_scan() path, it should have control on this
+ * pin just in case we ever need to disable write protection during
+ * chip detection/initialization.
+ */
+ /* Release write protection */
+ gpiod_set_value(priv->gpiod_nwp, 0);
+
/* Scan to find existence of the device */
err = nand_scan(this, 1);
if (err)
return err;
/* Register the partitions */
- err = mtd_device_register(mtd, partition_info,
- ARRAY_SIZE(partition_info));
+ err = mtd_device_register(mtd, partitions, num_partitions);
if (err)
goto err_nand_cleanup;
@@ -331,26 +383,47 @@ err_nand_cleanup:
/*
* Clean up routine
*/
-static int ams_delta_cleanup(struct platform_device *pdev)
+static int gpio_nand_remove(struct platform_device *pdev)
{
- struct ams_delta_nand *priv = platform_get_drvdata(pdev);
+ struct gpio_nand *priv = platform_get_drvdata(pdev);
struct mtd_info *mtd = nand_to_mtd(&priv->nand_chip);
+ /* Apply write protection */
+ gpiod_set_value(priv->gpiod_nwp, 1);
+
/* Unregister device */
nand_release(mtd_to_nand(mtd));
return 0;
}
-static struct platform_driver ams_delta_nand_driver = {
- .probe = ams_delta_init,
- .remove = ams_delta_cleanup,
+static const struct of_device_id gpio_nand_of_id_table[] = {
+ {
+ /* sentinel */
+ },
+};
+MODULE_DEVICE_TABLE(of, gpio_nand_of_id_table);
+
+static const struct platform_device_id gpio_nand_plat_id_table[] = {
+ {
+ .name = "ams-delta-nand",
+ }, {
+ /* sentinel */
+ },
+};
+MODULE_DEVICE_TABLE(platform, gpio_nand_plat_id_table);
+
+static struct platform_driver gpio_nand_driver = {
+ .probe = gpio_nand_probe,
+ .remove = gpio_nand_remove,
+ .id_table = gpio_nand_plat_id_table,
.driver = {
.name = "ams-delta-nand",
+ .of_match_table = of_match_ptr(gpio_nand_of_id_table),
},
};
-module_platform_driver(ams_delta_nand_driver);
+module_platform_driver(gpio_nand_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Jonathan McDowell <noodles@earth.li>");
diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.c b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
index 44518dada75b..e4e3ceeac38f 100644
--- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c
+++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
@@ -102,6 +102,45 @@ struct brcm_nand_dma_desc {
#define NAND_CTRL_RDY (INTFC_CTLR_READY | INTFC_FLASH_READY)
#define NAND_POLL_STATUS_TIMEOUT_MS 100
+#define EDU_CMD_WRITE 0x00
+#define EDU_CMD_READ 0x01
+#define EDU_STATUS_ACTIVE BIT(0)
+#define EDU_ERR_STATUS_ERRACK BIT(0)
+#define EDU_DONE_MASK GENMASK(1, 0)
+
+#define EDU_CONFIG_MODE_NAND BIT(0)
+#define EDU_CONFIG_SWAP_BYTE BIT(1)
+#ifdef CONFIG_CPU_BIG_ENDIAN
+#define EDU_CONFIG_SWAP_CFG EDU_CONFIG_SWAP_BYTE
+#else
+#define EDU_CONFIG_SWAP_CFG 0
+#endif
+
+/* edu registers */
+enum edu_reg {
+ EDU_CONFIG = 0,
+ EDU_DRAM_ADDR,
+ EDU_EXT_ADDR,
+ EDU_LENGTH,
+ EDU_CMD,
+ EDU_STOP,
+ EDU_STATUS,
+ EDU_DONE,
+ EDU_ERR_STATUS,
+};
+
+static const u16 edu_regs[] = {
+ [EDU_CONFIG] = 0x00,
+ [EDU_DRAM_ADDR] = 0x04,
+ [EDU_EXT_ADDR] = 0x08,
+ [EDU_LENGTH] = 0x0c,
+ [EDU_CMD] = 0x10,
+ [EDU_STOP] = 0x14,
+ [EDU_STATUS] = 0x18,
+ [EDU_DONE] = 0x1c,
+ [EDU_ERR_STATUS] = 0x20,
+};
+
/* flash_dma registers */
enum flash_dma_reg {
FLASH_DMA_REVISION = 0,
@@ -167,6 +206,8 @@ enum {
BRCMNAND_HAS_WP = BIT(3),
};
+struct brcmnand_host;
+
struct brcmnand_controller {
struct device *dev;
struct nand_controller controller;
@@ -185,17 +226,32 @@ struct brcmnand_controller {
int cmd_pending;
bool dma_pending;
+ bool edu_pending;
struct completion done;
struct completion dma_done;
+ struct completion edu_done;
/* List of NAND hosts (one for each chip-select) */
struct list_head host_list;
+ /* EDU info, per-transaction */
+ const u16 *edu_offsets;
+ void __iomem *edu_base;
+ int edu_irq;
+ int edu_count;
+ u64 edu_dram_addr;
+ u32 edu_ext_addr;
+ u32 edu_cmd;
+ u32 edu_config;
+
/* flash_dma reg */
const u16 *flash_dma_offsets;
struct brcm_nand_dma_desc *dma_desc;
dma_addr_t dma_pa;
+ int (*dma_trans)(struct brcmnand_host *host, u64 addr, u32 *buf,
+ u32 len, u8 dma_cmd);
+
/* in-memory cache of the FLASH_CACHE, used only for some commands */
u8 flash_cache[FC_BYTES];
@@ -216,6 +272,7 @@ struct brcmnand_controller {
u32 nand_cs_nand_xor;
u32 corr_stat_threshold;
u32 flash_dma_mode;
+ u32 flash_edu_mode;
bool pio_poll_mode;
};
@@ -657,6 +714,22 @@ static inline void brcmnand_write_fc(struct brcmnand_controller *ctrl,
__raw_writel(val, ctrl->nand_fc + word * 4);
}
+static inline void edu_writel(struct brcmnand_controller *ctrl,
+ enum edu_reg reg, u32 val)
+{
+ u16 offs = ctrl->edu_offsets[reg];
+
+ brcmnand_writel(val, ctrl->edu_base + offs);
+}
+
+static inline u32 edu_readl(struct brcmnand_controller *ctrl,
+ enum edu_reg reg)
+{
+ u16 offs = ctrl->edu_offsets[reg];
+
+ return brcmnand_readl(ctrl->edu_base + offs);
+}
+
static void brcmnand_clear_ecc_addr(struct brcmnand_controller *ctrl)
{
@@ -926,6 +999,16 @@ static inline bool has_flash_dma(struct brcmnand_controller *ctrl)
return ctrl->flash_dma_base;
}
+static inline bool has_edu(struct brcmnand_controller *ctrl)
+{
+ return ctrl->edu_base;
+}
+
+static inline bool use_dma(struct brcmnand_controller *ctrl)
+{
+ return has_flash_dma(ctrl) || has_edu(ctrl);
+}
+
static inline void disable_ctrl_irqs(struct brcmnand_controller *ctrl)
{
if (ctrl->pio_poll_mode)
@@ -1299,6 +1382,52 @@ static int write_oob_to_regs(struct brcmnand_controller *ctrl, int i,
return tbytes;
}
+static void brcmnand_edu_init(struct brcmnand_controller *ctrl)
+{
+ /* initialize edu */
+ edu_writel(ctrl, EDU_ERR_STATUS, 0);
+ edu_readl(ctrl, EDU_ERR_STATUS);
+ edu_writel(ctrl, EDU_DONE, 0);
+ edu_writel(ctrl, EDU_DONE, 0);
+ edu_writel(ctrl, EDU_DONE, 0);
+ edu_writel(ctrl, EDU_DONE, 0);
+ edu_readl(ctrl, EDU_DONE);
+}
+
+/* edu irq */
+static irqreturn_t brcmnand_edu_irq(int irq, void *data)
+{
+ struct brcmnand_controller *ctrl = data;
+
+ if (ctrl->edu_count) {
+ ctrl->edu_count--;
+ while (!(edu_readl(ctrl, EDU_DONE) & EDU_DONE_MASK))
+ udelay(1);
+ edu_writel(ctrl, EDU_DONE, 0);
+ edu_readl(ctrl, EDU_DONE);
+ }
+
+ if (ctrl->edu_count) {
+ ctrl->edu_dram_addr += FC_BYTES;
+ ctrl->edu_ext_addr += FC_BYTES;
+
+ edu_writel(ctrl, EDU_DRAM_ADDR, (u32)ctrl->edu_dram_addr);
+ edu_readl(ctrl, EDU_DRAM_ADDR);
+ edu_writel(ctrl, EDU_EXT_ADDR, ctrl->edu_ext_addr);
+ edu_readl(ctrl, EDU_EXT_ADDR);
+
+ mb(); /* flush previous writes */
+ edu_writel(ctrl, EDU_CMD, ctrl->edu_cmd);
+ edu_readl(ctrl, EDU_CMD);
+
+ return IRQ_HANDLED;
+ }
+
+ complete(&ctrl->edu_done);
+
+ return IRQ_HANDLED;
+}
+
static irqreturn_t brcmnand_ctlrdy_irq(int irq, void *data)
{
struct brcmnand_controller *ctrl = data;
@@ -1307,6 +1436,16 @@ static irqreturn_t brcmnand_ctlrdy_irq(int irq, void *data)
if (ctrl->dma_pending)
return IRQ_HANDLED;
+ /* check if you need to piggy back on the ctrlrdy irq */
+ if (ctrl->edu_pending) {
+ if (irq == ctrl->irq && ((int)ctrl->edu_irq >= 0))
+ /* Discard interrupts while using dedicated edu irq */
+ return IRQ_HANDLED;
+
+ /* no registered edu irq, call handler */
+ return brcmnand_edu_irq(irq, data);
+ }
+
complete(&ctrl->done);
return IRQ_HANDLED;
}
@@ -1645,6 +1784,81 @@ static void brcmnand_write_buf(struct nand_chip *chip, const uint8_t *buf,
}
/**
+ * Kick EDU engine
+ */
+static int brcmnand_edu_trans(struct brcmnand_host *host, u64 addr, u32 *buf,
+ u32 len, u8 cmd)
+{
+ struct brcmnand_controller *ctrl = host->ctrl;
+ unsigned long timeo = msecs_to_jiffies(200);
+ int ret = 0;
+ int dir = (cmd == CMD_PAGE_READ ? DMA_FROM_DEVICE : DMA_TO_DEVICE);
+ u8 edu_cmd = (cmd == CMD_PAGE_READ ? EDU_CMD_READ : EDU_CMD_WRITE);
+ unsigned int trans = len >> FC_SHIFT;
+ dma_addr_t pa;
+
+ pa = dma_map_single(ctrl->dev, buf, len, dir);
+ if (dma_mapping_error(ctrl->dev, pa)) {
+ dev_err(ctrl->dev, "unable to map buffer for EDU DMA\n");
+ return -ENOMEM;
+ }
+
+ ctrl->edu_pending = true;
+ ctrl->edu_dram_addr = pa;
+ ctrl->edu_ext_addr = addr;
+ ctrl->edu_cmd = edu_cmd;
+ ctrl->edu_count = trans;
+
+ edu_writel(ctrl, EDU_DRAM_ADDR, (u32)ctrl->edu_dram_addr);
+ edu_readl(ctrl, EDU_DRAM_ADDR);
+ edu_writel(ctrl, EDU_EXT_ADDR, ctrl->edu_ext_addr);
+ edu_readl(ctrl, EDU_EXT_ADDR);
+ edu_writel(ctrl, EDU_LENGTH, FC_BYTES);
+ edu_readl(ctrl, EDU_LENGTH);
+
+ /* Start edu engine */
+ mb(); /* flush previous writes */
+ edu_writel(ctrl, EDU_CMD, ctrl->edu_cmd);
+ edu_readl(ctrl, EDU_CMD);
+
+ if (wait_for_completion_timeout(&ctrl->edu_done, timeo) <= 0) {
+ dev_err(ctrl->dev,
+ "timeout waiting for EDU; status %#x, error status %#x\n",
+ edu_readl(ctrl, EDU_STATUS),
+ edu_readl(ctrl, EDU_ERR_STATUS));
+ }
+
+ dma_unmap_single(ctrl->dev, pa, len, dir);
+
+ /* for program page check NAND status */
+ if (((brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS) &
+ INTFC_FLASH_STATUS) & NAND_STATUS_FAIL) &&
+ edu_cmd == EDU_CMD_WRITE) {
+ dev_info(ctrl->dev, "program failed at %llx\n",
+ (unsigned long long)addr);
+ ret = -EIO;
+ }
+
+ /* Make sure the EDU status is clean */
+ if (edu_readl(ctrl, EDU_STATUS) & EDU_STATUS_ACTIVE)
+ dev_warn(ctrl->dev, "EDU still active: %#x\n",
+ edu_readl(ctrl, EDU_STATUS));
+
+ if (unlikely(edu_readl(ctrl, EDU_ERR_STATUS) & EDU_ERR_STATUS_ERRACK)) {
+ dev_warn(ctrl->dev, "EDU RBUS error at addr %llx\n",
+ (unsigned long long)addr);
+ ret = -EIO;
+ }
+
+ ctrl->edu_pending = false;
+ brcmnand_edu_init(ctrl);
+ edu_writel(ctrl, EDU_STOP, 0); /* force stop */
+ edu_readl(ctrl, EDU_STOP);
+
+ return ret;
+}
+
+/**
* Construct a FLASH_DMA descriptor as part of a linked list. You must know the
* following ahead of time:
* - Is this descriptor the beginning or end of a linked list?
@@ -1850,9 +2064,11 @@ static int brcmnand_read(struct mtd_info *mtd, struct nand_chip *chip,
try_dmaread:
brcmnand_clear_ecc_addr(ctrl);
- if (has_flash_dma(ctrl) && !oob && flash_dma_buf_ok(buf)) {
- err = brcmnand_dma_trans(host, addr, buf, trans * FC_BYTES,
- CMD_PAGE_READ);
+ if (ctrl->dma_trans && !oob && flash_dma_buf_ok(buf)) {
+ err = ctrl->dma_trans(host, addr, buf,
+ trans * FC_BYTES,
+ CMD_PAGE_READ);
+
if (err) {
if (mtd_is_bitflip_or_eccerr(err))
err_addr = addr;
@@ -1988,10 +2204,12 @@ static int brcmnand_write(struct mtd_info *mtd, struct nand_chip *chip,
for (i = 0; i < ctrl->max_oob; i += 4)
oob_reg_write(ctrl, i, 0xffffffff);
- if (has_flash_dma(ctrl) && !oob && flash_dma_buf_ok(buf)) {
- if (brcmnand_dma_trans(host, addr, (u32 *)buf,
- mtd->writesize, CMD_PROGRAM_PAGE))
+ if (use_dma(ctrl) && !oob && flash_dma_buf_ok(buf)) {
+ if (ctrl->dma_trans(host, addr, (u32 *)buf, mtd->writesize,
+ CMD_PROGRAM_PAGE))
+
ret = -EIO;
+
goto out;
}
@@ -2494,6 +2712,8 @@ static int brcmnand_suspend(struct device *dev)
if (has_flash_dma(ctrl))
ctrl->flash_dma_mode = flash_dma_readl(ctrl, FLASH_DMA_MODE);
+ else if (has_edu(ctrl))
+ ctrl->edu_config = edu_readl(ctrl, EDU_CONFIG);
return 0;
}
@@ -2508,6 +2728,14 @@ static int brcmnand_resume(struct device *dev)
flash_dma_writel(ctrl, FLASH_DMA_ERROR_STATUS, 0);
}
+ if (has_edu(ctrl))
+ ctrl->edu_config = edu_readl(ctrl, EDU_CONFIG);
+ else {
+ edu_writel(ctrl, EDU_CONFIG, ctrl->edu_config);
+ edu_readl(ctrl, EDU_CONFIG);
+ brcmnand_edu_init(ctrl);
+ }
+
brcmnand_write_reg(ctrl, BRCMNAND_CS_SELECT, ctrl->nand_cs_nand_select);
brcmnand_write_reg(ctrl, BRCMNAND_CS_XOR, ctrl->nand_cs_nand_xor);
brcmnand_write_reg(ctrl, BRCMNAND_CORR_THRESHOLD,
@@ -2553,6 +2781,49 @@ MODULE_DEVICE_TABLE(of, brcmnand_of_match);
/***********************************************************************
* Platform driver setup (per controller)
***********************************************************************/
+static int brcmnand_edu_setup(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct brcmnand_controller *ctrl = dev_get_drvdata(&pdev->dev);
+ struct resource *res;
+ int ret;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "flash-edu");
+ if (res) {
+ ctrl->edu_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(ctrl->edu_base))
+ return PTR_ERR(ctrl->edu_base);
+
+ ctrl->edu_offsets = edu_regs;
+
+ edu_writel(ctrl, EDU_CONFIG, EDU_CONFIG_MODE_NAND |
+ EDU_CONFIG_SWAP_CFG);
+ edu_readl(ctrl, EDU_CONFIG);
+
+ /* initialize edu */
+ brcmnand_edu_init(ctrl);
+
+ ctrl->edu_irq = platform_get_irq_optional(pdev, 1);
+ if (ctrl->edu_irq < 0) {
+ dev_warn(dev,
+ "FLASH EDU enabled, using ctlrdy irq\n");
+ } else {
+ ret = devm_request_irq(dev, ctrl->edu_irq,
+ brcmnand_edu_irq, 0,
+ "brcmnand-edu", ctrl);
+ if (ret < 0) {
+ dev_err(ctrl->dev, "can't allocate IRQ %d: error %d\n",
+ ctrl->edu_irq, ret);
+ return ret;
+ }
+
+ dev_info(dev, "FLASH EDU enabled using irq %u\n",
+ ctrl->edu_irq);
+ }<