summaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-imx
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2021-01-27 08:51:14 +0100
committerSascha Hauer <s.hauer@pengutronix.de>2021-02-01 11:53:25 +0100
commit7d50f13d3fd2e003661c78a497432ac021497043 (patch)
tree9bb9b21c3d1f415850ed923c774b5f3f4281d124 /arch/arm/mach-imx
parent71692dda04e0f0dab8e1f19ca71eee8d2ee5809d (diff)
downloadbarebox-7d50f13d3fd2e003661c78a497432ac021497043.tar.gz
barebox-7d50f13d3fd2e003661c78a497432ac021497043.tar.xz
ARM: i.MX: implement GPMI NAND xload
Commit is based on initial Sascha Hauer's work. It implements PBL xload mechanism to load image from GPMI NAND flash. Additional work was done, so that the NAND's size, page size and OOB's size are autodetected and not hardcoded. Detection method follows the same methods as used in NAND driver, meaning NAND ONFI support is probed and if NAND supports ONFI, NAND memory organization is read from ONFI parameter page otherwise "READ ID" is used. Currently only implemented for i.MX6 familly of SoCs. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de> Signed-off-by: Primoz Fiser <primoz.fiser@norik.com> Signed-off-by: Andrej Picej <andrej.picej@norik.com> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'arch/arm/mach-imx')
-rw-r--r--arch/arm/mach-imx/Makefile2
-rw-r--r--arch/arm/mach-imx/include/mach/xload.h1
-rw-r--r--arch/arm/mach-imx/xload-gpmi-nand.c1155
3 files changed, 1157 insertions, 1 deletions
diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile
index e45f758e9c..d94c846a13 100644
--- a/arch/arm/mach-imx/Makefile
+++ b/arch/arm/mach-imx/Makefile
@@ -26,4 +26,4 @@ obj-$(CONFIG_BAREBOX_UPDATE) += imx-bbu-internal.o
obj-$(CONFIG_BAREBOX_UPDATE_IMX_EXTERNAL_NAND) += imx-bbu-external-nand.o
obj-$(CONFIG_RESET_IMX_SRC) += src.o
lwl-y += cpu_init.o
-pbl-y += xload-spi.o xload-common.o xload-imx-nand.o
+pbl-y += xload-spi.o xload-common.o xload-imx-nand.o xload-gpmi-nand.o
diff --git a/arch/arm/mach-imx/include/mach/xload.h b/arch/arm/mach-imx/include/mach/xload.h
index 94b2f37616..7187787f5b 100644
--- a/arch/arm/mach-imx/include/mach/xload.h
+++ b/arch/arm/mach-imx/include/mach/xload.h
@@ -5,6 +5,7 @@ int imx53_nand_start_image(void);
int imx6_spi_load_image(int instance, unsigned int flash_offset, void *buf, int len);
int imx6_spi_start_image(int instance);
int imx6_esdhc_start_image(int instance);
+int imx6_nand_start_image(void);
int imx7_esdhc_start_image(int instance);
int imx8m_esdhc_load_image(int instance, bool start);
int imx8mp_esdhc_load_image(int instance, bool start);
diff --git a/arch/arm/mach-imx/xload-gpmi-nand.c b/arch/arm/mach-imx/xload-gpmi-nand.c
new file mode 100644
index 0000000000..c39ab7b35d
--- /dev/null
+++ b/arch/arm/mach-imx/xload-gpmi-nand.c
@@ -0,0 +1,1155 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "xload-gpmi-nand: " fmt
+
+#include <common.h>
+#include <stmp-device.h>
+#include <asm-generic/io.h>
+#include <linux/sizes.h>
+#include <linux/mtd/nand.h>
+#include <asm/cache.h>
+#include <mach/xload.h>
+#include <soc/imx/imx-nand-bcb.h>
+#include <linux/mtd/rawnand.h>
+
+/*
+ * MXS DMA hardware command.
+ *
+ * This structure describes the in-memory layout of an entire DMA command,
+ * including space for the maximum number of PIO accesses. See the appropriate
+ * reference manual for a detailed description of what these fields mean to the
+ * DMA hardware.
+ */
+#define DMACMD_COMMAND_DMA_WRITE 0x1
+#define DMACMD_COMMAND_DMA_READ 0x2
+#define DMACMD_COMMAND_DMA_SENSE 0x3
+#define DMACMD_CHAIN (1 << 2)
+#define DMACMD_IRQ (1 << 3)
+#define DMACMD_NAND_LOCK (1 << 4)
+#define DMACMD_NAND_WAIT_4_READY (1 << 5)
+#define DMACMD_DEC_SEM (1 << 6)
+#define DMACMD_WAIT4END (1 << 7)
+#define DMACMD_HALT_ON_TERMINATE (1 << 8)
+#define DMACMD_TERMINATE_FLUSH (1 << 9)
+#define DMACMD_PIO_WORDS(words) ((words) << 12)
+#define DMACMD_XFER_COUNT(x) ((x) << 16)
+
+struct mxs_dma_cmd {
+ unsigned long next;
+ unsigned long data;
+ unsigned long address;
+#define APBH_DMA_PIO_WORDS 6
+ unsigned long pio_words[APBH_DMA_PIO_WORDS];
+};
+
+enum mxs_dma_id {
+ IMX23_DMA,
+ IMX28_DMA,
+};
+
+struct apbh_dma {
+ void __iomem *regs;
+ enum mxs_dma_id id;
+};
+
+struct mxs_dma_chan {
+ unsigned int flags;
+ int channel;
+ struct apbh_dma *apbh;
+};
+
+#define HW_APBHX_CTRL0 0x000
+#define BM_APBH_CTRL0_APB_BURST8_EN (1 << 29)
+#define BM_APBH_CTRL0_APB_BURST_EN (1 << 28)
+#define BP_APBH_CTRL0_CLKGATE_CHANNEL 8
+#define BP_APBH_CTRL0_RESET_CHANNEL 16
+#define HW_APBHX_CTRL1 0x010
+#define BP_APBHX_CTRL1_CH_CMDCMPLT_IRQ_EN 16
+#define HW_APBHX_CTRL2 0x020
+#define HW_APBHX_CHANNEL_CTRL 0x030
+#define BP_APBHX_CHANNEL_CTRL_RESET_CHANNEL 16
+#define BP_APBHX_VERSION_MAJOR 24
+#define HW_APBHX_CHn_NXTCMDAR_MX23(n) (0x050 + (n) * 0x70)
+#define HW_APBHX_CHn_NXTCMDAR_MX28(n) (0x110 + (n) * 0x70)
+#define HW_APBHX_CHn_SEMA_MX23(n) (0x080 + (n) * 0x70)
+#define HW_APBHX_CHn_SEMA_MX28(n) (0x140 + (n) * 0x70)
+#define NAND_ONFI_CRC_BASE 0x4f4e
+
+#define apbh_dma_is_imx23(aphb) ((apbh)->id == IMX23_DMA)
+
+/* udelay() is not available in PBL, need to improvise */
+static void __udelay(int us)
+{
+ volatile int i;
+
+ for (i = 0; i < us * 4; i++);
+}
+
+/*
+ * Enable a DMA channel.
+ *
+ * If the given channel has any DMA descriptors on its active list, this
+ * function causes the DMA hardware to begin processing them.
+ *
+ * This function marks the DMA channel as "busy," whether or not there are any
+ * descriptors to process.
+ */
+static int mxs_dma_enable(struct mxs_dma_chan *pchan,
+ struct mxs_dma_cmd *pdesc)
+{
+ struct apbh_dma *apbh = pchan->apbh;
+ int channel_bit;
+ int channel = pchan->channel;
+
+ if (apbh_dma_is_imx23(apbh)) {
+ writel((uint32_t)pdesc,
+ apbh->regs + HW_APBHX_CHn_NXTCMDAR_MX23(channel));
+ writel(1, apbh->regs + HW_APBHX_CHn_SEMA_MX23(channel));
+ channel_bit = channel + BP_APBH_CTRL0_CLKGATE_CHANNEL;
+ } else {
+ writel((uint32_t)pdesc,
+ apbh->regs + HW_APBHX_CHn_NXTCMDAR_MX28(channel));
+ writel(1, apbh->regs + HW_APBHX_CHn_SEMA_MX28(channel));
+ channel_bit = channel;
+ }
+
+ writel(1 << channel_bit, apbh->regs +
+ HW_APBHX_CTRL0 + STMP_OFFSET_REG_CLR);
+
+ return 0;
+}
+
+/*
+ * Resets the DMA channel hardware.
+ */
+static int mxs_dma_reset(struct mxs_dma_chan *pchan)
+{
+ struct apbh_dma *apbh = pchan->apbh;
+
+ if (apbh_dma_is_imx23(apbh))
+ writel(1 << (pchan->channel + BP_APBH_CTRL0_RESET_CHANNEL),
+ apbh->regs + HW_APBHX_CTRL0 + STMP_OFFSET_REG_SET);
+ else
+ writel(1 << (pchan->channel +
+ BP_APBHX_CHANNEL_CTRL_RESET_CHANNEL),
+ apbh->regs + HW_APBHX_CHANNEL_CTRL +
+ STMP_OFFSET_REG_SET);
+
+ return 0;
+}
+
+static int mxs_dma_wait_complete(struct mxs_dma_chan *pchan)
+{
+ struct apbh_dma *apbh = pchan->apbh;
+ int timeout = 1000000;
+
+ while (1) {
+ if (readl(apbh->regs + HW_APBHX_CTRL1) & (1 << pchan->channel))
+ return 0;
+
+ if (!timeout--)
+ return -ETIMEDOUT;
+ }
+}
+
+/*
+ * Execute the DMA channel
+ */
+static int mxs_dma_run(struct mxs_dma_chan *pchan, struct mxs_dma_cmd *pdesc,
+ int num)
+{
+ struct apbh_dma *apbh = pchan->apbh;
+ int i, ret;
+
+ /* chain descriptors */
+ for (i = 0; i < num - 1; i++) {
+ pdesc[i].next = (uint32_t)(&pdesc[i + 1]);
+ pdesc[i].data |= DMACMD_CHAIN;
+ }
+
+ writel(1 << (pchan->channel + BP_APBHX_CTRL1_CH_CMDCMPLT_IRQ_EN),
+ apbh->regs + HW_APBHX_CTRL1 + STMP_OFFSET_REG_SET);
+
+ ret = mxs_dma_enable(pchan, pdesc);
+ if (ret) {
+ pr_err("%s: Failed to enable dma channel: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = mxs_dma_wait_complete(pchan);
+ if (ret) {
+ pr_err("%s: Failed to wait for completion: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /* Shut the DMA channel down. */
+ writel(1 << pchan->channel, apbh->regs +
+ HW_APBHX_CTRL1 + STMP_OFFSET_REG_CLR);
+ writel(1 << pchan->channel, apbh->regs +
+ HW_APBHX_CTRL2 + STMP_OFFSET_REG_CLR);
+
+ mxs_dma_reset(pchan);
+
+ writel(1 << (pchan->channel + BP_APBHX_CTRL1_CH_CMDCMPLT_IRQ_EN),
+ apbh->regs + HW_APBHX_CTRL1 + STMP_OFFSET_REG_CLR);
+
+ return 0;
+}
+
+/* ----------------------------- NAND driver part -------------------------- */
+
+#define GPMI_CTRL0 0x00000000
+#define GPMI_CTRL0_RUN (1 << 29)
+#define GPMI_CTRL0_DEV_IRQ_EN (1 << 28)
+#define GPMI_CTRL0_UDMA (1 << 26)
+#define GPMI_CTRL0_COMMAND_MODE_MASK (0x3 << 24)
+#define GPMI_CTRL0_COMMAND_MODE_OFFSET 24
+#define GPMI_CTRL0_COMMAND_MODE_WRITE (0x0 << 24)
+#define GPMI_CTRL0_COMMAND_MODE_READ (0x1 << 24)
+#define GPMI_CTRL0_COMMAND_MODE_READ_AND_COMPARE (0x2 << 24)
+#define GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY (0x3 << 24)
+#define GPMI_CTRL0_WORD_LENGTH (1 << 23)
+#define GPMI_CTRL0_CS(cs) ((cs) << 20)
+#define GPMI_CTRL0_ADDRESS_MASK (0x7 << 17)
+#define GPMI_CTRL0_ADDRESS_OFFSET 17
+#define GPMI_CTRL0_ADDRESS_NAND_DATA (0x0 << 17)
+#define GPMI_CTRL0_ADDRESS_NAND_CLE (0x1 << 17)
+#define GPMI_CTRL0_ADDRESS_NAND_ALE (0x2 << 17)
+#define GPMI_CTRL0_ADDRESS_INCREMENT (1 << 16)
+#define GPMI_CTRL0_XFER_COUNT_MASK 0xffff
+#define GPMI_CTRL0_XFER_COUNT_OFFSET 0
+
+#define GPMI_ECCCTRL_ECC_CMD_DECODE (0x0 << 13)
+#define GPMI_ECCCTRL_ENABLE_ECC (1 << 12)
+#define GPMI_ECCCTRL_BUFFER_MASK_BCH_PAGE 0x1ff
+
+#define BCH_CTRL 0x00000000
+#define BCH_CTRL_COMPLETE_IRQ (1 << 0)
+
+#define MXS_NAND_DMA_DESCRIPTOR_COUNT 6
+#define MXS_NAND_CHUNK_DATA_CHUNK_SIZE 512
+#define MXS_NAND_METADATA_SIZE 10
+#define MXS_NAND_COMMAND_BUFFER_SIZE 128
+
+struct mxs_nand_info {
+ void __iomem *io_base;
+ void __iomem *bch_base;
+ struct mxs_dma_chan *dma_channel;
+ int cs;
+ uint8_t *cmd_buf;
+ struct mxs_dma_cmd *desc;
+ struct fcb_block fcb;
+ int dbbt_num_entries;
+ u32 *dbbt;
+ struct nand_memory_organization organization;
+ unsigned long nand_size;
+};
+
+static uint32_t mxs_nand_aux_status_offset(void)
+{
+ return (MXS_NAND_METADATA_SIZE + 0x3) & ~0x3;
+}
+
+static int mxs_nand_read_page(struct mxs_nand_info *info, int writesize,
+ int oobsize, int pagenum, void *databuf, int raw)
+{
+ void __iomem *bch_regs = info->bch_base;
+ unsigned column = 0;
+ struct mxs_dma_cmd *d;
+ int cmd_queue_len;
+ u8 *cmd_buf;
+ int ret;
+ uint8_t *status;
+ int i;
+ int timeout;
+ int descnum = 0;
+ int max_pagenum = info->nand_size /
+ info->organization.pagesize;
+
+ memset(info->desc, 0,
+ sizeof(*info->desc) * MXS_NAND_DMA_DESCRIPTOR_COUNT);
+
+ /* Compile DMA descriptor - read0 */
+ cmd_buf = info->cmd_buf;
+ cmd_queue_len = 0;
+ d = &info->desc[descnum++];
+ d->address = (dma_addr_t)(cmd_buf);
+ cmd_buf[cmd_queue_len++] = NAND_CMD_READ0;
+ cmd_buf[cmd_queue_len++] = column;
+ cmd_buf[cmd_queue_len++] = column >> 8;
+ cmd_buf[cmd_queue_len++] = pagenum;
+ cmd_buf[cmd_queue_len++] = pagenum >> 8;
+
+ if ((max_pagenum - 1) >= SZ_64K)
+ cmd_buf[cmd_queue_len++] = pagenum >> 16;
+
+ d->data = DMACMD_COMMAND_DMA_READ |
+ DMACMD_WAIT4END |
+ DMACMD_PIO_WORDS(1) |
+ DMACMD_XFER_COUNT(cmd_queue_len);
+
+ d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WRITE |
+ GPMI_CTRL0_WORD_LENGTH |
+ GPMI_CTRL0_CS(info->cs) |
+ GPMI_CTRL0_ADDRESS_NAND_CLE |
+ GPMI_CTRL0_ADDRESS_INCREMENT |
+ cmd_queue_len;
+
+ /* Compile DMA descriptor - readstart */
+ cmd_buf = &info->cmd_buf[8];
+ cmd_queue_len = 0;
+ d = &info->desc[descnum++];
+ d->address = (dma_addr_t)(cmd_buf);
+
+ cmd_buf[cmd_queue_len++] = NAND_CMD_READSTART;
+
+ d->data = DMACMD_COMMAND_DMA_READ |
+ DMACMD_WAIT4END |
+ DMACMD_PIO_WORDS(1) |
+ DMACMD_XFER_COUNT(cmd_queue_len);
+
+ d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WRITE |
+ GPMI_CTRL0_WORD_LENGTH |
+ GPMI_CTRL0_CS(info->cs) |
+ GPMI_CTRL0_ADDRESS_NAND_CLE |
+ GPMI_CTRL0_ADDRESS_INCREMENT |
+ cmd_queue_len;
+
+ /* Compile DMA descriptor - wait for ready. */
+ d = &info->desc[descnum++];
+ d->data = DMACMD_CHAIN |
+ DMACMD_NAND_WAIT_4_READY |
+ DMACMD_WAIT4END |
+ DMACMD_PIO_WORDS(2);
+
+ d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY |
+ GPMI_CTRL0_WORD_LENGTH |
+ GPMI_CTRL0_CS(info->cs) |
+ GPMI_CTRL0_ADDRESS_NAND_DATA;
+
+ if (raw) {
+ /* Compile DMA descriptor - read. */
+ d = &info->desc[descnum++];
+ d->data = DMACMD_WAIT4END |
+ DMACMD_PIO_WORDS(1) |
+ DMACMD_XFER_COUNT(writesize + oobsize) |
+ DMACMD_COMMAND_DMA_WRITE;
+ d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_READ |
+ GPMI_CTRL0_WORD_LENGTH |
+ GPMI_CTRL0_CS(info->cs) |
+ GPMI_CTRL0_ADDRESS_NAND_DATA |
+ (writesize + oobsize);
+ d->address = (dma_addr_t)databuf;
+ } else {
+ /* Compile DMA descriptor - enable the BCH block and read. */
+ d = &info->desc[descnum++];
+ d->data = DMACMD_WAIT4END | DMACMD_PIO_WORDS(6);
+ d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_READ |
+ GPMI_CTRL0_WORD_LENGTH |
+ GPMI_CTRL0_CS(info->cs) |
+ GPMI_CTRL0_ADDRESS_NAND_DATA |
+ (writesize + oobsize);
+ d->pio_words[1] = 0;
+ d->pio_words[2] = GPMI_ECCCTRL_ENABLE_ECC |
+ GPMI_ECCCTRL_ECC_CMD_DECODE |
+ GPMI_ECCCTRL_BUFFER_MASK_BCH_PAGE;
+ d->pio_words[3] = writesize + oobsize;
+ d->pio_words[4] = (dma_addr_t)databuf;
+ d->pio_words[5] = (dma_addr_t)(databuf + writesize);
+
+ /* Compile DMA descriptor - disable the BCH block. */
+ d = &info->desc[descnum++];
+ d->data = DMACMD_NAND_WAIT_4_READY |
+ DMACMD_WAIT4END |
+ DMACMD_PIO_WORDS(3);
+ d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY |
+ GPMI_CTRL0_WORD_LENGTH |
+ GPMI_CTRL0_CS(info->cs) |
+ GPMI_CTRL0_ADDRESS_NAND_DATA |
+ (writesize + oobsize);
+ }
+
+ /* Compile DMA descriptor - de-assert the NAND lock and interrupt. */
+ d = &info->desc[descnum++];
+ d->data = DMACMD_IRQ | DMACMD_DEC_SEM;
+
+ /* Execute the DMA chain. */
+ ret = mxs_dma_run(info->dma_channel, info->desc, descnum);
+ if (ret) {
+ pr_err("DMA read error\n");
+ goto err;
+ }
+
+ if (raw)
+ return 0;
+
+ timeout = 1000000;
+
+ while (1) {
+ if (!timeout--) {
+ ret = -ETIMEDOUT;
+ goto err;
+ }
+
+ if (readl(bch_regs + BCH_CTRL) & BCH_CTRL_COMPLETE_IRQ)
+ break;
+ }
+
+ writel(BCH_CTRL_COMPLETE_IRQ,
+ bch_regs + BCH_CTRL + STMP_OFFSET_REG_CLR);
+
+ /* Loop over status bytes, accumulating ECC status. */
+ status = databuf + writesize + mxs_nand_aux_status_offset();
+ for (i = 0; i < writesize / MXS_NAND_CHUNK_DATA_CHUNK_SIZE; i++) {
+ if (status[i] == 0xfe) {
+ ret = -EBADMSG;
+ goto err;
+ }
+ }
+
+ ret = 0;
+err:
+ return ret;
+}
+
+static int mxs_nand_get_read_status(struct mxs_nand_info *info, void *databuf)
+{
+ int ret;
+ u8 *cmd_buf;
+ struct mxs_dma_cmd *d;
+ int descnum = 0;
+ int cmd_queue_len;
+
+ memset(info->desc, 0,
+ sizeof(*info->desc) * MXS_NAND_DMA_DESCRIPTOR_COUNT);
+
+ /* Compile DMA descriptor - READ STATUS */
+ cmd_buf = info->cmd_buf;
+ cmd_queue_len = 0;
+ d = &info->desc[descnum++];
+ d->address = (dma_addr_t)(cmd_buf);
+ cmd_buf[cmd_queue_len++] = NAND_CMD_STATUS;
+
+ d->data = DMACMD_COMMAND_DMA_READ |
+ DMACMD_WAIT4END |
+ DMACMD_PIO_WORDS(1) |
+ DMACMD_XFER_COUNT(cmd_queue_len);
+
+ d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WRITE |
+ GPMI_CTRL0_WORD_LENGTH |
+ GPMI_CTRL0_CS(info->cs) |
+ GPMI_CTRL0_ADDRESS_NAND_CLE |
+ GPMI_CTRL0_ADDRESS_INCREMENT |
+ cmd_queue_len;
+
+ /* Compile DMA descriptor - read. */
+ d = &info->desc[descnum++];
+ d->data = DMACMD_WAIT4END |
+ DMACMD_PIO_WORDS(1) |
+ DMACMD_XFER_COUNT(1) |
+ DMACMD_COMMAND_DMA_WRITE;
+ d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_READ |
+ GPMI_CTRL0_WORD_LENGTH |
+ GPMI_CTRL0_CS(info->cs) |
+ GPMI_CTRL0_ADDRESS_NAND_DATA |
+ (1);
+ d->address = (dma_addr_t)databuf;
+
+ /* Compile DMA descriptor - de-assert the NAND lock and interrupt. */
+ d = &info->desc[descnum++];
+ d->data = DMACMD_IRQ | DMACMD_DEC_SEM;
+
+ /* Execute the DMA chain. */
+ ret = mxs_dma_run(info->dma_channel, info->desc, descnum);
+ if (ret) {
+ pr_err("DMA read error\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static int mxs_nand_reset(struct mxs_nand_info *info, void *databuf)
+{
+ int ret, i;
+ u8 *cmd_buf;
+ struct mxs_dma_cmd *d;
+ int descnum = 0;
+ int cmd_queue_len;
+ u8 read_status;
+
+ memset(info->desc, 0,
+ sizeof(*info->desc) * MXS_NAND_DMA_DESCRIPTOR_COUNT);
+
+ /* Compile DMA descriptor - RESET */
+ cmd_buf = info->cmd_buf;
+ cmd_queue_len = 0;
+ d = &info->desc[descnum++];
+ d->address = (dma_addr_t)(cmd_buf);
+ cmd_buf[cmd_queue_len++] = NAND_CMD_RESET;
+
+ d->data = DMACMD_COMMAND_DMA_READ |
+ DMACMD_WAIT4END |
+ DMACMD_PIO_WORDS(1) |
+ DMACMD_XFER_COUNT(cmd_queue_len);
+
+ d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WRITE |
+ GPMI_CTRL0_WORD_LENGTH |
+ GPMI_CTRL0_CS(info->cs) |
+ GPMI_CTRL0_ADDRESS_NAND_CLE |
+ GPMI_CTRL0_ADDRESS_INCREMENT |
+ cmd_queue_len;
+
+ /* Compile DMA descriptor - de-assert the NAND lock and interrupt. */
+ d = &info->desc[descnum++];
+ d->data = DMACMD_IRQ | DMACMD_DEC_SEM;
+
+ /* Execute the DMA chain. */
+ ret = mxs_dma_run(info->dma_channel, info->desc, descnum);
+ if (ret) {
+ pr_err("DMA read error\n");
+ return ret;
+ }
+
+ /* Wait for NAND to wake up */
+ for (i = 0; i < 10; i++) {
+ __udelay(50000);
+ ret = mxs_nand_get_read_status(info, databuf);
+ if (ret)
+ return ret;
+ memcpy(&read_status, databuf, 1);
+ if (read_status & NAND_STATUS_READY)
+ return 0;
+ }
+
+ pr_warn("NAND Reset failed\n");
+ return -1;
+}
+
+/* function taken from nand_onfi.c */
+static u16 onfi_crc16(u16 crc, u8 const *p, size_t len)
+{
+ int i;
+
+ while (len--) {
+ crc ^= *p++ << 8;
+ for (i = 0; i < 8; i++)
+ crc = (crc << 1) ^ ((crc & 0x8000) ? 0x8005 : 0);
+ }
+ return crc;
+}
+
+static int mxs_nand_get_onfi(struct mxs_nand_info *info, void *databuf)
+{
+ int ret;
+ u8 *cmd_buf;
+ u16 crc;
+ struct mxs_dma_cmd *d;
+ int descnum = 0;
+ int cmd_queue_len;
+ struct nand_onfi_params nand_params;
+
+ memset(info->desc, 0,
+ sizeof(*info->desc) * MXS_NAND_DMA_DESCRIPTOR_COUNT);
+
+ /* Compile DMA descriptor - READ PARAMETER PAGE */
+ cmd_buf = info->cmd_buf;
+ cmd_queue_len = 0;
+ d = &info->desc[descnum++];
+ d->address = (dma_addr_t)(cmd_buf);
+ cmd_buf[cmd_queue_len++] = NAND_CMD_PARAM;
+ cmd_buf[cmd_queue_len++] = 0x00;
+
+ d->data = DMACMD_COMMAND_DMA_READ |
+ DMACMD_WAIT4END |
+ DMACMD_PIO_WORDS(1) |
+ DMACMD_XFER_COUNT(cmd_queue_len);
+
+ d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WRITE |
+ GPMI_CTRL0_WORD_LENGTH |
+ GPMI_CTRL0_CS(info->cs) |
+ GPMI_CTRL0_ADDRESS_NAND_CLE |
+ GPMI_CTRL0_ADDRESS_INCREMENT |
+ cmd_queue_len;
+
+ /* Compile DMA descriptor - wait for ready. */
+ d = &info->desc[descnum++];
+ d->data = DMACMD_CHAIN |
+ DMACMD_NAND_WAIT_4_READY |
+ DMACMD_WAIT4END |
+ DMACMD_PIO_WORDS(2);
+
+ d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY |
+ GPMI_CTRL0_WORD_LENGTH |
+ GPMI_CTRL0_CS(info->cs) |
+ GPMI_CTRL0_ADDRESS_NAND_DATA;
+
+ /* Compile DMA descriptor - read. */
+ d = &info->desc[descnum++];
+ d->data = DMACMD_WAIT4END |
+ DMACMD_PIO_WORDS(1) |
+ DMACMD_XFER_COUNT(sizeof(struct nand_onfi_params)) |
+ DMACMD_COMMAND_DMA_WRITE;
+ d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_READ |
+ GPMI_CTRL0_WORD_LENGTH |
+ GPMI_CTRL0_CS(info->cs) |
+ GPMI_CTRL0_ADDRESS_NAND_DATA |
+ (sizeof(struct nand_onfi_params));
+ d->address = (dma_addr_t)databuf;
+
+ /* Compile DMA descriptor - de-assert the NAND lock and interrupt. */
+ d = &info->desc[descnum++];
+ d->data = DMACMD_IRQ | DMACMD_DEC_SEM;
+
+ /* Execute the DMA chain. */
+ ret = mxs_dma_run(info->dma_channel, info->desc, descnum);
+ if (ret) {
+ pr_err("DMA read error\n");
+ return ret;
+ }
+
+ memcpy(&nand_params, databuf, sizeof(struct nand_onfi_params));
+
+ crc = onfi_crc16(NAND_ONFI_CRC_BASE, (u8 *)&nand_params, 254);
+ pr_debug("ONFI CRC: 0x%x, CALC. CRC 0x%x\n", nand_params.crc, crc);
+ if (crc != le16_to_cpu(nand_params.crc)) {
+ pr_debug("ONFI CRC mismatch!\n");
+ ret = -EUCLEAN;
+ return ret;
+ }
+
+ /* Fill the NAND organization struct with data */
+ info->organization.bits_per_cell = nand_params.bits_per_cell;
+ info->organization.pagesize = le32_to_cpu(nand_params.byte_per_page);
+ info->organization.oobsize =
+ le16_to_cpu(nand_params.spare_bytes_per_page);
+ info->organization.pages_per_eraseblock =
+ le32_to_cpu(nand_params.pages_per_block);
+ info->organization.eraseblocks_per_lun =
+ le32_to_cpu(nand_params.blocks_per_lun);
+ info->organization.max_bad_eraseblocks_per_lun =
+ le16_to_cpu(nand_params.bb_per_lun);
+ info->organization.luns_per_target = nand_params.lun_count;
+ info->nand_size = info->organization.pagesize *
+ info->organization.pages_per_eraseblock *
+ info->organization.eraseblocks_per_lun *
+ info->organization.luns_per_target;
+
+ return ret;
+}
+
+static int mxs_nand_check_onfi(struct mxs_nand_info *info, void *databuf)
+{
+ int ret;
+ u8 *cmd_buf;
+ struct mxs_dma_cmd *d;
+ int descnum = 0;
+ int cmd_queue_len;
+
+ struct onfi_header {
+ u8 byte0;
+ u8 byte1;
+ u8 byte2;
+ u8 byte3;
+ } onfi_head;
+
+ memset(info->desc, 0,
+ sizeof(*info->desc) * MXS_NAND_DMA_DESCRIPTOR_COUNT);
+
+ /* Compile DMA descriptor - READID + 0x20 (ADDR) */
+ cmd_buf = info->cmd_buf;
+ cmd_queue_len = 0;
+ d = &info->desc[descnum++];
+ d->address = (dma_addr_t)(cmd_buf);
+ cmd_buf[cmd_queue_len++] = NAND_CMD_READID;
+ cmd_buf[cmd_queue_len++] = 0x20;
+
+ d->data = DMACMD_COMMAND_DMA_READ |
+ DMACMD_WAIT4END |
+ DMACMD_PIO_WORDS(1) |
+ DMACMD_XFER_COUNT(cmd_queue_len);
+
+ d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WRITE |
+ GPMI_CTRL0_WORD_LENGTH |
+ GPMI_CTRL0_CS(info->cs) |
+ GPMI_CTRL0_ADDRESS_NAND_CLE |
+ GPMI_CTRL0_ADDRESS_INCREMENT |
+ cmd_queue_len;
+
+ /* Compile DMA descriptor - read. */
+ d = &info->desc[descnum++];
+ d->data = DMACMD_WAIT4END |
+ DMACMD_PIO_WORDS(1) |
+ DMACMD_XFER_COUNT(sizeof(struct onfi_header)) |
+ DMACMD_COMMAND_DMA_WRITE;
+ d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_READ |
+ GPMI_CTRL0_WORD_LENGTH |
+ GPMI_CTRL0_CS(info->cs) |
+ GPMI_CTRL0_ADDRESS_NAND_DATA |
+ (sizeof(struct onfi_header));
+ d->address = (dma_addr_t)databuf;
+
+ /* Compile DMA descriptor - de-assert the NAND lock and interrupt. */
+ d = &info->desc[descnum++];
+ d->data = DMACMD_IRQ | DMACMD_DEC_SEM;
+
+ /* Execute the DMA chain. */
+ ret = mxs_dma_run(info->dma_channel, info->desc, descnum);
+ if (ret) {
+ pr_err("DMA read error\n");
+ return ret;
+ }
+
+ memcpy(&onfi_head, databuf, sizeof(struct onfi_header));
+
+ pr_debug("ONFI Byte0: 0x%x\n", onfi_head.byte0);
+ pr_debug("ONFI Byte1: 0x%x\n", onfi_head.byte1);
+ pr_debug("ONFI Byte2: 0x%x\n", onfi_head.byte2);
+ pr_debug("ONFI Byte3: 0x%x\n", onfi_head.byte3);
+
+ /* check if returned values correspond to ascii characters "ONFI" */
+ if (onfi_head.byte0 != 0x4f || onfi_head.byte1 != 0x4e ||
+ onfi_head.byte2 != 0x46 || onfi_head.byte3 != 0x49)
+ return 1;
+
+ return 0;
+}
+
+static int mxs_nand_get_readid(struct mxs_nand_info *info, void *databuf)
+{
+ int ret;
+ u8 *cmd_buf;
+ struct mxs_dma_cmd *d;
+ int descnum = 0;
+ int cmd_queue_len;
+
+ struct readid_data {
+ u8 byte0;
+ u8 byte1;
+ u8 byte2;
+ u8 byte3;
+ u8 byte4;
+ } id_data;
+
+ memset(info->desc, 0,
+ sizeof(*info->desc) * MXS_NAND_DMA_DESCRIPTOR_COUNT);
+
+ /* Compile DMA descriptor - READID */
+ cmd_buf = info->cmd_buf;
+ cmd_queue_len = 0;
+ d = &info->desc[descnum++];
+ d->address = (dma_addr_t)(cmd_buf);
+ cmd_buf[cmd_queue_len++] = NAND_CMD_READID;
+ cmd_buf[cmd_queue_len++] = 0x00;
+
+ d->data = DMACMD_COMMAND_DMA_READ |
+ DMACMD_WAIT4END |
+ DMACMD_PIO_WORDS(1) |
+ DMACMD_XFER_COUNT(cmd_queue_len);
+
+ d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WRITE |
+ GPMI_CTRL0_WORD_LENGTH |
+ GPMI_CTRL0_CS(info->cs) |
+ GPMI_CTRL0_ADDRESS_NAND_CLE |
+ GPMI_CTRL0_ADDRESS_INCREMENT |
+ cmd_queue_len;
+
+ /* Compile DMA descriptor - read. */
+ d = &info->desc[descnum++];
+ d->data = DMACMD_WAIT4END |
+ DMACMD_PIO_WORDS(1) |
+ DMACMD_XFER_COUNT(sizeof(struct readid_data)) |
+ DMACMD_COMMAND_DMA_WRITE;
+ d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_READ |
+ GPMI_CTRL0_WORD_LENGTH |
+ GPMI_CTRL0_CS(info->cs) |
+ GPMI_CTRL0_ADDRESS_NAND_DATA |
+ (sizeof(struct readid_data));
+ d->address = (dma_addr_t)databuf;
+
+ /* Compile DMA descriptor - de-assert the NAND lock and interrupt. */
+ d = &info->desc[descnum++];
+ d->data = DMACMD_IRQ | DMACMD_DEC_SEM;
+
+ /* Execute the DMA chain. */
+ ret = mxs_dma_run(info->dma_channel, info->desc, descnum);
+ if (ret) {
+ pr_err("DMA read error\n");
+ return ret;
+ }
+
+ memcpy(&id_data, databuf, sizeof(struct readid_data));
+
+ pr_debug("NAND Byte0: 0x%x\n", id_data.byte0);
+ pr_debug("NAND Byte1: 0x%x\n", id_data.byte1);
+ pr_debug("NAND Byte2: 0x%x\n", id_data.byte2);
+ pr_debug("NAND Byte3: 0x%x\n", id_data.byte3);
+ pr_debug("NAND Byte4: 0x%x\n", id_data.byte4);
+
+ if (id_data.byte0 == 0xff || id_data.byte1 == 0xff ||
+ id_data.byte2 == 0xff || id_data.byte3 == 0xff ||
+ id_data.byte4 == 0xff) {
+ pr_err("\"READ ID\" returned 0xff, possible error!\n");
+ return -EOVERFLOW;
+ }
+
+ /* Fill the NAND organization struct with data */
+ info->organization.bits_per_cell =
+ (1 << ((id_data.byte2 >> 2) & 0x3)) * 2;
+ info->organization.pagesize =
+ (1 << (id_data.byte3 & 0x3)) * SZ_1K;
+ info->organization.oobsize = id_data.byte3 & 0x4 ?
+ info->organization.pagesize / 512 * 16 :
+ info->organization.pagesize / 512 * 8;
+ info->organization.pages_per_eraseblock =
+ (1 << ((id_data.byte3 >> 4) & 0x3)) * SZ_64K /
+ info->organization.pagesize;
+ info->organization.planes_per_lun =
+ 1 << ((id_data.byte4 >> 2) & 0x3);
+ info->nand_size = info->organization.planes_per_lun *
+ (1 << ((id_data.byte4 >> 4) & 0x7)) * SZ_8M;
+ info->organization.eraseblocks_per_lun = info->nand_size /
+ (info->organization.pages_per_eraseblock *
+ info->organization.pagesize);
+
+ return ret;
+}
+
+static int mxs_nand_get_info(struct mxs_nand_info *info, void *databuf)
+{
+ int ret, i;
+
+ ret = mxs_nand_check_onfi(info, databuf);
+ if (ret) {
+ if (ret != 1)
+ return ret;
+ pr_info("ONFI not supported, try \"READ ID\"...\n");
+ } else {
+ /*
+ * Some NAND's don't return the correct data the first time
+ * "READ PARAMETER PAGE" is returned. Execute the command
+ * multimple times
+ */
+ for (i = 0; i < 3; i++) {
+ /*
+ * Some NAND's need to be reset before "READ PARAMETER
+ * PAGE" can be successfully executed.
+ */
+ ret = mxs_nand_reset(info, databuf);
+ if (ret)
+ return ret;
+ ret = mxs_nand_get_onfi(info, databuf);
+ if (ret)
+ pr_err("ONFI error: %d\n", ret);
+ else
+ break;
+ }
+ if (!ret)
+ goto succ;
+ }
+
+ /*
+ * If ONFI is not supported or if it fails try to get NAND's info from
+ * "READ ID" command.
+ */
+ ret = mxs_nand_reset(info, databuf);
+ if (ret)
+ return ret;
+ pr_debug("Trying \"READ ID\" command...\n");
+ ret = mxs_nand_get_readid(info, databuf);
+ if (ret) {
+ pr_err("xloader supports only ONFI and generic \"READ ID\" " \
+ "supported NANDs\n");
+ return -1;
+ }
+succ:
+ pr_debug("NAND page_size: %d\n", info->organization.pagesize);
+ pr_debug("NAND block_size: %d\n",
+ info->organization.pages_per_eraseblock
+ * info->organization.pagesize);
+ pr_debug("NAND oob_size: %d\n", info->organization.oobsize);
+ pr_debug("NAND nand_size: %lu\n", info->nand_size);
+ pr_debug("NAND bits_per_cell: %d\n", info->organization.bits_per_cell);
+ pr_debug("NAND planes_per_lun: %d\n",
+ info->organization.planes_per_lun);
+ pr_debug("NAND luns_per_target: %d\n",
+ info->organization.luns_per_target);
+ pr_debug("NAND eraseblocks_per_lun: %d\n",
+ info->organization.eraseblocks_per_lun);
+ pr_debug("NAND ntargets: %d\n", info->organization.ntargets);
+
+
+ return 0;
+}
+
+/* ---------------------------- BCB handling part -------------------------- */
+
+static uint32_t calc_chksum(void *buf, size_t size)
+{
+ u32 chksum = 0;
+ u8 *bp = buf;
+ size_t i;
+
+ for (i = 0; i < size; i++)
+ chksum += bp[i];
+
+ return ~chksum;
+}
+
+static int get_fcb(struct mxs_nand_info *info, void *databuf)
+{
+ int i, pagenum, ret;
+ uint32_t checksum;
+ struct fcb_block *fcb = &info->fcb;
+
+ /* First page read fails, this shouldn't be necessary */
+ mxs_nand_read_page(info, info->organization.pagesize,
+ info->organization.oobsize, 0, databuf, 1);
+
+ for (i = 0; i < 4; i++) {
+ pagenum = info->organization.pages_per_eraseblock * i;
+
+ ret = mxs_nand_read_page(info, info->organization.pagesize,
+ info->organization.oobsize, pagenum, databuf, 1);
+ if (ret)
+ continue;
+
+ memcpy(fcb, databuf + mxs_nand_aux_status_offset(),
+ sizeof(*fcb));
+
+ if (fcb->FingerPrint != FCB_FINGERPRINT) {
+ pr_err("No FCB found on page %d\n", pagenum);
+ continue;
+ }
+
+ checksum = calc_chksum((void *)fcb +
+ sizeof(uint32_t), sizeof(*fcb) - sizeof(uint32_t));
+
+ if (checksum != fcb->Checksum) {
+ pr_err("FCB on page %d has invalid checksum. " \
+ "Expected: 0x%08x, calculated: 0x%08x",
+ pagenum, fcb->Checksum, checksum);
+ continue;
+ }
+
+ pr_debug("Found FCB:\n");
+ pr_debug("PageDataSize: 0x%08x\n", fcb->PageDataSize);
+ pr_debug("TotalPageSize: 0x%08x\n", fcb->TotalPageSize);
+ pr_debug("SectorsPerBlock: 0x%08x\n", fcb->SectorsPerBlock);
+ pr_debug("FW1_startingPage: 0x%08x\n",
+ fcb->Firmware1_startingPage);
+ pr_debug("PagesInFW1: 0x%08x\n", fcb->PagesInFirmware1);
+ pr_debug("FW2_startingPage: 0x%08x\n",
+ fcb->Firmware2_startingPage);
+ pr_debug("PagesInFW2: 0x%08x\n", fcb->PagesInFirmware2);
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int get_dbbt(struct mxs_nand_info *info, void *databuf)
+{
+ int i, ret;
+ int page;
+ int startpage = info->fcb.DBBTSearchAreaStartAddress;
+ struct dbbt_block dbbt;
+
+ for (i = 0; i < 4; i++) {
+ page = startpage + i * info->organization.pages_per_eraseblock;
+
+ ret = mxs_nand_read_page(info, info->organization.pagesize,
+ info->organization.oobsize, page, databuf, 0);
+ if (ret)
+ continue;
+
+ memcpy(&dbbt, databuf, sizeof(struct dbbt_block));
+
+ if (*(u32 *)(databuf + sizeof(u32)) != DBBT_FINGERPRINT)
+ continue;
+
+ /* Version check */
+ if (be32_to_cpup(databuf + 2 * sizeof(u32)) < 1)
+ return -ENOENT;
+
+ ret = mxs_nand_read_page(info, info->organization.pagesize,
+ info->organization.oobsize, page + 4, databuf, 0);
+ if (ret)
+ continue;
+
+ info->dbbt_num_entries = *(u32 *)(databuf + sizeof(u32));
+
+ pr_debug("Found DBBT with %d entries\n",
+ info->dbbt_num_entries);
+ pr_debug("Checksum = 0x%08x\n", dbbt.Checksum);
+ pr_debug("FingerPrint = 0x%08x\n", dbbt.FingerPrint);
+ pr_debug("Version = 0x%08x\n", dbbt.Version);
+ pr_debug("numberBB = 0x%08x\n", dbbt.numberBB);
+ pr_debug("DBBTNumOfPages= 0x%08x\n", dbbt.DBBTNumOfPages);
+
+ for (i = 0; i < info->dbbt_num_entries; i++)
+ pr_debug("badblock %d at block %d\n", i,
+ *(u32 *)(databuf + (2 + i) * sizeof(u32)));
+
+ info->dbbt = databuf + 2 * sizeof(u32);
+
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+static int block_is_bad(struct mxs_nand_info *info, int blocknum)
+{
+ int i;
+ u32 *dbbt = info->dbbt;
+
+ if (!dbbt)
+ return 0;
+
+ for (i = 0; i < info->dbbt_num_entries; i++) {
+ if (dbbt[i] == blocknum)
+ return 1;
+ }
+
+ return 0;
+}
+
+static int read_firmware(struct mxs_nand_info *info, int startpage,
+ void *dest, int len)
+{
+ int curpage = startpage;
+ struct fcb_block *fcb = &info->fcb;
+ int pagesperblock = fcb->SectorsPerBlock;
+ int numpages = (len / fcb->PageDataSize) + 1;
+ int ret;
+ int pagesize = fcb->PageDataSize;
+ int oobsize = fcb->TotalPageSize - pagesize;
+
+ pr_debug("Reading %d pages starting from page %d\n",
+ numpages, startpage);
+
+ if (block_is_bad(info, curpage / pagesperblock))
+ curpage = ALIGN_DOWN(curpage + pagesperblock, pagesperblock);
+
+ while (numpages) {
+ if (!(curpage & (pagesperblock - 1))) {
+ /* Check for bad blocks on each block boundary */
+ if (block_is_bad(info, curpage / pagesperblock)) {
+ pr_debug("Skipping bad block at page %d\n",
+ curpage);
+ curpage += pagesperblock;
+ continue;
+ }
+ }
+
+ ret = mxs_nand_read_page(info, pagesize, oobsize,
+ curpage, dest, 0);
+ if (ret) {
+ pr_debug("Failed to read page %d\n", curpage);
+ return ret;
+ }
+
+ *((u8 *)dest + fcb->BadBlockMarkerByte) =
+ *(u8 *)(dest + pagesize);
+
+ numpages--;
+ dest += pagesize;
+ curpage++;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused imx6_nand_load_image(void *cmdbuf, void *descs,
+ void *databuf, void *dest, int len)
+{
+ struct mxs_nand_info info = {
+ .io_base = (void *)0x00112000,
+ .bch_base = (void *)0x00114000,
+ };
+ struct apbh_dma apbh = {
+ .id = IMX28_DMA,
+ .regs = (void *)0x00110000,
+ };
+ struct mxs_dma_chan pchan = {
+ .channel = 0, /* MXS: MXS_DMA_CHANNEL_AHB_APBH_GPMI0 */
+ .apbh = &apbh,
+ };
+ int ret;
+ struct fcb_block *fcb;
+
+ info.dma_channel = &pchan;
+
+ pr_debug("cmdbuf: 0x%p descs: 0x%p databuf: 0x%p dest: 0x%p\n",
+ cmdbuf, descs, databuf, dest);
+
+ /* Command buffers */
+ info.cmd_buf = cmdbuf;
+ info.desc = descs;
+
+ ret = mxs_nand_get_info(&info, databuf);
+ if (ret)
+ return ret;
+
+ ret = get_fcb(&info, databuf);
+ if (ret)
+ return ret;
+
+ fcb = &info.fcb;
+
+ get_dbbt(&info, databuf);
+
+ ret = read_firmware(&info, fcb->Firmware1_startingPage, dest, len);
+ if (ret) {
+ pr_err("Failed to read firmware1, trying firmware2\n");
+ ret = read_firmware(&info, fcb->Firmware2_startingPage,
+ dest, len);
+ if (ret) {
+ pr_err("Failed to also read firmware2\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+int imx6_nand_start_image(void)
+{
+ int ret;
+ void *sdram = (void *)0x10000000;
+ void __noreturn (*bb)(void);
+ void *cmdbuf, *databuf, *descs;
+
+ cmdbuf = sdram;
+ descs = sdram + MXS_NAND_COMMAND_BUFFER_SIZE;
+ databuf = descs +
+ sizeof(struct mxs_dma_cmd) * MXS_NAND_DMA_DESCRIPTOR_COUNT;
+ bb = (void *)PAGE_ALIGN((unsigned long)databuf + SZ_8K);
+
+ ret = imx6_nand_load_image(cmdbuf, descs, databuf,
+ bb, imx_image_size());
+ if (ret) {
+ pr_err("Loading image failed: %d\n", ret);
+ return ret;
+ }
+
+ pr_debug("Starting barebox image at 0x%p\n", bb);
+
+ arm_early_mmu_cache_invalidate();
+ barrier();
+
+ bb();
+}