summaryrefslogtreecommitdiff
path: root/drivers/mtd/nand/gpmi
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd/nand/gpmi')
-rw-r--r--drivers/mtd/nand/gpmi/Makefile6
-rw-r--r--drivers/mtd/nand/gpmi/gpmi-base.c1979
-rw-r--r--drivers/mtd/nand/gpmi/gpmi-bbt.c565
-rw-r--r--drivers/mtd/nand/gpmi/gpmi-bch.c289
-rw-r--r--drivers/mtd/nand/gpmi/gpmi-ecc8.c382
-rw-r--r--drivers/mtd/nand/gpmi/gpmi-hamming-13-8.c131
-rw-r--r--drivers/mtd/nand/gpmi/gpmi-hamming-22-16.c196
-rw-r--r--drivers/mtd/nand/gpmi/gpmi-hamming-22-16.h43
-rw-r--r--drivers/mtd/nand/gpmi/gpmi.h291
9 files changed, 3882 insertions, 0 deletions
diff --git a/drivers/mtd/nand/gpmi/Makefile b/drivers/mtd/nand/gpmi/Makefile
new file mode 100644
index 000000000000..4a4b50d294fa
--- /dev/null
+++ b/drivers/mtd/nand/gpmi/Makefile
@@ -0,0 +1,6 @@
+obj-$(CONFIG_MTD_NAND_GPMI) += gpmi.o
+gpmi-objs += gpmi-base.o gpmi-bbt.o
+gpmi-objs += gpmi-hamming-22-16.o
+gpmi-objs += gpmi-hamming-13-8.o
+gpmi-objs += gpmi-bch.o
+gpmi-objs += gpmi-ecc8.o
diff --git a/drivers/mtd/nand/gpmi/gpmi-base.c b/drivers/mtd/nand/gpmi/gpmi-base.c
new file mode 100644
index 000000000000..547dec9f29f8
--- /dev/null
+++ b/drivers/mtd/nand/gpmi/gpmi-base.c
@@ -0,0 +1,1979 @@
+/*
+ * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface)
+ *
+ * Author: dmitry pervushin <dimka@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/concat.h>
+#include <linux/dma-mapping.h>
+#include <linux/ctype.h>
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/regulator/consumer.h>
+
+#include <mach/stmp3xxx.h>
+#include <mach/regs-ecc8.h>
+#include <mach/dma.h>
+#include "gpmi.h"
+
+static int debug;
+static int copies;
+static int map_buffers = true;
+static int ff_writes;
+static int raw_mode;
+static int add_mtd_entire;
+static int add_mtd_chip;
+static int ignorebad;
+static int max_chips = 4;
+static long clk = -1;
+static int bch /* = 0 */;
+
+static int gpmi_nand_init_hw(struct platform_device *pdev, int request_pins);
+static void gpmi_nand_release_hw(struct platform_device *pdev);
+static int gpmi_dma_exchange(struct gpmi_nand_data *g,
+ struct stmp3xxx_dma_descriptor *dma);
+static void gpmi_read_buf(struct mtd_info *mtd, uint8_t *buf, int len);
+
+struct gpmi_nand_timing gpmi_safe_timing = {
+ .address_setup = 25,
+ .data_setup = 80,
+ .data_hold = 60,
+ .dsample_time = 6,
+};
+
+/*
+ * define OOB placement schemes for 4k and 2k page devices
+ */
+static struct nand_ecclayout gpmi_oob_128 = {
+ .oobfree = {
+ {
+ .offset = 2,
+ .length = 56,
+ }, {
+ .length = 0,
+ },
+ },
+};
+
+static struct nand_ecclayout gpmi_oob_64 = {
+ .oobfree = {
+ {
+ .offset = 2,
+ .length = 16,
+ }, {
+ .length = 0,
+ },
+ },
+};
+
+static inline u32 gpmi_cycles_ceil(u32 ntime, u32 period)
+{
+ int k;
+
+ k = (ntime + period - 1) / period;
+ if (k == 0)
+ k++;
+ return k;
+}
+
+/**
+ * gpmi_timer_expiry - timer expiry handling
+ */
+static void gpmi_timer_expiry(unsigned long d)
+{
+#ifdef CONFIG_PM
+ struct gpmi_nand_data *g = (struct gpmi_nand_data *)d;
+
+ pr_debug("%s: timer expired\n", __func__);
+ del_timer_sync(&g->timer);
+
+ if (g->use_count ||
+ HW_GPMI_CTRL0_RD() & BM_GPMI_CTRL0_RUN) {
+ g->timer.expires = jiffies + 4*HZ;
+ add_timer(&g->timer);
+ } else {
+ HW_GPMI_CTRL0_SET(BM_GPMI_CTRL0_CLKGATE);
+ clk_disable(g->clk);
+ g->self_suspended = 1;
+ }
+#endif
+}
+
+/**
+ * gpmi_self_wakeup - wakeup from self-pm light suspend
+ */
+static void gpmi_self_wakeup(struct gpmi_nand_data *g)
+{
+#ifdef CONFIG_PM
+ int i = 1000;
+ clk_enable(g->clk);
+ HW_GPMI_CTRL0_CLR(BM_GPMI_CTRL0_CLKGATE);
+ while (i-- && HW_GPMI_CTRL0_RD() & BM_GPMI_CTRL0_CLKGATE);
+
+ pr_debug("%s: i stopped at %d, data %p\n", __func__, i, g);
+ g->self_suspended = 0;
+ g->timer.expires = jiffies + 4*HZ;
+ add_timer(&g->timer);
+#endif
+}
+
+/**
+ * gpmi_set_timings - set GPMI timings
+ * @pdev: pointer to GPMI platform device
+ * @tm: pointer to structure &gpmi_nand_timing with new timings
+ *
+ * During initialization, GPMI uses safe sub-optimal timings, which
+ * can be changed after reading boot control blocks
+ */
+void gpmi_set_timings(struct platform_device *pdev, struct gpmi_nand_timing *tm)
+{
+ struct gpmi_nand_data *g = platform_get_drvdata(pdev);
+ u32 period_ns = 1000000 / clk_get_rate(g->clk) + 1;
+ u32 address_cycles, data_setup_cycles;
+ u32 data_hold_cycles, data_sample_cycles;
+ u32 busy_timeout;
+ u32 t0;
+
+ if (g->self_suspended)
+ gpmi_self_wakeup(g);
+ g->use_count++;
+
+ g->timing = *tm;
+
+ address_cycles = gpmi_cycles_ceil(tm->address_setup, period_ns);
+ data_setup_cycles = gpmi_cycles_ceil(tm->data_setup, period_ns);
+ data_hold_cycles = gpmi_cycles_ceil(tm->data_hold, period_ns);
+ data_sample_cycles = gpmi_cycles_ceil(tm->dsample_time + period_ns / 4,
+ period_ns / 2);
+ busy_timeout = gpmi_cycles_ceil(10000000 / 4096, period_ns);
+
+ dev_dbg(&pdev->dev,
+ "%s: ADDR %u, DSETUP %u, DH %u, DSAMPLE %u, BTO %u\n",
+ __func__,
+ address_cycles, data_setup_cycles, data_hold_cycles,
+ data_sample_cycles, busy_timeout);
+
+ t0 = BF_GPMI_TIMING0_ADDRESS_SETUP(address_cycles) |
+ BF_GPMI_TIMING0_DATA_SETUP(data_setup_cycles) |
+ BF_GPMI_TIMING0_DATA_HOLD(data_hold_cycles);
+ HW_GPMI_TIMING0_WR(t0);
+
+ HW_GPMI_TIMING1_WR(BF_GPMI_TIMING1_DEVICE_BUSY_TIMEOUT(busy_timeout));
+
+#ifdef CONFIG_ARCH_STMP378X
+ HW_GPMI_CTRL1_CLR(BM_GPMI_CTRL1_RDN_DELAY);
+ HW_GPMI_CTRL1_SET(BF_GPMI_CTRL1_RDN_DELAY(data_sample_cycles));
+#else
+ HW_GPMI_CTRL1_CLR(BM_GPMI_CTRL1_DSAMPLE_TIME);
+ HW_GPMI_CTRL1_SET(BF_GPMI_CTRL1_DSAMPLE_TIME(data_sample_cycles));
+#endif
+
+ g->use_count--;
+}
+
+static inline u32 bch_mode(void)
+{
+ u32 c1 = 0;
+
+#ifdef CONFIG_MTD_NAND_GPMI_BCH
+ if (bch)
+ c1 |= BM_GPMI_CTRL1_BCH_MODE;
+#endif
+ return c1;
+}
+/**
+ * gpmi_nand_init_hw - initialize the hardware
+ * @pdev: pointer to platform device
+ *
+ * Initialize GPMI hardware and set default (safe) timings for NAND access.
+ * Returns error code or 0 on success
+ */
+static int gpmi_nand_init_hw(struct platform_device *pdev, int request_pins)
+{
+ struct gpmi_nand_data *g = platform_get_drvdata(pdev);
+ char *devname = pdev->dev.bus_id;
+ int err = 0;
+
+ g->clk = clk_get(NULL, "gpmi");
+ if (IS_ERR(g->clk)) {
+ err = PTR_ERR(g->clk);
+ dev_err(&pdev->dev, "cannot set failsafe clockrate\n");
+ goto out;
+ }
+ clk_enable(g->clk);
+ if (clk <= 0)
+ clk = 24000; /* safe setting, some chips do not work on
+ speeds >= 24kHz */
+ clk_set_rate(g->clk, clk);
+
+ clk = clk_get_rate(g->clk);
+
+ if (request_pins)
+ gpmi_pinmux_request(devname);
+
+ stmp3xxx_reset_block(HW_GPMI_CTRL0_OFFSET + REGS_GPMI_BASE, 1);
+
+ /* this CLEARS reset, despite of its name */
+ HW_GPMI_CTRL1_SET(BM_GPMI_CTRL1_DEV_RESET);
+
+ /* IRQ polarity */
+ HW_GPMI_CTRL1_SET(BM_GPMI_CTRL1_ATA_IRQRDY_POLARITY);
+
+ /* ...and ECC module */
+ HW_GPMI_CTRL1_SET(bch_mode());
+
+ /* choose NAND mode (1 means ATA, 0 - NAND */
+ HW_GPMI_CTRL1_CLR(BM_GPMI_CTRL1_GPMI_MODE);
+
+out:
+ return err;
+}
+
+/**
+ * gpmi_nand_release_hw - free the hardware
+ * @pdev: pointer to platform device
+ *
+ * In opposite to gpmi_nand_init_hw, release all acquired resources
+ */
+static void gpmi_nand_release_hw(struct platform_device *pdev)
+{
+ struct gpmi_nand_data *g = platform_get_drvdata(pdev);
+
+ HW_GPMI_CTRL0_SET(BM_GPMI_CTRL0_SFTRST);
+
+ clk_disable(g->clk);
+ clk_put(g->clk);
+ gpmi_pinmux_free(pdev->dev.bus_id);
+}
+
+static int gpmi_dma_is_error(struct gpmi_nand_data *g)
+{
+ /* u32 n = HW_APBH_CHn_NXTCMDAR_RD(g->dma_ch); */
+
+ /* CURrent DMA command */
+ u32 c = HW_APBH_CHn_NXTCMDAR_RD(g->cchip->dma_ch);
+
+ if (c == g->cchip->error.handle) {
+ pr_debug("%s: dma chain has reached error terminator\n",
+ __func__);
+ return -EIO;
+ }
+ return 0;
+}
+
+/**
+ * gpmi_dma_exchange - run DMA to exchange with NAND chip
+ *
+ * @g: structure associated with NAND chip
+ *
+ * Run DMA and wait for completion
+ */
+static int gpmi_dma_exchange(struct gpmi_nand_data *g,
+ struct stmp3xxx_dma_descriptor *d)
+{
+ struct platform_device *pdev = g->dev;
+ unsigned long timeout;
+ int err;
+
+ if (g->self_suspended)
+ gpmi_self_wakeup(g);
+ g->use_count++;
+
+ if (!g->regulator) {
+ g->regulator = regulator_get(&pdev->dev, "mmc_ssp-2");
+ if (g->regulator && !IS_ERR(g->regulator))
+ regulator_set_mode(
+ g->regulator,
+ REGULATOR_MODE_NORMAL);
+ else
+ g->regulator = NULL;
+ }
+
+ if (g->regulator)
+ regulator_set_current_limit(g->regulator, g->reg_uA, g->reg_uA);
+
+ init_completion(&g->done);
+ stmp3xxx_dma_enable_interrupt(g->cchip->dma_ch);
+ stmp3xxx_dma_go(g->cchip->dma_ch, d ? d : g->cchip->d, 1);
+
+ timeout = wait_for_completion_timeout(&g->done, msecs_to_jiffies(1000));
+ err = (timeout <= 0) ? -ETIMEDOUT : gpmi_dma_is_error(g);
+
+ if (err)
+ printk(KERN_ERR"%s: error %d, CS = %d, channel %d\n",
+ __func__, err, g->cchip->cs, g->cchip->dma_ch);
+
+ stmp3xxx_dma_reset_channel(g->cchip->dma_ch);
+ stmp3xxx_dma_clear_interrupt(g->cchip->dma_ch);
+
+ if (g->regulator)
+ regulator_set_current_limit(g->regulator, 0, 0);
+
+ mod_timer(&g->timer, jiffies + 4*HZ);
+ g->use_count--;
+
+ return err;
+}
+
+/**
+ * gpmi_ecc_read_page - replacement for nand_read_page
+ *
+ * @mtd: mtd info structure
+ * @chip: nand chip info structure
+ * @buf: buffer to store read data
+ */
+static int gpmi_ecc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
+ uint8_t *buf)
+{
+ struct gpmi_nand_data *g = chip->priv;
+ struct mtd_ecc_stats stats;
+ dma_addr_t bufphys, oobphys;
+ int err;
+
+ bufphys = oobphys = ~0;
+
+ if (map_buffers && virt_addr_valid(buf))
+ bufphys = dma_map_single(&g->dev->dev, buf,
+ mtd->writesize, DMA_FROM_DEVICE);
+ if (dma_mapping_error(&g->dev->dev, bufphys))
+ bufphys = g->data_buffer_handle;
+
+ if (map_buffers)
+ oobphys = dma_map_single(&g->dev->dev, chip->oob_poi,
+ mtd->oobsize, DMA_FROM_DEVICE);
+ if (dma_mapping_error(&g->dev->dev, oobphys))
+ oobphys = g->oob_buffer_handle;
+
+ /* ECC read */
+ (void)g->hc->read(g->hc, g->selected_chip, g->cchip->d,
+ g->cchip->error.handle,
+ bufphys, oobphys);
+
+ err = gpmi_dma_exchange(g, NULL);
+
+ g->hc->stat(g->hc, g->selected_chip, &stats);
+
+ if (stats.failed || stats.corrected) {
+
+ pr_debug("%s: ECC failed=%d, corrected=%d\n",
+ __func__, stats.failed, stats.corrected);
+
+ g->mtd.ecc_stats.failed += stats.failed;
+ g->mtd.ecc_stats.corrected += stats.corrected;
+ }
+
+
+ if (!dma_mapping_error(&g->dev->dev, oobphys)) {
+ if (oobphys != g->oob_buffer_handle)
+ dma_unmap_single(&g->dev->dev, oobphys,
+ mtd->oobsize, DMA_FROM_DEVICE);
+ else {
+ memcpy(chip->oob_poi, g->oob_buffer, mtd->oobsize);
+ copies++;
+ }
+ }
+
+ if (!dma_mapping_error(&g->dev->dev, bufphys)) {
+ if (bufphys != g->data_buffer_handle)
+ dma_unmap_single(&g->dev->dev, bufphys,
+ mtd->writesize, DMA_FROM_DEVICE);
+ else {
+ memcpy(buf, g->data_buffer, mtd->writesize);
+ copies++;
+ }
+ }
+
+ /* always fill the (possible ECC bytes with FF) */
+ memset(chip->oob_poi + g->oob_free, 0xff, mtd->oobsize - g->oob_free);
+
+ return err;
+}
+
+static inline int is_ff(const u8 *buffer, size_t size)
+{
+ while (size--) {
+ if (*buffer++ != 0xff)
+ return 0;
+ }
+ return 1;
+}
+
+/**
+ * gpmi_ecc_write_page - replacement for nand_write_page
+ *
+ * @mtd: mtd info structure
+ * @chip: nand chip info structure
+ * @buf: data buffer
+ */
+static void gpmi_ecc_write_page(struct mtd_info *mtd,
+ struct nand_chip *chip, const uint8_t *buf)
+{
+ struct gpmi_nand_data *g = chip->priv;
+ dma_addr_t bufphys, oobphys;
+ int err;
+
+ /* if we can't map it, copy it */
+ bufphys = oobphys = ~0;
+
+ if (map_buffers && virt_addr_valid(buf))
+ bufphys = dma_map_single(&g->dev->dev,
+ (void *)buf, mtd->writesize, DMA_TO_DEVICE);
+ if (dma_mapping_error(&g->dev->dev, bufphys)) {
+ bufphys = g->data_buffer_handle;
+ memcpy(g->data_buffer, buf, mtd->writesize);
+ copies++;
+ }
+
+ /* if OOB is all FF, leave it as such */
+ if (!is_ff(chip->oob_poi, mtd->oobsize)) {
+ if (map_buffers)
+ oobphys = dma_map_single(&g->dev->dev, chip->oob_poi,
+ mtd->oobsize, DMA_TO_DEVICE);
+ if (dma_mapping_error(&g->dev->dev, oobphys)) {
+ oobphys = g->oob_buffer_handle;
+ memcpy(g->oob_buffer, chip->oob_poi, mtd->oobsize);
+ copies++;
+ }
+ } else
+ ff_writes++;
+
+ /* call ECC */
+ g->hc->write(g->hc, g->selected_chip, g->cchip->d,
+ g->cchip->error.handle,
+ bufphys, oobphys);
+
+ err = gpmi_dma_exchange(g, NULL);
+ if (err < 0)
+ printk(KERN_ERR"%s: dma error\n", __func__);
+
+ if (!dma_mapping_error(&g->dev->dev, oobphys)) {
+ if (oobphys != g->oob_buffer_handle)
+ dma_unmap_single(&g->dev->dev, oobphys, mtd->oobsize,
+ DMA_TO_DEVICE);
+ }
+
+ if (bufphys != g->data_buffer_handle)
+ dma_unmap_single(&g->dev->dev, bufphys, mtd->writesize,
+ DMA_TO_DEVICE);
+}
+
+/**
+ * gpmi_write_buf - replacement for nand_write_buf
+ *
+ * @mtd: MTD device
+ * @buf: data buffer
+ * @len: length of the data buffer
+ */
+static void gpmi_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct gpmi_nand_data *g = chip->priv;
+ struct stmp3xxx_dma_descriptor *chain = g->cchip->d;
+ dma_addr_t phys;
+ int err;
+
+ BUG_ON(len > mtd->writesize);
+
+ phys = ~0;
+
+ if (map_buffers && virt_addr_valid(buf))
+ phys = dma_map_single(&g->dev->dev,
+ (void *)buf, len, DMA_TO_DEVICE);
+ if (dma_mapping_error(&g->dev->dev, phys)) {
+ phys = g->write_buffer_handle;
+ memcpy(g->write_buffer, buf, len);
+ copies++;
+ }
+
+ /* write plain data */
+ chain->command->cmd =
+ BF_APBH_CHn_CMD_XFER_COUNT(len) |
+ BF_APBH_CHn_CMD_CMDWORDS(4) |
+ BM_APBH_CHn_CMD_WAIT4ENDCMD |
+ BM_APBH_CHn_CMD_NANDLOCK |
+ BM_APBH_CHn_CMD_IRQONCMPLT |
+ BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__DMA_READ);
+ chain->command->pio_words[0] =
+ BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__WRITE) |
+ BM_GPMI_CTRL0_WORD_LENGTH |
+ BM_GPMI_CTRL0_LOCK_CS |
+ BF_GPMI_CTRL0_CS(g->selected_chip) |
+ BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_DATA) |
+ BF_GPMI_CTRL0_XFER_COUNT(len);
+
+ chain->command->pio_words[1] = 0;
+ chain->command->pio_words[2] = 0;
+ chain->command->pio_words[3] = 0;
+ chain->command->buf_ptr = phys;
+
+ err = gpmi_dma_exchange(g, NULL);
+ if (err)
+ printk(KERN_ERR"%s: dma error\n", __func__);
+
+ if (phys != g->write_buffer_handle)
+ dma_unmap_single(&g->dev->dev, phys, len, DMA_TO_DEVICE);
+
+ if (debug >= 2)
+ print_hex_dump_bytes("WBUF ", DUMP_PREFIX_OFFSET, buf, len);
+}
+
+/**
+ * gpmi_read_buf - replacement for nand_read_buf
+ *
+ * @mtd: MTD device
+ * @buf: pointer to the buffer
+ * @len: size of the buffer
+ */
+static void gpmi_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct gpmi_nand_data *g = chip->priv;
+ struct stmp3xxx_dma_descriptor *chain;
+ dma_addr_t phys;
+ int err;
+
+ phys = ~0;
+
+ if (map_buffers && virt_addr_valid(buf))
+ phys = dma_map_single(&g->dev->dev,
+ buf, len, DMA_FROM_DEVICE);
+ if (dma_mapping_error(&g->dev->dev, phys))
+ phys = g->read_buffer_handle;
+
+ chain = g->cchip->d;
+
+ /* read data */
+ chain->command->cmd =
+ BF_APBH_CHn_CMD_XFER_COUNT(len) |
+ BF_APBH_CHn_CMD_CMDWORDS(1) |
+ BM_APBH_CHn_CMD_WAIT4ENDCMD |
+ BM_APBH_CHn_CMD_CHAIN |
+ BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__DMA_WRITE);
+ chain->command->pio_words[0] =
+ BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__READ) |
+ BM_GPMI_CTRL0_WORD_LENGTH |
+ BM_GPMI_CTRL0_LOCK_CS |
+ BF_GPMI_CTRL0_CS(g->selected_chip) |
+ BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_DATA) |
+ BF_GPMI_CTRL0_XFER_COUNT(len);
+ chain->command->buf_ptr = phys;
+ chain++;
+
+ chain->command->cmd =
+ BF_APBH_CHn_CMD_CMDWORDS(4) |
+ BM_APBH_CHn_CMD_WAIT4ENDCMD |
+ BM_APBH_CHn_CMD_NANDWAIT4READY |
+ BM_APBH_CHn_CMD_NANDLOCK |
+ BM_APBH_CHn_CMD_IRQONCMPLT |
+ BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER);
+ chain->command->pio_words[0] =
+ BF_GPMI_CTRL0_COMMAND_MODE(
+ BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY) |
+ BM_GPMI_CTRL0_WORD_LENGTH |
+ BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_DATA) |
+ BM_GPMI_CTRL0_LOCK_CS |
+ BF_GPMI_CTRL0_CS(g->selected_chip);
+ chain->command->pio_words[1] =
+ chain->command->pio_words[2] =
+ chain->command->pio_words[3] = 0;
+ chain->command->buf_ptr = 0;
+
+ err = gpmi_dma_exchange(g, NULL);
+ if (err)
+ printk(KERN_ERR"%s: dma error\n", __func__);
+
+ if (phys != g->read_buffer_handle)
+ dma_unmap_single(&g->dev->dev, phys, len, DMA_FROM_DEVICE);
+ else {
+ memcpy(buf, g->read_buffer, len);
+ copies++;
+ }
+
+ if (debug >= 2)
+ print_hex_dump_bytes("RBUF ", DUMP_PREFIX_OFFSET, buf, len);
+}
+
+/**
+ * gpmi_read_byte - replacement for nand_read_byte
+ * @mtd: MTD device
+ *
+ * Uses gpmi_read_buf to read 1 byte from device
+ */
+static u8 gpmi_read_byte(struct mtd_info *mtd)
+{
+ u8 b;
+
+ gpmi_read_buf(mtd, (uint8_t *)&b, 1);
+ return b;
+}
+
+/**
+ * gpmi_read_word - replacement for nand_read_word
+ * @mtd: MTD device
+ *
+ * Uses gpmi_read_buf to read 2 bytes from device
+ */
+static u16 gpmi_read_word(struct mtd_info *mtd)
+{
+ u16 w;
+
+ gpmi_read_buf(mtd, (uint8_t *)&w, sizeof(u16));
+ return w;
+}
+
+/**
+ * gpmi_erase - erase a block and update BBT table
+ *
+ * @mtd: MTD device
+ * @instr: erase instruction
+ */
+int gpmi_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ int rc;
+ struct nand_chip *chip = mtd->priv;
+ struct gpmi_nand_data *g = chip->priv;
+ struct gpmi_nand_data *data = platform_get_drvdata(g->dev);
+
+ if (g->self_suspended)
+ gpmi_self_wakeup(data);
+ g->use_count++;
+
+ rc = nand_erase_nand(mtd, instr, 0);
+
+ if (rc == -EIO) /* block cannot be erased */
+ gpmi_block_mark_as(chip,
+ (instr->addr >> chip->bbt_erase_shift),
+ 0x01);
+
+ mod_timer(&g->timer, jiffies + 4*HZ);
+ g->use_count--;
+ return rc;
+}
+
+/**
+ * gpmi_dev_ready - poll the RDY pin
+ *
+ * @mtd: MTD device
+ */
+static int gpmi_dev_ready(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct gpmi_nand_data *g = chip->priv;
+ struct stmp3xxx_dma_descriptor *chain = g->cchip->d;
+ int ret;
+
+ /* wait for ready */
+ chain->command->cmd =
+ BF_APBH_CHn_CMD_CMDWORDS(4) |
+ BM_APBH_CHn_CMD_WAIT4ENDCMD |
+ BM_APBH_CHn_CMD_NANDWAIT4READY |
+ BM_APBH_CHn_CMD_NANDLOCK |
+ BM_APBH_CHn_CMD_IRQONCMPLT |
+ BF_APBH_CHn_CMD_COMMAND(
+ BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER);
+ chain->command->pio_words[0] =
+ BF_GPMI_CTRL0_COMMAND_MODE(
+ BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY) |
+ BM_GPMI_CTRL0_WORD_LENGTH |
+ BF_GPMI_CTRL0_ADDRESS(
+ BV_GPMI_CTRL0_ADDRESS__NAND_DATA) |
+ BF_GPMI_CTRL0_CS(g->selected_chip);
+ chain->command->pio_words[1] = 0;
+ chain->command->pio_words[2] = 0;
+ chain->command->pio_words[3] = 0;
+ chain->command->buf_ptr = 0;
+ chain++;
+
+ ret = gpmi_dma_exchange(g, NULL);
+ if (ret != 0)
+ printk(KERN_ERR "gpmi: gpmi_dma_exchange() timeout!\n");
+ return ret == 0;
+}
+
+/**
+ * gpmi_hwcontrol - set command/address byte to the device
+ *
+ * @mtd: MTD device
+ * @cmd: command byte
+ * @ctrl: control flags
+ */
+static void gpmi_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct gpmi_nand_data *g = chip->priv;
+ struct stmp3xxx_dma_descriptor *chain = g->cchip->d;
+ int ret;
+
+ if ((ctrl & (NAND_ALE | NAND_CLE))) {
+ if (cmd != NAND_CMD_NONE)
+ g->cmd_buffer[g->cmd_buffer_sz++] = cmd;
+ return;
+ }
+
+ if (g->cmd_buffer_sz == 0)
+ return;
+
+ /* output command */
+ chain->command->cmd =
+ BF_APBH_CHn_CMD_XFER_COUNT(g->cmd_buffer_sz) |
+ BF_APBH_CHn_CMD_CMDWORDS(3) |
+ BM_APBH_CHn_CMD_WAIT4ENDCMD |
+ BM_APBH_CHn_CMD_NANDLOCK |
+ BM_APBH_CHn_CMD_CHAIN |
+ BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__DMA_READ);
+ chain->command->pio_words[0] =
+ BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__WRITE) |
+ BM_GPMI_CTRL0_WORD_LENGTH |
+ BM_GPMI_CTRL0_LOCK_CS |
+ BF_GPMI_CTRL0_CS(g->selected_chip) |
+ BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_CLE) |
+ BF_GPMI_CTRL0_XFER_COUNT(g->cmd_buffer_sz);
+ if (g->cmd_buffer_sz > 0)
+ chain->command->pio_words[0] |= BM_GPMI_CTRL0_ADDRESS_INCREMENT;
+ chain->command->pio_words[1] = 0;
+ chain->command->pio_words[2] = 0;
+ chain->command->buf_ptr = g->cmd_buffer_handle;
+ chain++;
+
+ /* emit IRQ */
+ chain->command->cmd =
+ BF_APBH_CHn_CMD_CMDWORDS(0) |
+ BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER) |
+ BM_APBH_CHn_CMD_WAIT4ENDCMD;
+ chain++;
+
+ /* last in chain get the irq bit set */
+ chain[-1].command->cmd |= BM_APBH_CHn_CMD_IRQONCMPLT;
+
+ if (debug >= 3)
+ print_hex_dump(KERN_INFO, "CMD ", DUMP_PREFIX_OFFSET, 16, 1,
+ g->cmd_buffer, g->cmd_buffer_sz, 1);
+
+ ret = gpmi_dma_exchange(g, NULL);
+ if (ret != 0) {
+ printk(KERN_ERR"%s: chip %d, dma error %d on the command:\n",
+ __func__, g->selected_chip, ret);
+ print_hex_dump(KERN_INFO, "CMD ", DUMP_PREFIX_OFFSET, 16, 1,
+ g->cmd_buffer, g->cmd_buffer_sz, 1);
+ }
+
+ gpmi_dev_ready(mtd);
+
+ g->cmd_buffer_sz = 0;
+}
+
+/**
+ * gpmi_alloc_buffers - allocate DMA buffers for one chip
+ *
+ * @pdev: GPMI platform device
+ * @g: pointer to structure associated with NAND chip
+ *
+ * Allocate buffer using dma_alloc_coherent
+ */
+static int gpmi_alloc_buffers(struct platform_device *pdev,
+ struct gpmi_nand_data *g)
+{
+ g->cmd_buffer = dma_alloc_coherent(&pdev->dev,
+ g->cmd_buffer_size,
+ &g->cmd_buffer_handle, GFP_DMA);
+ if (!g->cmd_buffer)
+ goto out1;
+
+ g->write_buffer = dma_alloc_coherent(&pdev->dev,
+ g->write_buffer_size * 2,
+ &g->write_buffer_handle, GFP_DMA);
+ if (!g->write_buffer)
+ goto out2;
+
+ g->read_buffer = g->write_buffer + g->write_buffer_size;
+ g->read_buffer_handle = g->write_buffer_handle + g->write_buffer_size;
+
+ g->data_buffer = dma_alloc_coherent(&pdev->dev,
+ g->data_buffer_size,
+ &g->data_buffer_handle, GFP_DMA);
+ if (!g->data_buffer)
+ goto out3;
+
+ g->oob_buffer = dma_alloc_coherent(&pdev->dev,
+ g->oob_buffer_size,
+ &g->oob_buffer_handle, GFP_DMA);
+ if (!g->oob_buffer)
+ goto out4;
+
+ g->verify_buffer = kzalloc(2 * (g->data_buffer_size +
+ g->oob_buffer_size), GFP_KERNEL);
+ if (!g->verify_buffer)
+ goto out5;
+
+ return 0;
+
+out5:
+ dma_free_coherent(&pdev->dev, g->oob_buffer_size,
+ g->oob_buffer, g->oob_buffer_handle);
+out4:
+ dma_free_coherent(&pdev->dev, g->data_buffer_size,
+ g->data_buffer, g->data_buffer_handle);
+out3:
+ dma_free_coherent(&pdev->dev, g->write_buffer_size * 2,
+ g->write_buffer, g->write_buffer_handle);
+out2:
+ dma_free_coherent(&pdev->dev, g->cmd_buffer_size,
+ g->cmd_buffer, g->cmd_buffer_handle);
+out1:
+ return -ENOMEM;
+}
+
+/**
+ * gpmi_free_buffers - free buffers allocated by gpmi_alloc_buffers
+ *
+ * @pdev: platform device
+ * @g: pointer to structure associated with NAND chip
+ *
+ * Deallocate buffers on exit
+ */
+static void gpmi_free_buffers(struct platform_device *pdev,
+ struct gpmi_nand_data *g)
+{
+ kfree(g->verify_buffer);
+ dma_free_coherent(&pdev->dev, g->oob_buffer_size,
+ g->oob_buffer, g->oob_buffer_handle);
+ dma_free_coherent(&pdev->dev, g->write_buffer_size * 2,
+ g->write_buffer, g->write_buffer_handle);
+ dma_free_coherent(&pdev->dev, g->cmd_buffer_size,
+ g->cmd_buffer, g->cmd_buffer_handle);
+ dma_free_coherent(&pdev->dev, g->data_buffer_size,
+ g->data_buffer, g->data_buffer_handle);
+}
+
+/* only used in SW-ECC or NO-ECC cases */
+static int gpmi_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct gpmi_nand_data *g = chip->priv;
+
+ chip->read_buf(mtd, g->verify_buffer, len);
+
+ if (memcmp(buf, g->verify_buffer, len))
+ return -EFAULT;
+
+ return 0;
+}
+
+/**
+ * gpmi_ecc_read_oob - replacement for nand_read_oob
+ *
+ * @mtd: MTD device
+ * @chip: mtd->priv
+ * @page: page address
+ * @sndcmd: flag indicates that command should be sent
+ */
+int gpmi_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
+ int page, int sndcmd)
+{
+ struct gpmi_nand_data *g = chip->priv;
+ loff_t oob_offset;
+ struct mtd_ecc_stats stats;
+ dma_addr_t oobphys;
+ int ecc;
+ int ret;
+
+ ecc = g->raw_oob_mode == 0 && raw_mode == 0;
+
+ if (sndcmd) {
+ oob_offset = mtd->writesize;
+ if (likely(ecc))
+ oob_offset += chip->ecc.bytes * chip->ecc.steps;
+ chip->cmdfunc(mtd, NAND_CMD_READ0, oob_offset, page);
+ sndcmd = 0;
+ }
+
+ if (unlikely(!ecc)) {
+ chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+ return 1;
+ }
+
+ oobphys = ~0;
+
+ if (map_buffers)
+ oobphys = dma_map_single(&g->dev->dev, chip->oob_poi,
+ mtd->oobsize, DMA_FROM_DEVICE);
+ if (dma_mapping_error(&g->dev->dev, oobphys))
+ oobphys = g->oob_buffer_handle;
+
+ /* ECC read */
+ (void)g->hc->read(g->hc, g->selected_chip, g->cchip->d,
+ g->cchip->error.handle, ~0, oobphys);
+
+ ret = gpmi_dma_exchange(g, NULL);
+
+ g->hc->stat(g->hc, g->selected_chip, &stats);
+
+ if (stats.failed || stats.corrected) {
+
+ printk(KERN_DEBUG "%s: ECC failed=%d, corrected=%d\n",
+ __func__, stats.failed, stats.corrected);
+
+ g->mtd.ecc_stats.failed += stats.failed;
+ g->mtd.ecc_stats.corrected += stats.corrected;
+ }
+
+ if (oobphys != g->oob_buffer_handle)
+ dma_unmap_single(&g->dev->dev, oobphys, mtd->oobsize,
+ DMA_FROM_DEVICE);
+ else {
+ memcpy(chip->oob_poi, g->oob_buffer, mtd->oobsize);
+ copies++;
+ }
+
+ /* fill rest with ff */
+ memset(chip->oob_poi + g->oob_free, 0xff, mtd->oobsize - g->oob_free);
+
+ return ret ? ret : 1;
+}
+
+/**
+ * gpmi_ecc_write_oob - replacement for nand_write_oob
+ *
+ * @mtd: MTD device
+ * @chip: mtd->priv
+ * @page: page address
+ */
+static int gpmi_ecc_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
+ int page)
+{
+ int status = 0;
+ struct gpmi_nand_data *g = chip->priv;
+ loff_t oob_offset;
+ dma_addr_t oobphys;
+ int ecc;
+ int err = 0;
+
+ /* if OOB is all FF, leave it as such */
+ if (is_ff(chip->oob_poi, mtd->oobsize)) {
+ ff_writes++;
+
+ pr_debug("%s: Skipping an empty page 0x%x (0x%x)\n",
+ __func__,
+ page, page << chip->page_shift);
+ return 0;
+ }
+
+ ecc = g->raw_oob_mode == 0 && raw_mode == 0;
+
+ /* Send command to start input data */
+ oob_offset = mtd->writesize;
+ if (likely(ecc)) {
+ oob_offset += chip->ecc.bytes * chip->ecc.steps;
+ memset(chip->oob_poi + g->oob_free, 0xff,
+ mtd->oobsize - g->oob_free);
+ }
+ chip->cmdfunc(mtd, NAND_CMD_SEQIN, oob_offset, page);
+
+ /* call ECC */
+ if (likely(ecc)) {
+
+ oobphys = ~0;
+
+ if (map_buffers)
+ oobphys = dma_map_single(&g->dev->dev, chip->oob_poi,
+ mtd->oobsize, DMA_TO_DEVICE);
+ if (dma_mapping_error(&g->dev->dev, oobphys)) {
+ oobphys = g->oob_buffer_handle;
+ memcpy(g->oob_buffer, chip->oob_poi, mtd->oobsize);
+ copies++;
+ }
+
+ g->hc->write(g->hc, g->selected_chip, g->cchip->d,
+ g->cchip->error.handle, ~0, oobphys);
+
+ err = gpmi_dma_exchange(g, NULL);
+ } else
+ chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+
+ /* Send command to program the OOB data */
+ chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
+
+ /* ..and wait for result */
+ status = chip->waitfunc(mtd, chip);
+
+ if (likely(ecc)) {
+ if (oobphys != g->oob_buffer_handle)
+ dma_unmap_single(&g->dev->dev, oobphys, mtd->oobsize,
+ DMA_TO_DEVICE);
+ }
+
+ if (status & NAND_STATUS_FAIL) {
+ pr_debug("%s: NAND_STATUS_FAIL\n", __func__);
+ return -EIO;
+ }
+
+ return err;
+}
+
+/**
+ * gpmi_irq - IRQ handler
+ *
+ * @irq: irq no
+ * @context: IRQ context, pointer to gpmi_nand_data
+ */
+static irqreturn_t gpmi_irq(int irq, void *context)
+{
+ struct gpmi_nand_data *g = context;
+
+ if (stmp3xxx_dma_is_interrupt(g->cchip->dma_ch)) {
+ stmp3xxx_dma_clear_interrupt(g->cchip->dma_ch);
+ complete(&g->done);
+ }
+ HW_GPMI_CTRL1_CLR(BM_GPMI_CTRL1_DEV_IRQ | BM_GPMI_CTRL1_TIMEOUT_IRQ);
+ return IRQ_HANDLED;
+}
+
+static void gpmi_select_chip(struct mtd_info *mtd, int chipnr)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct gpmi_nand_data *g = chip->priv;
+
+ if (chipnr == g->selected_chip)
+ return;
+
+ g->selected_chip = chipnr;
+ g->cchip = NULL;
+
+ if (chipnr == -1)
+ return;
+
+ g->cchip = g->chips + chipnr;
+}
+
+static void gpmi_command(struct mtd_info *mtd, unsigned int command,
+ int column, int page_addr)
+{
+ register struct nand_chip *chip = mtd->priv;
+ struct gpmi_nand_data *g = chip->priv;
+
+ g->saved_command(mtd, command, column, page_addr);
+}
+
+static int gpmi_read_oob(struct mtd_info *mtd, loff_t from,
+ struct mtd_oob_ops *ops)
+{
+ register struct nand_chip *chip = mtd->priv;
+ struct gpmi_nand_data *g = chip->priv;
+ int ret;
+
+ g->raw_oob_mode = ops->mode == MTD_OOB_RAW;
+ ret = g->saved_read_oob(mtd, from, ops);
+ g->raw_oob_mode = 0;
+ return ret;
+}
+
+static int gpmi_write_oob(struct mtd_info *mtd, loff_t to,
+ struct mtd_oob_ops *ops)
+{
+ register struct nand_chip *chip = mtd->priv;
+ struct gpmi_nand_data *g = chip->priv;
+ int ret;
+
+ g->raw_oob_mode = ops->mode == MTD_OOB_RAW;
+ ret = g->saved_write_oob(mtd, to, ops);
+ g->raw_oob_mode = 0;
+ return ret;
+}
+
+/**
+ * perform the needed steps between nand_scan_ident and nand_scan_tail
+ */
+static int gpmi_scan_middle(struct gpmi_nand_data *g)
+{
+ int oobsize = 0;
+
+ /* Limit to 2G size due to Kernel larger 4G space support */
+ if (g->mtd.size == 0) {
+ g->mtd.size = 1 << 31;
+ g->chip.chipsize = g->mtd.size / g->chip.numchips;
+ }
+
+ g->ecc_oob_bytes = 9;
+ switch (g->mtd.writesize) {
+ case 2048: /* 2K page */
+ g->chip.ecc.layout = &gpmi_oob_64;
+ g->chip.ecc.bytes = 9;
+ g->oob_free = 19;
+ g->hwecc_type_read = GPMI_ECC4_RD;
+ g->hwecc_type_write = GPMI_ECC4_WR;
+ oobsize = 64;
+ break;
+ case 4096:
+ g->chip.ecc.layout = &gpmi_oob_128;
+ g->chip.ecc.bytes = 18;
+ g->oob_free = 65;
+ g->hwecc_type_read = GPMI_ECC8_RD;
+ g->hwecc_type_write = GPMI_ECC8_WR;
+ oobsize = 218;
+ break;
+ default:
+ printk(KERN_ERR "Unsupported write_size %d.",
+ g->mtd.writesize);
+ break;
+ }
+
+ g->mtd.ecclayout = g->chip.ecc.layout;
+ /* sanity check */
+ if (oobsize > NAND_MAX_OOBSIZE ||
+ g->mtd.writesize > NAND_MAX_PAGESIZE) {
+ printk(KERN_ERR "Internal error. Either page size "
+ "(%d) > max (%d) "
+ "or oob size (%d) > max(%d). Sorry.\n",
+ oobsize, NAND_MAX_OOBSIZE,
+ g->mtd.writesize, NAND_MAX_PAGESIZE);
+ return -ERANGE;
+ }
+
+ g->saved_command = g->chip.cmdfunc;
+ g->chip.cmdfunc = gpmi_command;
+
+ if (oobsize > 0) {
+ g->mtd.oobsize = oobsize;
+ /* otherwise error; oobsize should be set
+ in valid cases */
+ g->hc = gpmi_hwecc_chip_find("ecc8");
+ g->hc->setup(g->hc, 0, g->mtd.writesize, g->mtd.oobsize);
+ return 0;
+ }
+
+ return -ENXIO;
+}
+
+/**
+ * gpmi_write_page - [REPLACEABLE] write one page
+ * @mtd: MTD device structure
+ * @chip: NAND chip descriptor
+ * @buf: the data to write
+ * @page: page number to write
+ * @cached: cached programming
+ * @raw: use _raw version of write_page
+ */
+static int gpmi_write_page(struct mtd_info *mtd, struct nand_chip *chip,
+ const uint8_t *buf, int page, int cached, int raw)
+{
+ struct gpmi_nand_data *g = chip->priv;
+ int status, empty_data, empty_oob;
+ int oobsz;
+#if defined(CONFIG_MTD_NAND_VERIFY_WRITE)
+ void *vbuf, *obuf;
+#if 0
+ void *voob, *ooob;
+#endif
+#endif
+
+ oobsz = likely(g->raw_oob_mode == 0 && raw_mode == 0) ?
+ g->oob_free : mtd->oobsize;
+
+ empty_data = is_ff(buf, mtd->writesize);
+ empty_oob = is_ff(buf, oobsz);
+
+ if (empty_data && empty_oob) {
+ ff_writes++;
+
+ pr_debug("%s: Skipping an empty page 0x%x (0x%x)\n",
+ __func__,
+ page, page << chip->page_shift);
+ return 0;
+ }
+
+ chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
+
+ if (likely(raw == 0))
+ chip->ecc.write_page(mtd, chip, buf);
+ else
+ chip->ecc.write_page_raw(mtd, chip, buf);
+
+ /*
+ * Cached progamming disabled for now, Not sure if its worth the
+ * trouble. The speed gain is not very impressive. (2.3->2.6Mib/s)
+ */
+ cached = 0;
+
+ if (!cached || !(chip->options & NAND_CACHEPRG)) {
+
+ chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
+
+ status = chip->waitfunc(mtd, chip);
+
+ /*
+ * See if operation failed and additional status checks are
+ * available
+ */
+ if ((status & NAND_STATUS_FAIL) && (chip->errstat))
+ status = chip->errstat(mtd, chip, FL_WRITING, status,
+ page);
+
+ if (status & NAND_STATUS_FAIL) {
+ pr_debug("%s: NAND_STATUS_FAIL\n", __func__);
+ return -EIO;
+ }
+ } else {
+ chip->cmdfunc(mtd, NAND_CMD_CACHEDPROG, -1, -1);
+ status = chip->waitfunc(mtd, chip);
+ }
+
+#if defined(CONFIG_MTD_NAND_VERIFY_WRITE)
+ if (empty_data)
+ return 0;
+
+ obuf = g->verify_buffer;
+#if 1 /* make vbuf aligned by mtd->writesize */
+ vbuf = obuf + mtd->writesize;
+#else
+ ooob = obuf + mtd->writesize;
+ vbuf = ooob + mtd->oobsize;
+ voob = vbuf + mtd->writesize;
+#endif
+
+ /* keep data around */
+ memcpy(obuf, buf, mtd->writesize);
+#if 0
+ memcpy(ooob, chip->oob_poi, oobsz);
+#endif
+ /* Send command to read back the data */
+ chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+
+ if (likely(raw == 0))
+ chip->ecc.read_page(mtd, chip, vbuf);
+ else
+ chip->ecc.read_page_raw(mtd, chip, vbuf);
+
+#if 0
+ memcpy(voob, chip->oob_poi, oobsz);
+#endif
+
+ if (!empty_data && memcmp(obuf, vbuf, mtd->writesize) != 0)
+ return -EIO;
+#endif
+
+ return 0;
+}
+
+/**
+ * gpmi_read_page_raw - [Intern] read raw page data without ecc
+ * @mtd: mtd info structure
+ * @chip: nand chip info structure
+ * @buf: buffer to store read data
+ */
+static int gpmi_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
+ uint8_t *buf)
+{
+ chip->read_buf(mtd, buf, mtd->writesize);
+ chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+ return 0;
+}
+
+/**
+ * gpmi_write_page_raw - [Intern] raw page write function
+ * @mtd: mtd info structure
+ * @chip: nand chip info structure
+ * @buf: data buffer
+ */
+static void gpmi_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
+ const uint8_t *buf)
+{
+ chip->write_buf(mtd, buf, mtd->writesize);
+ chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+}
+
+static int gpmi_init_chip(struct platform_device *pdev,
+ struct gpmi_nand_data *g, int n, unsigned dma_ch)
+{
+ int err;
+
+ g->chips[n].dma_ch = dma_ch;
+ g->chips[n].cs = n;
+
+ err = stmp3xxx_dma_request(dma_ch, NULL, pdev->dev.bus_id);
+ if (err) {
+ dev_err(&pdev->dev,
+ "can't request DMA channel 0x%x\n", dma_ch);
+ goto out_all;
+ }
+
+ err = stmp3xxx_dma_make_chain(
+ dma_ch,
+ &g->chips[n].chain,
+ g->chips[n].d, ARRAY_SIZE(g->chips[n].d));
+ if (err) {
+ dev_err(&pdev->dev, "can't setup DMA chain\n");
+ goto out_all;
+ }
+
+ err = stmp3xxx_dma_allocate_command(
+ dma_ch,
+ &g->chips[n].error);
+ if (err) {
+ dev_err(&pdev->dev, "can't setup DMA chain\n");
+ goto out_all;
+ }
+
+ g->chips[n].error.command->cmd =
+ BM_APBH_CHn_CMD_IRQONCMPLT |
+ BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER);
+out_all:
+ return err;
+}
+
+static void gpmi_deinit_chip(struct platform_device *pdev,
+ struct gpmi_nand_data *g, int n)
+{
+ int dma_ch;
+
+ if (n < 0) {
+ for (n = 0; n < ARRAY_SIZE(g->chips); n++)
+ gpmi_deinit_chip(pdev, g, n);
+ return;
+ }
+
+ if (g->chips[n].dma_ch <= 0)
+ return;
+
+ dma_ch = g->chips[n].dma_ch;
+
+ stmp3xxx_dma_free_command(dma_ch, &g->chips[n].error);
+ stmp3xxx_dma_free_chain(&g->chips[n].chain);
+ stmp3xxx_dma_release(dma_ch);
+}
+
+static int gpmi_to_concat(struct mtd_partition *part, char **list)
+{
+ while (list && *list) {
+ if (strcmp(part->name, *list) == 0) {
+ pr_debug("Partition '%s' will be concatenated\n",
+ part->name);
+ return true;
+ }
+ list++;
+ }
+ pr_debug("Partition '%s' is left as-is", part->name);
+ return false;
+}
+
+static void gpmi_create_partitions(struct gpmi_nand_data *g,
+ struct gpmi_platform_data *gpd, uint64_t chipsize)
+{
+#ifdef CONFIG_MTD_PARTITIONS
+ int chip, p;
+ char chipname[20];
+
+ if (g->numchips == 1)
+ g->masters[0] = &g->mtd;
+ else {
+ for (chip = 0; chip < g->numchips; chip++) {
+ memset(g->chip_partitions + chip,
+ 0, sizeof(g->chip_partitions[chip]));
+ snprintf(chipname, sizeof(chipname),
+ "gpmi-chip-%d", chip);
+ g->chip_partitions[chip].name =
+ kstrdup(chipname, GFP_KERNEL);
+ g->chip_partitions[chip].size = chipsize;
+ g->chip_partitions[chip].offset = chipsize * chip;
+ g->chip_partitions[chip].mask_flags = 0;
+ g->chip_partitions[chip].mtdp = &g->masters[chip];
+ }
+ add_mtd_partitions(&g->mtd, g->chip_partitions, g->numchips);
+ }
+ g->n_concat = 0;
+ memset(g->concat, 0, sizeof(g->concat));
+ for (chip = 0; chip < g->numchips; chip++) {
+ if (add_mtd_chip) {
+ printk(KERN_NOTICE"Adding MTD for the chip %d\n", chip);
+ add_mtd_device(g->masters[chip]);
+ }
+ if (chip >= gpd->items)
+ continue;
+ for (p = 0; p < gpd->parts[chip].nr_partitions; p++) {
+ if (gpmi_to_concat(&gpd->parts[chip].partitions[p],
+ gpd->concat_parts))
+ gpd->parts[chip].partitions[p].mtdp =
+ &g->concat[g->n_concat++];
+ }
+ add_mtd_partitions(g->masters[chip],
+ gpd->parts[chip].partitions,
+ gpd->parts[chip].nr_partitions);
+ }
+ if (g->n_concat > 0) {
+#ifdef CONFIG_MTD_CONCAT
+ if (g->n_concat == 1)
+#endif
+ for (p = 0; p < g->n_concat; p++)
+ add_mtd_device(g->concat[p]);
+#ifdef CONFIG_MTD_CONCAT
+ if (g->n_concat > 1) {
+ g->concat_mtd = mtd_concat_create(g->concat,
+ g->n_concat, gpd->concat_name);
+ if (g->concat_mtd)
+ add_mtd_device(g->concat_mtd);
+ }
+#endif
+ }
+ g->custom_partitions = true;
+#endif
+}
+
+static void gpmi_delete_partitions(struct gpmi_nand_data *g)
+{
+#ifdef CONFIG_MTD_PARTITIONS
+ int chip, p;
+
+ if (!g->custom_partitions)
+ return;
+#ifdef CONFIG_MTD_CONCAT
+ if (g->concat_mtd)
+ del_mtd_device(g->concat_mtd);
+ if (g->n_concat == 1)
+#endif
+ for (p = 0; p < g->n_concat; p++)
+ del_mtd_device(g->concat[p]);
+
+ for (chip = 0; chip < g->numchips; chip++) {
+ del_mtd_partitions(g->masters[chip]);
+ if (add_mtd_chip)
+ del_mtd_device(g->masters[chip]);
+ kfree(g->chip_partitions[chip].name);
+ }
+#endif
+}
+/**
+ * gpmi_nand_probe - probe for the GPMI device
+ *
+ * Probe for GPMI device and discover NAND chips
+ */
+static int __init gpmi_nand_probe(struct platform_device *pdev)
+{
+ struct gpmi_nand_data *g;
+ struct gpmi_platform_data *gpd;
+ const char *part_type = 0;
+ int err = 0;
+ struct resource *r;
+ int dma;
+ unsigned long long chipsize;
+
+ /* Allocate memory for the device structure (and zero it) */
+ g = kzalloc(sizeof(*g), GFP_KERNEL);
+ if (!g) {
+ dev_err(&pdev->dev, "failed to allocate gpmi_nand_data\n");
+ err = -ENOMEM;
+ goto out1;
+ }
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!r) {
+ dev_err(&pdev->dev, "failed to get resource\n");
+ err = -ENXIO;
+ goto out2;
+ }
+ g->io_base = ioremap(r->start, r->end - r->start + 1);
+ if (!g->io_base) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ err = -EIO;
+ goto out2;
+ }
+
+ r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!r) {
+ err = -EIO;
+ dev_err(&pdev->dev, "can't get IRQ resource\n");
+ goto out3;
+ }
+
+ gpd = (struct gpmi_platform_data *)pdev->dev.platform_data;
+ platform_set_drvdata(pdev, g);
+ err = gpmi_nand_init_hw(pdev, 1);
+ if (err)
+ goto out3;
+
+ init_timer(&g->timer);
+ g->timer.data = (unsigned long)g;
+ g->timer.function = gpmi_timer_expiry;
+ g->timer.expires = jiffies + 4*HZ;
+ add_timer(&g->timer);
+ dev_dbg(&pdev->dev, "%s: timer set to %ld\n",
+ __func__, jiffies + 4*HZ);
+
+ g->reg_uA = gpd->io_uA;
+ g->regulator = regulator_get(&pdev->dev, "mmc_ssp-2");
+ if (g->regulator && !IS_ERR(g->regulator)) {
+ regulator_set_mode(
+ g->regulator,
+ REGULATOR_MODE_NORMAL);
+ } else
+ g->regulator = NULL;
+
+ gpmi_set_timings(pdev, &gpmi_safe_timing);
+
+ g->irq = r->start;
+ err = request_irq(g->irq,
+ gpmi_irq, 0, pdev->dev.bus_id, g);
+ if (err) {
+ dev_err(&pdev->dev, "can't request GPMI IRQ\n");
+ goto out4;
+ }
+
+ r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!r) {
+ dev_err(&pdev->dev, "can't get DMA resource\n");
+ goto out5;
+ }
+
+ if (r->end - r->start > GPMI_MAX_CHIPS)
+ dev_info(&pdev->dev, "too spread resource: max %d chips\n",
+ GPMI_MAX_CHIPS);
+
+ for (dma = r->start;
+ dma < min_t(int, r->end, r->start + GPMI_MAX_CHIPS);
+ dma++) {
+ err = gpmi_init_chip(pdev, g, dma - r->start, dma);
+ if (err)
+ goto out6;
+ }
+
+ g->cmd_buffer_size = GPMI_CMD_BUF_SZ;
+ g->write_buffer_size = GPMI_WRITE_BUF_SZ;
+ g->data_buffer_size = GPMI_DATA_BUF_SZ;
+ g->oob_buffer_size = GPMI_OOB_BUF_SZ;
+
+ err = gpmi_alloc_buffers(pdev, g);
+ if (err) {
+ dev_err(&pdev->dev, "can't setup buffers\n");
+ goto out6;
+ }
+
+ g->dev = pdev;
+ g->chip.priv = g;
+ g->timing = gpmi_safe_timing;
+ g->selected_chip = -1;
+ g->ignorebad = ignorebad; /* copy global setting */
+
+ g->mtd.priv = &g->chip;
+ g->mtd.name = pdev->dev.bus_id;
+ g->mtd.owner = THIS_MODULE;
+
+ g->chip.cmd_ctrl = gpmi_hwcontrol;
+ g->chip.read_word = gpmi_read_word;
+ g->chip.read_byte = gpmi_read_byte;
+ g->chip.read_buf = gpmi_read_buf;
+ g->chip.write_buf = gpmi_write_buf;
+ g->chip.select_chip = gpmi_select_chip;
+ g->chip.verify_buf = gpmi_verify_buf;
+ g->chip.dev_ready = gpmi_dev_ready;
+
+ g->chip.ecc.mode = NAND_ECC_HW_SYNDROME;
+ g->chip.ecc.write_oob = gpmi_ecc_write_oob;
+ g->chip.ecc.read_oob = gpmi_ecc_read_oob;
+ g->chip.ecc.write_page = gpmi_ecc_write_page;
+ g->chip.ecc.read_page = gpmi_ecc_read_page;
+ g->chip.ecc.read_page_raw = gpmi_read_page_raw;
+ g->chip.ecc.write_page_raw = gpmi_write_page_raw;
+ g->chip.ecc.size = 512;
+
+ g->chip.write_page = gpmi_write_page;
+
+ g->chip.scan_bbt = gpmi_scan_bbt;
+ g->chip.block_bad = gpmi_block_bad;
+
+ g->cmd_buffer_sz = 0;
+
+ /* first scan to find the device and get the page size */
+ if (nand_scan_ident(&g->mtd, max_chips)
+ || gpmi_scan_middle(g)
+ || nand_scan_tail(&g->mtd)) {
+ dev_err(&pdev->dev, "No NAND found\n");
+ /* errors found on some step */
+ goto out7;
+ }
+
+ g->chip.options |= NAND_NO_SUBPAGE_WRITE;
+ g->chip.subpagesize = g->mtd.writesize;
+ g->mtd.subpage_sft = 0;
+
+ g->mtd.erase = gpmi_erase;
+
+ g->saved_read_oob = g->mtd.read_oob;
+ g->saved_write_oob = g->mtd.write_oob;
+ g->mtd.read_oob = gpmi_read_oob;
+ g->mtd.write_oob = gpmi_write_oob;
+
+#ifdef CONFIG_MTD_PARTITIONS
+ if (gpd == NULL)
+ goto out_all;
+
+ if (gpd->parts[0].part_probe_types) {
+ g->nr_parts = parse_mtd_partitions(&g->mtd,
+ gpd->parts[0].part_probe_types,
+ &g->parts, 0);
+ if (g->nr_parts > 0)
+ part_type = "command line";
+ else
+ g->nr_parts = 0;
+ }
+
+ if (g->nr_parts == 0 && gpd->parts[0].partitions) {
+ g->parts = gpd->parts[0].partitions;
+ g->nr_parts = gpd->parts[0].nr_partitions;
+ part_type = "static";
+ }
+
+ if (g->nr_parts == 0) {
+ dev_err(&pdev->dev, "Neither part_probe_types nor "
+ "partitions was specified in platform_data");
+ goto out_all;
+ }
+
+ dev_info(&pdev->dev, "Using %s partition definition\n",
+ part_type);
+
+ g->numchips = g->chip.numchips;
+ chipsize = g->mtd.size;
+ do_div(chipsize, (unsigned long)g->numchips);
+
+ if (!strcmp(part_type, "command line"))
+ add_mtd_partitions(&g->mtd, g->parts, g->nr_parts);
+ else
+ gpmi_create_partitions(g, gpd, chipsize);
+
+ if (add_mtd_entire) {
+ printk(KERN_NOTICE "Adding MTD covering the whole flash\n");
+ add_mtd_device(&g->mtd);
+ }
+#else
+ add_mtd_device(&g->mtd);
+#endif
+ gpmi_uid_init("nand",
+ &g->mtd, gpd->uid_offset, gpd->uid_size);
+
+#ifdef CONFIG_MTD_NAND_GPMI_SYSFS_ENTRIES
+ gpmi_sysfs(pdev, true);
+#endif
+ return 0;
+
+out_all:
+ ecc8_exit();
+ bch_exit();
+out7:
+ nand_release(&g->mtd);
+ gpmi_free_buffers(pdev, g);
+out6:
+ gpmi_deinit_chip(pdev, g, -1);
+out5:
+ free_irq(g->irq, g);
+out4:
+ del_timer_sync(&g->timer);
+ gpmi_nand_release_hw(pdev);
+out3:
+ platform_set_drvdata(pdev, NULL);
+ iounmap(g->io_base);
+out2:
+ kfree(g);
+out1:
+ return err;
+}
+
+/**
+ * gpmi_nand_remove - remove a GPMI device
+ *
+ */
+static int __devexit gpmi_nand_remove(struct platform_device *pdev)
+{
+ struct gpmi_nand_data *g = platform_get_drvdata(pdev);
+ int i = 0;
+#ifdef CONFIG_MTD_PARTITIONS
+ struct gpmi_platform_data *gpd = pdev->dev.platform_data;
+ struct mtd_partition *platf_parts;
+#endif
+
+ gpmi_delete_partitions(g);
+ del_timer_sync(&g->timer);
+ gpmi_uid_remove("nand");
+#ifdef CONFIG_MTD_NAND_GPMI_SYSFS_ENTRIES
+ gpmi_sysfs(pdev, false);
+#endif
+ nand_release(&g->mtd);
+ gpmi_free_buffers(pdev, g);
+ gpmi_deinit_chip(pdev, g, -1);
+ gpmi_nand_release_hw(pdev);
+ free_irq(g->irq, g);
+ if (g->regulator)
+ regulator_put(g->regulator);
+
+#ifdef CONFIG_MTD_PARTITIONS
+ if (i < gpd->items && gpd->parts[i].partitions)
+ platf_parts = gpd->parts[i].partitions;
+ else
+ platf_parts = NULL;
+ if (g->parts && g->parts != platf_parts)
+ kfree(g->parts);
+#endif
+ iounmap(g->io_base);
+ kfree(g);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int gpmi_nand_suspend(struct platform_device *pdev, pm_message_t pm)
+{
+ struct gpmi_nand_data *g = platform_get_drvdata(pdev);
+ int r = 0;
+
+ if (g->self_suspended)
+ gpmi_self_wakeup(g);
+ del_timer_sync(&g->timer);
+
+ r = g->mtd.suspend(&g->mtd);
+ if (r == 0)
+ gpmi_nand_release_hw(pdev);
+
+ return r;
+}
+
+static int gpmi_nand_resume(struct platform_device *pdev)
+{
+ struct gpmi_nand_data *g = platform_get_drvdata(pdev);
+ int r;
+
+ r = gpmi_nand_init_hw(pdev, 1);
+ gpmi_set_timings(pdev, &g->timing);
+ g->mtd.resume(&g->mtd);
+ g->timer.expires = jiffies + 4*HZ;
+ add_timer(&g->timer);
+ return r;
+}
+#else
+#define gpmi_nand_suspend NULL
+#define gpmi_nand_resume NULL
+#endif
+
+#ifdef CONFIG_MTD_NAND_GPMI_SYSFS_ENTRIES
+static ssize_t show_timings(struct device *d, struct device_attribute *attr,
+ char *buf)
+{
+ struct gpmi_nand_timing *ptm;
+ struct gpmi_nand_data *g = dev_get_drvdata(d);
+
+ ptm = &g->timing;
+ return sprintf(buf, "DATA_SETUP %d, DATA_HOLD %d, "
+ "ADDR_SETUP %d, DSAMPLE_TIME %d\n",
+ ptm->data_setup, ptm->data_hold,
+ ptm->address_setup,
+ ptm->dsample_time);
+}
+
+static ssize_t store_timings(struct device *d, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ const char *p, *end;
+ struct gpmi_nand_timing t;
+ struct gpmi_nand_data *g = dev_get_drvdata(d);
+ char tmps[20];
+ u8 *timings[] = {
+ &t.data_setup,
+ &t.data_hold,
+ &t.address_setup,
+ &t.dsample_time,
+ NULL,
+ };
+ u8 **timing = timings;
+
+ p = buf;
+
+ /* parse values */
+ while (*timing != NULL) {
+ unsigned long t_long;
+
+ end = strchr(p, ',');
+ memset(tmps, 0, sizeof(tmps));
+ if (end)
+ strncpy(tmps, p, min_t(int, sizeof(tmps) - 1, end - p));
+ else
+ strncpy(tmps, p, sizeof(tmps) - 1);
+
+ if (strict_strtoul(tmps, 0, &t_long) < 0)
+ return -EINVAL;
+
+ if (t_long > 255)
+ return -EINVAL;
+
+ **timing = (u8)t_long;
+ timing++;
+
+ if (!end && *timing)
+ return -EINVAL;
+ p = end + 1;
+ }
+
+ gpmi_set_timings(g->dev, &t);
+
+ return size;
+}
+
+static ssize_t show_stat(struct device *d, struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "copies\t\t%dff pages\t%d\n", copies, ff_writes);
+}
+
+static ssize_t show_chips(struct device *d, struct device_attribute *attr,
+ char *buf)
+{
+ struct gpmi_nand_data *g = dev_get_drvdata(d);
+ return sprintf(buf, "%d\n", g->numchips);
+}
+
+
+static ssize_t show_ignorebad(struct device *d, struct device_attribute *attr,
+ char *buf)
+{
+ struct gpmi_nand_data *g = dev_get_drvdata(d);
+
+ return sprintf(buf, "%d\n", g->ignorebad);
+}
+
+static ssize_t store_ignorebad(struct device *d, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct gpmi_nand_data *g = dev_get_drvdata(d);
+ const char *p = buf;
+ unsigned long v;
+
+ if (strict_strtoul(p, 0, &v) < 0)
+ return size;
+ if (v > 0)
+ v = 1;
+ if (v != g->ignorebad) {
+ if (v) {
+ g->bbt = g->chip.bbt;
+ g->chip.bbt = NULL;
+ g->ignorebad = 1;
+ } else {
+ g->chip.bbt = g->bbt;
+ g->ignorebad = 0;
+ }
+ }
+ return size;
+}
+
+static DEVICE_ATTR(timings, 0644, show_timings, store_timings);
+static DEVICE_ATTR(stat, 0444, show_stat, NULL);
+static DEVICE_ATTR(ignorebad, 0644, show_ignorebad, store_ignorebad);
+static DEVICE_ATTR(numchips, 0444, show_chips, NULL);
+
+static struct device_attribute *gpmi_attrs[] = {
+ &dev_attr_timings,
+ &dev_attr_stat,
+ &dev_attr_ignorebad,
+ &dev_attr_numchips,
+ NULL,
+};
+
+int gpmi_sysfs(struct platform_device *pdev, int create)
+{
+ int err = 0;
+ int i;
+
+ if (create) {
+ for (i = 0; gpmi_attrs[i]; i++) {
+ err = device_create_file(&pdev->dev, gpmi_attrs[i]);
+ if (err)
+ break;
+ }
+ if (err)
+ while (--i >= 0)
+ device_remove_file(&pdev->dev, gpmi_attrs[i]);
+ } else {
+ for (i = 0; gpmi_attrs[i]; i++)
+ device_remove_file(&pdev->dev, gpmi_attrs[i]);
+ }
+ return err;
+}
+#endif
+
+static struct platform_driver gpmi_nand_driver = {
+ .probe = gpmi_nand_probe,
+ .remove = __devexit_p(gpmi_nand_remove),
+ .driver = {
+ .name = "gpmi",
+ .owner = THIS_MODULE,
+ },
+ .suspend = gpmi_nand_suspend,
+ .resume = gpmi_nand_resume,
+};
+
+static LIST_HEAD(gpmi_hwecc_chips);
+
+void gpmi_hwecc_chip_add(struct gpmi_hwecc_chip *chip)
+{
+ list_add(&chip->list, &gpmi_hwecc_chips);
+}
+EXPORT_SYMBOL_GPL(gpmi_hwecc_chip_add);
+
+void gpmi_hwecc_chip_remove(struct gpmi_hwecc_chip *chip)
+{
+ list_del(&chip->list);
+}
+EXPORT_SYMBOL_GPL(gpmi_hwecc_chip_remove);
+
+struct gpmi_hwecc_chip *gpmi_hwecc_chip_find(char *name)
+{
+ struct gpmi_hwecc_chip *c;
+
+ list_for_each_entry(c, &gpmi_hwecc_chips, list)
+ if (strncmp(c->name, name, sizeof(c->name)) == 0)
+ return c;
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(gpmi_hwecc_chip_find);
+
+static int __init gpmi_nand_init(void)
+{
+ bch_init();
+ ecc8_init();
+ return platform_driver_register(&gpmi_nand_driver);
+}
+
+static void __exit gpmi_nand_exit(void)
+{
+ platform_driver_unregister(&gpmi_nand_driver);
+}
+
+module_init(gpmi_nand_init);
+module_exit(gpmi_nand_exit);
+MODULE_AUTHOR("dmitry pervushin <dimka@embeddedalley.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("GPMI NAND driver");
+module_param(max_chips, int, 0400);
+module_param(clk, long, 0400);
+module_param(bch, int, 0600);
+module_param(map_buffers, int, 0600);
+module_param(raw_mode, int, 0600);
+module_param(debug, int, 0600);
+module_param(add_mtd_entire, int, 0400);
+module_param(add_mtd_chip, int, 0400);
+module_param(ignorebad, int, 0400);
diff --git a/drivers/mtd/nand/gpmi/gpmi-bbt.c b/drivers/mtd/nand/gpmi/gpmi-bbt.c
new file mode 100644
index 000000000000..54755f870440
--- /dev/null
+++ b/drivers/mtd/nand/gpmi/gpmi-bbt.c
@@ -0,0 +1,565 @@
+/*
+ * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface)
+ *
+ * Author: dmitry pervushin <dimka@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <linux/dma-mapping.h>
+#include <linux/ctype.h>
+#include <mach/dma.h>
+#include <mach/unique-id.h>
+#include "gpmi.h"
+
+static int boot_search_count;
+static int stride = 64;
+static int ncb_version = 3;
+module_param(boot_search_count, int, 0400);
+module_param(ncb_version, int, 0400);
+
+void *gpmi_read_page(struct mtd_info *mtd, loff_t start, void *data, int raw)
+{
+ int ret;
+ struct mtd_oob_ops ops;
+
+ if (!data)
+ data = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL);
+ if (!data)
+ return NULL;
+
+ if (raw)
+ ops.mode = MTD_OOB_RAW;
+ else
+ ops.mode = MTD_OOB_PLACE;
+ ops.datbuf = data;
+ ops.len = mtd->writesize;
+ ops.oobbuf = data + mtd->writesize;
+ ops.ooblen = mtd->oobsize;
+ ops.ooboffs = 0;
+ ret = nand_do_read_ops(mtd, start, &ops);
+
+ if (ret)
+ return NULL;
+ return data;
+}
+
+int gpmi_write_ncb(struct mtd_info *mtd, struct gpmi_bcb_info *b)
+{
+ struct gpmi_ncb *ncb = NULL, *unencoded_ncb = NULL;
+ struct nand_chip *chip = mtd->priv;
+ int err;
+ loff_t start = 0;
+ struct mtd_oob_ops ops;
+ struct erase_info instr;
+ int ncb_count;
+
+ ncb = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL);
+ if (!ncb) {
+ err = -ENOMEM;
+ goto out;
+ }
+ unencoded_ncb = kzalloc(mtd->writesize, GFP_KERNEL);
+ if (!unencoded_ncb) {
+ err = -ENOMEM;
+ goto out;
+ }
+ ops.mode = -1; /* if the value is not set in switch below,
+ this will cause BUG. Take care. */
+ if (b && b->pre_ncb)
+ memcpy(unencoded_ncb, b->pre_ncb, b->pre_ncb_size);
+ else {
+ memcpy(&unencoded_ncb->fingerprint1, SIG1, sizeof(u32));
+ memcpy(&unencoded_ncb->fingerprint2, SIG_NCB, sizeof(u32));
+ if (b)
+ unencoded_ncb->timing = b->timing;
+ }
+
+ switch (ncb_version) {
+ case 0:
+ ops.mode = MTD_OOB_AUTO;
+ memcpy(ncb, unencoded_ncb, sizeof(*unencoded_ncb));
+ break;
+#ifdef CONFIG_MTD_NAND_GPMI_TA1
+ case 1:
+ ops.mode = MTD_OOB_RAW;
+ gpmi_encode_hamming_ncb_22_16(unencoded_ncb,
+ NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES,
+ ncb, mtd->writesize + mtd->oobsize);
+ break;
+#endif
+#ifdef CONFIG_MTD_NAND_GPMI_TA3
+ case 3:
+ ops.mode = MTD_OOB_RAW;
+ gpmi_encode_hamming_ncb_13_8(unencoded_ncb,
+ NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES,
+ ncb, mtd->writesize + mtd->oobsize);
+ break;
+#endif
+
+ default:
+ printk(KERN_ERR"Incorrect ncb_version = %d\n", ncb_version);
+ err = -EINVAL;
+ goto out;
+ }
+
+ ops.datbuf = (u8 *)ncb;
+ ops.len = mtd->writesize;
+ ops.oobbuf = (u8 *)ncb + mtd->writesize;
+ ops.ooblen = mtd->oobsize;
+ ops.ooboffs = 0;
+
+ ncb_count = 0;
+ do {
+ printk(KERN_NOTICE"GPMI: Trying to store NCB at addr %lx\n",
+ (unsigned long)start);
+ memset(&instr, 0, sizeof(instr));
+ instr.mtd = mtd;
+ instr.addr = start;
+ instr.len = (1 << chip->phys_erase_shift);
+ err = nand_erase_nand(mtd, &instr, 0);
+ if (err == 0) {
+ printk(KERN_NOTICE"GPMI: Erased, storing\n");
+ err = nand_do_write_ops(mtd, start, &ops);
+ printk(KERN_NOTICE"GPMI: NCB update %s (%d).\n",
+ err ? "failed" : "succeeded", err);
+ }
+ start += (1 << chip->phys_erase_shift);
+ ncb_count++;
+ } while (err != 0 && ncb_count < 100);
+
+ if (b)
+ b->ncbblock = start >> chip->bbt_erase_shift;
+
+out:
+ kfree(ncb);
+ kfree(unencoded_ncb);
+
+ return 0;
+}
+
+static int gpmi_redundancy_check_one(u8 *pg, int dsize, int esize, int offset,
+ int o1, int o2)
+{
+ int r;
+
+ if (o1 == o2)
+ return 0;
+
+ r = memcmp(pg + o1 * dsize, pg + o2 * dsize, dsize);
+ if (r) {
+ pr_debug("DATA copies %d and %d are different: %d\n",
+ o1, o2, r);
+ return r;
+ }
+
+ r = memcmp(pg + o1 * esize + offset,
+ pg + o2 * esize + offset, esize);
+ if (r) {
+ pr_debug("ECC copies %d and %d are different: %d\n", o1, o2, r);
+ return r;
+ }
+ pr_debug("Both DATA and ECC copies %d and %d are identical\n", o1, o2);
+ return r;
+}
+
+static int gpmi_redundancy_check(u8 *pg, int dsize, int esize, int ecc_offset)
+{
+ if (gpmi_redundancy_check_one(pg, dsize, esize, ecc_offset, 0, 1) == 0)
+ return 0;
+ if (gpmi_redundancy_check_one(pg, dsize, esize, ecc_offset, 0, 2) == 0)
+ return 0;
+ if (gpmi_redundancy_check_one(pg, dsize, esize, ecc_offset, 1, 2) == 0)
+ return 1;
+ return -1;
+}
+
+static inline int gpmi_ncb1_redundancy_check(u8 *pg)
+{
+ return gpmi_redundancy_check(pg,
+ NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES,
+ NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES,
+ NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY);
+}
+
+static int gpmi_scan_sigmatel_bbt(
+ struct mtd_info *mtd, struct gpmi_bcb_info *nfo)
+{
+ int page, r;
+ u8 *pg;
+ struct gpmi_ncb *result = NULL;
+
+ if (boot_search_count == 0)
+ boot_search_count = 1;
+ if (nfo == NULL)
+ return -EINVAL;
+
+ pg = NULL;
+ printk(KERN_NOTICE"Scanning for NCB...\n");
+ for (page = 0; page < (1<<boot_search_count); page += stride) {
+ pg = gpmi_read_page(mtd, page * mtd->writesize,
+ pg, ncb_version != 0);
+
+ printk(KERN_NOTICE"GPMI: Checking page 0x%08X\n", page);
+
+ if (ncb_version == 0) {
+ if (memcmp(pg, SIG1, SIG_SIZE) != 0)
+ continue;
+ printk(KERN_NOTICE"GPMI: Signature found at 0x%08X\n",
+ page);
+ result = (struct gpmi_ncb *)pg;
+ }
+
+#ifdef CONFIG_MTD_NAND_GPMI_TA1
+ if (ncb_version == 1) {
+ void *dptr, *eccptr;
+
+ if (memcmp(pg, SIG1, SIG_SIZE) != 0)
+ continue;
+ printk(KERN_NOTICE"GPMI: Signature found at 0x%08X\n",
+ page);
+
+ r = gpmi_ncb1_redundancy_check(pg);
+
+ if (r < 0) {
+ printk(KERN_ERR"GPMI: Oops. All three "
+ "copies of NCB are differrent!\n");
+ continue;
+ }
+
+ dptr = pg + r * NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES;
+ eccptr = pg + NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY +
+ r * NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES;
+
+ if (gpmi_verify_hamming_22_16(dptr, eccptr,
+ NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES) < 0) {
+ printk(KERN_ERR"Verification failed.\n");
+ continue;
+ }
+ result = (struct gpmi_ncb *)pg;
+ }
+#endif
+
+#ifdef CONFIG_MTD_NAND_GPMI_TA3
+ if (ncb_version == 3) {
+
+ if (memcmp(pg + 12, SIG1, SIG_SIZE) != 0)
+ continue;
+
+ printk(KERN_NOTICE"GPMI: Signature found at 0x%08X\n",
+ page);
+
+ if (gpmi_verify_hamming_13_8(
+ pg + 12,
+ pg + 524,
+ NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES) < 0) {
+ printk(KERN_ERR"Verification failed.\n");
+ continue;
+ }
+ result = (struct gpmi_ncb *)(pg + 12);
+ }
+#endif
+ if (result) {
+ printk(KERN_NOTICE"GPMI: Valid NCB found "
+ "at 0x%08x\n", page);
+ nfo->timing = result->timing;
+ nfo->ncbblock = page * mtd->writesize;
+ break;
+ }
+ }
+ kfree(pg);
+
+ return result != NULL;
+}
+
+int gpmi_scan_bbt(struct mtd_info *mtd)
+{
+ struct gpmi_bcb_info stmp_bbt;
+ struct nand_chip *this = mtd->priv;
+ struct gpmi_nand_data *g = this->priv;
+ int r;
+ int numblocks, from, i, ign;
+
+ memset(&stmp_bbt, 0, sizeof(stmp_bbt));
+ g->transcribe_bbmark = 0;
+
+ /*
+ Since NCB uses the full page, including BB pattern bits,
+ driver has to ignore result of gpmi_block_bad when reading
+ these pages.
+ */
+ ign = g->ignorebad;
+
+ g->ignorebad = true; /* strictly speaking, I'd have to hide
+ * the BBT too.
+ * But we still scanning it :) */
+ r = gpmi_scan_sigmatel_bbt(mtd, &stmp_bbt);
+
+ /* and then, driver has to restore the setting */
+ g->ignorebad = ign;
+
+ if (r) {
+ printk(KERN_NOTICE"Setting discovered timings: %d:%d:%d:%d\n",
+ stmp_bbt.timing.data_setup,
+ stmp_bbt.timing.data_hold,
+ stmp_bbt.timing.address_setup,
+ stmp_bbt.timing.dsample_time);
+
+ gpmi_set_timings(g->dev, &stmp_bbt.timing);
+ g->timing = stmp_bbt.timing;
+
+ } else {
+ g->transcribe_bbmark = !0;
+ numblocks = this->chipsize >> this->bbt_erase_shift;
+ from = 0;
+ printk(KERN_NOTICE"Checking BB on common-formatted flash\n");
+ for (i = stmp_bbt.ncbblock + 1; i < numblocks; i++) {
+ /* check the block and transcribe the bb if needed */
+ gpmi_block_bad(mtd, from, 0);
+ from += (1 << this->bbt_erase_shift);
+ }
+ }
+
+ r = nand_default_bbt(mtd);
+
+ if (g->transcribe_bbmark) {
+ /* NCB has been not found, so create NCB now */
+ g->transcribe_bbmark = 0;
+
+ stmp_bbt.timing = gpmi_safe_timing;
+ r = gpmi_write_ncb(mtd, &stmp_bbt);
+ } else {
+ /* NCB found, and its block should be marked as "good" */
+ gpmi_block_mark_as(this, stmp_bbt.ncbblock, 0x00);
+ }
+
+ return r;
+}
+
+int gpmi_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip)
+
+{
+ int page, res = 0;
+ struct nand_chip *chip = mtd->priv;
+ u16 bad;
+ struct gpmi_nand_data *g = chip->priv;
+ int chipnr;
+
+ /* badblockpos is an offset in OOB area */
+ int badblockpos = chip->ecc.steps * chip->ecc.bytes;
+
+ if (g->ignorebad)
+ return 0;
+
+ page = (int)(ofs >> chip->page_shift) & chip->pagemask;
+
+ chipnr = (int)(ofs >> chip->chip_shift);
+ chip->select_chip(mtd, chipnr);
+
+ if (g->transcribe_bbmark)
+ /* bad block marks still are on first byte of OOB */
+ badblockpos = 0;
+
+ if (chip->options & NAND_BUSWIDTH_16) {
+ chip->cmdfunc(mtd, NAND_CMD_READOOB, badblockpos & 0xFE,
+ page);
+ bad = cpu_to_le16(chip->read_word(mtd));
+ if (badblockpos & 0x1)
+ bad >>= 8;
+ if ((bad & 0xFF) != 0xff)
+ res = 1;
+ } else {
+ chip->cmdfunc(mtd, NAND_CMD_READOOB, badblockpos, page);
+ if (chip->read_byte(mtd) != 0xff)
+ res = 1;
+ }
+
+ if (g->transcribe_bbmark && res)
+ chip->block_markbad(mtd, ofs);
+
+ chip->select_chip(mtd, -1);
+
+ return res;
+}
+
+#if defined(CONFIG_STMP3XXX_UNIQUE_ID)
+/*
+ * UID on NAND support
+ */
+const int uid_size = 256;
+
+struct gpmi_uid_context {
+ struct mtd_info *mtd;
+ struct nand_chip *nand;
+ u_int32_t start;
+ u_int32_t size;
+};
+
+static int gpmi_read_uid(struct gpmi_uid_context *ctx, void *result)
+{
+ void *pg = NULL;
+ int page, o;
+ int status = -ENOENT;
+ int h_size = gpmi_hamming_ecc_size_22_16(uid_size);
+
+ for (page = ctx->start >> ctx->nand->page_shift;
+ page < (ctx->start + ctx->size) >> ctx->nand->page_shift;) {
+ pr_debug("%s: reading page 0x%x\n", __func__, page);
+ if (gpmi_block_bad(ctx->mtd, page * ctx->mtd->writesize, 0)) {
+ pr_debug("%s: bad block %x, skipping it\n",
+ __func__, page * ctx->mtd->writesize);
+ page += (1 << ctx->nand->phys_erase_shift)
+ >> ctx->nand->page_shift;
+ continue;
+ }
+ pg = gpmi_read_page(ctx->mtd, page * ctx->mtd->writesize,
+ pg, 0);
+ if (pg)
+ break;
+ page++;
+ }
+
+ if (!pg)
+ return status;
+
+ o = gpmi_redundancy_check(pg, uid_size, h_size, 3 * uid_size);
+ if (o >= 0) {
+ if (gpmi_verify_hamming_22_16(
+ pg + o * uid_size,
+ pg + 3 * uid_size + h_size, uid_size) >= 0) {
+ memcpy(result, pg + o * uid_size, uid_size);
+ status = 0;
+ }
+ }
+ kfree(pg);
+ return status;
+}
+
+static int gpmi_write_uid(struct gpmi_uid_context *ctx, void *src)
+{
+ struct mtd_oob_ops ops;
+ struct erase_info instr;
+ u8 *data = kzalloc(ctx->mtd->writesize + ctx->mtd->oobsize, GFP_KERNEL);
+ int h_size = gpmi_hamming_ecc_size_22_16(uid_size);
+ char ecc[h_size];
+ u_int32_t start;
+ int i;
+ int err;
+
+ if (!data)
+ return -ENOMEM;
+
+ gpmi_encode_hamming_22_16(src, uid_size, ecc, h_size);
+ for (i = 0; i < 3; i++) {
+ memcpy(data + i * uid_size, src, uid_size);
+ memcpy(data + 3 * uid_size + i * h_size, ecc, h_size);
+ }
+
+ ops.mode = MTD_OOB_AUTO;
+ ops.datbuf = data;
+ ops.len = ctx->mtd->writesize;
+ ops.oobbuf = NULL;
+ ops.ooblen = ctx->mtd->oobsize;
+ ops.ooboffs = 0;
+
+ start = ctx->start;
+
+ do {
+ memset(&instr, 0, sizeof(instr));
+ instr.mtd = ctx->mtd;
+ instr.addr = start;
+ instr.len = (1 << ctx->nand->phys_erase_shift);
+ err = nand_erase_nand(ctx->mtd, &instr, 0);
+ if (err == 0)
+ err = nand_do_write_ops(ctx->mtd, start, &ops);
+ start += (1 << ctx->nand->phys_erase_shift);
+ if (start > ctx->start + ctx->size)
+ break;
+ } while (err != 0);
+
+ return err;
+}
+
+static ssize_t gpmi_uid_store(void *context, const char *page,
+ size_t count, int ascii)
+{
+ u8 data[uid_size];
+
+ memset(data, 0, sizeof(data));
+ memcpy(data, page, uid_size < count ? uid_size : count);
+ gpmi_write_uid(context, data);
+ return count;
+}
+
+static ssize_t gpmi_uid_show(void *context, char *page, int ascii)
+{
+ u8 result[uid_size];
+ int i;
+ char *p = page;
+ int r;
+
+ r = gpmi_read_uid(context, result);
+ if (r < 0)
+ return r;
+
+ if (ascii) {
+ for (i = 0; i < uid_size; i++) {
+ if (i % 16 == 0) {
+ if (i)
+ *p++ = '\n';
+ sprintf(p, "%04X: ", i);
+ p += strlen(p);
+ }
+ sprintf(p, "%02X ", result[i]);
+ p += strlen(p);
+ }
+ *p++ = '\n';
+ return p - page;
+
+ } else {
+ memcpy(page, result, uid_size);
+ return uid_size;
+ }
+}
+
+static struct uid_ops gpmi_uid_ops = {
+ .id_show = gpmi_uid_show,
+ .id_store = gpmi_uid_store,
+};
+
+static struct gpmi_uid_context gpmi_uid_context;
+
+int __init gpmi_uid_init(const char *name, struct mtd_info *mtd,
+ u_int32_t start, u_int32_t size)
+{
+ gpmi_uid_context.mtd = mtd;
+ gpmi_uid_context.nand = mtd->priv;
+ gpmi_uid_context.start = start;
+ gpmi_uid_context.size = size;
+ return uid_provider_init(name, &gpmi_uid_ops, &gpmi_uid_context) ?
+ 0 : -EFAULT;
+}
+
+void gpmi_uid_remove(const char *name)
+{
+ uid_provider_remove(name);
+}
+#endif
diff --git a/drivers/mtd/nand/gpmi/gpmi-bch.c b/drivers/mtd/nand/gpmi/gpmi-bch.c
new file mode 100644
index 000000000000..58f7354bcd18
--- /dev/null
+++ b/drivers/mtd/nand/gpmi/gpmi-bch.c
@@ -0,0 +1,289 @@
+/*
+ * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface)
+ *
+ * STMP378X BCH hardware ECC engine
+ *
+ * Author: dmitry pervushin <dimka@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/dma-mapping.h>
+
+#include <asm/dma.h>
+#include <mach/stmp3xxx.h>
+#include <mach/irqs.h>
+#include "gpmi.h"
+
+#define BCH_MAX_NANDS 4
+
+static int bch_available(void *context);
+static int bch_setup(void *context, int index, int writesize, int oobsize);
+static int bch_read(void *context,
+ int index,
+ struct stmp3xxx_dma_descriptor *chain,
+ dma_addr_t error,
+ dma_addr_t page, dma_addr_t oob);
+static int bch_stat(void *ctx, int index, struct mtd_ecc_stats *r);
+static int bch_reset(void *context, int index);
+
+struct bch_state_t {
+ struct gpmi_hwecc_chip chip;
+ struct {
+ struct mtd_ecc_stats stat;
+ struct completion done;
+ u32 writesize, oobsize;
+ } nands[BCH_MAX_NANDS];
+};
+
+static struct bch_state_t state = {
+ .chip = {
+ .name = "bch",
+ .setup = bch_setup,
+ .stat = bch_stat,
+ .read = bch_read,
+ .reset = bch_reset,
+ },
+};
+
+static int bch_reset(void *context, int index)
+{
+ stmp3xxx_reset_block(REGS_BCH_BASE, true);
+ HW_BCH_CTRL_SET(BM_BCH_CTRL_COMPLETE_IRQ_EN);
+ return 0;
+}
+
+static int bch_stat(void *context, int index, struct mtd_ecc_stats *r)
+{
+ struct bch_state_t *state = context;
+
+ *r = state->nands[index].stat;
+ state->nands[index].stat.failed = 0;
+ state->nands[index].stat.corrected = 0;
+ return 0;
+}
+
+static irqreturn_t bch_irq(int irq, void *context)
+{
+ u32 b0, s0;
+ struct mtd_ecc_stats stat;
+ int r;
+ struct bch_state_t *state = context;
+
+ s0 = HW_BCH_STATUS0_RD();
+ r = (s0 & BM_BCH_STATUS0_COMPLETED_CE) >> 16;
+
+ stat.corrected = stat.failed = 0;
+
+ b0 = (s0 & BM_BCH_STATUS0_STATUS_BLK0) >> 8;
+ if (b0 <= 4)
+ stat.corrected += b0;
+ if (b0 == 0xFE)
+ stat.failed++;
+
+ if (s0 & BM_BCH_STATUS0_CORRECTED)
+ stat.corrected += (s0 & BM_BCH_STATUS0_CORRECTED);
+ if (s0 & BM_BCH_STATUS0_UNCORRECTABLE)
+ stat.failed++;
+
+ HW_BCH_CTRL_CLR(BM_BCH_CTRL_COMPLETE_IRQ);
+
+ pr_debug("%s: chip %d, failed %d, corrected %d\n",
+ __func__, r,
+ state->nands[r].stat.failed,
+ state->nands[r].stat.corrected);
+ state->nands[r].stat.corrected += stat.corrected;
+ state->nands[r].stat.failed += stat.failed;
+ complete(&state->nands[r].done);
+
+ return IRQ_HANDLED;
+}
+
+static int bch_available(void *context)
+{
+ stmp3xxx_reset_block(REGS_BCH_BASE, 0);
+ return HW_BCH_BLOCKNAME_RD() == 0x20484342;
+}
+
+static int bch_setup(void *context, int index, int writesize, int oobsize)
+{
+ struct bch_state_t *state = context;
+ u32 layout = (REGS_BCH_BASE + 0x80 /* HW_BCH_FLASH0LAYOUT0_ADDR */)
+ + index * 0x20;
+ u32 ecc0, eccN;
+ int meta;
+
+ switch (writesize) {
+ case 2048:
+ ecc0 = 4;
+ eccN = 4;
+ meta = 5;
+ break;
+ case 4096:
+ ecc0 = 16;
+ eccN = 14;
+ meta = 10;
+ break;
+ default:
+ printk(KERN_ERR"%s: cannot tune BCH for page size %d\n",
+ __func__, writesize);
+ return -EINVAL;
+ }
+
+ state->nands[index].oobsize = oobsize;
+ state->nands[index].writesize = writesize;
+
+ __raw_writel(BF_BCH_FLASH0LAYOUT0_NBLOCKS(writesize/512) |
+ BF_BCH_FLASH0LAYOUT0_META_SIZE(oobsize) |
+ BF_BCH_FLASH0LAYOUT0_ECC0(ecc0 >> 1) | /* for oob */
+ BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(0x00), layout);
+ __raw_writel(BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(writesize + oobsize) |
+ BF_BCH_FLASH0LAYOUT1_ECCN(eccN >> 1) | /* for dblock */
+ BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(512), layout + 0x10);
+
+ /*
+ * since driver only supports CS 0..3, layouts are mapped 1:1 :
+ * FLASHnLAYOUT[1,2] => LAYOUTSELECT[n*2:n2*+1]
+ */
+ HW_BCH_LAYOUTSELECT_CLR(0x03 << (index * 2));
+ HW_BCH_LAYOUTSELECT_SET(index << (index * 2));
+
+ bch_reset(context, index);
+
+ printk(KERN_DEBUG"%s: CS = %d, LAYOUT = 0x%08X, layout_reg = "
+ "0x%08x+0x%08x: 0x%08x+0x%08x\n",
+ __func__,
+ index, HW_BCH_LAYOUTSELECT_RD(),
+ layout, layout + 0x10,
+ __raw_readl(layout),
+ __raw_readl(layout+0x10));
+ return 0;
+}
+
+static int bch_read(void *context,
+ int index,
+ struct stmp3xxx_dma_descriptor *chain,
+ dma_addr_t error,
+ dma_addr_t page, dma_addr_t oob)
+{
+ unsigned long readsize = 0;
+ u32 bufmask = 0;
+ struct bch_state_t *state = context;
+
+ if (!dma_mapping_error(NULL, oob)) {
+ bufmask |= BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY;
+ readsize += state->nands[index].oobsize;
+ }
+ if (!dma_mapping_error(NULL, page)) {
+ bufmask |= (BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE
+ & ~BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY);
+ readsize += state->nands[index].writesize;
+ }
+
+ printk(KERN_DEBUG"readsize = %ld, bufmask = 0x%X\n", readsize, bufmask);
+ bch_reset(context, index);
+
+ /* wait for ready */
+ chain->command->cmd =
+ BF_APBH_CHn_CMD_CMDWORDS(1) |
+ BM_APBH_CHn_CMD_WAIT4ENDCMD |
+ BM_APBH_CHn_CMD_NANDWAIT4READY |
+ BM_APBH_CHn_CMD_CHAIN |
+ BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER);
+ chain->command->pio_words[0] =
+ BF_GPMI_CTRL0_COMMAND_MODE(
+ BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY) |
+ BM_GPMI_CTRL0_WORD_LENGTH |
+ BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_DATA) |
+ BF_GPMI_CTRL0_CS(index);
+ chain->command->alternate = 0;
+ chain++;
+
+ /* enable BCH and read NAND data */
+ chain->command->cmd =
+ BF_APBH_CHn_CMD_CMDWORDS(6) |
+ BM_APBH_CHn_CMD_WAIT4ENDCMD |
+ BM_APBH_CHn_CMD_NANDLOCK |
+ BM_APBH_CHn_CMD_CHAIN |
+ BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER);
+ chain->command->pio_words[0] =
+ BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__READ) |
+ BM_GPMI_CTRL0_WORD_LENGTH |
+ BF_GPMI_CTRL0_CS(index) |
+ BF_GPMI_CTRL0_XFER_COUNT(readsize);
+ chain->command->pio_words[1] = 0;
+ chain->command->pio_words[2] =
+ BM_GPMI_ECCCTRL_ENABLE_ECC |
+ BF_GPMI_ECCCTRL_ECC_CMD(0x02) |
+ BF_GPMI_ECCCTRL_BUFFER_MASK(bufmask);
+ chain->command->pio_words[3] = readsize;
+ chain->command->pio_words[4] = !dma_mapping_error(NULL, page) ? page : 0;
+ chain->command->pio_words[5] = !dma_mapping_error(NULL, oob) ? oob : 0;
+ chain->command->alternate = 0;
+ chain++;
+
+ /* disable BCH block */
+ chain->command->cmd =
+ BF_APBH_CHn_CMD_CMDWORDS(3) |
+ BM_APBH_CHn_CMD_WAIT4ENDCMD |
+ BM_APBH_CHn_CMD_NANDWAIT4READY |
+ BM_APBH_CHn_CMD_NANDLOCK |
+ BM_APBH_CHn_CMD_CHAIN |
+ BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER);
+ chain->command->pio_words[0] =
+ BF_GPMI_CTRL0_COMMAND_MODE(
+ BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY) |
+ BM_GPMI_CTRL0_WORD_LENGTH |
+ BF_GPMI_CTRL0_CS(index) |
+ BF_GPMI_CTRL0_XFER_COUNT(readsize);
+ chain->command->pio_words[1] = 0;
+ chain->command->pio_words[2] = 0;
+ chain->command->alternate = 0;
+ chain++;
+
+ /* and deassert nand lock */
+ chain->command->cmd =
+ BF_APBH_CHn_CMD_CMDWORDS(0) |
+ BM_APBH_CHn_CMD_WAIT4ENDCMD |
+ BM_APBH_CHn_CMD_IRQONCMPLT |
+ BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER);
+ chain->command->alternate = 0;
+
+ return 0;
+}
+
+int __init bch_init(void)
+{
+ int err;
+
+ if (!bch_available(&state.chip))
+ return -ENXIO;
+ gpmi_hwecc_chip_add(&state.chip);
+ err = request_irq(IRQ_BCH, bch_irq, 0, state.chip.name, &state);
+ if (err)
+ return err;
+
+ printk(KERN_DEBUG"%s: initialized\n", __func__);
+ return 0;
+}
+
+void bch_exit(void)
+{
+ free_irq(IRQ_BCH, &state);
+ gpmi_hwecc_chip_remove(&state.chip);
+}
diff --git a/drivers/mtd/nand/gpmi/gpmi-ecc8.c b/drivers/mtd/nand/gpmi/gpmi-ecc8.c
new file mode 100644
index 000000000000..dad14c3809fc
--- /dev/null
+++ b/drivers/mtd/nand/gpmi/gpmi-ecc8.c
@@ -0,0 +1,382 @@
+/*
+ * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface)
+ *
+ * STMP37XX/STMP378X ECC8 hardware ECC engine
+ *
+ * Author: dmitry pervushin <dimka@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/dma-mapping.h>
+
+#include <asm/dma.h>
+#include <mach/stmp3xxx.h>
+#include <mach/irqs.h>
+#include "gpmi.h"
+
+#define ECC8_MAX_NANDS 4
+
+static int ecc8_available(void *context);
+static int ecc8_setup(void *context, int index, int writesize, int oobsize);
+static int ecc8_read(void *context, int index,
+ struct stmp3xxx_dma_descriptor *chain,
+ dma_addr_t error,
+ dma_addr_t page, dma_addr_t oob);
+static int ecc8_write(void *context, int index,
+ struct stmp3xxx_dma_descriptor *chain,
+ dma_addr_t error,
+ dma_addr_t page, dma_addr_t oob);
+static int ecc8_stat(void *ctx, int index, struct mtd_ecc_stats *r);
+static int ecc8_reset(void *context, int index);
+
+struct ecc8_nand {
+};
+
+struct ecc8_state_t {
+ struct gpmi_hwecc_chip chip;
+ struct completion done;
+ struct mtd_ecc_stats stat;
+ u32 writesize, oobsize;
+ u32 ecc_page, ecc_oob, oob_free;
+ u32 r, w;
+ int bits;
+ u32 s0_mask, s1_mask;
+};
+
+static struct ecc8_state_t state = {
+ .chip = {
+ .name = "ecc8",
+ .setup = ecc8_setup,
+ .stat = ecc8_stat,
+ .read = ecc8_read,
+ .write = ecc8_write,
+ .reset = ecc8_reset,
+ },
+};
+
+static int ecc8_reset(void *context, int index)
+{
+ stmp3xxx_reset_block(REGS_ECC8_BASE, false);
+ while (HW_ECC8_CTRL_RD() & BM_ECC8_CTRL_AHBM_SFTRST)
+ HW_ECC8_CTRL_CLR(BM_ECC8_CTRL_AHBM_SFTRST);
+ HW_ECC8_CTRL_SET(BM_ECC8_CTRL_COMPLETE_IRQ_EN);
+ return 0;
+}
+
+static int ecc8_stat(void *context, int index, struct mtd_ecc_stats *r)
+{
+ struct ecc8_state_t *state = context;
+
+ wait_for_completion(&state->done);
+
+ *r = state->stat;
+ state->stat.failed = 0;
+ state->stat.corrected = 0;
+ return 0;
+}
+
+static irqreturn_t ecc8_irq(int irq, void *context)
+{
+ int r;
+ struct mtd_ecc_stats ecc_stats;
+ struct ecc8_state_t *state = context;
+ u32 corr;
+ u32 s0 = HW_ECC8_STATUS0_RD(), s1 = HW_ECC8_STATUS1_RD();
+
+ r = (s0 & BM_ECC8_STATUS0_COMPLETED_CE) >> 16;
+
+ if (s0 & BM_ECC8_STATUS0_CORRECTED ||
+ s0 & BM_ECC8_STATUS0_UNCORRECTABLE) {
+
+ ecc_stats.failed = 0;
+ ecc_stats.corrected = 0;
+
+ s0 = (s0 & BM_ECC8_STATUS0_STATUS_AUX) >> 8;
+ if (s0 <= 4)
+ ecc_stats.corrected += s0;
+ if (s0 == 0xE)
+ ecc_stats.failed++;
+
+ for ( ; s1 != 0; s1 >>= 4) {
+ corr = s1 & 0xF;
+ if (corr == 0x0C)
+ continue;
+ if (corr == 0xE)
+ ecc_stats.failed++;
+ if (corr <= 8)
+ ecc_stats.corrected += corr;
+ s1 >>= 4;
+ }
+ state->stat.corrected += ecc_stats.corrected;
+ state->stat.failed += ecc_stats.failed;
+ }
+
+ complete(&state->done);
+
+ HW_ECC8_CTRL_CLR(BM_ECC8_CTRL_COMPLETE_IRQ);
+ return IRQ_HANDLED;
+}
+
+static int ecc8_available(void *context)
+{
+ return 1;
+}
+
+static int ecc8_setup(void *context, int index, int writesize, int oobsize)
+{
+ struct ecc8_state_t *state = context;
+
+ switch (writesize) {
+ case 2048:
+ state->ecc_page = 9;
+ state->ecc_oob = 9;
+ state->bits = 4;
+ state->r = BF_GPMI_ECCCTRL_ECC_CMD(
+ BV_GPMI_ECCCTRL_ECC_CMD__DECODE_4_BIT);
+ state->w = BF_GPMI_ECCCTRL_ECC_CMD(
+ BV_GPMI_ECCCTRL_ECC_CMD__ENCODE_4_BIT);
+ break;
+ case 4096:
+ state->ecc_page = 18;
+ state->ecc_oob = 9;
+ state->bits = 8;
+ state->r = BF_GPMI_ECCCTRL_ECC_CMD(
+ BV_GPMI_ECCCTRL_ECC_CMD__DECODE_8_BIT);
+ state->w = BF_GPMI_ECCCTRL_ECC_CMD(
+ BV_GPMI_ECCCTRL_ECC_CMD__ENCODE_8_BIT);
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ state->oob_free = oobsize -
+ (state->bits * state->ecc_page) - state->ecc_oob;
+ state->oobsize = oobsize;
+ state->writesize = writesize;
+
+ ecc8_reset(context, index);
+
+ return 0;
+}
+
+/**
+ * ecc8_read - create dma chain to read nand page
+ *
+ * @context: context data, pointer to ecc8_state
+ * @index: CS of nand to read
+ * @chain: dma chain to be filled
+ * @page: (physical) address of page data to be read to
+ * @oob: (physical) address of OOB data to be read to
+ *
+ * Return: status of operation -- 0 on success
+ */
+static int ecc8_read(void *context, int cs,
+ struct stmp3xxx_dma_descriptor *chain,
+ dma_addr_t error,
+ dma_addr_t page, dma_addr_t oob)
+{
+ unsigned long readsize = 0;
+ u32 bufmask = 0;
+ struct ecc8_state_t *state = context;
+
+ ecc8_reset(context, cs);
+
+ state->s0_mask = state->s1_mask = 0;
+
+ if (!dma_mapping_error(NULL, page)) {
+ bufmask |= (1 << state->bits) - 1;
+ readsize += (state->bits * state->ecc_page);
+ readsize += state->writesize;
+ }
+ if (!dma_mapping_error(NULL, oob)) {
+ bufmask |= BV_GPMI_ECCCTRL_BUFFER_MASK__AUXILIARY;
+ readsize += state->oob_free + state->ecc_oob;
+ state->s0_mask = BM_ECC8_STATUS0_STATUS_AUX;
+ if (dma_mapping_error(NULL, page))
+ page = oob;
+ }
+
+
+ /* wait for ready */
+ chain->command->cmd =
+ BF_APBH_CHn_CMD_CMDWORDS(1) |
+ BM_APBH_CHn_CMD_WAIT4ENDCMD |
+ BM_APBH_CHn_CMD_NANDWAIT4READY |
+ BM_APBH_CHn_CMD_CHAIN |
+ BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER);
+ chain->command->pio_words[0] =
+ BF_GPMI_CTRL0_COMMAND_MODE(
+ BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY) |
+ BM_GPMI_CTRL0_WORD_LENGTH |
+ BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_DATA) |
+ BF_GPMI_CTRL0_CS(cs);
+ chain->command->buf_ptr = 0;
+ chain++;
+
+ /* enable ECC and read NAND data */
+ chain->command->cmd =
+ BF_APBH_CHn_CMD_CMDWORDS(6) |
+ BM_APBH_CHn_CMD_WAIT4ENDCMD |
+ BM_APBH_CHn_CMD_NANDLOCK |
+ BM_APBH_CHn_CMD_CHAIN |
+ BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER);
+ chain->command->pio_words[0] =
+ BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__READ) |
+ BM_GPMI_CTRL0_WORD_LENGTH |
+ BF_GPMI_CTRL0_CS(cs) |
+ BF_GPMI_CTRL0_XFER_COUNT(readsize);
+ chain->command->pio_words[1] = 0;
+ chain->command->pio_words[2] =
+ BM_GPMI_ECCCTRL_ENABLE_ECC |
+ state->r |
+ BF_GPMI_ECCCTRL_BUFFER_MASK(bufmask);
+ chain->command->pio_words[3] = readsize;
+ chain->command->pio_words[4] = !dma_mapping_error(NULL, page) ? page : 0;
+ chain->command->pio_words[5] = !dma_mapping_error(NULL, oob) ? oob : 0;
+ chain->command->buf_ptr = 0;
+ chain++;
+
+ /* disable ECC block */
+ chain->command->cmd =
+ BF_APBH_CHn_CMD_CMDWORDS(3) |
+ BM_APBH_CHn_CMD_WAIT4ENDCMD |
+ BM_APBH_CHn_CMD_NANDWAIT4READY |
+ BM_APBH_CHn_CMD_NANDLOCK |
+ BM_APBH_CHn_CMD_CHAIN |
+ BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER);
+ chain->command->pio_words[0] =
+ BF_GPMI_CTRL0_COMMAND_MODE(
+ BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY) |
+ BM_GPMI_CTRL0_WORD_LENGTH |
+ BF_GPMI_CTRL0_CS(cs);
+ chain->command->pio_words[1] = 0;
+ chain->command->pio_words[2] = 0;
+ chain->command->buf_ptr = 0;
+ chain++;
+
+ /* and deassert nand lock */
+ chain->command->cmd =
+ BF_APBH_CHn_CMD_CMDWORDS(0) |
+ BM_APBH_CHn_CMD_WAIT4ENDCMD |
+ BM_APBH_CHn_CMD_IRQONCMPLT |
+ BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER);
+ chain->command->buf_ptr = 0;
+
+ init_completion(&state->done);
+
+ return 0;
+}
+
+static int ecc8_write(void *context, int cs,
+ struct stmp3xxx_dma_descriptor *chain,
+ dma_addr_t error,
+ dma_addr_t page, dma_addr_t oob)
+{
+ u32 bufmask = 0;
+ struct ecc8_state_t *state = context;
+ u32 data_w, data_w_ecc,
+ oob_w, oob_w_ecc;
+
+ ecc8_reset(context, cs);
+
+ data_w = data_w_ecc = oob_w = oob_w_ecc = 0;
+
+ if (!dma_mapping_error(NULL, oob)) {
+ bufmask |= BV_GPMI_ECCCTRL_BUFFER_MASK__AUXILIARY;
+ oob_w = state->oob_free;
+ oob_w_ecc = oob_w + state->ecc_oob;
+ }
+ if (!dma_mapping_error(NULL, page)) {
+ bufmask |= (1 << state->bits) - 1;
+ data_w = state->bits * 512;
+ data_w_ecc = data_w + state->bits * state->ecc_page;
+ }
+
+ /* enable ECC and send NAND data (page only) */
+ chain->command->cmd =
+ BF_APBH_CHn_CMD_XFER_COUNT(data_w ? data_w : oob_w) |
+ BF_APBH_CHn_CMD_CMDWORDS(4) |
+ BM_APBH_CHn_CMD_NANDLOCK |
+ BM_APBH_CHn_CMD_CHAIN |
+ BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__DMA_READ);
+ chain->command->pio_words[0] =
+ BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__WRITE) |
+ BM_GPMI_CTRL0_WORD_LENGTH |
+ BM_GPMI_CTRL0_LOCK_CS |
+ BF_GPMI_CTRL0_CS(cs) |
+ BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_DATA) |
+ BF_GPMI_CTRL0_XFER_COUNT(data_w + oob_w);
+ chain->command->pio_words[1] = 0;
+ chain->command->pio_words[2] =
+ BM_GPMI_ECCCTRL_ENABLE_ECC |
+ state->w |
+ BF_GPMI_ECCCTRL_BUFFER_MASK(bufmask);
+ chain->command->pio_words[3] =
+ data_w_ecc + oob_w_ecc;
+ if (!dma_mapping_error(NULL, page))
+ chain->command->buf_ptr = page;
+ else
+ chain->command->buf_ptr = oob;
+ chain++;
+
+ if (!dma_mapping_error(NULL, page) && !dma_mapping_error(NULL, oob)) {
+ /* send NAND data (OOB only) */
+ chain->command->cmd =
+ BM_APBH_CHn_CMD_CHAIN |
+ BF_APBH_CHn_CMD_XFER_COUNT(oob_w) |
+ BF_APBH_CHn_CMD_CMDWORDS(0) |
+ BM_APBH_CHn_CMD_WAIT4ENDCMD |
+ BM_APBH_CHn_CMD_NANDLOCK |
+ BF_APBH_CHn_CMD_COMMAND(
+ BV_APBH_CHn_CMD_COMMAND__DMA_READ);
+ chain->command->buf_ptr = oob; /* never dma_mapping_error() */
+ chain++;
+ }
+ /* emit IRQ */
+ chain->command->cmd =
+ BF_APBH_CHn_CMD_CMDWORDS(0) |
+ BF_APBH_CHn_CMD_COMMAND(
+ BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER) |
+ BM_APBH_CHn_CMD_WAIT4ENDCMD |
+ BM_APBH_CHn_CMD_IRQONCMPLT;
+
+ return 0;
+}
+
+int __init ecc8_init(void)
+{
+ int err;
+
+ if (!ecc8_available(&state))
+ return -ENXIO;
+
+ gpmi_hwecc_chip_add(&state.chip);
+ err = request_irq(IRQ_ECC8_IRQ, ecc8_irq, 0, state.chip.name, &state);
+ if (err)
+ return err;
+
+ printk(KERN_INFO"%s: initialized\n", __func__);
+ return 0;
+}
+
+void ecc8_exit(void)
+{
+ free_irq(IRQ_ECC8_IRQ, &state);
+ gpmi_hwecc_chip_remove(&state.chip);
+}
diff --git a/drivers/mtd/nand/gpmi/gpmi-hamming-13-8.c b/drivers/mtd/nand/gpmi/gpmi-hamming-13-8.c
new file mode 100644
index 000000000000..c48d70b38bc0
--- /dev/null
+++ b/drivers/mtd/nand/gpmi/gpmi-hamming-13-8.c
@@ -0,0 +1,131 @@
+/*
+ * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface)
+ *
+ * NCB software ECC Hamming code
+ *
+ * Author: dmitry pervushin <dimka@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/platform_device.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/slab.h>
+#include <linux/ctype.h>
+#include <mach/dma.h>
+#include "gpmi.h"
+
+#define BIT_VAL(v, n) (((v) >> (n)) & 0x1)
+#define B(n) (BIT_VAL(d, n))
+
+static u8 calculate_parity(u8 d)
+{
+ u8 p = 0;
+
+ if (d == 0 || d == 0xFF)
+ return 0; /* optimization :) */
+
+ p |= (B(6) ^ B(5) ^ B(3) ^ B(2)) << 0;
+ p |= (B(7) ^ B(5) ^ B(4) ^ B(2) ^ B(1)) << 1;
+ p |= (B(7) ^ B(6) ^ B(5) ^ B(1) ^ B(0)) << 2;
+ p |= (B(7) ^ B(4) ^ B(3) ^ B(0)) << 3;
+ p |= (B(6) ^ B(4) ^ B(3) ^ B(2) ^ B(1) ^ B(0)) << 4;
+ return p;
+}
+
+static inline int even_number_of_1s(u8 byte)
+{
+ int even = 1;
+
+ while (byte > 0) {
+ even ^= (byte & 0x1);
+ byte >>= 1;
+ }
+ return even;
+}
+
+static int lookup_single_error(u8 syndrome)
+{
+ int i;
+ u8 syndrome_table[] = {
+ 0x1C, 0x16, 0x13, 0x19,
+ 0x1A, 0x07, 0x15, 0x0E,
+ 0x01, 0x02, 0x04, 0x08,
+ 0x10,
+ };
+
+ for (i = 0; i < ARRAY_SIZE(syndrome_table); i++)
+ if (syndrome_table[i] == syndrome)
+ return i;
+ return -ENOENT;
+}
+
+int gpmi_verify_hamming_13_8(void *data, u8 *parity, size_t size)
+{
+ int i;
+ u8 *pdata = data;
+ int bit_to_flip;
+ u8 np, syndrome;
+ int errors = 0;
+
+ for (i = 0; i < size; i ++, pdata++) {
+ np = calculate_parity(*pdata);
+ syndrome = np ^ parity[i];
+ if (syndrome == 0) /* cool */ {
+ continue;
+ }
+
+ if (even_number_of_1s(syndrome))
+ return -i; /* can't recover */
+
+ bit_to_flip = lookup_single_error(syndrome);
+ if (bit_to_flip < 0)
+ return -i; /* can't fix the error */
+
+ if (bit_to_flip < 8) {
+ *pdata ^= (1 << bit_to_flip);
+ errors++;
+ }
+ }
+ return errors;
+}
+
+void gpmi_encode_hamming_13_8(void *source_block, size_t src_size,
+ void *source_ecc, size_t ecc_size)
+{
+ int i;
+ u8 *src = source_block;
+ u8 *ecc = source_ecc;
+
+ for (i = 0; i < src_size && i < ecc_size; i++)
+ ecc[i] = calculate_parity(src[i]);
+}
+
+void gpmi_encode_hamming_ncb_13_8(void *source_block, size_t source_size,
+ void *target_block, size_t target_size)
+{
+ if (target_size < 12 + 2 * NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES)
+ return;
+ memset(target_block, 0xFF, target_size);
+ memcpy((u8 *)target_block + 12, source_block, source_size);
+ gpmi_encode_hamming_13_8(source_block,
+ NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES,
+ (u8 *)target_block + 12 + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES,
+ NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES);
+}
+
+unsigned gpmi_hamming_ecc_size_13_8(int block_size)
+{
+ return block_size;
+}
+
diff --git a/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.c b/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.c
new file mode 100644
index 000000000000..8d3975efbc6e
--- /dev/null
+++ b/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.c
@@ -0,0 +1,196 @@
+/*
+ * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface)
+ *
+ * NCB software ECC Hamming code
+ *
+ * Author: dmitry pervushin <dimka@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/platform_device.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/slab.h>
+#include <linux/ctype.h>
+#include <mach/dma.h>
+#include "gpmi.h"
+#include "gpmi-hamming-22-16.h"
+
+#define BIT_VAL(v, n) (((v) >> (n)) & 0x1)
+#define B(n) (BIT_VAL(d, n))
+#define BSEQ(a1, a2, a3, a4, a5, a6, a7, a8) \
+ (B(a1) ^ B(a2) ^ B(a3) ^ B(a4) ^ B(a5) ^ B(a6) ^ B(a7) ^ B(a8))
+static u8 calculate_parity(u16 d)
+{
+ u8 p = 0;
+
+ if (d == 0 || d == 0xFFFF)
+ return 0; /* optimization :) */
+
+ p |= BSEQ(15, 12, 11, 8, 5, 4, 3, 2) << 0;
+ p |= BSEQ(13, 12, 11, 10, 9, 7, 3, 1) << 1;
+ p |= BSEQ(15, 14, 13, 11, 10, 9, 6, 5) << 2;
+ p |= BSEQ(15, 14, 13, 8, 7, 6, 4, 0) << 3;
+ p |= BSEQ(12, 9, 8, 7, 6, 2, 1, 0) << 4;
+ p |= BSEQ(14, 10, 5, 4, 3, 2, 1, 0) << 5;
+ return p;
+}
+
+static inline int even_number_of_1s(u8 byte)
+{
+ int even = 1;
+
+ while (byte > 0) {
+ even ^= (byte & 0x1);
+ byte >>= 1;
+ }
+ return even;
+}
+
+static int lookup_single_error(u8 syndrome)
+{
+ int i;
+ u8 syndrome_table[] = {
+ 0x38, 0x32, 0x31, 0x23, 0x29, 0x25, 0x1C, 0x1A,
+ 0x19, 0x16, 0x26, 0x07, 0x13, 0x0E, 0x2C, 0x0D,
+ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
+ };
+
+ for (i = 0; i < ARRAY_SIZE(syndrome_table); i++)
+ if (syndrome_table[i] == syndrome)
+ return i;
+ return -ENOENT;
+}
+
+int gpmi_verify_hamming_22_16(void *data, u8 *parity, size_t size)
+{
+ int i, j;
+ int bit_index, bit_to_flip;
+ u16 *pdata = data;
+ u8 p = 0, np, syndrome;
+ int errors = 0;
+
+ for (bit_index = i = j = 0;
+ i < size / sizeof(u16);
+ i ++, pdata++) {
+
+ switch (bit_index) {
+
+ case 0:
+ p = parity[j] & 0x3F;
+ break;
+ case 2:
+ p = (parity[j++] & 0xC0) >> 6;
+ p |= (parity[j] & 0x0F) << 2;
+ break;
+ case 4:
+ p = (parity[j++] & 0xF0) >> 4;
+ p |= (parity[j] & 0x03) << 4;
+ break;
+ case 6:
+ p = (parity[j++] & 0xFC) >> 2;
+ break;
+ default:
+ BUG(); /* how did you get this ?! */
+ break;
+ }
+ bit_index = (bit_index + 2) % 8;
+
+ np = calculate_parity(*pdata);
+ syndrome = np ^ p;
+ if (syndrome == 0) /* cool */ {
+ continue;
+ }
+
+ if (even_number_of_1s(syndrome))
+ return -i; /* can't recover */
+
+ bit_to_flip = lookup_single_error(syndrome);
+ if (bit_to_flip < 0)
+ return -i; /* can't fix the error */
+
+ if (bit_to_flip < 16) {
+ *pdata ^= (1 << bit_to_flip);
+ errors++;
+ }
+ }
+ return errors;
+}
+
+void gpmi_encode_hamming_22_16(void *source_block, size_t src_size,
+ void *source_ecc, size_t ecc_size)
+{
+ int i, j, bit_index;
+ u16 *src = source_block;
+ u8 *ecc = source_ecc;
+ u8 np;
+
+ for (bit_index = j = i = 0;
+ j < src_size/sizeof(u16) && i < ecc_size;
+ j++) {
+
+ np = calculate_parity(src[j]);
+
+ switch (bit_index) {
+
+ case 0:
+ ecc[i] = np & 0x3F;
+ break;
+ case 2:
+ ecc[i++] |= (np & 0x03) << 6;
+ ecc[i] = (np & 0x3C) >> 2;
+ break;
+ case 4:
+ ecc[i++] |= (np & 0x0F) << 4;
+ ecc[i] = (np & 0x30) >> 4;
+ break;
+ case 6:
+ ecc[i++] |= (np & 0x3F) << 2;
+ break;
+ }
+ bit_index = (bit_index + 2) % 8;
+ }
+
+}
+
+void gpmi_encode_hamming_ncb_22_16(void *source_block, size_t source_size,
+ void *target_block, size_t target_size)
+{
+ u8 *dst = target_block;
+ u8 ecc[NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES];
+
+ gpmi_encode_hamming_22_16(source_block,
+ NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES,
+ ecc,
+ NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES);
+ /* create THREE copies of source block */
+ memcpy(dst + NAND_HC_ECC_OFFSET_FIRST_DATA_COPY,
+ source_block, NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES);
+ memcpy(dst + NAND_HC_ECC_OFFSET_SECOND_DATA_COPY,
+ source_block, NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES);
+ memcpy(dst + NAND_HC_ECC_OFFSET_THIRD_DATA_COPY,
+ source_block, NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES);
+ /* ..and three copies of ECC block */
+ memcpy(dst + NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY,
+ ecc, NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES);
+ memcpy(dst + NAND_HC_ECC_OFFSET_SECOND_PARITY_COPY,
+ ecc, NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES);
+ memcpy(dst + NAND_HC_ECC_OFFSET_THIRD_PARITY_COPY,
+ ecc, NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES);
+}
+
+unsigned gpmi_hamming_ecc_size_22_16(int block_size)
+{
+ return (((block_size * 8) / 16) * 6) / 8;
+}
+
diff --git a/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.h b/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.h
new file mode 100644
index 000000000000..6959bf3c599e
--- /dev/null
+++ b/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.h
@@ -0,0 +1,43 @@
+/*
+ * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface)
+ *
+ * NCB software ECC Hamming code
+ *
+ * Author: dmitry pervushin <dimka@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#ifndef __LINUX_GPMI_H
+#define __LINUX_GPMI_H
+
+#define NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES (512)
+#define NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES \
+ ((((NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES*8)/16)*6)/8)
+#define NAND_HC_ECC_OFFSET_FIRST_DATA_COPY (0)
+#define NAND_HC_ECC_OFFSET_SECOND_DATA_COPY \
+ (NAND_HC_ECC_OFFSET_FIRST_DATA_COPY + \
+ NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES)
+#define NAND_HC_ECC_OFFSET_THIRD_DATA_COPY \
+ (NAND_HC_ECC_OFFSET_SECOND_DATA_COPY + \
+ NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES)
+#define NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY \
+ (NAND_HC_ECC_OFFSET_THIRD_DATA_COPY + \
+ NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES)
+#define NAND_HC_ECC_OFFSET_SECOND_PARITY_COPY \
+ (NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY + \
+ NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES)
+#define NAND_HC_ECC_OFFSET_THIRD_PARITY_COPY \
+ (NAND_HC_ECC_OFFSET_SECOND_PARITY_COPY + \
+ NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES)
+
+#endif
diff --git a/drivers/mtd/nand/gpmi/gpmi.h b/drivers/mtd/nand/gpmi/gpmi.h
new file mode 100644
index 000000000000..99c26988f370
--- /dev/null
+++ b/drivers/mtd/nand/gpmi/gpmi.h
@@ -0,0 +1,291 @@
+/*
+ * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface)
+ *
+ * Author: dmitry pervushin <dimka@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#ifndef __DRIVERS_GPMI_H
+#define __DRIVERS_GPMI_H
+
+#include <linux/mtd/partitions.h>
+#include <mach/gpmi.h>
+#include <mach/regs-gpmi.h>
+#include <mach/regs-apbh.h>
+#ifdef CONFIG_MTD_NAND_GPMI_BCH
+#include <mach/regs-bch.h>
+#endif
+#include <mach/regs-ecc8.h>
+
+#include "gpmi-hamming-22-16.h"
+
+#define GPMI_ECC4_WR \
+ (BM_GPMI_ECCCTRL_ENABLE_ECC | \
+ BF_GPMI_ECCCTRL_ECC_CMD(BV_GPMI_ECCCTRL_ECC_CMD__ENCODE_4_BIT))
+#define GPMI_ECC4_RD \
+ (BM_GPMI_ECCCTRL_ENABLE_ECC | \
+ BF_GPMI_ECCCTRL_ECC_CMD(BV_GPMI_ECCCTRL_ECC_CMD__DECODE_4_BIT))
+#define GPMI_ECC8_WR \
+ (BM_GPMI_ECCCTRL_ENABLE_ECC | \
+ BF_GPMI_ECCCTRL_ECC_CMD(BV_GPMI_ECCCTRL_ECC_CMD__ENCODE_8_BIT))
+#define GPMI_ECC8_RD \
+ (BM_GPMI_ECCCTRL_ENABLE_ECC | \
+ BF_GPMI_ECCCTRL_ECC_CMD(BV_GPMI_ECCCTRL_ECC_CMD__DECODE_8_BIT))
+
+/* fingerprints of BCB that can be found on STMP-formatted flash */
+#define SIG1 "STMP"
+#define SIG_NCB "NCB "
+#define SIG_LDLB "LDLB"
+#define SIG_DBBT "DBBT"
+#define SIG_SIZE 4
+
+struct gpmi_nand_timing {
+ u8 data_setup;
+ u8 data_hold;
+ u8 address_setup;
+ u8 dsample_time;
+};
+
+struct gpmi_bcb_info {
+ struct gpmi_nand_timing timing;
+ loff_t ncbblock;
+ const void *pre_ncb;
+ size_t pre_ncb_size;
+};
+
+struct gpmi_ncb;
+
+int gpmi_erase(struct mtd_info *mtd, struct erase_info *instr);
+int gpmi_block_markbad(struct mtd_info *mtd, loff_t ofs);
+int gpmi_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip);
+int gpmi_scan_bbt(struct mtd_info *mtd);
+int gpmi_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip);
+#ifdef CONFIG_MTD_NAND_GPMI_SYSFS_ENTRIES
+int gpmi_sysfs(struct platform_device *p, int create);
+#endif
+int gpmi_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
+ int page, int sndcmd);
+void gpmi_set_timings(struct platform_device *pdev,
+ struct gpmi_nand_timing *tm);
+int gpmi_write_ncb(struct mtd_info *mtd, struct gpmi_bcb_info *b);
+
+unsigned gpmi_hamming_ecc_size_22_16(int block_size);
+void gpmi_encode_hamming_ncb_22_16(void *source_block, size_t source_size,
+ void *target_block, size_t target_size);
+void gpmi_encode_hamming_22_16(void *source_block, size_t src_size,
+ void *source_ecc, size_t ecc_size);
+int gpmi_verify_hamming_22_16(void *data, u8 *parity, size_t size);
+
+unsigned gpmi_hamming_ecc_size_13_8(int block_size);
+void gpmi_encode_hamming_ncb_13_8(void *source_block, size_t source_size,
+ void *target_block, size_t target_size);
+void gpmi_encode_hamming_13_8(void *source_block, size_t src_size,
+ void *source_ecc, size_t ecc_size);
+int gpmi_verify_hamming_13_8(void *data, u8 *parity, size_t size);
+
+#define GPMI_DMA_MAX_CHAIN 20 /* max DMA commands in chain */
+
+/*
+ * Sizes of data buffers to exchange commands/data with NAND chip
+ * Default values cover 4K NAND page (4096 data bytes + 218 bytes OOB)
+ */
+#define GPMI_CMD_BUF_SZ 10
+#define GPMI_DATA_BUF_SZ NAND_MAX_PAGESIZE
+#define GPMI_WRITE_BUF_SZ NAND_MAX_PAGESIZE
+#define GPMI_OOB_BUF_SZ NAND_MAX_OOBSIZE
+
+#define GPMI_MAX_CHIPS 10
+
+struct gpmi_hwecc_chip {
+ char name[40];
+ struct list_head list;
+ int (*setup)(void *ctx, int index, int writesize, int oobsize);
+ int (*reset)(void *ctx, int index);
+ int (*read)(void *ctx, int index,
+ struct stmp3xxx_dma_descriptor *chain,
+ dma_addr_t error,
+ dma_addr_t page, dma_addr_t oob);
+ int (*write)(void *ctx, int index,
+ struct stmp3xxx_dma_descriptor *chain,
+ dma_addr_t error,
+ dma_addr_t page, dma_addr_t oob);
+ int (*stat)(void *ctx, int index, struct mtd_ecc_stats *r);
+};
+
+/* HWECC chips */
+struct gpmi_hwecc_chip *gpmi_hwecc_chip_find(char *name);
+void gpmi_hwecc_chip_add(struct gpmi_hwecc_chip *chip);
+void gpmi_hwecc_chip_remove(struct gpmi_hwecc_chip *chip);
+int bch_init(void);
+int ecc8_init(void);
+void bch_exit(void);
+void ecc8_exit(void);
+
+
+struct gpmi_nand_data {
+ void __iomem *io_base;
+ struct clk *clk;
+ int irq;
+ struct timer_list timer;
+ int self_suspended;
+ int use_count;
+ struct regulator *regulator;
+ int reg_uA;
+
+ int ignorebad;
+ void *bbt;
+
+ struct nand_chip chip;
+ struct mtd_info mtd;
+ struct platform_device *dev;
+
+#ifdef CONFIG_MTD_PARTITIONS
+ int nr_parts;
+ struct mtd_partition
+ *parts;
+#endif
+
+ struct completion done;
+
+ u8 *cmd_buffer;
+ dma_addr_t cmd_buffer_handle;
+ int cmd_buffer_size, cmd_buffer_sz;
+
+ u8 *write_buffer;
+ dma_addr_t write_buffer_handle;
+ int write_buffer_size;
+ u8 *read_buffer; /* point in write_buffer */
+ dma_addr_t read_buffer_handle;
+
+ u8 *data_buffer;
+ dma_addr_t data_buffer_handle;
+ int data_buffer_size;
+
+ u8 *oob_buffer;
+ dma_addr_t oob_buffer_handle;
+ int oob_buffer_size;
+
+ void *verify_buffer;
+
+ struct nchip {
+ unsigned dma_ch;
+ struct stmp37xx_circ_dma_chain chain;
+ struct stmp3xxx_dma_descriptor d[GPMI_DMA_MAX_CHAIN];
+ struct stmp3xxx_dma_descriptor error;
+ int cs;
+ } chips[GPMI_MAX_CHIPS];
+ struct nchip *cchip;
+ int selected_chip;
+
+ unsigned hwecc_type_read, hwecc_type_write;
+ int hwecc;
+
+ int ecc_oob_bytes, oob_free;
+
+ int transcribe_bbmark;
+ struct gpmi_nand_timing timing;
+
+ void (*saved_command)(struct mtd_info *mtd, unsigned int command,
+ int column, int page_addr);
+
+ int raw_oob_mode;
+ int (*saved_read_oob)(struct mtd_info *mtd, loff_t from,
+ struct mtd_oob_ops *ops);
+ int (*saved_write_oob)(struct mtd_info *mtd, loff_t to,
+ struct mtd_oob_ops *ops);
+
+ struct gpmi_hwecc_chip *hc;
+
+ int numchips;
+ int custom_partitions;
+ struct mtd_info *masters[GPMI_MAX_CHIPS];
+ struct mtd_partition chip_partitions[GPMI_MAX_CHIPS];
+ int n_concat;
+ struct mtd_info *concat[GPMI_MAX_CHIPS];
+ struct mtd_info *concat_mtd;
+};
+
+extern struct gpmi_nand_timing gpmi_safe_timing;
+
+struct gpmi_ncb {
+ u32 fingerprint1;
+ struct gpmi_nand_timing timing;
+ u32 pagesize;
+ u32 page_plus_oob_size;
+ u32 sectors_per_block;
+ u32 sector_in_page_mask;
+ u32 sector_to_page_shift;
+ u32 num_nands;
+ u32 reserved[3];
+ u32 fingerprint2; /* offset 0x2C */
+};
+
+struct gpmi_ldlb {
+ u32 fingerprint1;
+ u16 major, minor, sub, reserved;
+ u32 nand_bitmap;
+ u32 reserved1[7];
+ u32 fingerprint2;
+ struct {
+ u32 fw_starting_nand;
+ u32 fw_starting_sector;
+ u32 fw_sector_stride;
+ u32 fw_sectors_total;
+ } fw[2];
+ u16 fw_major, fw_minor, fw_sub, fw_reserved;
+ u32 bbt_blk;
+ u32 bbt_blk_backup;
+};
+
+static inline void gpmi_block_mark_as(struct nand_chip *chip,
+ int block, int mark)
+{
+ u32 o;
+ int shift = (block & 0x03) << 1,
+ index = block >> 2;
+
+ if (chip->bbt) {
+ mark &= 0x03;
+
+ o = chip->bbt[index];
+ o &= ~(0x03 << shift);
+ o |= (mark << shift);
+ chip->bbt[index] = o;
+ }
+}
+
+static inline int gpmi_block_badness(struct nand_chip *chip,
+ int block)
+{
+ u32 o;
+ int shift = (block & 0x03) << 1,
+ index = block >> 2;
+
+ if (chip->bbt) {
+ o = (chip->bbt[index] >> shift) & 0x03;
+ pr_debug("%s: block = %d, o = %d\n", __func__, block, o);
+ return o;
+ }
+ return -1;
+}
+
+#ifdef CONFIG_STMP3XXX_UNIQUE_ID
+int __init gpmi_uid_init(const char *name, struct mtd_info *mtd,
+ u_int32_t start, u_int32_t size);
+void gpmi_uid_remove(const char *name);
+#else
+#define gpmi_uid_init(name, mtd, start, size)
+#define gpmi_uid_remove(name)
+#endif
+
+#endif