diff --git a/arch/arm/mach-mxs/Kconfig b/arch/arm/mach-mxs/Kconfig index 9ce7c7a1b..746c98608 100644 --- a/arch/arm/mach-mxs/Kconfig +++ b/arch/arm/mach-mxs/Kconfig @@ -92,6 +92,14 @@ config MXS_OCOTP_WRITABLE enable writing: ocotp0.permanent_write_enable=1 +config MXS_CMD_BCB + depends on NAND_MXS + tristate "Nand bcb command" + help + To be able to boot from NAND the i.MX23/28 need a Boot Control Block + in flash. This option enabled the 'bcb' command which can be used to + generate this block during runtime. + endmenu menu "Board specific settings " diff --git a/arch/arm/mach-mxs/Makefile b/arch/arm/mach-mxs/Makefile index 9a42a5f17..fe93096da 100644 --- a/arch/arm/mach-mxs/Makefile +++ b/arch/arm/mach-mxs/Makefile @@ -1,5 +1,6 @@ -obj-y += imx.o iomux-imx.o power.o +obj-y += imx.o iomux-imx.o power.o common.o obj-$(CONFIG_DRIVER_VIDEO_STM) += imx_lcd_clk.o obj-$(CONFIG_ARCH_IMX23) += speed-imx23.o clocksource-imx23.o usb-imx23.o soc-imx23.o obj-$(CONFIG_ARCH_IMX28) += speed-imx28.o clocksource-imx28.o usb-imx28.o soc-imx28.o obj-$(CONFIG_MXS_OCOTP) += ocotp.o +obj-$(CONFIG_MXS_CMD_BCB) += bcb.o diff --git a/arch/arm/mach-mxs/bcb.c b/arch/arm/mach-mxs/bcb.c new file mode 100644 index 000000000..d0a3ddc8d --- /dev/null +++ b/arch/arm/mach-mxs/bcb.c @@ -0,0 +1,399 @@ +/* + * (C) Copyright 2011 Wolfram Sang, Pengutronix e.K. + * + * 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; either version 2 of + * the License, or (at your option) any later version. + * + * Based on a similar function in Karo Electronics TX28-U-Boot (flash.c). + * Probably written by Lothar Waßmann (like tx28.c). + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#define FCB_START_BLOCK 0 +#define NUM_FCB_BLOCKS 1 +#define MAX_FCB_BLOCKS 32768 + +#define GPMI_TIMING0 0x00000070 +#define GPMI_TIMING0_ADDRESS_SETUP_MASK (0xff << 16) +#define GPMI_TIMING0_ADDRESS_SETUP_OFFSET 16 +#define GPMI_TIMING0_DATA_HOLD_MASK (0xff << 8) +#define GPMI_TIMING0_DATA_HOLD_OFFSET 8 +#define GPMI_TIMING0_DATA_SETUP_MASK 0xff +#define GPMI_TIMING0_DATA_SETUP_OFFSET 0 + +#define GPMI_TIMING1 0x00000080 + +#define BCH_MODE 0x00000020 + +#define BCH_FLASH0LAYOUT0 0x00000080 +#define BCH_FLASHLAYOUT0_NBLOCKS_MASK (0xff << 24) +#define BCH_FLASHLAYOUT0_NBLOCKS_OFFSET 24 +#define BCH_FLASHLAYOUT0_META_SIZE_MASK (0xff << 16) +#define BCH_FLASHLAYOUT0_META_SIZE_OFFSET 16 +#define BCH_FLASHLAYOUT0_ECC0_MASK (0xf << 12) +#define BCH_FLASHLAYOUT0_ECC0_OFFSET 12 +#define BCH_FLASHLAYOUT0_DATA0_SIZE_MASK 0xfff +#define BCH_FLASHLAYOUT0_DATA0_SIZE_OFFSET 0 + +#define BCH_FLASH0LAYOUT1 0x00000090 +#define BCH_FLASHLAYOUT1_PAGE_SIZE_MASK (0xffff << 16) +#define BCH_FLASHLAYOUT1_PAGE_SIZE_OFFSET 16 +#define BCH_FLASHLAYOUT1_ECCN_MASK (0xf << 12) +#define BCH_FLASHLAYOUT1_ECCN_OFFSET 12 +#define BCH_FLASHLAYOUT1_DATAN_SIZE_MASK 0xfff +#define BCH_FLASHLAYOUT1_DATAN_SIZE_OFFSET 0 + +struct mx28_nand_timing { + u8 data_setup; + u8 data_hold; + u8 address_setup; + u8 dsample_time; + u8 nand_timing_state; + u8 tREA; + u8 tRLOH; + u8 tRHOH; +}; + +struct mx28_fcb { + u32 checksum; + u32 fingerprint; + u32 version; + struct mx28_nand_timing timing; + u32 page_data_size; + u32 total_page_size; + u32 sectors_per_block; + u32 number_of_nands; /* not used by ROM code */ + u32 total_internal_die; /* not used by ROM code */ + u32 cell_type; /* not used by ROM code */ + u32 ecc_blockn_type; + u32 ecc_block0_size; + u32 ecc_blockn_size; + u32 ecc_block0_type; + u32 metadata_size; + u32 ecc_blocks_per_page; + u32 rsrvd[6]; /* not used by ROM code */ + u32 bch_mode; + u32 boot_patch; + u32 patch_sectors; + u32 fw1_start_page; + u32 fw2_start_page; + u32 fw1_sectors; + u32 fw2_sectors; + u32 dbbt_search_area; + u32 bb_mark_byte; + u32 bb_mark_startbit; + u32 bb_mark_phys_offset; +}; + +struct mx28_dbbt_header { + u32 checksum; + u32 fingerprint; + u32 version; + u32 number_bb; + u32 number_pages; + u8 spare[492]; +}; + +struct mx28_dbbt { + u32 nand_number; + u32 number_bb; + u32 bb_num[2040 / 4]; +}; + +#define BF_VAL(v, bf) (((v) & bf##_MASK) >> bf##_OFFSET) +#define GETBIT(v,n) (((v) >> (n)) & 0x1) + +static u8 calculate_parity_13_8(u8 d) +{ + u8 p = 0; + + p |= (GETBIT(d, 6) ^ GETBIT(d, 5) ^ GETBIT(d, 3) ^ GETBIT(d, 2)) << 0; + p |= (GETBIT(d, 7) ^ GETBIT(d, 5) ^ GETBIT(d, 4) ^ GETBIT(d, 2) ^ GETBIT(d, 1)) << 1; + p |= (GETBIT(d, 7) ^ GETBIT(d, 6) ^ GETBIT(d, 5) ^ GETBIT(d, 1) ^ GETBIT(d, 0)) << 2; + p |= (GETBIT(d, 7) ^ GETBIT(d, 4) ^ GETBIT(d, 3) ^ GETBIT(d, 0)) << 3; + p |= (GETBIT(d, 6) ^ GETBIT(d, 4) ^ GETBIT(d, 3) ^ GETBIT(d, 2) ^ GETBIT(d, 1) ^ GETBIT(d, 0)) << 4; + return p; +} + +static void encode_hamming_13_8(void *_src, void *_ecc, size_t size) +{ + int i; + u8 *src = _src; + u8 *ecc = _ecc; + + for (i = 0; i < size; i++) + ecc[i] = calculate_parity_13_8(src[i]); +} + +static u32 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; +} + +/* + Physical organisation of data in NAND flash: + metadata + payload chunk 0 (may be empty) + ecc for metadata + payload chunk 0 + payload chunk 1 + ecc for payload chunk 1 +... + payload chunk n + ecc for payload chunk n + */ + +static int calc_bb_offset(struct mtd_info *mtd, struct mx28_fcb *fcb) +{ + int bb_mark_offset; + int chunk_data_size = fcb->ecc_blockn_size * 8; + int chunk_ecc_size = (fcb->ecc_blockn_type << 1) * 13; + int chunk_total_size = chunk_data_size + chunk_ecc_size; + int bb_mark_chunk, bb_mark_chunk_offs; + + bb_mark_offset = (mtd->writesize - fcb->metadata_size) * 8; + if (fcb->ecc_block0_size == 0) + bb_mark_offset -= (fcb->ecc_block0_type << 1) * 13; + + bb_mark_chunk = bb_mark_offset / chunk_total_size; + bb_mark_chunk_offs = bb_mark_offset - (bb_mark_chunk * chunk_total_size); + if (bb_mark_chunk_offs > chunk_data_size) { + printf("Unsupported ECC layout; BB mark resides in ECC data: %u\n", + bb_mark_chunk_offs); + return -EINVAL; + } + bb_mark_offset -= bb_mark_chunk * chunk_ecc_size; + return bb_mark_offset; +} + +static struct mx28_fcb *create_fcb(struct mtd_info *mtd, void *buf, unsigned fw1_start_block, + size_t fw_size, unsigned fw2_start_block) +{ + u32 fl0, fl1, t0; + int metadata_size; + int bb_mark_bit_offs; + struct mx28_fcb *fcb; + int fcb_offs; + void __iomem *bch_regs = (void *)MXS_BCH_BASE; + void __iomem *gpmi_regs = (void *)MXS_GPMI_BASE; + + fl0 = readl(bch_regs + BCH_FLASH0LAYOUT0); + fl1 = readl(bch_regs + BCH_FLASH0LAYOUT1); + t0 = readl(gpmi_regs + GPMI_TIMING0); + metadata_size = BF_VAL(fl0, BCH_FLASHLAYOUT0_META_SIZE); + + fcb = buf + ALIGN(metadata_size, 4); + fcb_offs = (void *)fcb - buf; + + memset(buf, 0x00, fcb_offs); + memset(fcb, 0x00, sizeof(*fcb)); + memset(fcb + 1, 0xff, mtd->erasesize - fcb_offs - sizeof(*fcb)); + + strncpy((char *)&fcb->fingerprint, "FCB ", 4); + fcb->version = cpu_to_be32(1); + + fcb->timing.data_setup = BF_VAL(t0, GPMI_TIMING0_DATA_SETUP); + fcb->timing.data_hold = BF_VAL(t0, GPMI_TIMING0_DATA_HOLD); + fcb->timing.address_setup = BF_VAL(t0, GPMI_TIMING0_ADDRESS_SETUP); + + fcb->page_data_size = mtd->writesize; + fcb->total_page_size = mtd->writesize + mtd->oobsize; + fcb->sectors_per_block = mtd->erasesize / mtd->writesize; + + fcb->ecc_block0_type = BF_VAL(fl0, BCH_FLASHLAYOUT0_ECC0); + fcb->ecc_block0_size = BF_VAL(fl0, BCH_FLASHLAYOUT0_DATA0_SIZE); + fcb->ecc_blockn_type = BF_VAL(fl1, BCH_FLASHLAYOUT1_ECCN); + fcb->ecc_blockn_size = BF_VAL(fl1, BCH_FLASHLAYOUT1_DATAN_SIZE); + + fcb->metadata_size = BF_VAL(fl0, BCH_FLASHLAYOUT0_META_SIZE); + fcb->ecc_blocks_per_page = BF_VAL(fl0, BCH_FLASHLAYOUT0_NBLOCKS); + fcb->bch_mode = readl(bch_regs + BCH_MODE); +/* + fcb->boot_patch = 0; + fcb->patch_sectors = 0; +*/ + fcb->fw1_start_page = fw1_start_block / mtd->writesize; + fcb->fw1_sectors = DIV_ROUND_UP(fw_size, mtd->writesize); + + if (fw2_start_block != 0 && fw2_start_block < mtd->size / mtd->erasesize) { + fcb->fw2_start_page = fw2_start_block / mtd->writesize; + fcb->fw2_sectors = fcb->fw1_sectors; + } + + fcb->dbbt_search_area = 1; + + bb_mark_bit_offs = calc_bb_offset(mtd, fcb); + if (bb_mark_bit_offs < 0) + return ERR_PTR(bb_mark_bit_offs); + fcb->bb_mark_byte = bb_mark_bit_offs / 8; + fcb->bb_mark_startbit = bb_mark_bit_offs % 8; + fcb->bb_mark_phys_offset = mtd->writesize; + + fcb->checksum = calc_chksum(&fcb->fingerprint, 512 - 4); + return fcb; +} + +static int find_fcb(struct mtd_info *mtd, void *ref, int page) +{ + int ret = 0; + struct nand_chip *chip = mtd->priv; + void *buf = malloc(mtd->erasesize); + + if (buf == NULL) + return -ENOMEM; + + chip->select_chip(mtd, 0); + chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page); + ret = chip->ecc.read_page_raw(mtd, chip, buf); + if (ret) { + printf("Failed to read FCB from page %u: %d\n", page, ret); + return ret; + } + chip->select_chip(mtd, -1); + if (memcmp(buf, ref, mtd->writesize) == 0) { + printf("%s: Found FCB in page %u (%08x)\n", __func__, + page, page * mtd->writesize); + ret = 1; + } + free(buf); + return ret; +} + +static int write_fcb(struct mtd_info *mtd, void *buf, int block) +{ + int ret; + struct nand_chip *chip = mtd->priv; + int page = block / mtd->writesize; + struct erase_info erase_opts = { + .mtd = mtd, + .addr = block, + .len = mtd->erasesize, + .callback = NULL, + }; + + ret = find_fcb(mtd, buf, page); + if (ret > 0) { + printf("FCB at block %08x is up to date\n", block); + return 0; + } + + ret = mtd->erase(mtd, &erase_opts); + if (ret) { + printf("Failed to erase FCB block %08x\n", block); + return ret; + } + + printf("Writing FCB to block %08x\n", block); + chip->select_chip(mtd, 0); + ret = chip->write_page(mtd, chip, buf, page, 0, 1); + if (ret) { + printf("Failed to write FCB to block %08x: %d\n", block, ret); + } + chip->select_chip(mtd, -1); + return ret; +} + +int update_bcb(int argc, char *argv[]) +{ + int ret; + int block; + void *buf; + struct mx28_fcb *fcb; + struct cdev *tmp_cdev, *bcb_cdev, *firmware_cdev; + unsigned long fw2_offset = 0; + struct mtd_info *mtd; + unsigned fcb_written = 0; + + if (argc == 1) + return COMMAND_ERROR_USAGE; + + tmp_cdev = cdev_by_name("nand0"); + if (!tmp_cdev || !tmp_cdev->mtd) { + pr_err("%s: No NAND device!\n", __func__); + return -ENODEV; + } + + mtd = tmp_cdev->mtd; + + bcb_cdev = cdev_by_name("nand0.bcb"); + if (!bcb_cdev) { + pr_err("%s: No FCB device!\n", __func__); + return -ENODEV; + } + + firmware_cdev = cdev_by_name(argv[1]); + if (!firmware_cdev) { + pr_err("%s: Bootstream-Image not found!\n", __func__); + return -ENODEV; + } + + if (argc > 2) { + tmp_cdev = cdev_by_name(argv[2]); + if (!tmp_cdev) { + pr_err("%s: Redundant Bootstream-Image not found!\n", __func__); + return -ENODEV; + } + fw2_offset = tmp_cdev->offset; + } + + buf = malloc(mtd->erasesize); + if (!buf) + return -ENOMEM; + + fcb = create_fcb(mtd, buf, firmware_cdev->offset, firmware_cdev->size, fw2_offset); + if (IS_ERR(fcb)) { + printf("Failed to initialize FCB: %ld\n", PTR_ERR(fcb)); + return PTR_ERR(fcb); + } + encode_hamming_13_8(fcb, (void *)fcb + 512, 512); + + for (block = bcb_cdev->offset; block < bcb_cdev->offset + bcb_cdev->size / 2; + block += mtd->erasesize) { + + if (nand_isbad_bbt(mtd, block, false)) + continue; + + ret = write_fcb(mtd, buf, block); + if (ret) { + printf("Failed to write FCB to block %u\n", block); + return ret; + } + + fcb_written++; + } + + return fcb_written ? 0 : -ENOSPC; +} + +BAREBOX_CMD_HELP_START(bcb) +BAREBOX_CMD_HELP_USAGE("bcb [second_bootstream]\n") +BAREBOX_CMD_HELP_SHORT("Write a BCB to NAND flash which an MX23/28 needs to boot.\n") +BAREBOX_CMD_HELP_TEXT ("Example: bcb nand0.bootstream\n") +BAREBOX_CMD_HELP_END + +BAREBOX_CMD_START(bcb) + .cmd = update_bcb, + .usage = "Writes a MX23/28 BCB data structure to flash", + BAREBOX_CMD_HELP(cmd_bcb_help) +BAREBOX_CMD_END diff --git a/arch/arm/mach-mxs/common.c b/arch/arm/mach-mxs/common.c new file mode 100644 index 000000000..3730633c7 --- /dev/null +++ b/arch/arm/mach-mxs/common.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +#define MXS_BLOCK_SFTRST (1 << 31) +#define MXS_BLOCK_CLKGATE (1 << 30) + +int mxs_reset_block(void __iomem *reg, int just_enable) +{ + /* Clear SFTRST */ + writel(MXS_BLOCK_SFTRST, reg + BIT_CLR); + mdelay(1); + + /* Clear CLKGATE */ + writel(MXS_BLOCK_CLKGATE, reg + BIT_CLR); + + if (!just_enable) { + /* Set SFTRST */ + writel(MXS_BLOCK_SFTRST, reg + BIT_SET); + mdelay(1); + } + + /* Clear SFTRST */ + writel(MXS_BLOCK_SFTRST, reg + BIT_CLR); + mdelay(1); + + /* Clear CLKGATE */ + writel(MXS_BLOCK_CLKGATE, reg + BIT_CLR); + mdelay(1); + + return 0; +} diff --git a/arch/arm/mach-mxs/include/mach/clock-imx23.h b/arch/arm/mach-mxs/include/mach/clock-imx23.h index 4f20e09d3..650779278 100644 --- a/arch/arm/mach-mxs/include/mach/clock-imx23.h +++ b/arch/arm/mach-mxs/include/mach/clock-imx23.h @@ -25,5 +25,6 @@ unsigned imx_set_sspclk(unsigned, unsigned, int); unsigned imx_set_ioclk(unsigned); unsigned imx_set_lcdifclk(unsigned); unsigned imx_get_lcdifclk(void); +void imx_enable_nandclk(void); #endif /* MACH_CLOCK_IMX23_H */ diff --git a/arch/arm/mach-mxs/include/mach/clock-imx28.h b/arch/arm/mach-mxs/include/mach/clock-imx28.h index 613c97b91..0604f0a55 100644 --- a/arch/arm/mach-mxs/include/mach/clock-imx28.h +++ b/arch/arm/mach-mxs/include/mach/clock-imx28.h @@ -27,6 +27,7 @@ unsigned imx_set_lcdifclk(unsigned); unsigned imx_get_lcdifclk(void); unsigned imx_get_fecclk(void); void imx_enable_enetclk(void); +void imx_enable_nandclk(void); #endif /* MACH_CLOCK_IMX28_H */ diff --git a/arch/arm/mach-mxs/include/mach/dma.h b/arch/arm/mach-mxs/include/mach/dma.h new file mode 100644 index 000000000..52747e2fb --- /dev/null +++ b/arch/arm/mach-mxs/include/mach/dma.h @@ -0,0 +1,145 @@ +/* + * Freescale i.MX28 APBH DMA + * + * Copyright (C) 2011 Marek Vasut + * on behalf of DENX Software Engineering GmbH + * + * Based on code from LTIB: + * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __DMA_H__ +#define __DMA_H__ + +#include + +#ifndef CONFIG_ARCH_DMA_PIO_WORDS +#define DMA_PIO_WORDS 15 +#else +#define DMA_PIO_WORDS CONFIG_ARCH_DMA_PIO_WORDS +#endif + +#define MXS_DMA_ALIGNMENT 32 + +/* + * MXS DMA channels + */ +enum { + MXS_DMA_CHANNEL_AHB_APBH_SSP0 = 0, + MXS_DMA_CHANNEL_AHB_APBH_SSP1, + MXS_DMA_CHANNEL_AHB_APBH_SSP2, + MXS_DMA_CHANNEL_AHB_APBH_SSP3, + MXS_DMA_CHANNEL_AHB_APBH_GPMI0, + MXS_DMA_CHANNEL_AHB_APBH_GPMI1, + MXS_DMA_CHANNEL_AHB_APBH_GPMI2, + MXS_DMA_CHANNEL_AHB_APBH_GPMI3, + MXS_DMA_CHANNEL_AHB_APBH_GPMI4, + MXS_DMA_CHANNEL_AHB_APBH_GPMI5, + MXS_DMA_CHANNEL_AHB_APBH_GPMI6, + MXS_DMA_CHANNEL_AHB_APBH_GPMI7, + MXS_DMA_CHANNEL_AHB_APBH_SSP, + MXS_MAX_DMA_CHANNELS, +}; + +/* + * 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 MXS_DMA_DESC_COMMAND_MASK 0x3 +#define MXS_DMA_DESC_COMMAND_OFFSET 0 +#define MXS_DMA_DESC_COMMAND_NO_DMAXFER 0x0 +#define MXS_DMA_DESC_COMMAND_DMA_WRITE 0x1 +#define MXS_DMA_DESC_COMMAND_DMA_READ 0x2 +#define MXS_DMA_DESC_COMMAND_DMA_SENSE 0x3 +#define MXS_DMA_DESC_CHAIN (1 << 2) +#define MXS_DMA_DESC_IRQ (1 << 3) +#define MXS_DMA_DESC_NAND_LOCK (1 << 4) +#define MXS_DMA_DESC_NAND_WAIT_4_READY (1 << 5) +#define MXS_DMA_DESC_DEC_SEM (1 << 6) +#define MXS_DMA_DESC_WAIT4END (1 << 7) +#define MXS_DMA_DESC_HALT_ON_TERMINATE (1 << 8) +#define MXS_DMA_DESC_TERMINATE_FLUSH (1 << 9) +#define MXS_DMA_DESC_PIO_WORDS_MASK (0xf << 12) +#define MXS_DMA_DESC_PIO_WORDS_OFFSET 12 +#define MXS_DMA_DESC_BYTES_MASK (0xffff << 16) +#define MXS_DMA_DESC_BYTES_OFFSET 16 + +struct mxs_dma_cmd { + unsigned long next; + unsigned long data; + union { + dma_addr_t address; + unsigned long alternate; + }; + unsigned long pio_words[DMA_PIO_WORDS]; +}; + +/* + * MXS DMA command descriptor. + * + * This structure incorporates an MXS DMA hardware command structure, along + * with metadata. + */ +#define MXS_DMA_DESC_FIRST (1 << 0) +#define MXS_DMA_DESC_LAST (1 << 1) +#define MXS_DMA_DESC_READY (1 << 31) + +struct mxs_dma_desc { + struct mxs_dma_cmd cmd; + unsigned int flags; + dma_addr_t address; + void *buffer; + struct list_head node; +}; + +/** + * MXS DMA channel + * + * This structure represents a single DMA channel. The MXS platform code + * maintains an array of these structures to represent every DMA channel in the + * system (see mxs_dma_channels). + */ +#define MXS_DMA_FLAGS_IDLE 0 +#define MXS_DMA_FLAGS_BUSY (1 << 0) +#define MXS_DMA_FLAGS_FREE 0 +#define MXS_DMA_FLAGS_ALLOCATED (1 << 16) +#define MXS_DMA_FLAGS_VALID (1 << 31) + +struct mxs_dma_chan { + const char *name; + unsigned long dev; + struct mxs_dma_device *dma; + unsigned int flags; + unsigned int active_num; + unsigned int pending_num; + struct list_head active; + struct list_head done; +}; + +struct mxs_dma_desc *mxs_dma_desc_alloc(void); +void mxs_dma_desc_free(struct mxs_dma_desc *); +int mxs_dma_desc_append(int channel, struct mxs_dma_desc *pdesc); + +int mxs_dma_go(int chan); +int mxs_dma_init(void); + +#endif /* __DMA_H__ */ diff --git a/arch/arm/mach-mxs/include/mach/imx23-regs.h b/arch/arm/mach-mxs/include/mach/imx23-regs.h index 60f5bf9d6..7ea305751 100644 --- a/arch/arm/mach-mxs/include/mach/imx23-regs.h +++ b/arch/arm/mach-mxs/include/mach/imx23-regs.h @@ -27,6 +27,9 @@ #endif #define IMX_MEMORY_BASE 0x40000000 +#define MXS_APBH_BASE 0x80004000 +#define MXS_BCH_BASE 0x8000a000 +#define MXS_GPMI_BASE 0x8000c000 #define IMX_UART1_BASE 0x8006c000 #define IMX_UART2_BASE 0x8006e000 #define IMX_DBGUART_BASE 0x80070000 diff --git a/arch/arm/mach-mxs/include/mach/imx28-regs.h b/arch/arm/mach-mxs/include/mach/imx28-regs.h index 900990a38..16bf5f7a6 100644 --- a/arch/arm/mach-mxs/include/mach/imx28-regs.h +++ b/arch/arm/mach-mxs/include/mach/imx28-regs.h @@ -23,7 +23,9 @@ #define IMX_SRAM_BASE 0x00000000 #define IMX_MEMORY_BASE 0x40000000 -#define IMX_NFC_BASE 0x8000C000 +#define MXS_APBH_BASE 0x80004000 +#define MXS_BCH_BASE 0x8000a000 +#define MXS_GPMI_BASE 0x8000c000 #define IMX_SSP0_BASE 0x80010000 #define IMX_SSP1_BASE 0x80012000 #define IMX_SSP2_BASE 0x80014000 diff --git a/arch/arm/mach-mxs/include/mach/mxs.h b/arch/arm/mach-mxs/include/mach/mxs.h new file mode 100644 index 000000000..182ed8a99 --- /dev/null +++ b/arch/arm/mach-mxs/include/mach/mxs.h @@ -0,0 +1,6 @@ +#ifndef __MACH_MXS_H +#define __MACH_MXS_H + +int mxs_reset_block(void __iomem *reg, int just_enable); + +#endif /* __MACH_MXS_H */ diff --git a/arch/arm/mach-mxs/speed-imx23.c b/arch/arm/mach-mxs/speed-imx23.c index f41b9bc75..5d0c90dc8 100644 --- a/arch/arm/mach-mxs/speed-imx23.c +++ b/arch/arm/mach-mxs/speed-imx23.c @@ -47,6 +47,8 @@ # define GET_SSP_DIV(x) ((x) & CLKCTRL_SSP_DIV_MASK) # define SET_SSP_DIV(x) ((x) & CLKCTRL_SSP_DIV_MASK) #define HW_CLKCTRL_GPMI 0x080 +# define CLKCTRL_GPMI_CLKGATE (1 << 31) +# define CLKCTRL_GPMI_DIV_MASK 0x3ff /* note: no set/clear register! */ #define HW_CLKCTRL_SPDIF 0x090 /* note: no set/clear register! */ @@ -288,6 +290,23 @@ unsigned imx_set_sspclk(unsigned index, unsigned nc, int high) return imx_get_sspclk(index); } +void imx_enable_nandclk(void) +{ + uint32_t reg; + + /* Clear bypass bit; refman says clear, but fsl-code does set. Hooray! */ + writel(CLKCTRL_CLKSEQ_BYPASS_GPMI, + IMX_CCM_BASE + HW_CLKCTRL_CLKSEQ + BIT_SET); + + reg = readl(IMX_CCM_BASE + HW_CLKCTRL_GPMI) & ~CLKCTRL_GPMI_CLKGATE; + writel(reg, IMX_CCM_BASE + HW_CLKCTRL_GPMI); + udelay(1000); + /* Initialize DIV to 1 */ + reg &= ~CLKCTRL_GPMI_DIV_MASK; + reg |= 1; + writel(reg, IMX_CCM_BASE + HW_CLKCTRL_GPMI); +} + void imx_dump_clocks(void) { printf("mpll: %10u kHz\n", imx_get_mpllclk() / 1000); diff --git a/arch/arm/mach-mxs/speed-imx28.c b/arch/arm/mach-mxs/speed-imx28.c index 2641fb6fb..df55f64c2 100644 --- a/arch/arm/mach-mxs/speed-imx28.c +++ b/arch/arm/mach-mxs/speed-imx28.c @@ -48,6 +48,8 @@ # define GET_SSP_DIV(x) ((x) & CLKCTRL_SSP_DIV_MASK) # define SET_SSP_DIV(x) ((x) & CLKCTRL_SSP_DIV_MASK) #define HW_CLKCTRL_GPMI 0x0d0 +# define CLKCTRL_GPMI_CLKGATE (1 << 31) +# define CLKCTRL_GPMI_DIV_MASK 0x3ff /* note: no set/clear register! */ #define HW_CLKCTRL_SPDIF 0x0e0 /* note: no set/clear register! */ @@ -398,6 +400,23 @@ void imx_enable_enetclk(void) IMX_CCM_BASE + HW_CLKCTRL_ENET); } +void imx_enable_nandclk(void) +{ + uint32_t reg; + + /* Clear bypass bit; refman says clear, but fsl-code does set. Hooray! */ + writel(CLKCTRL_CLKSEQ_BYPASS_GPMI, + IMX_CCM_BASE + HW_CLKCTRL_CLKSEQ + BIT_SET); + + reg = readl(IMX_CCM_BASE + HW_CLKCTRL_GPMI) & ~CLKCTRL_GPMI_CLKGATE; + writel(reg, IMX_CCM_BASE + HW_CLKCTRL_GPMI); + udelay(1000); + /* Initialize DIV to 1 */ + reg &= ~CLKCTRL_GPMI_DIV_MASK; + reg |= 1; + writel(reg, IMX_CCM_BASE + HW_CLKCTRL_GPMI); +} + void imx_dump_clocks(void) { printf("mpll: %10u kHz\n", imx_get_mpllclk() / 1000); diff --git a/drivers/Kconfig b/drivers/Kconfig index 089dc3f99..883b0e7bc 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -17,5 +17,6 @@ source "drivers/eeprom/Kconfig" source "drivers/input/Kconfig" source "drivers/watchdog/Kconfig" source "drivers/pwm/Kconfig" +source "drivers/dma/Kconfig" endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 72c4058fe..ea3263f61 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -15,4 +15,5 @@ obj-$(CONFIG_LED) += led/ obj-y += eeprom/ obj-$(CONFIG_PWM) += pwm/ obj-y += input/ +obj-y += dma/ obj-y += watchdog/ diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig new file mode 100644 index 000000000..ec6c89477 --- /dev/null +++ b/drivers/dma/Kconfig @@ -0,0 +1,8 @@ +menu "DMA support" + +config MXS_APBH_DMA + tristate "MXS APBH DMA ENGINE" + depends on ARCH_IMX23 || ARCH_IMX28 + help + Experimental! +endmenu diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile new file mode 100644 index 000000000..7a3a3b2bd --- /dev/null +++ b/drivers/dma/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MXS_APBH_DMA) += apbh_dma.o diff --git a/drivers/dma/apbh_dma.c b/drivers/dma/apbh_dma.c new file mode 100644 index 000000000..363878fb2 --- /dev/null +++ b/drivers/dma/apbh_dma.c @@ -0,0 +1,598 @@ +/* + * Freescale i.MX28 APBH DMA driver + * + * Copyright (C) 2011 Wolfram Sang + * + * Copyright (C) 2011 Marek Vasut + * on behalf of DENX Software Engineering GmbH + * + * Based on code from LTIB: + * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 HW_APBH_VERSION (cpu_is_mx23() ? 0x3f0 : 0x800) +#define HW_APBX_VERSION 0x800 +#define BP_APBHX_VERSION_MAJOR 24 +#define HW_APBHX_CHn_NXTCMDAR(n) \ + ((apbh_is_old ? 0x050 : 0x110) + (n) * 0x70) +#define HW_APBHX_CHn_SEMA(n) \ + ((apbh_is_old ? 0x080 : 0x140) + (n) * 0x70) +#define BM_APBHX_CHn_SEMA_PHORE (0xff << 16) +#define BP_APBHX_CHn_SEMA_PHORE 16 + +static struct mxs_dma_chan mxs_dma_channels[MXS_MAX_DMA_CHANNELS]; +static bool apbh_is_old; + +/* + * Test is the DMA channel is valid channel + */ +int mxs_dma_validate_chan(int channel) +{ + struct mxs_dma_chan *pchan; + + if ((channel < 0) || (channel >= MXS_MAX_DMA_CHANNELS)) + return -EINVAL; + + pchan = mxs_dma_channels + channel; + if (!(pchan->flags & MXS_DMA_FLAGS_ALLOCATED)) + return -EINVAL; + + return 0; +} + +/* + * Return the address of the command within a descriptor. + */ +static unsigned int mxs_dma_cmd_address(struct mxs_dma_desc *desc) +{ + return desc->address + offsetof(struct mxs_dma_desc, cmd); +} + +/* + * Read a DMA channel's hardware semaphore. + * + * As used by the MXS platform's DMA software, the DMA channel's hardware + * semaphore reflects the number of DMA commands the hardware will process, but + * has not yet finished. This is a volatile value read directly from hardware, + * so it must be be viewed as immediately stale. + * + * If the channel is not marked busy, or has finished processing all its + * commands, this value should be zero. + * + * See mxs_dma_append() for details on how DMA command blocks must be configured + * to maintain the expected behavior of the semaphore's value. + */ +static int mxs_dma_read_semaphore(int channel) +{ + void __iomem *apbh_regs = (void *)MXS_APBH_BASE; + uint32_t tmp; + int ret; + + ret = mxs_dma_validate_chan(channel); + if (ret) + return ret; + + tmp = readl(apbh_regs + HW_APBHX_CHn_SEMA(channel)); + + tmp &= BM_APBHX_CHn_SEMA_PHORE; + tmp >>= BP_APBHX_CHn_SEMA_PHORE; + + return tmp; +} + +/* + * 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(int channel) +{ + void __iomem *apbh_regs = (void *)MXS_APBH_BASE; + unsigned int sem; + struct mxs_dma_chan *pchan; + struct mxs_dma_desc *pdesc; + int channel_bit, ret; + + ret = mxs_dma_validate_chan(channel); + if (ret) + return ret; + + pchan = mxs_dma_channels + channel; + + if (pchan->pending_num == 0) { + pchan->flags |= MXS_DMA_FLAGS_BUSY; + return 0; + } + + pdesc = list_first_entry(&pchan->active, struct mxs_dma_desc, node); + if (pdesc == NULL) + return -EFAULT; + + if (pchan->flags & MXS_DMA_FLAGS_BUSY) { + if (!(pdesc->cmd.data & MXS_DMA_DESC_CHAIN)) + return 0; + + sem = mxs_dma_read_semaphore(channel); + if (sem == 0) + return 0; + + if (sem == 1) { + pdesc = list_entry(pdesc->node.next, + struct mxs_dma_desc, node); + writel(mxs_dma_cmd_address(pdesc), + apbh_regs + HW_APBHX_CHn_NXTCMDAR(channel)); + } + writel(pchan->pending_num, + apbh_regs + HW_APBHX_CHn_SEMA(channel)); + pchan->active_num += pchan->pending_num; + pchan->pending_num = 0; + } else { + pchan->active_num += pchan->pending_num; + pchan->pending_num = 0; + writel(mxs_dma_cmd_address(pdesc), + apbh_regs + HW_APBHX_CHn_NXTCMDAR(channel)); + writel(pchan->active_num, + apbh_regs + HW_APBHX_CHn_SEMA(channel)); + channel_bit = channel + (apbh_is_old ? BP_APBH_CTRL0_CLKGATE_CHANNEL : 0); + writel(1 << channel_bit, apbh_regs + HW_APBHX_CTRL0 + BIT_CLR); + } + + pchan->flags |= MXS_DMA_FLAGS_BUSY; + return 0; +} + +/* + * Disable a DMA channel. + * + * This function shuts down a DMA channel and marks it as "not busy." Any + * descriptors on the active list are immediately moved to the head of the + * "done" list, whether or not they have actually been processed by the + * hardware. The "ready" flags of these descriptors are NOT cleared, so they + * still appear to be active. + * + * This function immediately shuts down a DMA channel's hardware, aborting any + * I/O that may be in progress, potentially leaving I/O hardware in an undefined + * state. It is unwise to call this function if there is ANY chance the hardware + * is still processing a command. + */ +static int mxs_dma_disable(int channel) +{ + struct mxs_dma_chan *pchan; + void __iomem *apbh_regs = (void *)MXS_APBH_BASE; + int channel_bit, ret; + + ret = mxs_dma_validate_chan(channel); + if (ret) + return ret; + + pchan = mxs_dma_channels + channel; + + if (!(pchan->flags & MXS_DMA_FLAGS_BUSY)) + return -EINVAL; + + channel_bit = channel + (apbh_is_old ? BP_APBH_CTRL0_CLKGATE_CHANNEL : 0); + writel(1 << channel_bit, apbh_regs + HW_APBHX_CTRL0 + BIT_SET); + + pchan->flags &= ~MXS_DMA_FLAGS_BUSY; + pchan->active_num = 0; + pchan->pending_num = 0; + list_splice_init(&pchan->active, &pchan->done); + + return 0; +} + +/* + * Resets the DMA channel hardware. + */ +static int mxs_dma_reset(int channel) +{ + void __iomem *apbh_regs = (void *)MXS_APBH_BASE; + int ret; + + ret = mxs_dma_validate_chan(channel); + if (ret) + return ret; + + if (apbh_is_old) + writel(1 << (channel + BP_APBH_CTRL0_RESET_CHANNEL), + apbh_regs + HW_APBHX_CTRL0 + BIT_SET); + else + writel(1 << (channel + BP_APBHX_CHANNEL_CTRL_RESET_CHANNEL), + apbh_regs + HW_APBHX_CHANNEL_CTRL + BIT_SET); + + return 0; +} + +/* + * Enable or disable DMA interrupt. + * + * This function enables the given DMA channel to interrupt the CPU. + */ +static int mxs_dma_enable_irq(int channel, int enable) +{ + void __iomem *apbh_regs = (void *)MXS_APBH_BASE; + int ret; + + ret = mxs_dma_validate_chan(channel); + if (ret) + return ret; + + if (enable) + writel(1 << (channel + BP_APBHX_CTRL1_CH_CMDCMPLT_IRQ_EN), + apbh_regs + HW_APBHX_CTRL1 + BIT_SET); + else + writel(1 << (channel + BP_APBHX_CTRL1_CH_CMDCMPLT_IRQ_EN), + apbh_regs + HW_APBHX_CTRL1 + BIT_CLR); + + return 0; +} + +/* + * Clear DMA interrupt. + * + * The software that is using the DMA channel must register to receive its + * interrupts and, when they arrive, must call this function to clear them. + */ +static int mxs_dma_ack_irq(int channel) +{ + void __iomem *apbh_regs = (void *)MXS_APBH_BASE; + int ret; + + ret = mxs_dma_validate_chan(channel); + if (ret) + return ret; + + writel(1 << channel, apbh_regs + HW_APBHX_CTRL1 + BIT_CLR); + writel(1 << channel, apbh_regs + HW_APBHX_CTRL2 + BIT_CLR); + + return 0; +} + +/* + * Request to reserve a DMA channel + */ +static int mxs_dma_request(int channel) +{ + struct mxs_dma_chan *pchan; + + if ((channel < 0) || (channel >= MXS_MAX_DMA_CHANNELS)) + return -EINVAL; + + pchan = mxs_dma_channels + channel; + if ((pchan->flags & MXS_DMA_FLAGS_VALID) != MXS_DMA_FLAGS_VALID) + return -ENODEV; + + if (pchan->flags & MXS_DMA_FLAGS_ALLOCATED) + return -EBUSY; + + pchan->flags |= MXS_DMA_FLAGS_ALLOCATED; + pchan->active_num = 0; + pchan->pending_num = 0; + + INIT_LIST_HEAD(&pchan->active); + INIT_LIST_HEAD(&pchan->done); + + return 0; +} + +/* + * Release a DMA channel. + * + * This function releases a DMA channel from its current owner. + * + * The channel will NOT be released if it's marked "busy" (see + * mxs_dma_enable()). + */ +static int mxs_dma_release(int channel) +{ + struct mxs_dma_chan *pchan; + int ret; + + ret = mxs_dma_validate_chan(channel); + if (ret) + return ret; + + pchan = mxs_dma_channels + channel; + + if (pchan->flags & MXS_DMA_FLAGS_BUSY) + return -EBUSY; + + pchan->dev = 0; + pchan->active_num = 0; + pchan->pending_num = 0; + pchan->flags &= ~MXS_DMA_FLAGS_ALLOCATED; + + return 0; +} + +/* + * Allocate DMA descriptor + */ +struct mxs_dma_desc *mxs_dma_desc_alloc(void) +{ + struct mxs_dma_desc *pdesc; + + pdesc = dma_alloc_coherent(sizeof(struct mxs_dma_desc)); + + if (pdesc == NULL) + return NULL; + + memset(pdesc, 0, sizeof(*pdesc)); + pdesc->address = (dma_addr_t)pdesc; + + return pdesc; +}; + +/* + * Free DMA descriptor + */ +void mxs_dma_desc_free(struct mxs_dma_desc *pdesc) +{ + if (pdesc == NULL) + return; + + free(pdesc); +} + +/* + * Add a DMA descriptor to a channel. + * + * If the descriptor list for this channel is not empty, this function sets the + * CHAIN bit and the NEXTCMD_ADDR fields in the last descriptor's DMA command so + * it will chain to the new descriptor's command. + * + * Then, this function marks the new descriptor as "ready," adds it to the end + * of the active descriptor list, and increments the count of pending + * descriptors. + * + * The MXS platform DMA software imposes some rules on DMA commands to maintain + * important invariants. These rules are NOT checked, but they must be carefully + * applied by software that uses MXS DMA channels. + * + * Invariant: + * The DMA channel's hardware semaphore must reflect the number of DMA + * commands the hardware will process, but has not yet finished. + * + * Explanation: + * A DMA channel begins processing commands when its hardware semaphore is + * written with a value greater than zero, and it stops processing commands + * when the semaphore returns to zero. + * + * When a channel finishes a DMA command, it will decrement its semaphore if + * the DECREMENT_SEMAPHORE bit is set in that command's flags bits. + * + * In principle, it's not necessary for the DECREMENT_SEMAPHORE to be set, + * unless it suits the purposes of the software. For example, one could + * construct a series of five DMA commands, with the DECREMENT_SEMAPHORE + * bit set only in the last one. Then, setting the DMA channel's hardware + * semaphore to one would cause the entire series of five commands to be + * processed. However, this example would violate the invariant given above. + * + * Rule: + * ALL DMA commands MUST have the DECREMENT_SEMAPHORE bit set so that the DMA + * channel's hardware semaphore will be decremented EVERY time a command is + * processed. + */ +int mxs_dma_desc_append(int channel, struct mxs_dma_desc *pdesc) +{ + struct mxs_dma_chan *pchan; + struct mxs_dma_desc *last; + int ret; + + ret = mxs_dma_validate_chan(channel); + if (ret) + return ret; + + pchan = mxs_dma_channels + channel; + + pdesc->cmd.next = mxs_dma_cmd_address(pdesc); + pdesc->flags |= MXS_DMA_DESC_FIRST | MXS_DMA_DESC_LAST; + + if (!list_empty(&pchan->active)) { + last = list_entry(pchan->active.prev, struct mxs_dma_desc, + node); + + pdesc->flags &= ~MXS_DMA_DESC_FIRST; + last->flags &= ~MXS_DMA_DESC_LAST; + + last->cmd.next = mxs_dma_cmd_address(pdesc); + last->cmd.data |= MXS_DMA_DESC_CHAIN; + } + pdesc->flags |= MXS_DMA_DESC_READY; + if (pdesc->flags & MXS_DMA_DESC_FIRST) + pchan->pending_num++; + list_add_tail(&pdesc->node, &pchan->active); + + return ret; +} + +/* + * Clean up processed DMA descriptors. + * + * This function removes processed DMA descriptors from the "active" list. Pass + * in a non-NULL list head to get the descriptors moved to your list. Pass NULL + * to get the descriptors moved to the channel's "done" list. Descriptors on + * the "done" list can be retrieved with mxs_dma_get_finished(). + * + * This function marks the DMA channel as "not busy" if no unprocessed + * descriptors remain on the "active" list. + */ +static int mxs_dma_finish(int channel, struct list_head *head) +{ + int sem; + struct mxs_dma_chan *pchan; + struct list_head *p, *q; + struct mxs_dma_desc *pdesc; + int ret; + + ret = mxs_dma_validate_chan(channel); + if (ret) + return ret; + + pchan = mxs_dma_channels + channel; + + sem = mxs_dma_read_semaphore(channel); + if (sem < 0) + return sem; + + if (sem == pchan->active_num) + return 0; + + list_for_each_safe(p, q, &pchan->active) { + if ((pchan->active_num) <= sem) + break; + + pdesc = list_entry(p, struct mxs_dma_desc, node); + pdesc->flags &= ~MXS_DMA_DESC_READY; + + if (head) + list_move_tail(p, head); + else + list_move_tail(p, &pchan->done); + + if (pdesc->flags & MXS_DMA_DESC_LAST) + pchan->active_num--; + } + + if (sem == 0) + pchan->flags &= ~MXS_DMA_FLAGS_BUSY; + + return 0; +} + +/* + * Wait for DMA channel to complete + */ +static int mxs_dma_wait_complete(uint32_t timeout, unsigned int chan) +{ + void __iomem *apbh_regs = (void *)MXS_APBH_BASE; + int ret; + + ret = mxs_dma_validate_chan(chan); + if (ret) + return ret; + + while (--timeout) { + if (readl(apbh_regs + HW_APBHX_CTRL1) & (1 << chan)) + break; + udelay(1); + } + + if (timeout == 0) { + ret = -ETIMEDOUT; + mxs_dma_reset(chan); + } + + return ret; +} + +/* + * Execute the DMA channel + */ +int mxs_dma_go(int chan) +{ + uint32_t timeout = 10000; + int ret; + + LIST_HEAD(tmp_desc_list); + + mxs_dma_enable_irq(chan, 1); + mxs_dma_enable(chan); + + /* Wait for DMA to finish. */ + ret = mxs_dma_wait_complete(timeout, chan); + + /* Clear out the descriptors we just ran. */ + mxs_dma_finish(chan, &tmp_desc_list); + + /* Shut the DMA channel down. */ + mxs_dma_ack_irq(chan); + mxs_dma_reset(chan); + mxs_dma_enable_irq(chan, 0); + mxs_dma_disable(chan); + + return ret; +} + +/* + * Initialize the DMA hardware + */ +int mxs_dma_init(void) +{ + void __iomem *apbh_regs = (void *)MXS_APBH_BASE; + struct mxs_dma_chan *pchan; + int ret, channel; + u32 val, reg; + + mxs_reset_block(apbh_regs, 0); + + /* HACK: Get CPUID and determine APBH version */ + val = readl(0x8001c310) >> 16; + if (val == 0x2800) + reg = MXS_APBH_BASE + 0x0800; + else + reg = MXS_APBH_BASE + 0x03f0; + + apbh_is_old = (readl((void *)reg) >> 24) < 3; + + writel(BM_APBH_CTRL0_APB_BURST8_EN, + apbh_regs + HW_APBHX_CTRL0 + BIT_SET); + + writel(BM_APBH_CTRL0_APB_BURST_EN, + apbh_regs + HW_APBHX_CTRL0 + BIT_SET); + + for (channel = 0; channel < MXS_MAX_DMA_CHANNELS; channel++) { + pchan = mxs_dma_channels + channel; + pchan->flags = MXS_DMA_FLAGS_VALID; + + ret = mxs_dma_request(channel); + + if (ret) { + printf("MXS DMA: Can't acquire DMA channel %i\n", + channel); + + goto err; + } + + mxs_dma_reset(channel); + mxs_dma_ack_irq(channel); + } + + return 0; + +err: + while (--channel >= 0) + mxs_dma_release(channel); + return ret; +} diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 926a64bf9..3f9064328 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -53,6 +53,11 @@ config NAND_IMX prompt "i.MX NAND driver" depends on ARCH_IMX +config NAND_MXS + bool + prompt "i.MX23/28 NAND driver" + depends on MXS_APBH_DMA + config NAND_OMAP_GPMC tristate "NAND Flash Support for GPMC based OMAP platforms" depends on OMAP_GPMC diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index 5c6d8b3cc..417961870 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -15,3 +15,5 @@ obj-$(CONFIG_NAND_IMX) += nand_imx.o obj-$(CONFIG_NAND_OMAP_GPMC) += nand_omap_gpmc.o nand_omap_bch_decoder.o obj-$(CONFIG_NAND_ATMEL) += atmel_nand.o obj-$(CONFIG_NAND_S3C24XX) += nand_s3c24xx.o +obj-$(CONFIG_NAND_S3C24X0) += nand_s3c2410.o +obj-$(CONFIG_NAND_MXS) += nand_mxs.o diff --git a/drivers/mtd/nand/nand_mxs.c b/drivers/mtd/nand/nand_mxs.c new file mode 100644 index 000000000..ba49287b1 --- /dev/null +++ b/drivers/mtd/nand/nand_mxs.c @@ -0,0 +1,1258 @@ +/* + * Freescale i.MX28 NAND flash driver + * + * Copyright (C) 2011 Wolfram Sang + * + * Copyright (C) 2011 Marek Vasut + * on behalf of DENX Software Engineering GmbH + * + * Based on code from LTIB: + * Freescale GPMI NFC NAND Flash Driver + * + * Copyright (C) 2010 Freescale Semiconductor, Inc. + * Copyright (C) 2008 Embedded Alley Solutions, Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MX28_BLOCK_SFTRST (1 << 31) +#define MX28_BLOCK_CLKGATE (1 << 30) + +#define GPMI_CTRL0 0x00000000 +#define GPMI_CTRL0_RUN (1 << 29) +#define GPMI_CTRL0_DEV_IRQ_EN (1 << 28) +/* Disable for now since we don't need it and it is different on MX23. +#define GPMI_CTRL0_LOCK_CS (1 << 27) +*/ +#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) +/* Careful: Is 0x3 on MX23 +#define GPMI_CTRL0_CS_MASK (0x7 << 20) +*/ +#define GPMI_CTRL0_CS_OFFSET 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_CTRL1 0x00000060 +#define GPMI_CTRL1_DECOUPLE_CS (1 << 24) +#define GPMI_CTRL1_WRN_DLY_SEL_MASK (0x3 << 22) +#define GPMI_CTRL1_WRN_DLY_SEL_OFFSET 22 +#define GPMI_CTRL1_TIMEOUT_IRQ_EN (1 << 20) +#define GPMI_CTRL1_GANGED_RDYBUSY (1 << 19) +#define GPMI_CTRL1_BCH_MODE (1 << 18) +#define GPMI_CTRL1_DLL_ENABLE (1 << 17) +#define GPMI_CTRL1_HALF_PERIOD (1 << 16) +#define GPMI_CTRL1_RDN_DELAY_MASK (0xf << 12) +#define GPMI_CTRL1_RDN_DELAY_OFFSET 12 +#define GPMI_CTRL1_DMA2ECC_MODE (1 << 11) +#define GPMI_CTRL1_DEV_IRQ (1 << 10) +#define GPMI_CTRL1_TIMEOUT_IRQ (1 << 9) +#define GPMI_CTRL1_BURST_EN (1 << 8) +#define GPMI_CTRL1_ABORT_WAIT_REQUEST (1 << 7) +#define GPMI_CTRL1_ABORT_WAIT_FOR_READY_CHANNEL_MASK (0x7 << 4) +#define GPMI_CTRL1_ABORT_WAIT_FOR_READY_CHANNEL_OFFSET 4 +#define GPMI_CTRL1_DEV_RESET (1 << 3) +#define GPMI_CTRL1_ATA_IRQRDY_POLARITY (1 << 2) +#define GPMI_CTRL1_CAMERA_MODE (1 << 1) +#define GPMI_CTRL1_GPMI_MODE (1 << 0) + +#define GPMI_ECCCTRL_HANDLE_MASK (0xffff << 16) +#define GPMI_ECCCTRL_HANDLE_OFFSET 16 +#define GPMI_ECCCTRL_ECC_CMD_MASK (0x3 << 13) +#define GPMI_ECCCTRL_ECC_CMD_OFFSET 13 +#define GPMI_ECCCTRL_ECC_CMD_DECODE (0x0 << 13) +#define GPMI_ECCCTRL_ECC_CMD_ENCODE (0x1 << 13) +#define GPMI_ECCCTRL_ENABLE_ECC (1 << 12) +#define GPMI_ECCCTRL_BUFFER_MASK_MASK 0x1ff +#define GPMI_ECCCTRL_BUFFER_MASK_OFFSET 0 +#define GPMI_ECCCTRL_BUFFER_MASK_BCH_AUXONLY 0x100 +#define GPMI_ECCCTRL_BUFFER_MASK_BCH_PAGE 0x1ff + +#define GPMI_STAT 0x000000b0 +#define GPMI_STAT_READY_BUSY_OFFSET 24 + +#define GPMI_DEBUG 0x000000c0 +#define GPMI_DEBUG_READY0_OFFSET 28 + +#define GPMI_VERSION 0x000000d0 +#define GPMI_VERSION_MINOR_OFFSET 16 +#define GPMI_VERSION_TYPE_MX23 0x0300 + +#define BCH_CTRL 0x00000000 +#define BCH_CTRL_COMPLETE_IRQ (1 << 0) +#define BCH_CTRL_COMPLETE_IRQ_EN (1 << 8) + +#define BCH_LAYOUTSELECT 0x00000070 + +#define BCH_FLASH0LAYOUT0 0x00000080 +#define BCH_FLASHLAYOUT0_NBLOCKS_MASK (0xff << 24) +#define BCH_FLASHLAYOUT0_NBLOCKS_OFFSET 24 +#define BCH_FLASHLAYOUT0_META_SIZE_MASK (0xff << 16) +#define BCH_FLASHLAYOUT0_META_SIZE_OFFSET 16 +#define BCH_FLASHLAYOUT0_ECC0_MASK (0xf << 12) +#define BCH_FLASHLAYOUT0_ECC0_OFFSET 12 + +#define BCH_FLASH0LAYOUT1 0x00000090 +#define BCH_FLASHLAYOUT1_PAGE_SIZE_MASK (0xffff << 16) +#define BCH_FLASHLAYOUT1_PAGE_SIZE_OFFSET 16 +#define BCH_FLASHLAYOUT1_ECCN_MASK (0xf << 12) +#define BCH_FLASHLAYOUT1_ECCN_OFFSET 12 + +#define MXS_NAND_DMA_DESCRIPTOR_COUNT 4 + +#define MXS_NAND_CHUNK_DATA_CHUNK_SIZE 512 +#define MXS_NAND_METADATA_SIZE 10 + +#define MXS_NAND_COMMAND_BUFFER_SIZE 32 + +#define MXS_NAND_BCH_TIMEOUT 10000 + +struct mxs_nand_info { + struct nand_chip nand_chip; + void __iomem *io_base; + struct mtd_info mtd; + u32 version; + + int cur_chip; + + uint32_t cmd_queue_len; + + uint8_t *cmd_buf; + uint8_t *data_buf; + uint8_t *oob_buf; + + uint8_t marking_block_bad; + uint8_t raw_oob_mode; + + /* Functions with altered behaviour */ + int (*hooked_read_oob)(struct mtd_info *mtd, + loff_t from, struct mtd_oob_ops *ops); + int (*hooked_write_oob)(struct mtd_info *mtd, + loff_t to, struct mtd_oob_ops *ops); + int (*hooked_block_markbad)(struct mtd_info *mtd, + loff_t ofs); + + /* DMA descriptors */ + struct mxs_dma_desc **desc; + uint32_t desc_index; +}; + +struct nand_ecclayout fake_ecc_layout; + +static struct mxs_dma_desc *mxs_nand_get_dma_desc(struct mxs_nand_info *info) +{ + struct mxs_dma_desc *desc; + + if (info->desc_index >= MXS_NAND_DMA_DESCRIPTOR_COUNT) { + printf("MXS NAND: Too many DMA descriptors requested\n"); + return NULL; + } + + desc = info->desc[info->desc_index]; + info->desc_index++; + + return desc; +} + +static void mxs_nand_return_dma_descs(struct mxs_nand_info *info) +{ + int i; + struct mxs_dma_desc *desc; + + for (i = 0; i < info->desc_index; i++) { + desc = info->desc[i]; + memset(desc, 0, sizeof(struct mxs_dma_desc)); + desc->address = (dma_addr_t)desc; + } + + info->desc_index = 0; +} + +static uint32_t mxs_nand_ecc_chunk_cnt(uint32_t page_data_size) +{ + return page_data_size / MXS_NAND_CHUNK_DATA_CHUNK_SIZE; +} + +static uint32_t mxs_nand_ecc_size_in_bits(uint32_t ecc_strength) +{ + return ecc_strength * 13; +} + +static uint32_t mxs_nand_aux_status_offset(void) +{ + return (MXS_NAND_METADATA_SIZE + 0x3) & ~0x3; +} + +static inline uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size, + uint32_t page_oob_size) +{ + if (page_data_size == 2048) + return 8; + + if (page_data_size == 4096) { + if (page_oob_size == 128) + return 8; + + if (page_oob_size == 218) + return 16; + } + + return 0; +} + +static inline uint32_t mxs_nand_get_mark_offset(uint32_t page_data_size, + uint32_t ecc_strength) +{ + uint32_t chunk_data_size_in_bits; + uint32_t chunk_ecc_size_in_bits; + uint32_t chunk_total_size_in_bits; + uint32_t block_mark_chunk_number; + uint32_t block_mark_chunk_bit_offset; + uint32_t block_mark_bit_offset; + + chunk_data_size_in_bits = MXS_NAND_CHUNK_DATA_CHUNK_SIZE * 8; + chunk_ecc_size_in_bits = mxs_nand_ecc_size_in_bits(ecc_strength); + + chunk_total_size_in_bits = + chunk_data_size_in_bits + chunk_ecc_size_in_bits; + + /* Compute the bit offset of the block mark within the physical page. */ + block_mark_bit_offset = page_data_size * 8; + + /* Subtract the metadata bits. */ + block_mark_bit_offset -= MXS_NAND_METADATA_SIZE * 8; + + /* + * Compute the chunk number (starting at zero) in which the block mark + * appears. + */ + block_mark_chunk_number = + block_mark_bit_offset / chunk_total_size_in_bits; + + /* + * Compute the bit offset of the block mark within its chunk, and + * validate it. + */ + block_mark_chunk_bit_offset = block_mark_bit_offset - + (block_mark_chunk_number * chunk_total_size_in_bits); + + if (block_mark_chunk_bit_offset > chunk_data_size_in_bits) + return 1; + + /* + * Now that we know the chunk number in which the block mark appears, + * we can subtract all the ECC bits that appear before it. + */ + block_mark_bit_offset -= + block_mark_chunk_number * chunk_ecc_size_in_bits; + + return block_mark_bit_offset; +} + +static uint32_t mxs_nand_mark_byte_offset(struct mtd_info *mtd) +{ + uint32_t ecc_strength; + ecc_strength = mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize); + return mxs_nand_get_mark_offset(mtd->writesize, ecc_strength) >> 3; +} + +static uint32_t mxs_nand_mark_bit_offset(struct mtd_info *mtd) +{ + uint32_t ecc_strength; + ecc_strength = mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize); + return mxs_nand_get_mark_offset(mtd->writesize, ecc_strength) & 0x7; +} + +/* + * Wait for BCH complete IRQ and clear the IRQ + */ +static int mxs_nand_wait_for_bch_complete(void) +{ + void __iomem *bch_regs = (void __iomem *)MXS_BCH_BASE; + int timeout = MXS_NAND_BCH_TIMEOUT; + int ret; + + while (--timeout) { + if (readl(bch_regs + BCH_CTRL) & BCH_CTRL_COMPLETE_IRQ) + break; + udelay(1); + } + + ret = (timeout == 0) ? -ETIMEDOUT : 0; + + writel(BCH_CTRL_COMPLETE_IRQ, bch_regs + BCH_CTRL + BIT_CLR); + + return ret; +} + +/* + * This is the function that we install in the cmd_ctrl function pointer of the + * owning struct nand_chip. The only functions in the reference implementation + * that use these functions pointers are cmdfunc and select_chip. + * + * In this driver, we implement our own select_chip, so this function will only + * be called by the reference implementation's cmdfunc. For this reason, we can + * ignore the chip enable bit and concentrate only on sending bytes to the NAND + * Flash. + */ +static void mxs_nand_cmd_ctrl(struct mtd_info *mtd, int data, unsigned int ctrl) +{ + struct nand_chip *nand = mtd->priv; + struct mxs_nand_info *nand_info = nand->priv; + struct mxs_dma_desc *d; + uint32_t channel = MXS_DMA_CHANNEL_AHB_APBH_GPMI0 + nand_info->cur_chip; + int ret; + + /* + * If this condition is true, something is _VERY_ wrong in MTD + * subsystem! + */ + if (nand_info->cmd_queue_len == MXS_NAND_COMMAND_BUFFER_SIZE) { + printf("MXS NAND: Command queue too long\n"); + return; + } + + /* + * Every operation begins with a command byte and a series of zero or + * more address bytes. These are distinguished by either the Address + * Latch Enable (ALE) or Command Latch Enable (CLE) signals being + * asserted. When MTD is ready to execute the command, it will + * deasert both latch enables. + * + * Rather than run a separate DMA operation for every single byte, we + * queue them up and run a single DMA operation for the entire series + * of command and data bytes. + */ + if (ctrl & (NAND_ALE | NAND_CLE)) { + if (data != NAND_CMD_NONE) + nand_info->cmd_buf[nand_info->cmd_queue_len++] = data; + return; + } + + /* + * If control arrives here, MTD has deasserted both the ALE and CLE, + * which means it's ready to run an operation. Check if we have any + * bytes to send. + */ + if (nand_info->cmd_queue_len == 0) + return; + + /* Compile the DMA descriptor -- a descriptor that sends command. */ + d = mxs_nand_get_dma_desc(nand_info); + d->cmd.data = + MXS_DMA_DESC_COMMAND_DMA_READ | MXS_DMA_DESC_IRQ | + MXS_DMA_DESC_CHAIN | MXS_DMA_DESC_DEC_SEM | + MXS_DMA_DESC_WAIT4END | (3 << MXS_DMA_DESC_PIO_WORDS_OFFSET) | + (nand_info->cmd_queue_len << MXS_DMA_DESC_BYTES_OFFSET); + + d->cmd.address = (dma_addr_t)nand_info->cmd_buf; + + d->cmd.pio_words[0] = + GPMI_CTRL0_COMMAND_MODE_WRITE | + GPMI_CTRL0_WORD_LENGTH | + (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) | + GPMI_CTRL0_ADDRESS_NAND_CLE | + GPMI_CTRL0_ADDRESS_INCREMENT | + nand_info->cmd_queue_len; + + mxs_dma_desc_append(channel, d); + + /* Execute the DMA chain. */ + ret = mxs_dma_go(channel); + if (ret) + printf("MXS NAND: Error sending command\n"); + + mxs_nand_return_dma_descs(nand_info); + + /* Reset the command queue. */ + nand_info->cmd_queue_len = 0; +} + +/* + * Test if the NAND flash is ready. + */ +static int mxs_nand_device_ready(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct mxs_nand_info *nand_info = chip->priv; + void __iomem *gpmi_regs = (void *)MXS_GPMI_BASE; + uint32_t tmp; + + if (nand_info->version > GPMI_VERSION_TYPE_MX23) { + tmp = readl(gpmi_regs + GPMI_STAT); + tmp >>= (GPMI_STAT_READY_BUSY_OFFSET + nand_info->cur_chip); + } else { + tmp = readl(gpmi_regs + GPMI_DEBUG); + tmp >>= (GPMI_DEBUG_READY0_OFFSET + nand_info->cur_chip); + } + return tmp & 1; +} + +/* + * Select the NAND chip. + */ +static void mxs_nand_select_chip(struct mtd_info *mtd, int chip) +{ + struct nand_chip *nand = mtd->priv; + struct mxs_nand_info *nand_info = nand->priv; + + nand_info->cur_chip = chip; +} + +/* + * Handle block mark swapping. + * + * Note that, when this function is called, it doesn't know whether it's + * swapping the block mark, or swapping it *back* -- but it doesn't matter + * because the the operation is the same. + */ +static void mxs_nand_swap_block_mark(struct mtd_info *mtd, + uint8_t *data_buf, uint8_t *oob_buf) +{ + struct nand_chip *chip = mtd->priv; + struct mxs_nand_info *nand_info = chip->priv; + + uint32_t bit_offset; + uint32_t buf_offset; + + uint32_t src; + uint32_t dst; + + /* Don't do swapping on MX23 */ + if (nand_info->version == GPMI_VERSION_TYPE_MX23) + return; + + bit_offset = mxs_nand_mark_bit_offset(mtd); + buf_offset = mxs_nand_mark_byte_offset(mtd); + + /* + * Get the byte from the data area that overlays the block mark. Since + * the ECC engine applies its own view to the bits in the page, the + * physical block mark won't (in general) appear on a byte boundary in + * the data. + */ + src = data_buf[buf_offset] >> bit_offset; + src |= data_buf[buf_offset + 1] << (8 - bit_offset); + + dst = oob_buf[0]; + + oob_buf[0] = src; + + data_buf[buf_offset] &= ~(0xff << bit_offset); + data_buf[buf_offset + 1] &= 0xff << bit_offset; + + data_buf[buf_offset] |= dst << bit_offset; + data_buf[buf_offset + 1] |= dst >> (8 - bit_offset); +} + +/* + * Read data from NAND. + */ +static void mxs_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int length) +{ + struct nand_chip *nand = mtd->priv; + struct mxs_nand_info *nand_info = nand->priv; + struct mxs_dma_desc *d; + uint32_t channel = MXS_DMA_CHANNEL_AHB_APBH_GPMI0 + nand_info->cur_chip; + int ret; + + if (length > NAND_MAX_PAGESIZE) { + printf("MXS NAND: DMA buffer too big\n"); + return; + } + + if (!buf) { + printf("MXS NAND: DMA buffer is NULL\n"); + return; + } + + /* Compile the DMA descriptor - a descriptor that reads data. */ + d = mxs_nand_get_dma_desc(nand_info); + d->cmd.data = + MXS_DMA_DESC_COMMAND_DMA_WRITE | MXS_DMA_DESC_IRQ | + MXS_DMA_DESC_DEC_SEM | MXS_DMA_DESC_WAIT4END | + (1 << MXS_DMA_DESC_PIO_WORDS_OFFSET) | + (length << MXS_DMA_DESC_BYTES_OFFSET); + + d->cmd.address = (dma_addr_t)nand_info->data_buf; + + d->cmd.pio_words[0] = + GPMI_CTRL0_COMMAND_MODE_READ | + GPMI_CTRL0_WORD_LENGTH | + (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) | + GPMI_CTRL0_ADDRESS_NAND_DATA | + length; + + mxs_dma_desc_append(channel, d); + + /* + * A DMA descriptor that waits for the command to end and the chip to + * become ready. + * + * I think we actually should *not* be waiting for the chip to become + * ready because, after all, we don't care. I think the original code + * did that and no one has re-thought it yet. + */ + d = mxs_nand_get_dma_desc(nand_info); + d->cmd.data = + MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_IRQ | + MXS_DMA_DESC_NAND_WAIT_4_READY | MXS_DMA_DESC_DEC_SEM | + MXS_DMA_DESC_WAIT4END | (4 << MXS_DMA_DESC_PIO_WORDS_OFFSET); + + d->cmd.address = 0; + + d->cmd.pio_words[0] = + GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY | + GPMI_CTRL0_WORD_LENGTH | + (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) | + GPMI_CTRL0_ADDRESS_NAND_DATA; + + mxs_dma_desc_append(channel, d); + + /* Execute the DMA chain. */ + ret = mxs_dma_go(channel); + if (ret) { + printf("MXS NAND: DMA read error\n"); + goto rtn; + } + + memcpy(buf, nand_info->data_buf, length); + +rtn: + mxs_nand_return_dma_descs(nand_info); +} + +/* + * Write data to NAND. + */ +static void mxs_nand_write_buf(struct mtd_info *mtd, const uint8_t *buf, + int length) +{ + struct nand_chip *nand = mtd->priv; + struct mxs_nand_info *nand_info = nand->priv; + struct mxs_dma_desc *d; + uint32_t channel = MXS_DMA_CHANNEL_AHB_APBH_GPMI0 + nand_info->cur_chip; + int ret; + + if (length > NAND_MAX_PAGESIZE) { + printf("MXS NAND: DMA buffer too big\n"); + return; + } + + if (!buf) { + printf("MXS NAND: DMA buffer is NULL\n"); + return; + } + + memcpy(nand_info->data_buf, buf, length); + + /* Compile the DMA descriptor - a descriptor that writes data. */ + d = mxs_nand_get_dma_desc(nand_info); + d->cmd.data = + MXS_DMA_DESC_COMMAND_DMA_READ | MXS_DMA_DESC_IRQ | + MXS_DMA_DESC_DEC_SEM | MXS_DMA_DESC_WAIT4END | + (4 << MXS_DMA_DESC_PIO_WORDS_OFFSET) | + (length << MXS_DMA_DESC_BYTES_OFFSET); + + d->cmd.address = (dma_addr_t)nand_info->data_buf; + + d->cmd.pio_words[0] = + GPMI_CTRL0_COMMAND_MODE_WRITE | + GPMI_CTRL0_WORD_LENGTH | + (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) | + GPMI_CTRL0_ADDRESS_NAND_DATA | + length; + + mxs_dma_desc_append(channel, d); + + /* Execute the DMA chain. */ + ret = mxs_dma_go(channel); + if (ret) + printf("MXS NAND: DMA write error\n"); + + mxs_nand_return_dma_descs(nand_info); +} + +/* + * Read a single byte from NAND. + */ +static uint8_t mxs_nand_read_byte(struct mtd_info *mtd) +{ + uint8_t buf; + mxs_nand_read_buf(mtd, &buf, 1); + return buf; +} + +/* + * Read a page from NAND. + */ +static int mxs_nand_ecc_read_page(struct mtd_info *mtd, struct nand_chip *nand, + uint8_t *buf) +{ + struct mxs_nand_info *nand_info = nand->priv; + struct mxs_dma_desc *d; + uint32_t channel = MXS_DMA_CHANNEL_AHB_APBH_GPMI0 + nand_info->cur_chip; + uint32_t corrected = 0, failed = 0; + uint8_t *status; + int i, ret; + + /* Compile the DMA descriptor - wait for ready. */ + d = mxs_nand_get_dma_desc(nand_info); + d->cmd.data = + MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_CHAIN | + MXS_DMA_DESC_NAND_WAIT_4_READY | MXS_DMA_DESC_WAIT4END | + (1 << MXS_DMA_DESC_PIO_WORDS_OFFSET); + + d->cmd.address = 0; + + d->cmd.pio_words[0] = + GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY | + GPMI_CTRL0_WORD_LENGTH | + (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) | + GPMI_CTRL0_ADDRESS_NAND_DATA; + + mxs_dma_desc_append(channel, d); + + /* Compile the DMA descriptor - enable the BCH block and read. */ + d = mxs_nand_get_dma_desc(nand_info); + d->cmd.data = + MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_CHAIN | + MXS_DMA_DESC_WAIT4END | (6 << MXS_DMA_DESC_PIO_WORDS_OFFSET); + + d->cmd.address = 0; + + d->cmd.pio_words[0] = + GPMI_CTRL0_COMMAND_MODE_READ | + GPMI_CTRL0_WORD_LENGTH | + (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) | + GPMI_CTRL0_ADDRESS_NAND_DATA | + (mtd->writesize + mtd->oobsize); + d->cmd.pio_words[1] = 0; + d->cmd.pio_words[2] = + GPMI_ECCCTRL_ENABLE_ECC | + GPMI_ECCCTRL_ECC_CMD_DECODE | + GPMI_ECCCTRL_BUFFER_MASK_BCH_PAGE; + d->cmd.pio_words[3] = mtd->writesize + mtd->oobsize; + d->cmd.pio_words[4] = (dma_addr_t)nand_info->data_buf; + d->cmd.pio_words[5] = (dma_addr_t)nand_info->oob_buf; + + mxs_dma_desc_append(channel, d); + + /* Compile the DMA descriptor - disable the BCH block. */ + d = mxs_nand_get_dma_desc(nand_info); + d->cmd.data = + MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_CHAIN | + MXS_DMA_DESC_NAND_WAIT_4_READY | MXS_DMA_DESC_WAIT4END | + (3 << MXS_DMA_DESC_PIO_WORDS_OFFSET); + + d->cmd.address = 0; + + d->cmd.pio_words[0] = + GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY | + GPMI_CTRL0_WORD_LENGTH | + (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) | + GPMI_CTRL0_ADDRESS_NAND_DATA | + (mtd->writesize + mtd->oobsize); + d->cmd.pio_words[1] = 0; + d->cmd.pio_words[2] = 0; + + mxs_dma_desc_append(channel, d); + + /* Compile the DMA descriptor - deassert the NAND lock and interrupt. */ + d = mxs_nand_get_dma_desc(nand_info); + d->cmd.data = + MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_IRQ | + MXS_DMA_DESC_DEC_SEM; + + d->cmd.address = 0; + + mxs_dma_desc_append(channel, d); + + /* Execute the DMA chain. */ + ret = mxs_dma_go(channel); + if (ret) { + printf("MXS NAND: DMA read error\n"); + goto rtn; + } + + ret = mxs_nand_wait_for_bch_complete(); + if (ret) { + printf("MXS NAND: BCH read timeout\n"); + goto rtn; + } + + /* Read DMA completed, now do the mark swapping. */ + mxs_nand_swap_block_mark(mtd, nand_info->data_buf, nand_info->oob_buf); + + /* Loop over status bytes, accumulating ECC status. */ + status = nand_info->oob_buf + mxs_nand_aux_status_offset(); + for (i = 0; i < mxs_nand_ecc_chunk_cnt(mtd->writesize); i++) { + if (status[i] == 0x00) + continue; + + if (status[i] == 0xff) + continue; + + if (status[i] == 0xfe) { + failed++; + continue; + } + + corrected += status[i]; + } + + /* Propagate ECC status to the owning MTD. */ + mtd->ecc_stats.failed += failed; + mtd->ecc_stats.corrected += corrected; + + /* + * It's time to deliver the OOB bytes. See mxs_nand_ecc_read_oob() for + * details about our policy for delivering the OOB. + * + * We fill the caller's buffer with set bits, and then copy the block + * mark to the caller's buffer. Note that, if block mark swapping was + * necessary, it has already been done, so we can rely on the first + * byte of the auxiliary buffer to contain the block mark. + */ + memset(nand->oob_poi, 0xff, mtd->oobsize); + + nand->oob_poi[0] = nand_info->oob_buf[0]; + + memcpy(buf, nand_info->data_buf, mtd->writesize); + +rtn: + mxs_nand_return_dma_descs(nand_info); + + return ret; +} + +/* + * Write a page to NAND. + */ +static void mxs_nand_ecc_write_page(struct mtd_info *mtd, + struct nand_chip *nand, const uint8_t *buf) +{ + struct mxs_nand_info *nand_info = nand->priv; + struct mxs_dma_desc *d; + uint32_t channel = MXS_DMA_CHANNEL_AHB_APBH_GPMI0 + nand_info->cur_chip; + int ret; + + memcpy(nand_info->data_buf, buf, mtd->writesize); + memcpy(nand_info->oob_buf, nand->oob_poi, mtd->oobsize); + + /* Handle block mark swapping. */ + mxs_nand_swap_block_mark(mtd, nand_info->data_buf, nand_info->oob_buf); + + /* Compile the DMA descriptor - write data. */ + d = mxs_nand_get_dma_desc(nand_info); + d->cmd.data = + MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_IRQ | + MXS_DMA_DESC_DEC_SEM | MXS_DMA_DESC_WAIT4END | + (6 << MXS_DMA_DESC_PIO_WORDS_OFFSET); + + d->cmd.address = 0; + + d->cmd.pio_words[0] = + GPMI_CTRL0_COMMAND_MODE_WRITE | + GPMI_CTRL0_WORD_LENGTH | + (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) | + GPMI_CTRL0_ADDRESS_NAND_DATA; + d->cmd.pio_words[1] = 0; + d->cmd.pio_words[2] = + GPMI_ECCCTRL_ENABLE_ECC | + GPMI_ECCCTRL_ECC_CMD_ENCODE | + GPMI_ECCCTRL_BUFFER_MASK_BCH_PAGE; + d->cmd.pio_words[3] = (mtd->writesize + mtd->oobsize); + d->cmd.pio_words[4] = (dma_addr_t)nand_info->data_buf; + d->cmd.pio_words[5] = (dma_addr_t)nand_info->oob_buf; + + mxs_dma_desc_append(channel, d); + + /* Execute the DMA chain. */ + ret = mxs_dma_go(channel); + if (ret) { + printf("MXS NAND: DMA write error\n"); + goto rtn; + } + + ret = mxs_nand_wait_for_bch_complete(); + if (ret) { + printf("MXS NAND: BCH write timeout\n"); + goto rtn; + } + +rtn: + mxs_nand_return_dma_descs(nand_info); +} + +/* + * Read OOB from NAND. + * + * This function is a veneer that replaces the function originally installed by + * the NAND Flash MTD code. + */ +static int mxs_nand_hook_read_oob(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct nand_chip *chip = mtd->priv; + struct mxs_nand_info *nand_info = chip->priv; + int ret; + + if (ops->mode == MTD_OOB_RAW) + nand_info->raw_oob_mode = 1; + else + nand_info->raw_oob_mode = 0; + + ret = nand_info->hooked_read_oob(mtd, from, ops); + + nand_info->raw_oob_mode = 0; + + return ret; +} + +/* + * Write OOB to NAND. + * + * This function is a veneer that replaces the function originally installed by + * the NAND Flash MTD code. + */ +static int mxs_nand_hook_write_oob(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + struct nand_chip *chip = mtd->priv; + struct mxs_nand_info *nand_info = chip->priv; + int ret; + + if (ops->mode == MTD_OOB_RAW) + nand_info->raw_oob_mode = 1; + else + nand_info->raw_oob_mode = 0; + + ret = nand_info->hooked_write_oob(mtd, to, ops); + + nand_info->raw_oob_mode = 0; + + return ret; +} + +/* + * Mark a block bad in NAND. + * + * This function is a veneer that replaces the function originally installed by + * the NAND Flash MTD code. + */ +static int mxs_nand_hook_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + struct nand_chip *chip = mtd->priv; + struct mxs_nand_info *nand_info = chip->priv; + int ret; + + nand_info->marking_block_bad = 1; + + ret = nand_info->hooked_block_markbad(mtd, ofs); + + nand_info->marking_block_bad = 0; + + return ret; +} + +/* + * There are several places in this driver where we have to handle the OOB and + * block marks. This is the function where things are the most complicated, so + * this is where we try to explain it all. All the other places refer back to + * here. + * + * These are the rules, in order of decreasing importance: + * + * 1) Nothing the caller does can be allowed to imperil the block mark, so all + * write operations take measures to protect it. + * + * 2) In read operations, the first byte of the OOB we return must reflect the + * true state of the block mark, no matter where that block mark appears in + * the physical page. + * + * 3) ECC-based read operations return an OOB full of set bits (since we never + * allow ECC-based writes to the OOB, it doesn't matter what ECC-based reads + * return). + * + * 4) "Raw" read operations return a direct view of the physical bytes in the + * page, using the conventional definition of which bytes are data and which + * are OOB. This gives the caller a way to see the actual, physical bytes + * in the page, without the distortions applied by our ECC engine. + * + * What we do for this specific read operation depends on whether we're doing + * "raw" read, or an ECC-based read. + * + * It turns out that knowing whether we want an "ECC-based" or "raw" read is not + * easy. When reading a page, for example, the NAND Flash MTD code calls our + * ecc.read_page or ecc.read_page_raw function. Thus, the fact that MTD wants an + * ECC-based or raw view of the page is implicit in which function it calls + * (there is a similar pair of ECC-based/raw functions for writing). + * + * Since MTD assumes the OOB is not covered by ECC, there is no pair of + * ECC-based/raw functions for reading or or writing the OOB. The fact that the + * caller wants an ECC-based or raw view of the page is not propagated down to + * this driver. + * + * Since our OOB *is* covered by ECC, we need this information. So, we hook the + * ecc.read_oob and ecc.write_oob function pointers in the owning + * struct mtd_info with our own functions. These hook functions set the + * raw_oob_mode field so that, when control finally arrives here, we'll know + * what to do. + */ +static int mxs_nand_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *nand, + int page, int cmd) +{ + struct mxs_nand_info *nand_info = nand->priv; + int column; + + /* + * First, fill in the OOB buffer. If we're doing a raw read, we need to + * get the bytes from the physical page. If we're not doing a raw read, + * we need to fill the buffer with set bits. + */ + if (nand_info->raw_oob_mode && nand_info->version > GPMI_VERSION_TYPE_MX23) { + /* + * If control arrives here, we're doing a "raw" read. Send the + * command to read the conventional OOB and read it. + */ + nand->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page); + nand->read_buf(mtd, nand->oob_poi, mtd->oobsize); + } else { + /* + * If control arrives here, we're not doing a "raw" read. Fill + * the OOB buffer with set bits and correct the block mark. + */ + memset(nand->oob_poi, 0xff, mtd->oobsize); + + column = nand_info->version == GPMI_VERSION_TYPE_MX23 ? 0 : mtd->writesize; + nand->cmdfunc(mtd, NAND_CMD_READ0, column, page); + mxs_nand_read_buf(mtd, nand->oob_poi, 1); + } + + return 0; + +} + +/* + * Write OOB data to NAND. + */ +static int mxs_nand_ecc_write_oob(struct mtd_info *mtd, struct nand_chip *nand, + int page) +{ + struct mxs_nand_info *nand_info = nand->priv; + int column; + uint8_t block_mark = 0; + + /* + * There are fundamental incompatibilities between the i.MX GPMI NFC and + * the NAND Flash MTD model that make it essentially impossible to write + * the out-of-band bytes. + * + * We permit *ONE* exception. If the *intent* of writing the OOB is to + * mark a block bad, we can do that. + */ + + if (!nand_info->marking_block_bad) { + printf("NXS NAND: Writing OOB isn't supported\n"); + return -EIO; + } + + column = nand_info->version == GPMI_VERSION_TYPE_MX23 ? 0 : mtd->writesize; + /* Write the block mark. */ + nand->cmdfunc(mtd, NAND_CMD_SEQIN, column, page); + nand->write_buf(mtd, &block_mark, 1); + nand->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + /* Check if it worked. */ + if (nand->waitfunc(mtd, nand) & NAND_STATUS_FAIL) + return -EIO; + + return 0; +} + +/* + * Claims all blocks are good. + * + * In principle, this function is *only* called when the NAND Flash MTD system + * isn't allowed to keep an in-memory bad block table, so it is forced to ask + * the driver for bad block information. + * + * In fact, we permit the NAND Flash MTD system to have an in-memory BBT, so + * this function is *only* called when we take it away. + * + * Thus, this function is only called when we want *all* blocks to look good, + * so it *always* return success. + */ +static int mxs_nand_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip) +{ + return 0; +} + +/* + * Nominally, the purpose of this function is to look for or create the bad + * block table. In fact, since the we call this function at the very end of + * the initialization process started by nand_scan(), and we doesn't have a + * more formal mechanism, we "hook" this function to continue init process. + * + * At this point, the physical NAND Flash chips have been identified and + * counted, so we know the physical geometry. This enables us to make some + * important configuration decisions. + * + * The return value of this function propogates directly back to this driver's + * call to nand_scan(). Anything other than zero will cause this driver to + * tear everything down and declare failure. + */ +static int mxs_nand_scan_bbt(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct mxs_nand_info *nand_info = nand->priv; + void __iomem *bch_regs = (void __iomem *)MXS_BCH_BASE; + uint32_t tmp; + + /* Reset BCH. Don't use SFTRST on MX23 due to Errata #2847 */ + mxs_reset_block(bch_regs + BCH_CTRL, nand_info->version == GPMI_VERSION_TYPE_MX23); + + /* Configure layout 0 */ + tmp = (mxs_nand_ecc_chunk_cnt(mtd->writesize) - 1) + << BCH_FLASHLAYOUT0_NBLOCKS_OFFSET; + tmp |= MXS_NAND_METADATA_SIZE << BCH_FLASHLAYOUT0_META_SIZE_OFFSET; + tmp |= (mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize) >> 1) + << BCH_FLASHLAYOUT0_ECC0_OFFSET; + tmp |= MXS_NAND_CHUNK_DATA_CHUNK_SIZE; + writel(tmp, bch_regs + BCH_FLASH0LAYOUT0); + + tmp = (mtd->writesize + mtd->oobsize) + << BCH_FLASHLAYOUT1_PAGE_SIZE_OFFSET; + tmp |= (mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize) >> 1) + << BCH_FLASHLAYOUT1_ECCN_OFFSET; + tmp |= MXS_NAND_CHUNK_DATA_CHUNK_SIZE; + writel(tmp, bch_regs + BCH_FLASH0LAYOUT1); + + /* Set *all* chip selects to use layout 0 */ + writel(0, bch_regs + BCH_LAYOUTSELECT); + + /* Enable BCH complete interrupt */ + writel(BCH_CTRL_COMPLETE_IRQ_EN, bch_regs + BCH_CTRL + BIT_SET); + + /* Hook some operations at the MTD level. */ + if (mtd->read_oob != mxs_nand_hook_read_oob) { + nand_info->hooked_read_oob = mtd->read_oob; + mtd->read_oob = mxs_nand_hook_read_oob; + } + + if (mtd->write_oob != mxs_nand_hook_write_oob) { + nand_info->hooked_write_oob = mtd->write_oob; + mtd->write_oob = mxs_nand_hook_write_oob; + } + + if (mtd->block_markbad != mxs_nand_hook_block_markbad) { + nand_info->hooked_block_markbad = mtd->block_markbad; + mtd->block_markbad = mxs_nand_hook_block_markbad; + } + + /* We use the reference implementation for bad block management. */ + return nand_default_bbt(mtd); +} + +/* + * Allocate DMA buffers + */ +int mxs_nand_alloc_buffers(struct mxs_nand_info *nand_info) +{ + uint8_t *buf; + const int size = NAND_MAX_PAGESIZE + NAND_MAX_OOBSIZE; + + /* DMA buffers */ + buf = dma_alloc_coherent(size); + if (!buf) { + printf("MXS NAND: Error allocating DMA buffers\n"); + return -ENOMEM; + } + + memset(buf, 0, size); + + nand_info->data_buf = buf; + nand_info->oob_buf = buf + NAND_MAX_PAGESIZE; + + /* Command buffers */ + nand_info->cmd_buf = dma_alloc_coherent(MXS_NAND_COMMAND_BUFFER_SIZE); + if (!nand_info->cmd_buf) { + free(buf); + printf("MXS NAND: Error allocating command buffers\n"); + return -ENOMEM; + } + memset(nand_info->cmd_buf, 0, MXS_NAND_COMMAND_BUFFER_SIZE); + nand_info->cmd_queue_len = 0; + + return 0; +} + +/* + * Initializes the NFC hardware. + */ +int mxs_nand_hw_init(struct mxs_nand_info *info) +{ + void __iomem *gpmi_regs = (void *)MXS_GPMI_BASE; + int i = 0; + u32 val; + + info->desc = malloc(sizeof(struct mxs_dma_desc *) * + MXS_NAND_DMA_DESCRIPTOR_COUNT); + if (!info->desc) + goto err1; + + /* Allocate the DMA descriptors. */ + for (i = 0; i < MXS_NAND_DMA_DESCRIPTOR_COUNT; i++) { + info->desc[i] = mxs_dma_desc_alloc(); + if (!info->desc[i]) + goto err2; + } + + /* Init the DMA controller. */ + mxs_dma_init(); + + imx_enable_nandclk(); + + /* Reset the GPMI block. */ + mxs_reset_block(gpmi_regs + GPMI_CTRL0, 0); + + /* + * Choose NAND mode, set IRQ polarity, disable write protection and + * select BCH ECC. + */ + val = readl(gpmi_regs + GPMI_CTRL1); + val &= ~GPMI_CTRL1_GPMI_MODE; + val |= GPMI_CTRL1_ATA_IRQRDY_POLARITY | GPMI_CTRL1_DEV_RESET | + GPMI_CTRL1_BCH_MODE; + writel(val, gpmi_regs + GPMI_CTRL1); + + val = readl(gpmi_regs + GPMI_VERSION); + info->version = val >> GPMI_VERSION_MINOR_OFFSET; + + return 0; + +err2: + free(info->desc); +err1: + for (--i; i >= 0; i--) + mxs_dma_desc_free(info->desc[i]); + printf("MXS NAND: Unable to allocate DMA descriptors\n"); + return -ENOMEM; +} + +static int mxs_nand_probe(struct device_d *dev) +{ + struct mxs_nand_info *nand_info; + struct nand_chip *nand; + struct mtd_info *mtd; + int err; + + nand_info = kzalloc(sizeof(struct mxs_nand_info), GFP_KERNEL); + if (!nand_info) { + printf("MXS NAND: Failed to allocate private data\n"); + return -ENOMEM; + } + + /* XXX: Remove u-boot specific access pointers and use io_base instead? */ + nand_info->io_base = dev_request_mem_region(dev, 0); + + err = mxs_nand_alloc_buffers(nand_info); + if (err) + goto err1; + + err = mxs_nand_hw_init(nand_info); + if (err) + goto err2; + + memset(&fake_ecc_layout, 0, sizeof(fake_ecc_layout)); + + /* structures must be linked */ + nand = &nand_info->nand_chip; + mtd = &nand_info->mtd; + mtd->priv = nand; + + nand->priv = nand_info; + nand->options |= NAND_NO_SUBPAGE_WRITE; + + nand->cmd_ctrl = mxs_nand_cmd_ctrl; + + nand->dev_ready = mxs_nand_device_ready; + nand->select_chip = mxs_nand_select_chip; + nand->block_bad = mxs_nand_block_bad; + nand->scan_bbt = mxs_nand_scan_bbt; + + nand->read_byte = mxs_nand_read_byte; + + nand->read_buf = mxs_nand_read_buf; + nand->write_buf = mxs_nand_write_buf; + + nand->ecc.read_page = mxs_nand_ecc_read_page; + nand->ecc.write_page = mxs_nand_ecc_write_page; + nand->ecc.read_oob = mxs_nand_ecc_read_oob; + nand->ecc.write_oob = mxs_nand_ecc_write_oob; + + nand->ecc.layout = &fake_ecc_layout; + nand->ecc.mode = NAND_ECC_HW; + nand->ecc.bytes = 9; + nand->ecc.size = 512; + + /* Scan to find existence of the device */ + err = nand_scan(mtd, 1); + if (err) + goto err2; + + return add_mtd_device(mtd, "nand"); +err2: + free(nand_info->data_buf); + free(nand_info->cmd_buf); +err1: + free(nand_info); + return err; +} + +static struct driver_d mxs_nand_driver = { + .name = "mxs_nand", + .probe = mxs_nand_probe, +}; + +static int __init mxs_nand_init(void) +{ + return register_driver(&mxs_nand_driver); +} + +device_initcall(mxs_nand_init); + +MODULE_AUTHOR("Denx Software Engeneering and Wolfram Sang"); +MODULE_DESCRIPTION("MXS NAND MTD driver"); +MODULE_LICENSE("GPL");