From 4114b17af41272e14939b000ce8f3ed7ba937e3c Mon Sep 17 00:00:00 2001 From: Christophe Kerello Date: Mon, 16 Dec 2019 10:01:55 +0100 Subject: mtd: rawnand: stm32_fmc2: avoid to lock the CPU bus We are currently using nand_soft_waitrdy to poll the status of the NAND flash. FMC2 enables the wait feature bit (this feature is mandatory for the sequencer mode). By enabling this feature, we can't poll the status of the NAND flash, the read status command is stucked in FMC2 pipeline until R/B# signal is high, and locks the CPU bus. To avoid to lock the CPU bus, we poll FMC2 ISR register. This register reports the status of the R/B# signal. Fixes: 2cd457f328c1 ("mtd: rawnand: stm32_fmc2: add STM32 FMC2 NAND flash controller driver") Signed-off-by: Christophe Kerello Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/stm32_fmc2_nand.c | 38 ++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/stm32_fmc2_nand.c b/drivers/mtd/nand/raw/stm32_fmc2_nand.c index 9e63800f768a..3ba73f18841f 100644 --- a/drivers/mtd/nand/raw/stm32_fmc2_nand.c +++ b/drivers/mtd/nand/raw/stm32_fmc2_nand.c @@ -37,6 +37,7 @@ /* Max ECC buffer length */ #define FMC2_MAX_ECC_BUF_LEN (FMC2_BCHDSRS_LEN * FMC2_MAX_SG) +#define FMC2_TIMEOUT_US 1000 #define FMC2_TIMEOUT_MS 1000 /* Timings */ @@ -53,6 +54,8 @@ #define FMC2_PMEM 0x88 #define FMC2_PATT 0x8c #define FMC2_HECCR 0x94 +#define FMC2_ISR 0x184 +#define FMC2_ICR 0x188 #define FMC2_CSQCR 0x200 #define FMC2_CSQCFGR1 0x204 #define FMC2_CSQCFGR2 0x208 @@ -118,6 +121,12 @@ #define FMC2_PATT_ATTHIZ(x) (((x) & 0xff) << 24) #define FMC2_PATT_DEFAULT 0x0a0a0a0a +/* Register: FMC2_ISR */ +#define FMC2_ISR_IHLF BIT(1) + +/* Register: FMC2_ICR */ +#define FMC2_ICR_CIHLF BIT(1) + /* Register: FMC2_CSQCR */ #define FMC2_CSQCR_CSQSTART BIT(0) @@ -1322,6 +1331,31 @@ static void stm32_fmc2_write_data(struct nand_chip *chip, const void *buf, stm32_fmc2_set_buswidth_16(fmc2, true); } +static int stm32_fmc2_waitrdy(struct nand_chip *chip, unsigned long timeout_ms) +{ + struct stm32_fmc2_nfc *fmc2 = to_stm32_nfc(chip->controller); + const struct nand_sdr_timings *timings; + u32 isr, sr; + + /* Check if there is no pending requests to the NAND flash */ + if (readl_relaxed_poll_timeout_atomic(fmc2->io_base + FMC2_SR, sr, + sr & FMC2_SR_NWRF, 1, + FMC2_TIMEOUT_US)) + dev_warn(fmc2->dev, "Waitrdy timeout\n"); + + /* Wait tWB before R/B# signal is low */ + timings = nand_get_sdr_timings(&chip->data_interface); + ndelay(PSEC_TO_NSEC(timings->tWB_max)); + + /* R/B# signal is low, clear high level flag */ + writel_relaxed(FMC2_ICR_CIHLF, fmc2->io_base + FMC2_ICR); + + /* Wait R/B# signal is high */ + return readl_relaxed_poll_timeout_atomic(fmc2->io_base + FMC2_ISR, + isr, isr & FMC2_ISR_IHLF, + 5, 1000 * timeout_ms); +} + static int stm32_fmc2_exec_op(struct nand_chip *chip, const struct nand_operation *op, bool check_only) @@ -1366,8 +1400,8 @@ static int stm32_fmc2_exec_op(struct nand_chip *chip, break; case NAND_OP_WAITRDY_INSTR: - ret = nand_soft_waitrdy(chip, - instr->ctx.waitrdy.timeout_ms); + ret = stm32_fmc2_waitrdy(chip, + instr->ctx.waitrdy.timeout_ms); break; } } -- cgit From 4aa906f1859614842818dc3b4cb5b27bc35961e2 Mon Sep 17 00:00:00 2001 From: Vasyl Gomonovych Date: Wed, 18 Dec 2019 11:57:15 +0200 Subject: mtd: cadence: Fix cast to pointer from integer of different size warning Use dma_addr_t type to pass memory address and control data in DMA descriptor fields memory_pointer and ctrl_data_ptr To fix warning: cast to pointer from integer of different size Signed-off-by: Vasyl Gomonovych Acked-by: Olof Johansson Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/cadence-nand-controller.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/cadence-nand-controller.c b/drivers/mtd/nand/raw/cadence-nand-controller.c index 3a36285a8d8a..f6c7102a1e32 100644 --- a/drivers/mtd/nand/raw/cadence-nand-controller.c +++ b/drivers/mtd/nand/raw/cadence-nand-controller.c @@ -914,8 +914,8 @@ static void cadence_nand_get_caps(struct cdns_nand_ctrl *cdns_ctrl) /* Prepare CDMA descriptor. */ static void cadence_nand_cdma_desc_prepare(struct cdns_nand_ctrl *cdns_ctrl, - char nf_mem, u32 flash_ptr, char *mem_ptr, - char *ctrl_data_ptr, u16 ctype) + char nf_mem, u32 flash_ptr, dma_addr_t mem_ptr, + dma_addr_t ctrl_data_ptr, u16 ctype) { struct cadence_nand_cdma_desc *cdma_desc = cdns_ctrl->cdma_desc; @@ -931,13 +931,13 @@ cadence_nand_cdma_desc_prepare(struct cdns_nand_ctrl *cdns_ctrl, cdma_desc->command_flags |= CDMA_CF_DMA_MASTER; cdma_desc->command_flags |= CDMA_CF_INT; - cdma_desc->memory_pointer = (uintptr_t)mem_ptr; + cdma_desc->memory_pointer = mem_ptr; cdma_desc->status = 0; cdma_desc->sync_flag_pointer = 0; cdma_desc->sync_arguments = 0; cdma_desc->command_type = ctype; - cdma_desc->ctrl_data_ptr = (uintptr_t)ctrl_data_ptr; + cdma_desc->ctrl_data_ptr = ctrl_data_ptr; } static u8 cadence_nand_check_desc_error(struct cdns_nand_ctrl *cdns_ctrl, @@ -1280,8 +1280,7 @@ cadence_nand_cdma_transfer(struct cdns_nand_ctrl *cdns_ctrl, u8 chip_nr, } cadence_nand_cdma_desc_prepare(cdns_ctrl, chip_nr, page, - (void *)dma_buf, (void *)dma_ctrl_dat, - ctype); + dma_buf, dma_ctrl_dat, ctype); status = cadence_nand_cdma_send_and_wait(cdns_ctrl, thread_nr); @@ -1360,7 +1359,7 @@ static int cadence_nand_erase(struct nand_chip *chip, u32 page) cadence_nand_cdma_desc_prepare(cdns_ctrl, cdns_chip->cs[chip->cur_cs], - page, NULL, NULL, + page, 0, 0, CDMA_CT_ERASE); status = cadence_nand_cdma_send_and_wait(cdns_ctrl, thread_nr); if (status) { -- cgit From 44f45994f438b4f4e0ba977b173980268983c60f Mon Sep 17 00:00:00 2001 From: Amir Mahdi Ghorbanian Date: Thu, 2 Jan 2020 12:10:08 -0500 Subject: mtd: onenand: omap2: Fix errors in style Correct mispelling, spacing, and coding style flaws caught by checkpatch.pl script in the Omap2 Onenand driver . Signed-off-by: Amir Mahdi Ghorbanian Signed-off-by: Miquel Raynal --- drivers/mtd/nand/onenand/omap2.c | 11 ++++++----- drivers/mtd/nand/onenand/onenand_base.c | 14 +++++++------- 2 files changed, 13 insertions(+), 12 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/onenand/omap2.c b/drivers/mtd/nand/onenand/omap2.c index edf94ee54ec7..8cb2294bc837 100644 --- a/drivers/mtd/nand/onenand/omap2.c +++ b/drivers/mtd/nand/onenand/omap2.c @@ -148,13 +148,13 @@ static int omap2_onenand_wait(struct mtd_info *mtd, int state) unsigned long timeout; u32 syscfg; - if (state == FL_RESETING || state == FL_PREPARING_ERASE || + if (state == FL_RESETTING || state == FL_PREPARING_ERASE || state == FL_VERIFYING_ERASE) { int i = 21; unsigned int intr_flags = ONENAND_INT_MASTER; switch (state) { - case FL_RESETING: + case FL_RESETTING: intr_flags |= ONENAND_INT_RESET; break; case FL_PREPARING_ERASE: @@ -375,7 +375,7 @@ static int omap2_onenand_read_bufferram(struct mtd_info *mtd, int area, * context fallback to PIO mode. */ if (!virt_addr_valid(buf) || bram_offset & 3 || (size_t)buf & 3 || - count < 384 || in_interrupt() || oops_in_progress ) + count < 384 || in_interrupt() || oops_in_progress) goto out_copy; xtra = count & 3; @@ -422,7 +422,7 @@ static int omap2_onenand_write_bufferram(struct mtd_info *mtd, int area, * context fallback to PIO mode. */ if (!virt_addr_valid(buf) || bram_offset & 3 || (size_t)buf & 3 || - count < 384 || in_interrupt() || oops_in_progress ) + count < 384 || in_interrupt() || oops_in_progress) goto out_copy; dma_src = dma_map_single(dev, buf, count, DMA_TO_DEVICE); @@ -528,7 +528,8 @@ static int omap2_onenand_probe(struct platform_device *pdev) c->gpmc_cs, c->phys_base, c->onenand.base, c->dma_chan ? "DMA" : "PIO"); - if ((r = onenand_scan(&c->mtd, 1)) < 0) + r = onenand_scan(&c->mtd, 1); + if (r < 0) goto err_release_dma; freq = omap2_onenand_get_freq(c->onenand.version_id); diff --git a/drivers/mtd/nand/onenand/onenand_base.c b/drivers/mtd/nand/onenand/onenand_base.c index 77bd32a683e1..85640ee11c86 100644 --- a/drivers/mtd/nand/onenand/onenand_base.c +++ b/drivers/mtd/nand/onenand/onenand_base.c @@ -2853,7 +2853,7 @@ static int onenand_otp_write_oob_nolock(struct mtd_info *mtd, loff_t to, /* Exit OTP access mode */ this->command(mtd, ONENAND_CMD_RESET, 0, 0); - this->wait(mtd, FL_RESETING); + this->wait(mtd, FL_RESETTING); status = this->read_word(this->base + ONENAND_REG_CTRL_STATUS); status &= 0x60; @@ -2924,7 +2924,7 @@ static int do_otp_read(struct mtd_info *mtd, loff_t from, size_t len, /* Exit OTP access mode */ this->command(mtd, ONENAND_CMD_RESET, 0, 0); - this->wait(mtd, FL_RESETING); + this->wait(mtd, FL_RESETTING); return ret; } @@ -2968,7 +2968,7 @@ static int do_otp_write(struct mtd_info *mtd, loff_t to, size_t len, /* Exit OTP access mode */ this->command(mtd, ONENAND_CMD_RESET, 0, 0); - this->wait(mtd, FL_RESETING); + this->wait(mtd, FL_RESETTING); return ret; } @@ -3008,7 +3008,7 @@ static int do_otp_lock(struct mtd_info *mtd, loff_t from, size_t len, /* Exit OTP access mode */ this->command(mtd, ONENAND_CMD_RESET, 0, 0); - this->wait(mtd, FL_RESETING); + this->wait(mtd, FL_RESETTING); } else { ops.mode = MTD_OPS_PLACE_OOB; ops.ooblen = len; @@ -3413,7 +3413,7 @@ static int flexonenand_get_boundary(struct mtd_info *mtd) this->boundary[die] = bdry & FLEXONENAND_PI_MASK; this->command(mtd, ONENAND_CMD_RESET, 0, 0); - this->wait(mtd, FL_RESETING); + this->wait(mtd, FL_RESETTING); printk(KERN_INFO "Die %d boundary: %d%s\n", die, this->boundary[die], locked ? "(Locked)" : "(Unlocked)"); @@ -3635,7 +3635,7 @@ static int flexonenand_set_boundary(struct mtd_info *mtd, int die, ret = this->wait(mtd, FL_WRITING); out: this->write_word(ONENAND_CMD_RESET, this->base + ONENAND_REG_COMMAND); - this->wait(mtd, FL_RESETING); + this->wait(mtd, FL_RESETTING); if (!ret) /* Recalculate device size on boundary change*/ flexonenand_get_size(mtd); @@ -3671,7 +3671,7 @@ static int onenand_chip_probe(struct mtd_info *mtd) /* Reset OneNAND to read default register values */ this->write_word(ONENAND_CMD_RESET, this->base + ONENAND_BOOTRAM); /* Wait reset */ - this->wait(mtd, FL_RESETING); + this->wait(mtd, FL_RESETTING); /* Restore system configuration 1 */ this->write_word(syscfg, this->base + ONENAND_REG_SYS_CFG1); -- cgit From 14ebf24175df0f216256c8483ee2974f35a1a89c Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 3 Jan 2020 17:41:58 +0100 Subject: mtd: onenand: samsung: Fix iomem access with regular memcpy The __iomem memory should be copied with memcpy_fromio. This fixes Sparse warnings like: drivers/mtd/nand/onenand/samsung_mtd.c:678:40: warning: incorrect type in argument 2 (different address spaces) drivers/mtd/nand/onenand/samsung_mtd.c:678:40: expected void const *from drivers/mtd/nand/onenand/samsung_mtd.c:678:40: got void [noderef] *[assigned] p drivers/mtd/nand/onenand/samsung_mtd.c:679:19: warning: incorrect type in assignment (different address spaces) drivers/mtd/nand/onenand/samsung_mtd.c:679:19: expected void [noderef] *[assigned] p drivers/mtd/nand/onenand/samsung_mtd.c:679:19: got unsigned char * Reported-by: kbuild test robot Signed-off-by: Krzysztof Kozlowski Signed-off-by: Miquel Raynal --- drivers/mtd/nand/onenand/samsung_mtd.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/onenand/samsung_mtd.c b/drivers/mtd/nand/onenand/samsung_mtd.c index 55e5536a5850..beb7987e4c2b 100644 --- a/drivers/mtd/nand/onenand/samsung_mtd.c +++ b/drivers/mtd/nand/onenand/samsung_mtd.c @@ -675,12 +675,12 @@ static int s5pc110_read_bufferram(struct mtd_info *mtd, int area, normal: if (count != mtd->writesize) { /* Copy the bufferram to memory to prevent unaligned access */ - memcpy(this->page_buf, p, mtd->writesize); - p = this->page_buf + offset; + memcpy_fromio(this->page_buf, p, mtd->writesize); + memcpy(buffer, this->page_buf + offset, count); + } else { + memcpy_fromio(buffer, p, count); } - memcpy(buffer, p, count); - return 0; } -- cgit From 8bcef0d54067077cf9a6cb129022c77559926e8c Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 7 Jan 2020 10:45:44 +0200 Subject: mtd: onenand: omap2: Pass correct flags for prep_dma_memcpy The commit converting the driver to DMAengine was missing the flags for the memcpy prepare call. It went unnoticed since the omap-dma driver was ignoring them. Fixes: 3ed6a4d1de2c5 (" mtd: onenand: omap2: Convert to use dmaengine for memcp") Reported-by: Aaro Koskinen Signed-off-by: Peter Ujfalusi Tested-by: H. Nikolaus Schaller Tested-by: Aaro Koskinen Signed-off-by: Miquel Raynal --- drivers/mtd/nand/onenand/omap2.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/onenand/omap2.c b/drivers/mtd/nand/onenand/omap2.c index 8cb2294bc837..aa9368bf7a0c 100644 --- a/drivers/mtd/nand/onenand/omap2.c +++ b/drivers/mtd/nand/onenand/omap2.c @@ -328,7 +328,8 @@ static inline int omap2_onenand_dma_transfer(struct omap2_onenand *c, struct dma_async_tx_descriptor *tx; dma_cookie_t cookie; - tx = dmaengine_prep_dma_memcpy(c->dma_chan, dst, src, count, 0); + tx = dmaengine_prep_dma_memcpy(c->dma_chan, dst, src, count, + DMA_CTRL_ACK | DMA_PREP_INTERRUPT); if (!tx) { dev_err(&c->pdev->dev, "Failed to prepare DMA memcpy\n"); return -EIO; -- cgit From de08b5ac10420db597cb24c41b4d8d06cce15ffd Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 7 Jan 2020 22:24:52 +0100 Subject: mtd: sm_ftl: fix NULL pointer warning With gcc -O3, we get a new warning: In file included from arch/arm64/include/asm/processor.h:28, from drivers/mtd/sm_ftl.c:8: In function 'memset', inlined from 'sm_read_sector.constprop' at drivers/mtd/sm_ftl.c:250:3: include/linux/string.h:411:9: error: argument 1 null where non-null expected [-Werror=nonnull] return __builtin_memset(p, c, size); >From all I can tell, this cannot happen (the function is called either with a NULL buffer or with a -1 block number but not both), but adding a check makes it more robust and avoids the warning. Fixes: mmtom ("init/Kconfig: enable -O3 for all arches") Signed-off-by: Arnd Bergmann Signed-off-by: Miquel Raynal --- drivers/mtd/sm_ftl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/sm_ftl.c b/drivers/mtd/sm_ftl.c index 4744bf94ad9a..b9f272408c4d 100644 --- a/drivers/mtd/sm_ftl.c +++ b/drivers/mtd/sm_ftl.c @@ -247,7 +247,8 @@ static int sm_read_sector(struct sm_ftl *ftl, /* FTL can contain -1 entries that are by default filled with bits */ if (block == -1) { - memset(buffer, 0xFF, SM_SECTOR_SIZE); + if (buffer) + memset(buffer, 0xFF, SM_SECTOR_SIZE); return 0; } -- cgit From 82de6a6fb67e16a30ec2f586b1f6976c2d7b4b62 Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Tue, 3 Dec 2019 14:50:01 +0000 Subject: mtd: spi-nor: Fix the writing of the Status Register on micron flashes Micron flashes do not support 16 bit writes on the Status Register. According to micron datasheets, when using the Write Status Register (01h) command, the chip select should be driven LOW and held LOW until the eighth bit of the last data byte has been latched in, after which it must be driven HIGH. If CS is not driven HIGH, the command is not executed, flag status register error bits are not set, and the write enable latch remains set to 1. This fixes the lock operations on micron flashes. Reported-by: John Garry Fixes: 39d1e3340c73 ("mtd: spi-nor: Fix clearing of QE bit on lock()/unlock()") Signed-off-by: Tudor Ambarus Tested-by: John Garry Signed-off-by: Miquel Raynal --- drivers/mtd/spi-nor/spi-nor.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index f4afe123e9dc..aeb3ad2dbfb8 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -4596,6 +4596,7 @@ static void sst_set_default_init(struct spi_nor *nor) static void st_micron_set_default_init(struct spi_nor *nor) { nor->flags |= SNOR_F_HAS_LOCK; + nor->flags &= ~SNOR_F_HAS_16BIT_SR; nor->params.quad_enable = NULL; nor->params.set_4byte = st_micron_set_4byte; } -- cgit From 296a32b54a73898ad8828630423b399b5d1cb3d7 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 20 Nov 2019 21:40:50 +0800 Subject: mtd: onenand: Fix Kconfig indentation Adjust indentation from spaces to tab (+optional two spaces) as in coding style with command like: $ sed -e 's/^ /\t/' -i */Kconfig Signed-off-by: Krzysztof Kozlowski Signed-off-by: Miquel Raynal --- drivers/mtd/nand/onenand/Kconfig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/onenand/Kconfig b/drivers/mtd/nand/onenand/Kconfig index ae0b8fe5b990..ea382fc48432 100644 --- a/drivers/mtd/nand/onenand/Kconfig +++ b/drivers/mtd/nand/onenand/Kconfig @@ -33,12 +33,12 @@ config MTD_ONENAND_OMAP2 Enable dmaengine and gpiolib for better performance. config MTD_ONENAND_SAMSUNG - tristate "OneNAND on Samsung SOC controller support" - depends on ARCH_S3C64XX || ARCH_S5PV210 || ARCH_EXYNOS4 - help - Support for a OneNAND flash device connected to an Samsung SOC. - S3C64XX uses command mapping method. - S5PC110/S5PC210 use generic OneNAND method. + tristate "OneNAND on Samsung SOC controller support" + depends on ARCH_S3C64XX || ARCH_S5PV210 || ARCH_EXYNOS4 + help + Support for a OneNAND flash device connected to an Samsung SOC. + S3C64XX uses command mapping method. + S5PC110/S5PC210 use generic OneNAND method. config MTD_ONENAND_OTP bool "OneNAND OTP Support" -- cgit From 75b3ff79c5656d75b18501c56610f79596dd1441 Mon Sep 17 00:00:00 2001 From: Chen Wandun Date: Fri, 22 Nov 2019 20:08:54 +0800 Subject: mtd: onenand: samsung: remove set but not used variable Fixes gcc '-Wunused-but-set-variable' warning: drivers/mtd/nand/onenand/samsung_mtd.c: In function s3c_onenand_check_lock_status: drivers/mtd/nand/onenand/samsung_mtd.c:731:6: warning: variable tmp set but not used [-Wunused-but-set-variable] Signed-off-by: Chen Wandun Signed-off-by: Miquel Raynal --- drivers/mtd/nand/onenand/samsung_mtd.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/onenand/samsung_mtd.c b/drivers/mtd/nand/onenand/samsung_mtd.c index beb7987e4c2b..d61317a77a84 100644 --- a/drivers/mtd/nand/onenand/samsung_mtd.c +++ b/drivers/mtd/nand/onenand/samsung_mtd.c @@ -728,13 +728,12 @@ static void s3c_onenand_check_lock_status(struct mtd_info *mtd) struct onenand_chip *this = mtd->priv; struct device *dev = &onenand->pdev->dev; unsigned int block, end; - int tmp; end = this->chipsize >> this->erase_shift; for (block = 0; block < end; block++) { unsigned int mem_addr = onenand->mem_addr(block, 0, 0); - tmp = s3c_read_cmd(CMD_MAP_01(onenand, mem_addr)); + s3c_read_cmd(CMD_MAP_01(onenand, mem_addr)); if (s3c_read_reg(INT_ERR_STAT_OFFSET) & LOCKED_BLK) { dev_err(dev, "block %d is write-protected!\n", block); -- cgit From 73b265ae7bdbb5f4ce13b2afc53afa7c14cd0980 Mon Sep 17 00:00:00 2001 From: zhengbin Date: Thu, 28 Nov 2019 11:14:12 +0800 Subject: mtd: rawnand: mpc5121: Remove unneeded semicolon Fixes coccicheck warning: drivers/mtd/nand/raw/mpc5121_nfc.c:441:2-3: Unneeded semicolon Reported-by: Hulk Robot Signed-off-by: zhengbin Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/mpc5121_nfc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/mpc5121_nfc.c b/drivers/mtd/nand/raw/mpc5121_nfc.c index 8b90def6686f..a2fcb739e5f8 100644 --- a/drivers/mtd/nand/raw/mpc5121_nfc.c +++ b/drivers/mtd/nand/raw/mpc5121_nfc.c @@ -438,7 +438,7 @@ static void mpc5121_nfc_copy_spare(struct mtd_info *mtd, uint offset, buffer += blksize; offset += blksize; size -= blksize; - }; + } } /* Copy data from/to NFC main and spare buffers */ -- cgit From 0e7ca83e82d021c928dadf4c13c137d57337540d Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Mon, 9 Dec 2019 14:44:23 -0700 Subject: mtd: onenand_base: Adjust indentation in onenand_read_ops_nolock Clang warns: ../drivers/mtd/nand/onenand/onenand_base.c:1269:3: warning: misleading indentation; statement is not part of the previous 'if' [-Wmisleading-indentation] while (!ret) { ^ ../drivers/mtd/nand/onenand/onenand_base.c:1266:2: note: previous statement is here if (column + thislen > writesize) ^ 1 warning generated. This warning occurs because there is a space before the tab of the while loop. There are spaces at the beginning of a lot of the lines in this block, remove them so that the indentation is consistent with the Linux kernel coding style and clang no longer warns. Fixes: a8de85d55700 ("[MTD] OneNAND: Implement read-while-load") Link: https://github.com/ClangBuiltLinux/linux/issues/794 Signed-off-by: Nathan Chancellor Signed-off-by: Miquel Raynal --- drivers/mtd/nand/onenand/onenand_base.c | 82 ++++++++++++++++----------------- 1 file changed, 41 insertions(+), 41 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/onenand/onenand_base.c b/drivers/mtd/nand/onenand/onenand_base.c index 85640ee11c86..d5326d19b136 100644 --- a/drivers/mtd/nand/onenand/onenand_base.c +++ b/drivers/mtd/nand/onenand/onenand_base.c @@ -1248,44 +1248,44 @@ static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from, stats = mtd->ecc_stats; - /* Read-while-load method */ + /* Read-while-load method */ - /* Do first load to bufferRAM */ - if (read < len) { - if (!onenand_check_bufferram(mtd, from)) { + /* Do first load to bufferRAM */ + if (read < len) { + if (!onenand_check_bufferram(mtd, from)) { this->command(mtd, ONENAND_CMD_READ, from, writesize); - ret = this->wait(mtd, FL_READING); - onenand_update_bufferram(mtd, from, !ret); + ret = this->wait(mtd, FL_READING); + onenand_update_bufferram(mtd, from, !ret); if (mtd_is_eccerr(ret)) ret = 0; - } - } + } + } thislen = min_t(int, writesize, len - read); column = from & (writesize - 1); if (column + thislen > writesize) thislen = writesize - column; - while (!ret) { - /* If there is more to load then start next load */ - from += thislen; - if (read + thislen < len) { + while (!ret) { + /* If there is more to load then start next load */ + from += thislen; + if (read + thislen < len) { this->command(mtd, ONENAND_CMD_READ, from, writesize); - /* - * Chip boundary handling in DDP - * Now we issued chip 1 read and pointed chip 1 + /* + * Chip boundary handling in DDP + * Now we issued chip 1 read and pointed chip 1 * bufferram so we have to point chip 0 bufferram. - */ - if (ONENAND_IS_DDP(this) && - unlikely(from == (this->chipsize >> 1))) { - this->write_word(ONENAND_DDP_CHIP0, this->base + ONENAND_REG_START_ADDRESS2); - boundary = 1; - } else - boundary = 0; - ONENAND_SET_PREV_BUFFERRAM(this); - } - /* While load is going, read from last bufferRAM */ - this->read_bufferram(mtd, ONENAND_DATARAM, buf, column, thislen); + */ + if (ONENAND_IS_DDP(this) && + unlikely(from == (this->chipsize >> 1))) { + this->write_word(ONENAND_DDP_CHIP0, this->base + ONENAND_REG_START_ADDRESS2); + boundary = 1; + } else + boundary = 0; + ONENAND_SET_PREV_BUFFERRAM(this); + } + /* While load is going, read from last bufferRAM */ + this->read_bufferram(mtd, ONENAND_DATARAM, buf, column, thislen); /* Read oob area if needed */ if (oobbuf) { @@ -1301,24 +1301,24 @@ static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from, oobcolumn = 0; } - /* See if we are done */ - read += thislen; - if (read == len) - break; - /* Set up for next read from bufferRAM */ - if (unlikely(boundary)) - this->write_word(ONENAND_DDP_CHIP1, this->base + ONENAND_REG_START_ADDRESS2); - ONENAND_SET_NEXT_BUFFERRAM(this); - buf += thislen; + /* See if we are done */ + read += thislen; + if (read == len) + break; + /* Set up for next read from bufferRAM */ + if (unlikely(boundary)) + this->write_word(ONENAND_DDP_CHIP1, this->base + ONENAND_REG_START_ADDRESS2); + ONENAND_SET_NEXT_BUFFERRAM(this); + buf += thislen; thislen = min_t(int, writesize, len - read); - column = 0; - cond_resched(); - /* Now wait for load */ - ret = this->wait(mtd, FL_READING); - onenand_update_bufferram(mtd, from, !ret); + column = 0; + cond_resched(); + /* Now wait for load */ + ret = this->wait(mtd, FL_READING); + onenand_update_bufferram(mtd, from, !ret); if (mtd_is_eccerr(ret)) ret = 0; - } + } /* * Return success, if no ECC failures, else -EBADMSG -- cgit From 393947e5823fdfe96bd290e5d6311d5b08d03aa7 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 17 Dec 2019 16:56:35 -0800 Subject: mtd: rawnand: brcmnand: Set appropriate DMA mask NAND controllers >= 7.0 with FLASH_DMA support physical addresses up to 40-bit, set an appropriate DMA mask for that purpose. Signed-off-by: Florian Fainelli Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/brcmnand/brcmnand.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.c b/drivers/mtd/nand/raw/brcmnand/brcmnand.c index 1a66b1cd51c0..44518dada75b 100644 --- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c @@ -2635,6 +2635,16 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc) /* initialize the dma version */ brcmnand_flash_dma_revision_init(ctrl); + ret = -EIO; + if (ctrl->nand_version >= 0x0700) + ret = dma_set_mask_and_coherent(&pdev->dev, + DMA_BIT_MASK(40)); + if (ret) + ret = dma_set_mask_and_coherent(&pdev->dev, + DMA_BIT_MASK(32)); + if (ret) + goto err; + /* linked-list and stop on error */ flash_dma_writel(ctrl, FLASH_DMA_MODE, FLASH_DMA_MODE_MASK); flash_dma_writel(ctrl, FLASH_DMA_ERROR_STATUS, 0); -- cgit From 82348201384d4481ab92beecfbca7c195120670e Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Fri, 20 Dec 2019 20:31:51 +0900 Subject: mtd: rawnand: denali_dt: error out if platform has no associated data denali->ecc_caps is a mandatory parameter. If it were left unset, nand_ecc_choose_conf() would end up with NULL pointer access. So, every compatible must be associated with proper denali_dt_data. If of_device_get_match_data() returns NULL, let it fail immediately. Signed-off-by: Masahiro Yamada Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/denali_dt.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/denali_dt.c b/drivers/mtd/nand/raw/denali_dt.c index 8b779a899dcf..276187939689 100644 --- a/drivers/mtd/nand/raw/denali_dt.c +++ b/drivers/mtd/nand/raw/denali_dt.c @@ -118,11 +118,12 @@ static int denali_dt_probe(struct platform_device *pdev) denali = &dt->controller; data = of_device_get_match_data(dev); - if (data) { - denali->revision = data->revision; - denali->caps = data->caps; - denali->ecc_caps = data->ecc_caps; - } + if (WARN_ON(!data)) + return -EINVAL; + + denali->revision = data->revision; + denali->caps = data->caps; + denali->ecc_caps = data->ecc_caps; denali->dev = dev; denali->irq = platform_get_irq(pdev, 0); -- cgit From f5561a7c42d690b51151d955d0a6a80fa3ad6689 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 20 Dec 2019 20:31:52 +0900 Subject: mtd: rawnand: denali_dt: Add support for configuring SPARE_AREA_SKIP_BYTES The SPARE_AREA_SKIP_BYTES register is reset when the controller reset signal is toggled. Yet, this register must be configured to match the content of the NAND OOB area. The current default value is always set to 8 and is programmed into the hardware in case the hardware was not programmed before (e.g. in a bootloader) with a different value. This however does not work when the block is reset properly by Linux. On Altera SoCFPGA CycloneV, ArriaV and Arria10, which are the SoCFPGA platforms which support booting from NAND, the SPARE_AREA_SKIP_BYTES value must be set to 2. On Socionext Uniphier, the value is 8. This patch adds support for preconfiguring the default value and handles the special SoCFPGA case by setting the default to 2 on all SoCFPGA platforms, while retaining the original behavior and default value of 8 on all the other platforms. Signed-off-by: Marek Vasut Cc: Miquel Raynal Cc: Richard Weinberger Cc: Vignesh Raghavendra To: linux-mtd@lists.infradead.org Reviewed-by: Tudor Ambarus Acked-by: Masahiro Yamada Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/denali.c | 13 ++++++++++--- drivers/mtd/nand/raw/denali_dt.c | 5 +++++ 2 files changed, 15 insertions(+), 3 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/denali.c b/drivers/mtd/nand/raw/denali.c index 3102ddbd8abd..b6c463d02167 100644 --- a/drivers/mtd/nand/raw/denali.c +++ b/drivers/mtd/nand/raw/denali.c @@ -1302,14 +1302,21 @@ int denali_init(struct denali_controller *denali) /* * Set how many bytes should be skipped before writing data in OOB. + * If a non-zero value has already been configured, update it in HW. * If a non-zero value has already been set (by firmware or something), * just use it. Otherwise, set the driver's default. */ - denali->oob_skip_bytes = ioread32(denali->reg + SPARE_AREA_SKIP_BYTES); - if (!denali->oob_skip_bytes) { - denali->oob_skip_bytes = DENALI_DEFAULT_OOB_SKIP_BYTES; + if (denali->oob_skip_bytes) { iowrite32(denali->oob_skip_bytes, denali->reg + SPARE_AREA_SKIP_BYTES); + } else { + denali->oob_skip_bytes = + ioread32(denali->reg + SPARE_AREA_SKIP_BYTES); + if (!denali->oob_skip_bytes) { + denali->oob_skip_bytes = DENALI_DEFAULT_OOB_SKIP_BYTES; + iowrite32(denali->oob_skip_bytes, + denali->reg + SPARE_AREA_SKIP_BYTES); + } } iowrite32(0, denali->reg + TRANSFER_SPARE_REG); diff --git a/drivers/mtd/nand/raw/denali_dt.c b/drivers/mtd/nand/raw/denali_dt.c index 276187939689..699255fb2dd8 100644 --- a/drivers/mtd/nand/raw/denali_dt.c +++ b/drivers/mtd/nand/raw/denali_dt.c @@ -27,6 +27,7 @@ struct denali_dt { struct denali_dt_data { unsigned int revision; unsigned int caps; + unsigned int oob_skip_bytes; const struct nand_ecc_caps *ecc_caps; }; @@ -34,6 +35,7 @@ NAND_ECC_CAPS_SINGLE(denali_socfpga_ecc_caps, denali_calc_ecc_bytes, 512, 8, 15); static const struct denali_dt_data denali_socfpga_data = { .caps = DENALI_CAP_HW_ECC_FIXUP, + .oob_skip_bytes = 2, .ecc_caps = &denali_socfpga_ecc_caps, }; @@ -42,6 +44,7 @@ NAND_ECC_CAPS_SINGLE(denali_uniphier_v5a_ecc_caps, denali_calc_ecc_bytes, static const struct denali_dt_data denali_uniphier_v5a_data = { .caps = DENALI_CAP_HW_ECC_FIXUP | DENALI_CAP_DMA_64BIT, + .oob_skip_bytes = 8, .ecc_caps = &denali_uniphier_v5a_ecc_caps, }; @@ -51,6 +54,7 @@ static const struct denali_dt_data denali_uniphier_v5b_data = { .revision = 0x0501, .caps = DENALI_CAP_HW_ECC_FIXUP | DENALI_CAP_DMA_64BIT, + .oob_skip_bytes = 8, .ecc_caps = &denali_uniphier_v5b_ecc_caps, }; @@ -123,6 +127,7 @@ static int denali_dt_probe(struct platform_device *pdev) denali->revision = data->revision; denali->caps = data->caps; + denali->oob_skip_bytes = data->oob_skip_bytes; denali->ecc_caps = data->ecc_caps; denali->dev = dev; -- cgit From 711fafc287e1be25b4420752062f852930e4c1d2 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Fri, 20 Dec 2019 20:31:54 +0900 Subject: mtd: rawnand: denali_dt: add reset controlling According to the Denali NAND Flash Memory Controller User's Guide, this IP has two reset signals. rst_n: reset most of FFs in the controller core reg_rst_n: reset all FFs in the register interface, and in the initialization sequencer This commit supports controlling those reset signals. It is possible to control them separately from the IP point of view although they might be often tied up together in actual SoC integration. The IP spec says, asserting only the reg_rst_n without asserting rst_n will cause unpredictable behavior in the controller. So, the driver deasserts ->rst_reg and ->rst in this order. Another thing that should be kept in mind is the automated initialization sequence (a.k.a. 'bootstrap' process) is kicked off when reg_rst_n is deasserted. When the reset is deasserted, the controller issues a RESET command to the chip select 0, and attempts to read out the chip ID, and further more, ONFI parameters if it is an ONFI-compliant device. Then, the controller sets up the relevant registers based on the detected device parameters. This process might be useful for tiny boot firmware, but is redundant for Linux Kernel because nand_scan_ident() probes devices and sets up parameters accordingly. Rather, this hardware feature is annoying because it ends up with misdetection due to bugs. So, commit 0615e7ad5d52 ("mtd: nand: denali: remove Toshiba and Hynix specific fixup code") changed the driver to not rely on it. However, there is no way to prevent it from running. The IP provides the 'bootstrap_inhibit_init' port to suppress this sequence, but it is usually out of software control, and dependent on SoC implementation. As for the Socionext UniPhier platform, LD4 always enables it. For the later SoCs, the bootstrap sequence runs depending on the boot mode. I added usleep_range() to make the driver wait until the sequence finishes. Otherwise, the driver would fail to detect the chip due to the race between the driver and hardware-controlled sequence. Signed-off-by: Masahiro Yamada Reviewed-by: Philipp Zabel Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/denali_dt.c | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/denali_dt.c b/drivers/mtd/nand/raw/denali_dt.c index 699255fb2dd8..f08740ae282b 100644 --- a/drivers/mtd/nand/raw/denali_dt.c +++ b/drivers/mtd/nand/raw/denali_dt.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -14,6 +15,7 @@ #include #include #include +#include #include "denali.h" @@ -22,6 +24,8 @@ struct denali_dt { struct clk *clk; /* core clock */ struct clk *clk_x; /* bus interface clock */ struct clk *clk_ecc; /* ECC circuit clock */ + struct reset_control *rst; /* core reset */ + struct reset_control *rst_reg; /* register reset */ }; struct denali_dt_data { @@ -157,6 +161,14 @@ static int denali_dt_probe(struct platform_device *pdev) if (IS_ERR(dt->clk_ecc)) return PTR_ERR(dt->clk_ecc); + dt->rst = devm_reset_control_get_optional_shared(dev, "nand"); + if (IS_ERR(dt->rst)) + return PTR_ERR(dt->rst); + + dt->rst_reg = devm_reset_control_get_optional_shared(dev, "reg"); + if (IS_ERR(dt->rst_reg)) + return PTR_ERR(dt->rst_reg); + ret = clk_prepare_enable(dt->clk); if (ret) return ret; @@ -172,10 +184,30 @@ static int denali_dt_probe(struct platform_device *pdev) denali->clk_rate = clk_get_rate(dt->clk); denali->clk_x_rate = clk_get_rate(dt->clk_x); - ret = denali_init(denali); + /* + * Deassert the register reset, and the core reset in this order. + * Deasserting the core reset while the register reset is asserted + * will cause unpredictable behavior in the controller. + */ + ret = reset_control_deassert(dt->rst_reg); if (ret) goto out_disable_clk_ecc; + ret = reset_control_deassert(dt->rst); + if (ret) + goto out_assert_rst_reg; + + /* + * When the reset is deasserted, the initialization sequence is kicked + * (bootstrap process). The driver must wait until it finished. + * Otherwise, it will result in unpredictable behavior. + */ + usleep_range(200, 1000); + + ret = denali_init(denali); + if (ret) + goto out_assert_rst; + for_each_child_of_node(dev->of_node, np) { ret = denali_dt_chip_init(denali, np); if (ret) { @@ -190,6 +222,10 @@ static int denali_dt_probe(struct platform_device *pdev) out_remove_denali: denali_remove(denali); +out_assert_rst: + reset_control_assert(dt->rst); +out_assert_rst_reg: + reset_control_assert(dt->rst_reg); out_disable_clk_ecc: clk_disable_unprepare(dt->clk_ecc); out_disable_clk_x: @@ -205,6 +241,8 @@ static int denali_dt_remove(struct platform_device *pdev) struct denali_dt *dt = platform_get_drvdata(pdev); denali_remove(&dt->controller); + reset_control_assert(dt->rst); + reset_control_assert(dt->rst_reg); clk_disable_unprepare(dt->clk_ecc); clk_disable_unprepare(dt->clk_x); clk_disable_unprepare(dt->clk); -- cgit From a3b839e4e061c60c5f9abf4f3af638f712bf4285 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Fri, 20 Dec 2019 20:31:55 +0900 Subject: mtd: rawnand: denali: remove hard-coded DENALI_DEFAULT_OOB_SKIP_BYTES As commit 0d55c668b218 (mtd: rawnand: denali: set SPARE_AREA_SKIP_BYTES register to 8 if unset") says, there were three solutions discussed: [1] Add a DT property to specify the skipped bytes in OOB [2] Associate the preferred value with compatible [3] Hard-code the default value in the driver At that time, [3] was chosen because I did not have enough information about the other platforms than UniPhier. That commit also says "The preferred value may vary by platform. If so, please trade up to a different solution." My intention was to replace [3] with [2], not keep both [2] and [3]. Now that we have switched to [2] for SOCFPGA's SPARE_AREA_SKIP_BYTES=2, [3] should be removed. This should be OK because denali_pci.c just gets back to the original behavior. Signed-off-by: Masahiro Yamada Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/denali.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/denali.c b/drivers/mtd/nand/raw/denali.c index b6c463d02167..fafd0a0aa8e2 100644 --- a/drivers/mtd/nand/raw/denali.c +++ b/drivers/mtd/nand/raw/denali.c @@ -21,7 +21,6 @@ #include "denali.h" #define DENALI_NAND_NAME "denali-nand" -#define DENALI_DEFAULT_OOB_SKIP_BYTES 8 /* for Indexed Addressing */ #define DENALI_INDEXED_CTRL 0x00 @@ -1302,22 +1301,16 @@ int denali_init(struct denali_controller *denali) /* * Set how many bytes should be skipped before writing data in OOB. - * If a non-zero value has already been configured, update it in HW. - * If a non-zero value has already been set (by firmware or something), - * just use it. Otherwise, set the driver's default. + * If a platform requests a non-zero value, set it to the register. + * Otherwise, read the value out, expecting it has already been set up + * by firmware. */ - if (denali->oob_skip_bytes) { + if (denali->oob_skip_bytes) iowrite32(denali->oob_skip_bytes, denali->reg + SPARE_AREA_SKIP_BYTES); - } else { - denali->oob_skip_bytes = - ioread32(denali->reg + SPARE_AREA_SKIP_BYTES); - if (!denali->oob_skip_bytes) { - denali->oob_skip_bytes = DENALI_DEFAULT_OOB_SKIP_BYTES; - iowrite32(denali->oob_skip_bytes, - denali->reg + SPARE_AREA_SKIP_BYTES); - } - } + else + denali->oob_skip_bytes = ioread32(denali->reg + + SPARE_AREA_SKIP_BYTES); iowrite32(0, denali->reg + TRANSFER_SPARE_REG); iowrite32(GENMASK(denali->nbanks - 1, 0), denali->reg + RB_PIN_ENABLED); -- cgit From 14b292adcc780b2a319862f22a60054fe72e382f Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 29 Dec 2019 19:36:10 +0100 Subject: mtd: onenand: samsung: Fix pointer cast -Wpointer-to-int-cast warnings on 64 bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iomem pointers should be casted to unsigned long to avoid -Wpointer-to-int-cast warnings when compiling on 64-bit platform (e.g. with COMPILE_TEST): drivers/mtd/nand/onenand/samsung_mtd.c: In function ‘s3c_onenand_readw’: drivers/mtd/nand/onenand/samsung_mtd.c:251:6: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast] if ((unsigned int) addr < ONENAND_DATARAM && onenand->bootram_command) { ^ Signed-off-by: Krzysztof Kozlowski Signed-off-by: Miquel Raynal --- drivers/mtd/nand/onenand/samsung_mtd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/onenand/samsung_mtd.c b/drivers/mtd/nand/onenand/samsung_mtd.c index d61317a77a84..2a75fa794e13 100644 --- a/drivers/mtd/nand/onenand/samsung_mtd.c +++ b/drivers/mtd/nand/onenand/samsung_mtd.c @@ -248,7 +248,7 @@ static unsigned short s3c_onenand_readw(void __iomem *addr) } /* BootRAM access control */ - if ((unsigned int) addr < ONENAND_DATARAM && onenand->bootram_command) { + if ((unsigned long)addr < ONENAND_DATARAM && onenand->bootram_command) { if (word_addr == 0) return s3c_read_reg(MANUFACT_ID_OFFSET); if (word_addr == 1) @@ -289,7 +289,7 @@ static void s3c_onenand_writew(unsigned short value, void __iomem *addr) } /* BootRAM access control */ - if ((unsigned int)addr < ONENAND_DATARAM) { + if ((unsigned long)addr < ONENAND_DATARAM) { if (value == ONENAND_CMD_READID) { onenand->bootram_command = 1; return; -- cgit From 440c24535caf9a2ae393a555b2e23854037d65e2 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 29 Dec 2019 19:36:11 +0100 Subject: mtd: onenand: samsung: Fix printing format for size_t on 64-bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Print size_t as %zu to fix -Wformat warnings when compiling on 64-bit platform (e.g. with COMPILE_TEST): drivers/mtd/nand/onenand/samsung_mtd.c: In function ‘s5pc110_read_bufferram’: drivers/mtd/nand/onenand/samsung_mtd.c:661:16: warning: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘size_t {aka long unsigned int}’ [-Wformat=] dev_err(dev, "Couldn't map a %d byte buffer for DMA\n", count); Signed-off-by: Krzysztof Kozlowski Signed-off-by: Miquel Raynal --- drivers/mtd/nand/onenand/samsung_mtd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/onenand/samsung_mtd.c b/drivers/mtd/nand/onenand/samsung_mtd.c index 2a75fa794e13..87b28e397d67 100644 --- a/drivers/mtd/nand/onenand/samsung_mtd.c +++ b/drivers/mtd/nand/onenand/samsung_mtd.c @@ -658,7 +658,7 @@ static int s5pc110_read_bufferram(struct mtd_info *mtd, int area, dma_dst = dma_map_single(dev, buf, count, DMA_FROM_DEVICE); } if (dma_mapping_error(dev, dma_dst)) { - dev_err(dev, "Couldn't map a %d byte buffer for DMA\n", count); + dev_err(dev, "Couldn't map a %zu byte buffer for DMA\n", count); goto normal; } err = s5pc110_dma_ops(dma_dst, dma_src, -- cgit From 05a5a6e57e6aebdab588d47acc8643e566c170fa Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 29 Dec 2019 19:36:12 +0100 Subject: mtd: onenand: Enable compile testing of OMAP and Samsung drivers OMAP and Samsung OneNAND drivers can be compile tested. The OMAP drivers still depends on mach header so limit the compile testing to ARMv7. Signed-off-by: Krzysztof Kozlowski Reported-by: kbuild test robot Signed-off-by: Miquel Raynal --- drivers/mtd/nand/onenand/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/onenand/Kconfig b/drivers/mtd/nand/onenand/Kconfig index ea382fc48432..572b8fe69abb 100644 --- a/drivers/mtd/nand/onenand/Kconfig +++ b/drivers/mtd/nand/onenand/Kconfig @@ -25,7 +25,7 @@ config MTD_ONENAND_GENERIC config MTD_ONENAND_OMAP2 tristate "OneNAND on OMAP2/OMAP3 support" - depends on ARCH_OMAP2 || ARCH_OMAP3 + depends on ARCH_OMAP2 || ARCH_OMAP3 || (COMPILE_TEST && ARM) depends on OF || COMPILE_TEST help Support for a OneNAND flash device connected to an OMAP2/OMAP3 SoC @@ -34,7 +34,7 @@ config MTD_ONENAND_OMAP2 config MTD_ONENAND_SAMSUNG tristate "OneNAND on Samsung SOC controller support" - depends on ARCH_S3C64XX || ARCH_S5PV210 || ARCH_EXYNOS4 + depends on ARCH_S3C64XX || ARCH_S5PV210 || ARCH_EXYNOS4 || COMPILE_TEST help Support for a OneNAND flash device connected to an Samsung SOC. S3C64XX uses command mapping method. -- cgit From f33113b542219448fa02d77ca1c6f4265bd7f130 Mon Sep 17 00:00:00 2001 From: YueHaibing Date: Mon, 30 Dec 2019 11:29:45 +0800 Subject: mtd: sharpslpart: Fix unsigned comparison to zero The unsigned variable log_num is being assigned a return value from the call to sharpsl_nand_get_logical_num that can return -EINVAL. Detected using Coccinelle: ./drivers/mtd/parsers/sharpslpart.c:207:6-13: WARNING: Unsigned expression compared with zero: log_num > 0 Fixes: 8a4580e4d298 ("mtd: sharpslpart: Add sharpslpart partition parser") Signed-off-by: YueHaibing Signed-off-by: Miquel Raynal --- drivers/mtd/parsers/sharpslpart.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/parsers/sharpslpart.c b/drivers/mtd/parsers/sharpslpart.c index e5ea6127ab5a..671a61845bd5 100644 --- a/drivers/mtd/parsers/sharpslpart.c +++ b/drivers/mtd/parsers/sharpslpart.c @@ -165,10 +165,10 @@ static int sharpsl_nand_get_logical_num(u8 *oob) static int sharpsl_nand_init_ftl(struct mtd_info *mtd, struct sharpsl_ftl *ftl) { - unsigned int block_num, log_num, phymax; + unsigned int block_num, phymax; + int i, ret, log_num; loff_t block_adr; u8 *oob; - int i, ret; oob = kzalloc(mtd->oobsize, GFP_KERNEL); if (!oob) -- cgit From db7b6aeca2a7cc500a3f31d8dd1625272771f36b Mon Sep 17 00:00:00 2001 From: YueHaibing Date: Tue, 31 Dec 2019 09:36:48 +0800 Subject: mtd: rawnand: macronix: Use match_string() helper to simplify the code match_string() returns the array index of a matching string. Use it instead of the open-coded implementation. Signed-off-by: YueHaibing Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/nand_macronix.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/nand_macronix.c b/drivers/mtd/nand/raw/nand_macronix.c index 58511aeb0c9a..3ff7ce00cbdb 100644 --- a/drivers/mtd/nand/raw/nand_macronix.c +++ b/drivers/mtd/nand/raw/nand_macronix.c @@ -59,7 +59,7 @@ static void macronix_nand_onfi_init(struct nand_chip *chip) */ static void macronix_nand_fix_broken_get_timings(struct nand_chip *chip) { - unsigned int i; + int i; static const char * const broken_get_timings[] = { "MX30LF1G18AC", "MX30LF1G28AC", @@ -80,12 +80,9 @@ static void macronix_nand_fix_broken_get_timings(struct nand_chip *chip) if (!chip->parameters.supports_set_get_features) return; - for (i = 0; i < ARRAY_SIZE(broken_get_timings); i++) { - if (!strcmp(broken_get_timings[i], chip->parameters.model)) - break; - } - - if (i == ARRAY_SIZE(broken_get_timings)) + i = match_string(broken_get_timings, ARRAY_SIZE(broken_get_timings), + chip->parameters.model); + if (i < 0) return; bitmap_clear(chip->parameters.get_feature_list, -- cgit From 9ee0f956cfbb87b19dfbbb9d588fef282eb5e95e Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 3 Jan 2020 17:14:27 +0100 Subject: mtd: spinand: add support for Toshiba TC58CVG2S0HRAIJ Toshiba recently launched new revisions of their serial SLC NAND series. TC58CVG2S0HRAIJ is a refresh of previous series with minor improvements. Basic parameters are same so lets add support for this new revision. Datasheet: https://business.kioxia.com/info/docget.jsp?did=58601&prodName=TC58CVG2S0HRAIJ Tested under kernel 5.4.7. Signed-off-by: Robert Marko Cc: Luka Perkov Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/toshiba.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/spi/toshiba.c b/drivers/mtd/nand/spi/toshiba.c index 1cb3760ff779..0db5ee4e82af 100644 --- a/drivers/mtd/nand/spi/toshiba.c +++ b/drivers/mtd/nand/spi/toshiba.c @@ -124,6 +124,16 @@ static const struct spinand_info toshiba_spinand_table[] = { 0, SPINAND_ECCINFO(&tc58cxgxsx_ooblayout, tc58cxgxsx_ecc_get_status)), + /* 3.3V 4Gb */ + SPINAND_INFO("TC58CVG2S0", 0xED, + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&tc58cxgxsx_ooblayout, + tc58cxgxsx_ecc_get_status)), /* 1.8V 1Gb */ SPINAND_INFO("TC58CYG0S3", 0xB2, NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), -- cgit From 7a95a72e052a7d15333de932e0a89b4934ce6085 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sat, 4 Jan 2020 12:27:23 -0800 Subject: mtd: rawnand: atmel: switch to using devm_fwnode_gpiod_get() devm_fwnode_get_index_gpiod_from_child() is going away as the name is too unwieldy, let's switch to using the new devm_fwnode_gpiod_get(). Signed-off-by: Dmitry Torokhov Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/atmel/nand-controller.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c index 8d6be90a6fe8..3ba17a98df4d 100644 --- a/drivers/mtd/nand/raw/atmel/nand-controller.c +++ b/drivers/mtd/nand/raw/atmel/nand-controller.c @@ -1578,9 +1578,8 @@ static struct atmel_nand *atmel_nand_create(struct atmel_nand_controller *nc, nand->numcs = numcs; - gpio = devm_fwnode_get_index_gpiod_from_child(nc->dev, "det", 0, - &np->fwnode, GPIOD_IN, - "nand-det"); + gpio = devm_fwnode_gpiod_get(nc->dev, of_fwnode_handle(np), + "det", GPIOD_IN, "nand-det"); if (IS_ERR(gpio) && PTR_ERR(gpio) != -ENOENT) { dev_err(nc->dev, "Failed to get detect gpio (err = %ld)\n", @@ -1624,9 +1623,10 @@ static struct atmel_nand *atmel_nand_create(struct atmel_nand_controller *nc, nand->cs[i].rb.type = ATMEL_NAND_NATIVE_RB; nand->cs[i].rb.id = val; } else { - gpio = devm_fwnode_get_index_gpiod_from_child(nc->dev, - "rb", i, &np->fwnode, - GPIOD_IN, "nand-rb"); + gpio = devm_fwnode_gpiod_get_index(nc->dev, + of_fwnode_handle(np), + "rb", i, GPIOD_IN, + "nand-rb"); if (IS_ERR(gpio) && PTR_ERR(gpio) != -ENOENT) { dev_err(nc->dev, "Failed to get R/B gpio (err = %ld)\n", @@ -1640,10 +1640,10 @@ static struct atmel_nand *atmel_nand_create(struct atmel_nand_controller *nc, } } - gpio = devm_fwnode_get_index_gpiod_from_child(nc->dev, "cs", - i, &np->fwnode, - GPIOD_OUT_HIGH, - "nand-cs"); + gpio = devm_fwnode_gpiod_get_index(nc->dev, + of_fwnode_handle(np), + "cs", i, GPIOD_OUT_HIGH, + "nand-cs"); if (IS_ERR(gpio) && PTR_ERR(gpio) != -ENOENT) { dev_err(nc->dev, "Failed to get CS gpio (err = %ld)\n", -- cgit From 446b6dc8d87482af53fa30d29780a78bbae1e9b8 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 30 Dec 2019 18:31:02 +0100 Subject: mtd: onenand: Use a better name for samsung driver Commit 55ed51fff224 ("{tty: serial, nand: onenand}: samsung: rename to fix build warning") has changed the samsung.c driver to be samsung_mtd.c in order to avoid a conflict in module names with the tty driver. Since the *_mtd suffix is very undescriptive, rename it to onenand_samsung.c, following the folder's convention. Same will be applied to the omap2 onenand driver. Signed-off-by: Miquel Raynal --- drivers/mtd/nand/onenand/Makefile | 2 +- drivers/mtd/nand/onenand/onenand_samsung.c | 1005 ++++++++++++++++++++++++++++ drivers/mtd/nand/onenand/samsung_mtd.c | 1005 ---------------------------- 3 files changed, 1006 insertions(+), 1006 deletions(-) create mode 100644 drivers/mtd/nand/onenand/onenand_samsung.c delete mode 100644 drivers/mtd/nand/onenand/samsung_mtd.c (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/onenand/Makefile b/drivers/mtd/nand/onenand/Makefile index a27b635eb23a..0f2f460adbe4 100644 --- a/drivers/mtd/nand/onenand/Makefile +++ b/drivers/mtd/nand/onenand/Makefile @@ -9,6 +9,6 @@ obj-$(CONFIG_MTD_ONENAND) += onenand.o # Board specific. obj-$(CONFIG_MTD_ONENAND_GENERIC) += generic.o obj-$(CONFIG_MTD_ONENAND_OMAP2) += omap2.o -obj-$(CONFIG_MTD_ONENAND_SAMSUNG) += samsung_mtd.o +obj-$(CONFIG_MTD_ONENAND_SAMSUNG) += onenand_samsung.o onenand-objs = onenand_base.o onenand_bbt.o diff --git a/drivers/mtd/nand/onenand/onenand_samsung.c b/drivers/mtd/nand/onenand/onenand_samsung.c new file mode 100644 index 000000000000..87b28e397d67 --- /dev/null +++ b/drivers/mtd/nand/onenand/onenand_samsung.c @@ -0,0 +1,1005 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung S3C64XX/S5PC1XX OneNAND driver + * + * Copyright © 2008-2010 Samsung Electronics + * Kyungmin Park + * Marek Szyprowski + * + * Implementation: + * S3C64XX: emulate the pseudo BufferRAM + * S5PC110: use DMA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "samsung.h" + +enum soc_type { + TYPE_S3C6400, + TYPE_S3C6410, + TYPE_S5PC110, +}; + +#define ONENAND_ERASE_STATUS 0x00 +#define ONENAND_MULTI_ERASE_SET 0x01 +#define ONENAND_ERASE_START 0x03 +#define ONENAND_UNLOCK_START 0x08 +#define ONENAND_UNLOCK_END 0x09 +#define ONENAND_LOCK_START 0x0A +#define ONENAND_LOCK_END 0x0B +#define ONENAND_LOCK_TIGHT_START 0x0C +#define ONENAND_LOCK_TIGHT_END 0x0D +#define ONENAND_UNLOCK_ALL 0x0E +#define ONENAND_OTP_ACCESS 0x12 +#define ONENAND_SPARE_ACCESS_ONLY 0x13 +#define ONENAND_MAIN_ACCESS_ONLY 0x14 +#define ONENAND_ERASE_VERIFY 0x15 +#define ONENAND_MAIN_SPARE_ACCESS 0x16 +#define ONENAND_PIPELINE_READ 0x4000 + +#define MAP_00 (0x0) +#define MAP_01 (0x1) +#define MAP_10 (0x2) +#define MAP_11 (0x3) + +#define S3C64XX_CMD_MAP_SHIFT 24 + +#define S3C6400_FBA_SHIFT 10 +#define S3C6400_FPA_SHIFT 4 +#define S3C6400_FSA_SHIFT 2 + +#define S3C6410_FBA_SHIFT 12 +#define S3C6410_FPA_SHIFT 6 +#define S3C6410_FSA_SHIFT 4 + +/* S5PC110 specific definitions */ +#define S5PC110_DMA_SRC_ADDR 0x400 +#define S5PC110_DMA_SRC_CFG 0x404 +#define S5PC110_DMA_DST_ADDR 0x408 +#define S5PC110_DMA_DST_CFG 0x40C +#define S5PC110_DMA_TRANS_SIZE 0x414 +#define S5PC110_DMA_TRANS_CMD 0x418 +#define S5PC110_DMA_TRANS_STATUS 0x41C +#define S5PC110_DMA_TRANS_DIR 0x420 +#define S5PC110_INTC_DMA_CLR 0x1004 +#define S5PC110_INTC_ONENAND_CLR 0x1008 +#define S5PC110_INTC_DMA_MASK 0x1024 +#define S5PC110_INTC_ONENAND_MASK 0x1028 +#define S5PC110_INTC_DMA_PEND 0x1044 +#define S5PC110_INTC_ONENAND_PEND 0x1048 +#define S5PC110_INTC_DMA_STATUS 0x1064 +#define S5PC110_INTC_ONENAND_STATUS 0x1068 + +#define S5PC110_INTC_DMA_TD (1 << 24) +#define S5PC110_INTC_DMA_TE (1 << 16) + +#define S5PC110_DMA_CFG_SINGLE (0x0 << 16) +#define S5PC110_DMA_CFG_4BURST (0x2 << 16) +#define S5PC110_DMA_CFG_8BURST (0x3 << 16) +#define S5PC110_DMA_CFG_16BURST (0x4 << 16) + +#define S5PC110_DMA_CFG_INC (0x0 << 8) +#define S5PC110_DMA_CFG_CNT (0x1 << 8) + +#define S5PC110_DMA_CFG_8BIT (0x0 << 0) +#define S5PC110_DMA_CFG_16BIT (0x1 << 0) +#define S5PC110_DMA_CFG_32BIT (0x2 << 0) + +#define S5PC110_DMA_SRC_CFG_READ (S5PC110_DMA_CFG_16BURST | \ + S5PC110_DMA_CFG_INC | \ + S5PC110_DMA_CFG_16BIT) +#define S5PC110_DMA_DST_CFG_READ (S5PC110_DMA_CFG_16BURST | \ + S5PC110_DMA_CFG_INC | \ + S5PC110_DMA_CFG_32BIT) +#define S5PC110_DMA_SRC_CFG_WRITE (S5PC110_DMA_CFG_16BURST | \ + S5PC110_DMA_CFG_INC | \ + S5PC110_DMA_CFG_32BIT) +#define S5PC110_DMA_DST_CFG_WRITE (S5PC110_DMA_CFG_16BURST | \ + S5PC110_DMA_CFG_INC | \ + S5PC110_DMA_CFG_16BIT) + +#define S5PC110_DMA_TRANS_CMD_TDC (0x1 << 18) +#define S5PC110_DMA_TRANS_CMD_TEC (0x1 << 16) +#define S5PC110_DMA_TRANS_CMD_TR (0x1 << 0) + +#define S5PC110_DMA_TRANS_STATUS_TD (0x1 << 18) +#define S5PC110_DMA_TRANS_STATUS_TB (0x1 << 17) +#define S5PC110_DMA_TRANS_STATUS_TE (0x1 << 16) + +#define S5PC110_DMA_DIR_READ 0x0 +#define S5PC110_DMA_DIR_WRITE 0x1 + +struct s3c_onenand { + struct mtd_info *mtd; + struct platform_device *pdev; + enum soc_type type; + void __iomem *base; + void __iomem *ahb_addr; + int bootram_command; + void *page_buf; + void *oob_buf; + unsigned int (*mem_addr)(int fba, int fpa, int fsa); + unsigned int (*cmd_map)(unsigned int type, unsigned int val); + void __iomem *dma_addr; + unsigned long phys_base; + struct completion complete; +}; + +#define CMD_MAP_00(dev, addr) (dev->cmd_map(MAP_00, ((addr) << 1))) +#define CMD_MAP_01(dev, mem_addr) (dev->cmd_map(MAP_01, (mem_addr))) +#define CMD_MAP_10(dev, mem_addr) (dev->cmd_map(MAP_10, (mem_addr))) +#define CMD_MAP_11(dev, addr) (dev->cmd_map(MAP_11, ((addr) << 2))) + +static struct s3c_onenand *onenand; + +static inline int s3c_read_reg(int offset) +{ + return readl(onenand->base + offset); +} + +static inline void s3c_write_reg(int value, int offset) +{ + writel(value, onenand->base + offset); +} + +static inline int s3c_read_cmd(unsigned int cmd) +{ + return readl(onenand->ahb_addr + cmd); +} + +static inline void s3c_write_cmd(int value, unsigned int cmd) +{ + writel(value, onenand->ahb_addr + cmd); +} + +#ifdef SAMSUNG_DEBUG +static void s3c_dump_reg(void) +{ + int i; + + for (i = 0; i < 0x400; i += 0x40) { + printk(KERN_INFO "0x%08X: 0x%08x 0x%08x 0x%08x 0x%08x\n", + (unsigned int) onenand->base + i, + s3c_read_reg(i), s3c_read_reg(i + 0x10), + s3c_read_reg(i + 0x20), s3c_read_reg(i + 0x30)); + } +} +#endif + +static unsigned int s3c64xx_cmd_map(unsigned type, unsigned val) +{ + return (type << S3C64XX_CMD_MAP_SHIFT) | val; +} + +static unsigned int s3c6400_mem_addr(int fba, int fpa, int fsa) +{ + return (fba << S3C6400_FBA_SHIFT) | (fpa << S3C6400_FPA_SHIFT) | + (fsa << S3C6400_FSA_SHIFT); +} + +static unsigned int s3c6410_mem_addr(int fba, int fpa, int fsa) +{ + return (fba << S3C6410_FBA_SHIFT) | (fpa << S3C6410_FPA_SHIFT) | + (fsa << S3C6410_FSA_SHIFT); +} + +static void s3c_onenand_reset(void) +{ + unsigned long timeout = 0x10000; + int stat; + + s3c_write_reg(ONENAND_MEM_RESET_COLD, MEM_RESET_OFFSET); + while (1 && timeout--) { + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + if (stat & RST_CMP) + break; + } + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + s3c_write_reg(stat, INT_ERR_ACK_OFFSET); + + /* Clear interrupt */ + s3c_write_reg(0x0, INT_ERR_ACK_OFFSET); + /* Clear the ECC status */ + s3c_write_reg(0x0, ECC_ERR_STAT_OFFSET); +} + +static unsigned short s3c_onenand_readw(void __iomem *addr) +{ + struct onenand_chip *this = onenand->mtd->priv; + struct device *dev = &onenand->pdev->dev; + int reg = addr - this->base; + int word_addr = reg >> 1; + int value; + + /* It's used for probing time */ + switch (reg) { + case ONENAND_REG_MANUFACTURER_ID: + return s3c_read_reg(MANUFACT_ID_OFFSET); + case ONENAND_REG_DEVICE_ID: + return s3c_read_reg(DEVICE_ID_OFFSET); + case ONENAND_REG_VERSION_ID: + return s3c_read_reg(FLASH_VER_ID_OFFSET); + case ONENAND_REG_DATA_BUFFER_SIZE: + return s3c_read_reg(DATA_BUF_SIZE_OFFSET); + case ONENAND_REG_TECHNOLOGY: + return s3c_read_reg(TECH_OFFSET); + case ONENAND_REG_SYS_CFG1: + return s3c_read_reg(MEM_CFG_OFFSET); + + /* Used at unlock all status */ + case ONENAND_REG_CTRL_STATUS: + return 0; + + case ONENAND_REG_WP_STATUS: + return ONENAND_WP_US; + + default: + break; + } + + /* BootRAM access control */ + if ((unsigned long)addr < ONENAND_DATARAM && onenand->bootram_command) { + if (word_addr == 0) + return s3c_read_reg(MANUFACT_ID_OFFSET); + if (word_addr == 1) + return s3c_read_reg(DEVICE_ID_OFFSET); + if (word_addr == 2) + return s3c_read_reg(FLASH_VER_ID_OFFSET); + } + + value = s3c_read_cmd(CMD_MAP_11(onenand, word_addr)) & 0xffff; + dev_info(dev, "%s: Illegal access at reg 0x%x, value 0x%x\n", __func__, + word_addr, value); + return value; +} + +static void s3c_onenand_writew(unsigned short value, void __iomem *addr) +{ + struct onenand_chip *this = onenand->mtd->priv; + struct device *dev = &onenand->pdev->dev; + unsigned int reg = addr - this->base; + unsigned int word_addr = reg >> 1; + + /* It's used for probing time */ + switch (reg) { + case ONENAND_REG_SYS_CFG1: + s3c_write_reg(value, MEM_CFG_OFFSET); + return; + + case ONENAND_REG_START_ADDRESS1: + case ONENAND_REG_START_ADDRESS2: + return; + + /* Lock/lock-tight/unlock/unlock_all */ + case ONENAND_REG_START_BLOCK_ADDRESS: + return; + + default: + break; + } + + /* BootRAM access control */ + if ((unsigned long)addr < ONENAND_DATARAM) { + if (value == ONENAND_CMD_READID) { + onenand->bootram_command = 1; + return; + } + if (value == ONENAND_CMD_RESET) { + s3c_write_reg(ONENAND_MEM_RESET_COLD, MEM_RESET_OFFSET); + onenand->bootram_command = 0; + return; + } + } + + dev_info(dev, "%s: Illegal access at reg 0x%x, value 0x%x\n", __func__, + word_addr, value); + + s3c_write_cmd(value, CMD_MAP_11(onenand, word_addr)); +} + +static int s3c_onenand_wait(struct mtd_info *mtd, int state) +{ + struct device *dev = &onenand->pdev->dev; + unsigned int flags = INT_ACT; + unsigned int stat, ecc; + unsigned long timeout; + + switch (state) { + case FL_READING: + flags |= BLK_RW_CMP | LOAD_CMP; + break; + case FL_WRITING: + flags |= BLK_RW_CMP | PGM_CMP; + break; + case FL_ERASING: + flags |= BLK_RW_CMP | ERS_CMP; + break; + case FL_LOCKING: + flags |= BLK_RW_CMP; + break; + default: + break; + } + + /* The 20 msec is enough */ + timeout = jiffies + msecs_to_jiffies(20); + while (time_before(jiffies, timeout)) { + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + if (stat & flags) + break; + + if (state != FL_READING) + cond_resched(); + } + /* To get correct interrupt status in timeout case */ + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + s3c_write_reg(stat, INT_ERR_ACK_OFFSET); + + /* + * In the Spec. it checks the controller status first + * However if you get the correct information in case of + * power off recovery (POR) test, it should read ECC status first + */ + if (stat & LOAD_CMP) { + ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET); + if (ecc & ONENAND_ECC_4BIT_UNCORRECTABLE) { + dev_info(dev, "%s: ECC error = 0x%04x\n", __func__, + ecc); + mtd->ecc_stats.failed++; + return -EBADMSG; + } + } + + if (stat & (LOCKED_BLK | ERS_FAIL | PGM_FAIL | LD_FAIL_ECC_ERR)) { + dev_info(dev, "%s: controller error = 0x%04x\n", __func__, + stat); + if (stat & LOCKED_BLK) + dev_info(dev, "%s: it's locked error = 0x%04x\n", + __func__, stat); + + return -EIO; + } + + return 0; +} + +static int s3c_onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, + size_t len) +{ + struct onenand_chip *this = mtd->priv; + unsigned int *m, *s; + int fba, fpa, fsa = 0; + unsigned int mem_addr, cmd_map_01, cmd_map_10; + int i, mcount, scount; + int index; + + fba = (int) (addr >> this->erase_shift); + fpa = (int) (addr >> this->page_shift); + fpa &= this->page_mask; + + mem_addr = onenand->mem_addr(fba, fpa, fsa); + cmd_map_01 = CMD_MAP_01(onenand, mem_addr); + cmd_map_10 = CMD_MAP_10(onenand, mem_addr); + + switch (cmd) { + case ONENAND_CMD_READ: + case ONENAND_CMD_READOOB: + case ONENAND_CMD_BUFFERRAM: + ONENAND_SET_NEXT_BUFFERRAM(this); + default: + break; + } + + index = ONENAND_CURRENT_BUFFERRAM(this); + + /* + * Emulate Two BufferRAMs and access with 4 bytes pointer + */ + m = onenand->page_buf; + s = onenand->oob_buf; + + if (index) { + m += (this->writesize >> 2); + s += (mtd->oobsize >> 2); + } + + mcount = mtd->writesize >> 2; + scount = mtd->oobsize >> 2; + + switch (cmd) { + case ONENAND_CMD_READ: + /* Main */ + for (i = 0; i < mcount; i++) + *m++ = s3c_read_cmd(cmd_map_01); + return 0; + + case ONENAND_CMD_READOOB: + s3c_write_reg(TSRF, TRANS_SPARE_OFFSET); + /* Main */ + for (i = 0; i < mcount; i++) + *m++ = s3c_read_cmd(cmd_map_01); + + /* Spare */ + for (i = 0; i < scount; i++) + *s++ = s3c_read_cmd(cmd_map_01); + + s3c_write_reg(0, TRANS_SPARE_OFFSET); + return 0; + + case ONENAND_CMD_PROG: + /* Main */ + for (i = 0; i < mcount; i++) + s3c_write_cmd(*m++, cmd_map_01); + return 0; + + case ONENAND_CMD_PROGOOB: + s3c_write_reg(TSRF, TRANS_SPARE_OFFSET); + + /* Main - dummy write */ + for (i = 0; i < mcount; i++) + s3c_write_cmd(0xffffffff, cmd_map_01); + + /* Spare */ + for (i = 0; i < scount; i++) + s3c_write_cmd(*s++, cmd_map_01); + + s3c_write_reg(0, TRANS_SPARE_OFFSET); + return 0; + + case ONENAND_CMD_UNLOCK_ALL: + s3c_write_cmd(ONENAND_UNLOCK_ALL, cmd_map_10); + return 0; + + case ONENAND_CMD_ERASE: + s3c_write_cmd(ONENAND_ERASE_START, cmd_map_10); + return 0; + + default: + break; + } + + return 0; +} + +static unsigned char *s3c_get_bufferram(struct mtd_info *mtd, int area) +{ + struct onenand_chip *this = mtd->priv; + int index = ONENAND_CURRENT_BUFFERRAM(this); + unsigned char *p; + + if (area == ONENAND_DATARAM) { + p = onenand->page_buf; + if (index == 1) + p += this->writesize; + } else { + p = onenand->oob_buf; + if (index == 1) + p += mtd->oobsize; + } + + return p; +} + +static int onenand_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, + size_t count) +{ + unsigned char *p; + + p = s3c_get_bufferram(mtd, area); + memcpy(buffer, p + offset, count); + return 0; +} + +static int onenand_write_bufferram(struct mtd_info *mtd, int area, + const unsigned char *buffer, int offset, + size_t count) +{ + unsigned char *p; + + p = s3c_get_bufferram(mtd, area); + memcpy(p + offset, buffer, count); + return 0; +} + +static int (*s5pc110_dma_ops)(dma_addr_t dst, dma_addr_t src, size_t count, int direction); + +static int s5pc110_dma_poll(dma_addr_t dst, dma_addr_t src, size_t count, int direction) +{ + void __iomem *base = onenand->dma_addr; + int status; + unsigned long timeout; + + writel(src, base + S5PC110_DMA_SRC_ADDR); + writel(dst, base + S5PC110_DMA_DST_ADDR); + + if (direction == S5PC110_DMA_DIR_READ) { + writel(S5PC110_DMA_SRC_CFG_READ, base + S5PC110_DMA_SRC_CFG); + writel(S5PC110_DMA_DST_CFG_READ, base + S5PC110_DMA_DST_CFG); + } else { + writel(S5PC110_DMA_SRC_CFG_WRITE, base + S5PC110_DMA_SRC_CFG); + writel(S5PC110_DMA_DST_CFG_WRITE, base + S5PC110_DMA_DST_CFG); + } + + writel(count, base + S5PC110_DMA_TRANS_SIZE); + writel(direction, base + S5PC110_DMA_TRANS_DIR); + + writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD); + + /* + * There's no exact timeout values at Spec. + * In real case it takes under 1 msec. + * So 20 msecs are enough. + */ + timeout = jiffies + msecs_to_jiffies(20); + + do { + status = readl(base + S5PC110_DMA_TRANS_STATUS); + if (status & S5PC110_DMA_TRANS_STATUS_TE) { + writel(S5PC110_DMA_TRANS_CMD_TEC, + base + S5PC110_DMA_TRANS_CMD); + return -EIO; + } + } while (!(status & S5PC110_DMA_TRANS_STATUS_TD) && + time_before(jiffies, timeout)); + + writel(S5PC110_DMA_TRANS_CMD_TDC, base + S5PC110_DMA_TRANS_CMD); + + return 0; +} + +static irqreturn_t s5pc110_onenand_irq(int irq, void *data) +{ + void __iomem *base = onenand->dma_addr; + int status, cmd = 0; + + status = readl(base + S5PC110_INTC_DMA_STATUS); + + if (likely(status & S5PC110_INTC_DMA_TD)) + cmd = S5PC110_DMA_TRANS_CMD_TDC; + + if (unlikely(status & S5PC110_INTC_DMA_TE)) + cmd = S5PC110_DMA_TRANS_CMD_TEC; + + writel(cmd, base + S5PC110_DMA_TRANS_CMD); + writel(status, base + S5PC110_INTC_DMA_CLR); + + if (!onenand->complete.done) + complete(&onenand->complete); + + return IRQ_HANDLED; +} + +static int s5pc110_dma_irq(dma_addr_t dst, dma_addr_t src, size_t count, int direction) +{ + void __iomem *base = onenand->dma_addr; + int status; + + status = readl(base + S5PC110_INTC_DMA_MASK); + if (status) { + status &= ~(S5PC110_INTC_DMA_TD | S5PC110_INTC_DMA_TE); + writel(status, base + S5PC110_INTC_DMA_MASK); + } + + writel(src, base + S5PC110_DMA_SRC_ADDR); + writel(dst, base + S5PC110_DMA_DST_ADDR); + + if (direction == S5PC110_DMA_DIR_READ) { + writel(S5PC110_DMA_SRC_CFG_READ, base + S5PC110_DMA_SRC_CFG); + writel(S5PC110_DMA_DST_CFG_READ, base + S5PC110_DMA_DST_CFG); + } else { + writel(S5PC110_DMA_SRC_CFG_WRITE, base + S5PC110_DMA_SRC_CFG); + writel(S5PC110_DMA_DST_CFG_WRITE, base + S5PC110_DMA_DST_CFG); + } + + writel(count, base + S5PC110_DMA_TRANS_SIZE); + writel(direction, base + S5PC110_DMA_TRANS_DIR); + + writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD); + + wait_for_completion_timeout(&onenand->complete, msecs_to_jiffies(20)); + + return 0; +} + +static int s5pc110_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, size_t count) +{ + struct onenand_chip *this = mtd->priv; + void __iomem *p; + void *buf = (void *) buffer; + dma_addr_t dma_src, dma_dst; + int err, ofs, page_dma = 0; + struct device *dev = &onenand->pdev->dev; + + p = this->base + area; + if (ONENAND_CURRENT_BUFFERRAM(this)) { + if (area == ONENAND_DATARAM) + p += this->writesize; + else + p += mtd->oobsize; + } + + if (offset & 3 || (size_t) buf & 3 || + !onenand->dma_addr || count != mtd->writesize) + goto normal; + + /* Handle vmalloc address */ + if (buf >= high_memory) { + struct page *page; + + if (((size_t) buf & PAGE_MASK) != + ((size_t) (buf + count - 1) & PAGE_MASK)) + goto normal; + page = vmalloc_to_page(buf); + if (!page) + goto normal; + + /* Page offset */ + ofs = ((size_t) buf & ~PAGE_MASK); + page_dma = 1; + + /* DMA routine */ + dma_src = onenand->phys_base + (p - this->base); + dma_dst = dma_map_page(dev, page, ofs, count, DMA_FROM_DEVICE); + } else { + /* DMA routine */ + dma_src = onenand->phys_base + (p - this->base); + dma_dst = dma_map_single(dev, buf, count, DMA_FROM_DEVICE); + } + if (dma_mapping_error(dev, dma_dst)) { + dev_err(dev, "Couldn't map a %zu byte buffer for DMA\n", count); + goto normal; + } + err = s5pc110_dma_ops(dma_dst, dma_src, + count, S5PC110_DMA_DIR_READ); + + if (page_dma) + dma_unmap_page(dev, dma_dst, count, DMA_FROM_DEVICE); + else + dma_unmap_single(dev, dma_dst, count, DMA_FROM_DEVICE); + + if (!err) + return 0; + +normal: + if (count != mtd->writesize) { + /* Copy the bufferram to memory to prevent unaligned access */ + memcpy_fromio(this->page_buf, p, mtd->writesize); + memcpy(buffer, this->page_buf + offset, count); + } else { + memcpy_fromio(buffer, p, count); + } + + return 0; +} + +static int s5pc110_chip_probe(struct mtd_info *mtd) +{ + /* Now just return 0 */ + return 0; +} + +static int s3c_onenand_bbt_wait(struct mtd_info *mtd, int state) +{ + unsigned int flags = INT_ACT | LOAD_CMP; + unsigned int stat; + unsigned long timeout; + + /* The 20 msec is enough */ + timeout = jiffies + msecs_to_jiffies(20); + while (time_before(jiffies, timeout)) { + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + if (stat & flags) + break; + } + /* To get correct interrupt status in timeout case */ + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + s3c_write_reg(stat, INT_ERR_ACK_OFFSET); + + if (stat & LD_FAIL_ECC_ERR) { + s3c_onenand_reset(); + return ONENAND_BBT_READ_ERROR; + } + + if (stat & LOAD_CMP) { + int ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET); + if (ecc & ONENAND_ECC_4BIT_UNCORRECTABLE) { + s3c_onenand_reset(); + return ONENAND_BBT_READ_ERROR; + } + } + + return 0; +} + +static void s3c_onenand_check_lock_status(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + struct device *dev = &onenand->pdev->dev; + unsigned int block, end; + + end = this->chipsize >> this->erase_shift; + + for (block = 0; block < end; block++) { + unsigned int mem_addr = onenand->mem_addr(block, 0, 0); + s3c_read_cmd(CMD_MAP_01(onenand, mem_addr)); + + if (s3c_read_reg(INT_ERR_STAT_OFFSET) & LOCKED_BLK) { + dev_err(dev, "block %d is write-protected!\n", block); + s3c_write_reg(LOCKED_BLK, INT_ERR_ACK_OFFSET); + } + } +} + +static void s3c_onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs, + size_t len, int cmd) +{ + struct onenand_chip *this = mtd->priv; + int start, end, start_mem_addr, end_mem_addr; + + start = ofs >> this->erase_shift; + start_mem_addr = onenand->mem_addr(start, 0, 0); + end = start + (len >> this->erase_shift) - 1; + end_mem_addr = onenand->mem_addr(end, 0, 0); + + if (cmd == ONENAND_CMD_LOCK) { + s3c_write_cmd(ONENAND_LOCK_START, CMD_MAP_10(onenand, + start_mem_addr)); + s3c_write_cmd(ONENAND_LOCK_END, CMD_MAP_10(onenand, + end_mem_addr)); + } else { + s3c_write_cmd(ONENAND_UNLOCK_START, CMD_MAP_10(onenand, + start_mem_addr)); + s3c_write_cmd(ONENAND_UNLOCK_END, CMD_MAP_10(onenand, + end_mem_addr)); + } + + this->wait(mtd, FL_LOCKING); +} + +static void s3c_unlock_all(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + loff_t ofs = 0; + size_t len = this->chipsize; + + if (this->options & ONENAND_HAS_UNLOCK_ALL) { + /* Write unlock command */ + this->command(mtd, ONENAND_CMD_UNLOCK_ALL, 0, 0); + + /* No need to check return value */ + this->wait(mtd, FL_LOCKING); + + /* Workaround for all block unlock in DDP */ + if (!ONENAND_IS_DDP(this)) { + s3c_onenand_check_lock_status(mtd); + return; + } + + /* All blocks on another chip */ + ofs = this->chipsize >> 1; + len = this->chipsize >> 1; + } + + s3c_onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK); + + s3c_onenand_check_lock_status(mtd); +} + +static void s3c_onenand_setup(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + + onenand->mtd = mtd; + + if (onenand->type == TYPE_S3C6400) { + onenand->mem_addr = s3c6400_mem_addr; + onenand->cmd_map = s3c64xx_cmd_map; + } else if (onenand->type == TYPE_S3C6410) { + onenand->mem_addr = s3c6410_mem_addr; + onenand->cmd_map = s3c64xx_cmd_map; + } else if (onenand->type == TYPE_S5PC110) { + /* Use generic onenand functions */ + this->read_bufferram = s5pc110_read_bufferram; + this->chip_probe = s5pc110_chip_probe; + return; + } else { + BUG(); + } + + this->read_word = s3c_onenand_readw; + this->write_word = s3c_onenand_writew; + + this->wait = s3c_onenand_wait; + this->bbt_wait = s3c_onenand_bbt_wait; + this->unlock_all = s3c_unlock_all; + this->command = s3c_onenand_command; + + this->read_bufferram = onenand_read_bufferram; + this->write_bufferram = onenand_write_bufferram; +} + +static int s3c_onenand_probe(struct platform_device *pdev) +{ + struct onenand_platform_data *pdata; + struct onenand_chip *this; + struct mtd_info *mtd; + struct resource *r; + int size, err; + + pdata = dev_get_platdata(&pdev->dev); + /* No need to check pdata. the platform data is optional */ + + size = sizeof(struct mtd_info) + sizeof(struct onenand_chip); + mtd = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); + if (!mtd) + return -ENOMEM; + + onenand = devm_kzalloc(&pdev->dev, sizeof(struct s3c_onenand), + GFP_KERNEL); + if (!onenand) + return -ENOMEM; + + this = (struct onenand_chip *) &mtd[1]; + mtd->priv = this; + mtd->dev.parent = &pdev->dev; + onenand->pdev = pdev; + onenand->type = platform_get_device_id(pdev)->driver_data; + + s3c_onenand_setup(mtd); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + onenand->base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(onenand->base)) + return PTR_ERR(onenand->base); + + onenand->phys_base = r->start; + + /* Set onenand_chip also */ + this->base = onenand->base; + + /* Use runtime badblock check */ + this->options |= ONENAND_SKIP_UNLOCK_CHECK; + + if (onenand->type != TYPE_S5PC110) { + r = platform_get_resource(pdev, IORESOURCE_MEM, 1); + onenand->ahb_addr = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(onenand->ahb_addr)) + return PTR_ERR(onenand->ahb_addr); + + /* Allocate 4KiB BufferRAM */ + onenand->page_buf = devm_kzalloc(&pdev->dev, SZ_4K, + GFP_KERNEL); + if (!onenand->page_buf) + return -ENOMEM; + + /* Allocate 128 SpareRAM */ + onenand->oob_buf = devm_kzalloc(&pdev->dev, 128, GFP_KERNEL); + if (!onenand->oob_buf) + return -ENOMEM; + + /* S3C doesn't handle subpage write */ + mtd->subpage_sft = 0; + this->subpagesize = mtd->writesize; + + } else { /* S5PC110 */ + r = platform_get_resource(pdev, IORESOURCE_MEM, 1); + onenand->dma_addr = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(onenand->dma_addr)) + return PTR_ERR(onenand->dma_addr); + + s5pc110_dma_ops = s5pc110_dma_poll; + /* Interrupt support */ + r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (r) { + init_completion(&onenand->complete); + s5pc110_dma_ops = s5pc110_dma_irq; + err = devm_request_irq(&pdev->dev, r->start, + s5pc110_onenand_irq, + IRQF_SHARED, "onenand", + &onenand); + if (err) { + dev_err(&pdev->dev, "failed to get irq\n"); + return err; + } + } + } + + err = onenand_scan(mtd, 1); + if (err) + return err; + + if (onenand->type != TYPE_S5PC110) { + /* S3C doesn't handle subpage write */ + mtd->subpage_sft = 0; + this->subpagesize = mtd->writesize; + } + + if (s3c_read_reg(MEM_CFG_OFFSET) & ONENAND_SYS_CFG1_SYNC_READ) + dev_info(&onenand->pdev->dev, "OneNAND Sync. Burst Read enabled\n"); + + err = mtd_device_register(mtd, pdata ? pdata->parts : NULL, + pdata ? pdata->nr_parts : 0); + if (err) { + dev_err(&pdev->dev, "failed to parse partitions and register the MTD device\n"); + onenand_release(mtd); + return err; + } + + platform_set_drvdata(pdev, mtd); + + return 0; +} + +static int s3c_onenand_remove(struct platform_device *pdev) +{ + struct mtd_info *mtd = platform_get_drvdata(pdev); + + onenand_release(mtd); + + return 0; +} + +static int s3c_pm_ops_suspend(struct device *dev) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + struct onenand_chip *this = mtd->priv; + + this->wait(mtd, FL_PM_SUSPENDED); + return 0; +} + +static int s3c_pm_ops_resume(struct device *dev) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + struct onenand_chip *this = mtd->priv; + + this->unlock_all(mtd); + return 0; +} + +static const struct dev_pm_ops s3c_pm_ops = { + .suspend = s3c_pm_ops_suspend, + .resume = s3c_pm_ops_resume, +}; + +static const struct platform_device_id s3c_onenand_driver_ids[] = { + { + .name = "s3c6400-onenand", + .driver_data = TYPE_S3C6400, + }, { + .name = "s3c6410-onenand", + .driver_data = TYPE_S3C6410, + }, { + .name = "s5pc110-onenand", + .driver_data = TYPE_S5PC110, + }, { }, +}; +MODULE_DEVICE_TABLE(platform, s3c_onenand_driver_ids); + +static struct platform_driver s3c_onenand_driver = { + .driver = { + .name = "samsung-onenand", + .pm = &s3c_pm_ops, + }, + .id_table = s3c_onenand_driver_ids, + .probe = s3c_onenand_probe, + .remove = s3c_onenand_remove, +}; + +module_platform_driver(s3c_onenand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kyungmin Park "); +MODULE_DESCRIPTION("Samsung OneNAND controller support"); diff --git a/drivers/mtd/nand/onenand/samsung_mtd.c b/drivers/mtd/nand/onenand/samsung_mtd.c deleted file mode 100644 index 87b28e397d67..000000000000 --- a/drivers/mtd/nand/onenand/samsung_mtd.c +++ /dev/null @@ -1,1005 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Samsung S3C64XX/S5PC1XX OneNAND driver - * - * Copyright © 2008-2010 Samsung Electronics - * Kyungmin Park - * Marek Szyprowski - * - * Implementation: - * S3C64XX: emulate the pseudo BufferRAM - * S5PC110: use DMA - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "samsung.h" - -enum soc_type { - TYPE_S3C6400, - TYPE_S3C6410, - TYPE_S5PC110, -}; - -#define ONENAND_ERASE_STATUS 0x00 -#define ONENAND_MULTI_ERASE_SET 0x01 -#define ONENAND_ERASE_START 0x03 -#define ONENAND_UNLOCK_START 0x08 -#define ONENAND_UNLOCK_END 0x09 -#define ONENAND_LOCK_START 0x0A -#define ONENAND_LOCK_END 0x0B -#define ONENAND_LOCK_TIGHT_START 0x0C -#define ONENAND_LOCK_TIGHT_END 0x0D -#define ONENAND_UNLOCK_ALL 0x0E -#define ONENAND_OTP_ACCESS 0x12 -#define ONENAND_SPARE_ACCESS_ONLY 0x13 -#define ONENAND_MAIN_ACCESS_ONLY 0x14 -#define ONENAND_ERASE_VERIFY 0x15 -#define ONENAND_MAIN_SPARE_ACCESS 0x16 -#define ONENAND_PIPELINE_READ 0x4000 - -#define MAP_00 (0x0) -#define MAP_01 (0x1) -#define MAP_10 (0x2) -#define MAP_11 (0x3) - -#define S3C64XX_CMD_MAP_SHIFT 24 - -#define S3C6400_FBA_SHIFT 10 -#define S3C6400_FPA_SHIFT 4 -#define S3C6400_FSA_SHIFT 2 - -#define S3C6410_FBA_SHIFT 12 -#define S3C6410_FPA_SHIFT 6 -#define S3C6410_FSA_SHIFT 4 - -/* S5PC110 specific definitions */ -#define S5PC110_DMA_SRC_ADDR 0x400 -#define S5PC110_DMA_SRC_CFG 0x404 -#define S5PC110_DMA_DST_ADDR 0x408 -#define S5PC110_DMA_DST_CFG 0x40C -#define S5PC110_DMA_TRANS_SIZE 0x414 -#define S5PC110_DMA_TRANS_CMD 0x418 -#define S5PC110_DMA_TRANS_STATUS 0x41C -#define S5PC110_DMA_TRANS_DIR 0x420 -#define S5PC110_INTC_DMA_CLR 0x1004 -#define S5PC110_INTC_ONENAND_CLR 0x1008 -#define S5PC110_INTC_DMA_MASK 0x1024 -#define S5PC110_INTC_ONENAND_MASK 0x1028 -#define S5PC110_INTC_DMA_PEND 0x1044 -#define S5PC110_INTC_ONENAND_PEND 0x1048 -#define S5PC110_INTC_DMA_STATUS 0x1064 -#define S5PC110_INTC_ONENAND_STATUS 0x1068 - -#define S5PC110_INTC_DMA_TD (1 << 24) -#define S5PC110_INTC_DMA_TE (1 << 16) - -#define S5PC110_DMA_CFG_SINGLE (0x0 << 16) -#define S5PC110_DMA_CFG_4BURST (0x2 << 16) -#define S5PC110_DMA_CFG_8BURST (0x3 << 16) -#define S5PC110_DMA_CFG_16BURST (0x4 << 16) - -#define S5PC110_DMA_CFG_INC (0x0 << 8) -#define S5PC110_DMA_CFG_CNT (0x1 << 8) - -#define S5PC110_DMA_CFG_8BIT (0x0 << 0) -#define S5PC110_DMA_CFG_16BIT (0x1 << 0) -#define S5PC110_DMA_CFG_32BIT (0x2 << 0) - -#define S5PC110_DMA_SRC_CFG_READ (S5PC110_DMA_CFG_16BURST | \ - S5PC110_DMA_CFG_INC | \ - S5PC110_DMA_CFG_16BIT) -#define S5PC110_DMA_DST_CFG_READ (S5PC110_DMA_CFG_16BURST | \ - S5PC110_DMA_CFG_INC | \ - S5PC110_DMA_CFG_32BIT) -#define S5PC110_DMA_SRC_CFG_WRITE (S5PC110_DMA_CFG_16BURST | \ - S5PC110_DMA_CFG_INC | \ - S5PC110_DMA_CFG_32BIT) -#define S5PC110_DMA_DST_CFG_WRITE (S5PC110_DMA_CFG_16BURST | \ - S5PC110_DMA_CFG_INC | \ - S5PC110_DMA_CFG_16BIT) - -#define S5PC110_DMA_TRANS_CMD_TDC (0x1 << 18) -#define S5PC110_DMA_TRANS_CMD_TEC (0x1 << 16) -#define S5PC110_DMA_TRANS_CMD_TR (0x1 << 0) - -#define S5PC110_DMA_TRANS_STATUS_TD (0x1 << 18) -#define S5PC110_DMA_TRANS_STATUS_TB (0x1 << 17) -#define S5PC110_DMA_TRANS_STATUS_TE (0x1 << 16) - -#define S5PC110_DMA_DIR_READ 0x0 -#define S5PC110_DMA_DIR_WRITE 0x1 - -struct s3c_onenand { - struct mtd_info *mtd; - struct platform_device *pdev; - enum soc_type type; - void __iomem *base; - void __iomem *ahb_addr; - int bootram_command; - void *page_buf; - void *oob_buf; - unsigned int (*mem_addr)(int fba, int fpa, int fsa); - unsigned int (*cmd_map)(unsigned int type, unsigned int val); - void __iomem *dma_addr; - unsigned long phys_base; - struct completion complete; -}; - -#define CMD_MAP_00(dev, addr) (dev->cmd_map(MAP_00, ((addr) << 1))) -#define CMD_MAP_01(dev, mem_addr) (dev->cmd_map(MAP_01, (mem_addr))) -#define CMD_MAP_10(dev, mem_addr) (dev->cmd_map(MAP_10, (mem_addr))) -#define CMD_MAP_11(dev, addr) (dev->cmd_map(MAP_11, ((addr) << 2))) - -static struct s3c_onenand *onenand; - -static inline int s3c_read_reg(int offset) -{ - return readl(onenand->base + offset); -} - -static inline void s3c_write_reg(int value, int offset) -{ - writel(value, onenand->base + offset); -} - -static inline int s3c_read_cmd(unsigned int cmd) -{ - return readl(onenand->ahb_addr + cmd); -} - -static inline void s3c_write_cmd(int value, unsigned int cmd) -{ - writel(value, onenand->ahb_addr + cmd); -} - -#ifdef SAMSUNG_DEBUG -static void s3c_dump_reg(void) -{ - int i; - - for (i = 0; i < 0x400; i += 0x40) { - printk(KERN_INFO "0x%08X: 0x%08x 0x%08x 0x%08x 0x%08x\n", - (unsigned int) onenand->base + i, - s3c_read_reg(i), s3c_read_reg(i + 0x10), - s3c_read_reg(i + 0x20), s3c_read_reg(i + 0x30)); - } -} -#endif - -static unsigned int s3c64xx_cmd_map(unsigned type, unsigned val) -{ - return (type << S3C64XX_CMD_MAP_SHIFT) | val; -} - -static unsigned int s3c6400_mem_addr(int fba, int fpa, int fsa) -{ - return (fba << S3C6400_FBA_SHIFT) | (fpa << S3C6400_FPA_SHIFT) | - (fsa << S3C6400_FSA_SHIFT); -} - -static unsigned int s3c6410_mem_addr(int fba, int fpa, int fsa) -{ - return (fba << S3C6410_FBA_SHIFT) | (fpa << S3C6410_FPA_SHIFT) | - (fsa << S3C6410_FSA_SHIFT); -} - -static void s3c_onenand_reset(void) -{ - unsigned long timeout = 0x10000; - int stat; - - s3c_write_reg(ONENAND_MEM_RESET_COLD, MEM_RESET_OFFSET); - while (1 && timeout--) { - stat = s3c_read_reg(INT_ERR_STAT_OFFSET); - if (stat & RST_CMP) - break; - } - stat = s3c_read_reg(INT_ERR_STAT_OFFSET); - s3c_write_reg(stat, INT_ERR_ACK_OFFSET); - - /* Clear interrupt */ - s3c_write_reg(0x0, INT_ERR_ACK_OFFSET); - /* Clear the ECC status */ - s3c_write_reg(0x0, ECC_ERR_STAT_OFFSET); -} - -static unsigned short s3c_onenand_readw(void __iomem *addr) -{ - struct onenand_chip *this = onenand->mtd->priv; - struct device *dev = &onenand->pdev->dev; - int reg = addr - this->base; - int word_addr = reg >> 1; - int value; - - /* It's used for probing time */ - switch (reg) { - case ONENAND_REG_MANUFACTURER_ID: - return s3c_read_reg(MANUFACT_ID_OFFSET); - case ONENAND_REG_DEVICE_ID: - return s3c_read_reg(DEVICE_ID_OFFSET); - case ONENAND_REG_VERSION_ID: - return s3c_read_reg(FLASH_VER_ID_OFFSET); - case ONENAND_REG_DATA_BUFFER_SIZE: - return s3c_read_reg(DATA_BUF_SIZE_OFFSET); - case ONENAND_REG_TECHNOLOGY: - return s3c_read_reg(TECH_OFFSET); - case ONENAND_REG_SYS_CFG1: - return s3c_read_reg(MEM_CFG_OFFSET); - - /* Used at unlock all status */ - case ONENAND_REG_CTRL_STATUS: - return 0; - - case ONENAND_REG_WP_STATUS: - return ONENAND_WP_US; - - default: - break; - } - - /* BootRAM access control */ - if ((unsigned long)addr < ONENAND_DATARAM && onenand->bootram_command) { - if (word_addr == 0) - return s3c_read_reg(MANUFACT_ID_OFFSET); - if (word_addr == 1) - return s3c_read_reg(DEVICE_ID_OFFSET); - if (word_addr == 2) - return s3c_read_reg(FLASH_VER_ID_OFFSET); - } - - value = s3c_read_cmd(CMD_MAP_11(onenand, word_addr)) & 0xffff; - dev_info(dev, "%s: Illegal access at reg 0x%x, value 0x%x\n", __func__, - word_addr, value); - return value; -} - -static void s3c_onenand_writew(unsigned short value, void __iomem *addr) -{ - struct onenand_chip *this = onenand->mtd->priv; - struct device *dev = &onenand->pdev->dev; - unsigned int reg = addr - this->base; - unsigned int word_addr = reg >> 1; - - /* It's used for probing time */ - switch (reg) { - case ONENAND_REG_SYS_CFG1: - s3c_write_reg(value, MEM_CFG_OFFSET); - return; - - case ONENAND_REG_START_ADDRESS1: - case ONENAND_REG_START_ADDRESS2: - return; - - /* Lock/lock-tight/unlock/unlock_all */ - case ONENAND_REG_START_BLOCK_ADDRESS: - return; - - default: - break; - } - - /* BootRAM access control */ - if ((unsigned long)addr < ONENAND_DATARAM) { - if (value == ONENAND_CMD_READID) { - onenand->bootram_command = 1; - return; - } - if (value == ONENAND_CMD_RESET) { - s3c_write_reg(ONENAND_MEM_RESET_COLD, MEM_RESET_OFFSET); - onenand->bootram_command = 0; - return; - } - } - - dev_info(dev, "%s: Illegal access at reg 0x%x, value 0x%x\n", __func__, - word_addr, value); - - s3c_write_cmd(value, CMD_MAP_11(onenand, word_addr)); -} - -static int s3c_onenand_wait(struct mtd_info *mtd, int state) -{ - struct device *dev = &onenand->pdev->dev; - unsigned int flags = INT_ACT; - unsigned int stat, ecc; - unsigned long timeout; - - switch (state) { - case FL_READING: - flags |= BLK_RW_CMP | LOAD_CMP; - break; - case FL_WRITING: - flags |= BLK_RW_CMP | PGM_CMP; - break; - case FL_ERASING: - flags |= BLK_RW_CMP | ERS_CMP; - break; - case FL_LOCKING: - flags |= BLK_RW_CMP; - break; - default: - break; - } - - /* The 20 msec is enough */ - timeout = jiffies + msecs_to_jiffies(20); - while (time_before(jiffies, timeout)) { - stat = s3c_read_reg(INT_ERR_STAT_OFFSET); - if (stat & flags) - break; - - if (state != FL_READING) - cond_resched(); - } - /* To get correct interrupt status in timeout case */ - stat = s3c_read_reg(INT_ERR_STAT_OFFSET); - s3c_write_reg(stat, INT_ERR_ACK_OFFSET); - - /* - * In the Spec. it checks the controller status first - * However if you get the correct information in case of - * power off recovery (POR) test, it should read ECC status first - */ - if (stat & LOAD_CMP) { - ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET); - if (ecc & ONENAND_ECC_4BIT_UNCORRECTABLE) { - dev_info(dev, "%s: ECC error = 0x%04x\n", __func__, - ecc); - mtd->ecc_stats.failed++; - return -EBADMSG; - } - } - - if (stat & (LOCKED_BLK | ERS_FAIL | PGM_FAIL | LD_FAIL_ECC_ERR)) { - dev_info(dev, "%s: controller error = 0x%04x\n", __func__, - stat); - if (stat & LOCKED_BLK) - dev_info(dev, "%s: it's locked error = 0x%04x\n", - __func__, stat); - - return -EIO; - } - - return 0; -} - -static int s3c_onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, - size_t len) -{ - struct onenand_chip *this = mtd->priv; - unsigned int *m, *s; - int fba, fpa, fsa = 0; - unsigned int mem_addr, cmd_map_01, cmd_map_10; - int i, mcount, scount; - int index; - - fba = (int) (addr >> this->erase_shift); - fpa = (int) (addr >> this->page_shift); - fpa &= this->page_mask; - - mem_addr = onenand->mem_addr(fba, fpa, fsa); - cmd_map_01 = CMD_MAP_01(onenand, mem_addr); - cmd_map_10 = CMD_MAP_10(onenand, mem_addr); - - switch (cmd) { - case ONENAND_CMD_READ: - case ONENAND_CMD_READOOB: - case ONENAND_CMD_BUFFERRAM: - ONENAND_SET_NEXT_BUFFERRAM(this); - default: - break; - } - - index = ONENAND_CURRENT_BUFFERRAM(this); - - /* - * Emulate Two BufferRAMs and access with 4 bytes pointer - */ - m = onenand->page_buf; - s = onenand->oob_buf; - - if (index) { - m += (this->writesize >> 2); - s += (mtd->oobsize >> 2); - } - - mcount = mtd->writesize >> 2; - scount = mtd->oobsize >> 2; - - switch (cmd) { - case ONENAND_CMD_READ: - /* Main */ - for (i = 0; i < mcount; i++) - *m++ = s3c_read_cmd(cmd_map_01); - return 0; - - case ONENAND_CMD_READOOB: - s3c_write_reg(TSRF, TRANS_SPARE_OFFSET); - /* Main */ - for (i = 0; i < mcount; i++) - *m++ = s3c_read_cmd(cmd_map_01); - - /* Spare */ - for (i = 0; i < scount; i++) - *s++ = s3c_read_cmd(cmd_map_01); - - s3c_write_reg(0, TRANS_SPARE_OFFSET); - return 0; - - case ONENAND_CMD_PROG: - /* Main */ - for (i = 0; i < mcount; i++) - s3c_write_cmd(*m++, cmd_map_01); - return 0; - - case ONENAND_CMD_PROGOOB: - s3c_write_reg(TSRF, TRANS_SPARE_OFFSET); - - /* Main - dummy write */ - for (i = 0; i < mcount; i++) - s3c_write_cmd(0xffffffff, cmd_map_01); - - /* Spare */ - for (i = 0; i < scount; i++) - s3c_write_cmd(*s++, cmd_map_01); - - s3c_write_reg(0, TRANS_SPARE_OFFSET); - return 0; - - case ONENAND_CMD_UNLOCK_ALL: - s3c_write_cmd(ONENAND_UNLOCK_ALL, cmd_map_10); - return 0; - - case ONENAND_CMD_ERASE: - s3c_write_cmd(ONENAND_ERASE_START, cmd_map_10); - return 0; - - default: - break; - } - - return 0; -} - -static unsigned char *s3c_get_bufferram(struct mtd_info *mtd, int area) -{ - struct onenand_chip *this = mtd->priv; - int index = ONENAND_CURRENT_BUFFERRAM(this); - unsigned char *p; - - if (area == ONENAND_DATARAM) { - p = onenand->page_buf; - if (index == 1) - p += this->writesize; - } else { - p = onenand->oob_buf; - if (index == 1) - p += mtd->oobsize; - } - - return p; -} - -static int onenand_read_bufferram(struct mtd_info *mtd, int area, - unsigned char *buffer, int offset, - size_t count) -{ - unsigned char *p; - - p = s3c_get_bufferram(mtd, area); - memcpy(buffer, p + offset, count); - return 0; -} - -static int onenand_write_bufferram(struct mtd_info *mtd, int area, - const unsigned char *buffer, int offset, - size_t count) -{ - unsigned char *p; - - p = s3c_get_bufferram(mtd, area); - memcpy(p + offset, buffer, count); - return 0; -} - -static int (*s5pc110_dma_ops)(dma_addr_t dst, dma_addr_t src, size_t count, int direction); - -static int s5pc110_dma_poll(dma_addr_t dst, dma_addr_t src, size_t count, int direction) -{ - void __iomem *base = onenand->dma_addr; - int status; - unsigned long timeout; - - writel(src, base + S5PC110_DMA_SRC_ADDR); - writel(dst, base + S5PC110_DMA_DST_ADDR); - - if (direction == S5PC110_DMA_DIR_READ) { - writel(S5PC110_DMA_SRC_CFG_READ, base + S5PC110_DMA_SRC_CFG); - writel(S5PC110_DMA_DST_CFG_READ, base + S5PC110_DMA_DST_CFG); - } else { - writel(S5PC110_DMA_SRC_CFG_WRITE, base + S5PC110_DMA_SRC_CFG); - writel(S5PC110_DMA_DST_CFG_WRITE, base + S5PC110_DMA_DST_CFG); - } - - writel(count, base + S5PC110_DMA_TRANS_SIZE); - writel(direction, base + S5PC110_DMA_TRANS_DIR); - - writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD); - - /* - * There's no exact timeout values at Spec. - * In real case it takes under 1 msec. - * So 20 msecs are enough. - */ - timeout = jiffies + msecs_to_jiffies(20); - - do { - status = readl(base + S5PC110_DMA_TRANS_STATUS); - if (status & S5PC110_DMA_TRANS_STATUS_TE) { - writel(S5PC110_DMA_TRANS_CMD_TEC, - base + S5PC110_DMA_TRANS_CMD); - return -EIO; - } - } while (!(status & S5PC110_DMA_TRANS_STATUS_TD) && - time_before(jiffies, timeout)); - - writel(S5PC110_DMA_TRANS_CMD_TDC, base + S5PC110_DMA_TRANS_CMD); - - return 0; -} - -static irqreturn_t s5pc110_onenand_irq(int irq, void *data) -{ - void __iomem *base = onenand->dma_addr; - int status, cmd = 0; - - status = readl(base + S5PC110_INTC_DMA_STATUS); - - if (likely(status & S5PC110_INTC_DMA_TD)) - cmd = S5PC110_DMA_TRANS_CMD_TDC; - - if (unlikely(status & S5PC110_INTC_DMA_TE)) - cmd = S5PC110_DMA_TRANS_CMD_TEC; - - writel(cmd, base + S5PC110_DMA_TRANS_CMD); - writel(status, base + S5PC110_INTC_DMA_CLR); - - if (!onenand->complete.done) - complete(&onenand->complete); - - return IRQ_HANDLED; -} - -static int s5pc110_dma_irq(dma_addr_t dst, dma_addr_t src, size_t count, int direction) -{ - void __iomem *base = onenand->dma_addr; - int status; - - status = readl(base + S5PC110_INTC_DMA_MASK); - if (status) { - status &= ~(S5PC110_INTC_DMA_TD | S5PC110_INTC_DMA_TE); - writel(status, base + S5PC110_INTC_DMA_MASK); - } - - writel(src, base + S5PC110_DMA_SRC_ADDR); - writel(dst, base + S5PC110_DMA_DST_ADDR); - - if (direction == S5PC110_DMA_DIR_READ) { - writel(S5PC110_DMA_SRC_CFG_READ, base + S5PC110_DMA_SRC_CFG); - writel(S5PC110_DMA_DST_CFG_READ, base + S5PC110_DMA_DST_CFG); - } else { - writel(S5PC110_DMA_SRC_CFG_WRITE, base + S5PC110_DMA_SRC_CFG); - writel(S5PC110_DMA_DST_CFG_WRITE, base + S5PC110_DMA_DST_CFG); - } - - writel(count, base + S5PC110_DMA_TRANS_SIZE); - writel(direction, base + S5PC110_DMA_TRANS_DIR); - - writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD); - - wait_for_completion_timeout(&onenand->complete, msecs_to_jiffies(20)); - - return 0; -} - -static int s5pc110_read_bufferram(struct mtd_info *mtd, int area, - unsigned char *buffer, int offset, size_t count) -{ - struct onenand_chip *this = mtd->priv; - void __iomem *p; - void *buf = (void *) buffer; - dma_addr_t dma_src, dma_dst; - int err, ofs, page_dma = 0; - struct device *dev = &onenand->pdev->dev; - - p = this->base + area; - if (ONENAND_CURRENT_BUFFERRAM(this)) { - if (area == ONENAND_DATARAM) - p += this->writesize; - else - p += mtd->oobsize; - } - - if (offset & 3 || (size_t) buf & 3 || - !onenand->dma_addr || count != mtd->writesize) - goto normal; - - /* Handle vmalloc address */ - if (buf >= high_memory) { - struct page *page; - - if (((size_t) buf & PAGE_MASK) != - ((size_t) (buf + count - 1) & PAGE_MASK)) - goto normal; - page = vmalloc_to_page(buf); - if (!page) - goto normal; - - /* Page offset */ - ofs = ((size_t) buf & ~PAGE_MASK); - page_dma = 1; - - /* DMA routine */ - dma_src = onenand->phys_base + (p - this->base); - dma_dst = dma_map_page(dev, page, ofs, count, DMA_FROM_DEVICE); - } else { - /* DMA routine */ - dma_src = onenand->phys_base + (p - this->base); - dma_dst = dma_map_single(dev, buf, count, DMA_FROM_DEVICE); - } - if (dma_mapping_error(dev, dma_dst)) { - dev_err(dev, "Couldn't map a %zu byte buffer for DMA\n", count); - goto normal; - } - err = s5pc110_dma_ops(dma_dst, dma_src, - count, S5PC110_DMA_DIR_READ); - - if (page_dma) - dma_unmap_page(dev, dma_dst, count, DMA_FROM_DEVICE); - else - dma_unmap_single(dev, dma_dst, count, DMA_FROM_DEVICE); - - if (!err) - return 0; - -normal: - if (count != mtd->writesize) { - /* Copy the bufferram to memory to prevent unaligned access */ - memcpy_fromio(this->page_buf, p, mtd->writesize); - memcpy(buffer, this->page_buf + offset, count); - } else { - memcpy_fromio(buffer, p, count); - } - - return 0; -} - -static int s5pc110_chip_probe(struct mtd_info *mtd) -{ - /* Now just return 0 */ - return 0; -} - -static int s3c_onenand_bbt_wait(struct mtd_info *mtd, int state) -{ - unsigned int flags = INT_ACT | LOAD_CMP; - unsigned int stat; - unsigned long timeout; - - /* The 20 msec is enough */ - timeout = jiffies + msecs_to_jiffies(20); - while (time_before(jiffies, timeout)) { - stat = s3c_read_reg(INT_ERR_STAT_OFFSET); - if (stat & flags) - break; - } - /* To get correct interrupt status in timeout case */ - stat = s3c_read_reg(INT_ERR_STAT_OFFSET); - s3c_write_reg(stat, INT_ERR_ACK_OFFSET); - - if (stat & LD_FAIL_ECC_ERR) { - s3c_onenand_reset(); - return ONENAND_BBT_READ_ERROR; - } - - if (stat & LOAD_CMP) { - int ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET); - if (ecc & ONENAND_ECC_4BIT_UNCORRECTABLE) { - s3c_onenand_reset(); - return ONENAND_BBT_READ_ERROR; - } - } - - return 0; -} - -static void s3c_onenand_check_lock_status(struct mtd_info *mtd) -{ - struct onenand_chip *this = mtd->priv; - struct device *dev = &onenand->pdev->dev; - unsigned int block, end; - - end = this->chipsize >> this->erase_shift; - - for (block = 0; block < end; block++) { - unsigned int mem_addr = onenand->mem_addr(block, 0, 0); - s3c_read_cmd(CMD_MAP_01(onenand, mem_addr)); - - if (s3c_read_reg(INT_ERR_STAT_OFFSET) & LOCKED_BLK) { - dev_err(dev, "block %d is write-protected!\n", block); - s3c_write_reg(LOCKED_BLK, INT_ERR_ACK_OFFSET); - } - } -} - -static void s3c_onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs, - size_t len, int cmd) -{ - struct onenand_chip *this = mtd->priv; - int start, end, start_mem_addr, end_mem_addr; - - start = ofs >> this->erase_shift; - start_mem_addr = onenand->mem_addr(start, 0, 0); - end = start + (len >> this->erase_shift) - 1; - end_mem_addr = onenand->mem_addr(end, 0, 0); - - if (cmd == ONENAND_CMD_LOCK) { - s3c_write_cmd(ONENAND_LOCK_START, CMD_MAP_10(onenand, - start_mem_addr)); - s3c_write_cmd(ONENAND_LOCK_END, CMD_MAP_10(onenand, - end_mem_addr)); - } else { - s3c_write_cmd(ONENAND_UNLOCK_START, CMD_MAP_10(onenand, - start_mem_addr)); - s3c_write_cmd(ONENAND_UNLOCK_END, CMD_MAP_10(onenand, - end_mem_addr)); - } - - this->wait(mtd, FL_LOCKING); -} - -static void s3c_unlock_all(struct mtd_info *mtd) -{ - struct onenand_chip *this = mtd->priv; - loff_t ofs = 0; - size_t len = this->chipsize; - - if (this->options & ONENAND_HAS_UNLOCK_ALL) { - /* Write unlock command */ - this->command(mtd, ONENAND_CMD_UNLOCK_ALL, 0, 0); - - /* No need to check return value */ - this->wait(mtd, FL_LOCKING); - - /* Workaround for all block unlock in DDP */ - if (!ONENAND_IS_DDP(this)) { - s3c_onenand_check_lock_status(mtd); - return; - } - - /* All blocks on another chip */ - ofs = this->chipsize >> 1; - len = this->chipsize >> 1; - } - - s3c_onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK); - - s3c_onenand_check_lock_status(mtd); -} - -static void s3c_onenand_setup(struct mtd_info *mtd) -{ - struct onenand_chip *this = mtd->priv; - - onenand->mtd = mtd; - - if (onenand->type == TYPE_S3C6400) { - onenand->mem_addr = s3c6400_mem_addr; - onenand->cmd_map = s3c64xx_cmd_map; - } else if (onenand->type == TYPE_S3C6410) { - onenand->mem_addr = s3c6410_mem_addr; - onenand->cmd_map = s3c64xx_cmd_map; - } else if (onenand->type == TYPE_S5PC110) { - /* Use generic onenand functions */ - this->read_bufferram = s5pc110_read_bufferram; - this->chip_probe = s5pc110_chip_probe; - return; - } else { - BUG(); - } - - this->read_word = s3c_onenand_readw; - this->write_word = s3c_onenand_writew; - - this->wait = s3c_onenand_wait; - this->bbt_wait = s3c_onenand_bbt_wait; - this->unlock_all = s3c_unlock_all; - this->command = s3c_onenand_command; - - this->read_bufferram = onenand_read_bufferram; - this->write_bufferram = onenand_write_bufferram; -} - -static int s3c_onenand_probe(struct platform_device *pdev) -{ - struct onenand_platform_data *pdata; - struct onenand_chip *this; - struct mtd_info *mtd; - struct resource *r; - int size, err; - - pdata = dev_get_platdata(&pdev->dev); - /* No need to check pdata. the platform data is optional */ - - size = sizeof(struct mtd_info) + sizeof(struct onenand_chip); - mtd = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); - if (!mtd) - return -ENOMEM; - - onenand = devm_kzalloc(&pdev->dev, sizeof(struct s3c_onenand), - GFP_KERNEL); - if (!onenand) - return -ENOMEM; - - this = (struct onenand_chip *) &mtd[1]; - mtd->priv = this; - mtd->dev.parent = &pdev->dev; - onenand->pdev = pdev; - onenand->type = platform_get_device_id(pdev)->driver_data; - - s3c_onenand_setup(mtd); - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - onenand->base = devm_ioremap_resource(&pdev->dev, r); - if (IS_ERR(onenand->base)) - return PTR_ERR(onenand->base); - - onenand->phys_base = r->start; - - /* Set onenand_chip also */ - this->base = onenand->base; - - /* Use runtime badblock check */ - this->options |= ONENAND_SKIP_UNLOCK_CHECK; - - if (onenand->type != TYPE_S5PC110) { - r = platform_get_resource(pdev, IORESOURCE_MEM, 1); - onenand->ahb_addr = devm_ioremap_resource(&pdev->dev, r); - if (IS_ERR(onenand->ahb_addr)) - return PTR_ERR(onenand->ahb_addr); - - /* Allocate 4KiB BufferRAM */ - onenand->page_buf = devm_kzalloc(&pdev->dev, SZ_4K, - GFP_KERNEL); - if (!onenand->page_buf) - return -ENOMEM; - - /* Allocate 128 SpareRAM */ - onenand->oob_buf = devm_kzalloc(&pdev->dev, 128, GFP_KERNEL); - if (!onenand->oob_buf) - return -ENOMEM; - - /* S3C doesn't handle subpage write */ - mtd->subpage_sft = 0; - this->subpagesize = mtd->writesize; - - } else { /* S5PC110 */ - r = platform_get_resource(pdev, IORESOURCE_MEM, 1); - onenand->dma_addr = devm_ioremap_resource(&pdev->dev, r); - if (IS_ERR(onenand->dma_addr)) - return PTR_ERR(onenand->dma_addr); - - s5pc110_dma_ops = s5pc110_dma_poll; - /* Interrupt support */ - r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (r) { - init_completion(&onenand->complete); - s5pc110_dma_ops = s5pc110_dma_irq; - err = devm_request_irq(&pdev->dev, r->start, - s5pc110_onenand_irq, - IRQF_SHARED, "onenand", - &onenand); - if (err) { - dev_err(&pdev->dev, "failed to get irq\n"); - return err; - } - } - } - - err = onenand_scan(mtd, 1); - if (err) - return err; - - if (onenand->type != TYPE_S5PC110) { - /* S3C doesn't handle subpage write */ - mtd->subpage_sft = 0; - this->subpagesize = mtd->writesize; - } - - if (s3c_read_reg(MEM_CFG_OFFSET) & ONENAND_SYS_CFG1_SYNC_READ) - dev_info(&onenand->pdev->dev, "OneNAND Sync. Burst Read enabled\n"); - - err = mtd_device_register(mtd, pdata ? pdata->parts : NULL, - pdata ? pdata->nr_parts : 0); - if (err) { - dev_err(&pdev->dev, "failed to parse partitions and register the MTD device\n"); - onenand_release(mtd); - return err; - } - - platform_set_drvdata(pdev, mtd); - - return 0; -} - -static int s3c_onenand_remove(struct platform_device *pdev) -{ - struct mtd_info *mtd = platform_get_drvdata(pdev); - - onenand_release(mtd); - - return 0; -} - -static int s3c_pm_ops_suspend(struct device *dev) -{ - struct mtd_info *mtd = dev_get_drvdata(dev); - struct onenand_chip *this = mtd->priv; - - this->wait(mtd, FL_PM_SUSPENDED); - return 0; -} - -static int s3c_pm_ops_resume(struct device *dev) -{ - struct mtd_info *mtd = dev_get_drvdata(dev); - struct onenand_chip *this = mtd->priv; - - this->unlock_all(mtd); - return 0; -} - -static const struct dev_pm_ops s3c_pm_ops = { - .suspend = s3c_pm_ops_suspend, - .resume = s3c_pm_ops_resume, -}; - -static const struct platform_device_id s3c_onenand_driver_ids[] = { - { - .name = "s3c6400-onenand", - .driver_data = TYPE_S3C6400, - }, { - .name = "s3c6410-onenand", - .driver_data = TYPE_S3C6410, - }, { - .name = "s5pc110-onenand", - .driver_data = TYPE_S5PC110, - }, { }, -}; -MODULE_DEVICE_TABLE(platform, s3c_onenand_driver_ids); - -static struct platform_driver s3c_onenand_driver = { - .driver = { - .name = "samsung-onenand", - .pm = &s3c_pm_ops, - }, - .id_table = s3c_onenand_driver_ids, - .probe = s3c_onenand_probe, - .remove = s3c_onenand_remove, -}; - -module_platform_driver(s3c_onenand_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Kyungmin Park "); -MODULE_DESCRIPTION("Samsung OneNAND controller support"); -- cgit From d85339d9ea2660b550f12aca8bd040be4395c963 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 30 Dec 2019 18:31:03 +0100 Subject: mtd: onenand: Rename omap2 driver to avoid a build warning As previously reported by Sudip Mukherjee for the Samsung driver, the omap2 onenand driver is called omap2.c in our directory and omap2.c in the tty/serial/ directory. If both drivers are compiled as modules, it would produce the following warning: warning: same module names found: drivers/tty/serial/omap2.ko drivers/mtd/nand/onenand/omap2.ko Rename the onenand omap2 driver so that it fits the folder's convention: onenand_omap2.c. Signed-off-by: Miquel Raynal --- drivers/mtd/nand/onenand/Makefile | 2 +- drivers/mtd/nand/onenand/omap2.c | 620 ------------------------------- drivers/mtd/nand/onenand/onenand_omap2.c | 620 +++++++++++++++++++++++++++++++ 3 files changed, 621 insertions(+), 621 deletions(-) delete mode 100644 drivers/mtd/nand/onenand/omap2.c create mode 100644 drivers/mtd/nand/onenand/onenand_omap2.c (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/onenand/Makefile b/drivers/mtd/nand/onenand/Makefile index 0f2f460adbe4..a0761c7e0288 100644 --- a/drivers/mtd/nand/onenand/Makefile +++ b/drivers/mtd/nand/onenand/Makefile @@ -8,7 +8,7 @@ obj-$(CONFIG_MTD_ONENAND) += onenand.o # Board specific. obj-$(CONFIG_MTD_ONENAND_GENERIC) += generic.o -obj-$(CONFIG_MTD_ONENAND_OMAP2) += omap2.o +obj-$(CONFIG_MTD_ONENAND_OMAP2) += onenand_omap2.o obj-$(CONFIG_MTD_ONENAND_SAMSUNG) += onenand_samsung.o onenand-objs = onenand_base.o onenand_bbt.o diff --git a/drivers/mtd/nand/onenand/omap2.c b/drivers/mtd/nand/onenand/omap2.c deleted file mode 100644 index aa9368bf7a0c..000000000000 --- a/drivers/mtd/nand/onenand/omap2.c +++ /dev/null @@ -1,620 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * OneNAND driver for OMAP2 / OMAP3 - * - * Copyright © 2005-2006 Nokia Corporation - * - * Author: Jarkko Lavinen and Juha Yrjölä - * IRQ and DMA support written by Timo Teras - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define DRIVER_NAME "omap2-onenand" - -#define ONENAND_BUFRAM_SIZE (1024 * 5) - -struct omap2_onenand { - struct platform_device *pdev; - int gpmc_cs; - unsigned long phys_base; - struct gpio_desc *int_gpiod; - struct mtd_info mtd; - struct onenand_chip onenand; - struct completion irq_done; - struct completion dma_done; - struct dma_chan *dma_chan; -}; - -static void omap2_onenand_dma_complete_func(void *completion) -{ - complete(completion); -} - -static irqreturn_t omap2_onenand_interrupt(int irq, void *dev_id) -{ - struct omap2_onenand *c = dev_id; - - complete(&c->irq_done); - - return IRQ_HANDLED; -} - -static inline unsigned short read_reg(struct omap2_onenand *c, int reg) -{ - return readw(c->onenand.base + reg); -} - -static inline void write_reg(struct omap2_onenand *c, unsigned short value, - int reg) -{ - writew(value, c->onenand.base + reg); -} - -static int omap2_onenand_set_cfg(struct omap2_onenand *c, - bool sr, bool sw, - int latency, int burst_len) -{ - unsigned short reg = ONENAND_SYS_CFG1_RDY | ONENAND_SYS_CFG1_INT; - - reg |= latency << ONENAND_SYS_CFG1_BRL_SHIFT; - - switch (burst_len) { - case 0: /* continuous */ - break; - case 4: - reg |= ONENAND_SYS_CFG1_BL_4; - break; - case 8: - reg |= ONENAND_SYS_CFG1_BL_8; - break; - case 16: - reg |= ONENAND_SYS_CFG1_BL_16; - break; - case 32: - reg |= ONENAND_SYS_CFG1_BL_32; - break; - default: - return -EINVAL; - } - - if (latency > 5) - reg |= ONENAND_SYS_CFG1_HF; - if (latency > 7) - reg |= ONENAND_SYS_CFG1_VHF; - if (sr) - reg |= ONENAND_SYS_CFG1_SYNC_READ; - if (sw) - reg |= ONENAND_SYS_CFG1_SYNC_WRITE; - - write_reg(c, reg, ONENAND_REG_SYS_CFG1); - - return 0; -} - -static int omap2_onenand_get_freq(int ver) -{ - switch ((ver >> 4) & 0xf) { - case 0: - return 40; - case 1: - return 54; - case 2: - return 66; - case 3: - return 83; - case 4: - return 104; - } - - return -EINVAL; -} - -static void wait_err(char *msg, int state, unsigned int ctrl, unsigned int intr) -{ - printk(KERN_ERR "onenand_wait: %s! state %d ctrl 0x%04x intr 0x%04x\n", - msg, state, ctrl, intr); -} - -static void wait_warn(char *msg, int state, unsigned int ctrl, - unsigned int intr) -{ - printk(KERN_WARNING "onenand_wait: %s! state %d ctrl 0x%04x " - "intr 0x%04x\n", msg, state, ctrl, intr); -} - -static int omap2_onenand_wait(struct mtd_info *mtd, int state) -{ - struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); - struct onenand_chip *this = mtd->priv; - unsigned int intr = 0; - unsigned int ctrl, ctrl_mask; - unsigned long timeout; - u32 syscfg; - - if (state == FL_RESETTING || state == FL_PREPARING_ERASE || - state == FL_VERIFYING_ERASE) { - int i = 21; - unsigned int intr_flags = ONENAND_INT_MASTER; - - switch (state) { - case FL_RESETTING: - intr_flags |= ONENAND_INT_RESET; - break; - case FL_PREPARING_ERASE: - intr_flags |= ONENAND_INT_ERASE; - break; - case FL_VERIFYING_ERASE: - i = 101; - break; - } - - while (--i) { - udelay(1); - intr = read_reg(c, ONENAND_REG_INTERRUPT); - if (intr & ONENAND_INT_MASTER) - break; - } - ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); - if (ctrl & ONENAND_CTRL_ERROR) { - wait_err("controller error", state, ctrl, intr); - return -EIO; - } - if ((intr & intr_flags) == intr_flags) - return 0; - /* Continue in wait for interrupt branch */ - } - - if (state != FL_READING) { - int result; - - /* Turn interrupts on */ - syscfg = read_reg(c, ONENAND_REG_SYS_CFG1); - if (!(syscfg & ONENAND_SYS_CFG1_IOBE)) { - syscfg |= ONENAND_SYS_CFG1_IOBE; - write_reg(c, syscfg, ONENAND_REG_SYS_CFG1); - /* Add a delay to let GPIO settle */ - syscfg = read_reg(c, ONENAND_REG_SYS_CFG1); - } - - reinit_completion(&c->irq_done); - result = gpiod_get_value(c->int_gpiod); - if (result < 0) { - ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); - intr = read_reg(c, ONENAND_REG_INTERRUPT); - wait_err("gpio error", state, ctrl, intr); - return result; - } else if (result == 0) { - int retry_cnt = 0; -retry: - if (!wait_for_completion_io_timeout(&c->irq_done, - msecs_to_jiffies(20))) { - /* Timeout after 20ms */ - ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); - if (ctrl & ONENAND_CTRL_ONGO && - !this->ongoing) { - /* - * The operation seems to be still going - * so give it some more time. - */ - retry_cnt += 1; - if (retry_cnt < 3) - goto retry; - intr = read_reg(c, - ONENAND_REG_INTERRUPT); - wait_err("timeout", state, ctrl, intr); - return -EIO; - } - intr = read_reg(c, ONENAND_REG_INTERRUPT); - if ((intr & ONENAND_INT_MASTER) == 0) - wait_warn("timeout", state, ctrl, intr); - } - } - } else { - int retry_cnt = 0; - - /* Turn interrupts off */ - syscfg = read_reg(c, ONENAND_REG_SYS_CFG1); - syscfg &= ~ONENAND_SYS_CFG1_IOBE; - write_reg(c, syscfg, ONENAND_REG_SYS_CFG1); - - timeout = jiffies + msecs_to_jiffies(20); - while (1) { - if (time_before(jiffies, timeout)) { - intr = read_reg(c, ONENAND_REG_INTERRUPT); - if (intr & ONENAND_INT_MASTER) - break; - } else { - /* Timeout after 20ms */ - ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); - if (ctrl & ONENAND_CTRL_ONGO) { - /* - * The operation seems to be still going - * so give it some more time. - */ - retry_cnt += 1; - if (retry_cnt < 3) { - timeout = jiffies + - msecs_to_jiffies(20); - continue; - } - } - break; - } - } - } - - intr = read_reg(c, ONENAND_REG_INTERRUPT); - ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); - - if (intr & ONENAND_INT_READ) { - int ecc = read_reg(c, ONENAND_REG_ECC_STATUS); - - if (ecc) { - unsigned int addr1, addr8; - - addr1 = read_reg(c, ONENAND_REG_START_ADDRESS1); - addr8 = read_reg(c, ONENAND_REG_START_ADDRESS8); - if (ecc & ONENAND_ECC_2BIT_ALL) { - printk(KERN_ERR "onenand_wait: ECC error = " - "0x%04x, addr1 %#x, addr8 %#x\n", - ecc, addr1, addr8); - mtd->ecc_stats.failed++; - return -EBADMSG; - } else if (ecc & ONENAND_ECC_1BIT_ALL) { - printk(KERN_NOTICE "onenand_wait: correctable " - "ECC error = 0x%04x, addr1 %#x, " - "addr8 %#x\n", ecc, addr1, addr8); - mtd->ecc_stats.corrected++; - } - } - } else if (state == FL_READING) { - wait_err("timeout", state, ctrl, intr); - return -EIO; - } - - if (ctrl & ONENAND_CTRL_ERROR) { - wait_err("controller error", state, ctrl, intr); - if (ctrl & ONENAND_CTRL_LOCK) - printk(KERN_ERR "onenand_wait: " - "Device is write protected!!!\n"); - return -EIO; - } - - ctrl_mask = 0xFE9F; - if (this->ongoing) - ctrl_mask &= ~0x8000; - - if (ctrl & ctrl_mask) - wait_warn("unexpected controller status", state, ctrl, intr); - - return 0; -} - -static inline int omap2_onenand_bufferram_offset(struct mtd_info *mtd, int area) -{ - struct onenand_chip *this = mtd->priv; - - if (ONENAND_CURRENT_BUFFERRAM(this)) { - if (area == ONENAND_DATARAM) - return this->writesize; - if (area == ONENAND_SPARERAM) - return mtd->oobsize; - } - - return 0; -} - -static inline int omap2_onenand_dma_transfer(struct omap2_onenand *c, - dma_addr_t src, dma_addr_t dst, - size_t count) -{ - struct dma_async_tx_descriptor *tx; - dma_cookie_t cookie; - - tx = dmaengine_prep_dma_memcpy(c->dma_chan, dst, src, count, - DMA_CTRL_ACK | DMA_PREP_INTERRUPT); - if (!tx) { - dev_err(&c->pdev->dev, "Failed to prepare DMA memcpy\n"); - return -EIO; - } - - reinit_completion(&c->dma_done); - - tx->callback = omap2_onenand_dma_complete_func; - tx->callback_param = &c->dma_done; - - cookie = tx->tx_submit(tx); - if (dma_submit_error(cookie)) { - dev_err(&c->pdev->dev, "Failed to do DMA tx_submit\n"); - return -EIO; - } - - dma_async_issue_pending(c->dma_chan); - - if (!wait_for_completion_io_timeout(&c->dma_done, - msecs_to_jiffies(20))) { - dmaengine_terminate_sync(c->dma_chan); - return -ETIMEDOUT; - } - - return 0; -} - -static int omap2_onenand_read_bufferram(struct mtd_info *mtd, int area, - unsigned char *buffer, int offset, - size_t count) -{ - struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); - struct onenand_chip *this = mtd->priv; - struct device *dev = &c->pdev->dev; - void *buf = (void *)buffer; - dma_addr_t dma_src, dma_dst; - int bram_offset, err; - size_t xtra; - - bram_offset = omap2_onenand_bufferram_offset(mtd, area) + area + offset; - /* - * If the buffer address is not DMA-able, len is not long enough to make - * DMA transfers profitable or panic_write() may be in an interrupt - * context fallback to PIO mode. - */ - if (!virt_addr_valid(buf) || bram_offset & 3 || (size_t)buf & 3 || - count < 384 || in_interrupt() || oops_in_progress) - goto out_copy; - - xtra = count & 3; - if (xtra) { - count -= xtra; - memcpy(buf + count, this->base + bram_offset + count, xtra); - } - - dma_dst = dma_map_single(dev, buf, count, DMA_FROM_DEVICE); - dma_src = c->phys_base + bram_offset; - - if (dma_mapping_error(dev, dma_dst)) { - dev_err(dev, "Couldn't DMA map a %d byte buffer\n", count); - goto out_copy; - } - - err = omap2_onenand_dma_transfer(c, dma_src, dma_dst, count); - dma_unmap_single(dev, dma_dst, count, DMA_FROM_DEVICE); - if (!err) - return 0; - - dev_err(dev, "timeout waiting for DMA\n"); - -out_copy: - memcpy(buf, this->base + bram_offset, count); - return 0; -} - -static int omap2_onenand_write_bufferram(struct mtd_info *mtd, int area, - const unsigned char *buffer, - int offset, size_t count) -{ - struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); - struct onenand_chip *this = mtd->priv; - struct device *dev = &c->pdev->dev; - void *buf = (void *)buffer; - dma_addr_t dma_src, dma_dst; - int bram_offset, err; - - bram_offset = omap2_onenand_bufferram_offset(mtd, area) + area + offset; - /* - * If the buffer address is not DMA-able, len is not long enough to make - * DMA transfers profitable or panic_write() may be in an interrupt - * context fallback to PIO mode. - */ - if (!virt_addr_valid(buf) || bram_offset & 3 || (size_t)buf & 3 || - count < 384 || in_interrupt() || oops_in_progress) - goto out_copy; - - dma_src = dma_map_single(dev, buf, count, DMA_TO_DEVICE); - dma_dst = c->phys_base + bram_offset; - if (dma_mapping_error(dev, dma_src)) { - dev_err(dev, "Couldn't DMA map a %d byte buffer\n", count); - goto out_copy; - } - - err = omap2_onenand_dma_transfer(c, dma_src, dma_dst, count); - dma_unmap_page(dev, dma_src, count, DMA_TO_DEVICE); - if (!err) - return 0; - - dev_err(dev, "timeout waiting for DMA\n"); - -out_copy: - memcpy(this->base + bram_offset, buf, count); - return 0; -} - -static void omap2_onenand_shutdown(struct platform_device *pdev) -{ - struct omap2_onenand *c = dev_get_drvdata(&pdev->dev); - - /* With certain content in the buffer RAM, the OMAP boot ROM code - * can recognize the flash chip incorrectly. Zero it out before - * soft reset. - */ - memset((__force void *)c->onenand.base, 0, ONENAND_BUFRAM_SIZE); -} - -static int omap2_onenand_probe(struct platform_device *pdev) -{ - u32 val; - dma_cap_mask_t mask; - int freq, latency, r; - struct resource *res; - struct omap2_onenand *c; - struct gpmc_onenand_info info; - struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(dev, "error getting memory resource\n"); - return -EINVAL; - } - - r = of_property_read_u32(np, "reg", &val); - if (r) { - dev_err(dev, "reg not found in DT\n"); - return r; - } - - c = devm_kzalloc(dev, sizeof(struct omap2_onenand), GFP_KERNEL); - if (!c) - return -ENOMEM; - - init_completion(&c->irq_done); - init_completion(&c->dma_done); - c->gpmc_cs = val; - c->phys_base = res->start; - - c->onenand.base = devm_ioremap_resource(dev, res); - if (IS_ERR(c->onenand.base)) - return PTR_ERR(c->onenand.base); - - c->int_gpiod = devm_gpiod_get_optional(dev, "int", GPIOD_IN); - if (IS_ERR(c->int_gpiod)) { - r = PTR_ERR(c->int_gpiod); - /* Just try again if this happens */ - if (r != -EPROBE_DEFER) - dev_err(dev, "error getting gpio: %d\n", r); - return r; - } - - if (c->int_gpiod) { - r = devm_request_irq(dev, gpiod_to_irq(c->int_gpiod), - omap2_onenand_interrupt, - IRQF_TRIGGER_RISING, "onenand", c); - if (r) - return r; - - c->onenand.wait = omap2_onenand_wait; - } - - dma_cap_zero(mask); - dma_cap_set(DMA_MEMCPY, mask); - - c->dma_chan = dma_request_channel(mask, NULL, NULL); - if (c->dma_chan) { - c->onenand.read_bufferram = omap2_onenand_read_bufferram; - c->onenand.write_bufferram = omap2_onenand_write_bufferram; - } - - c->pdev = pdev; - c->mtd.priv = &c->onenand; - c->mtd.dev.parent = dev; - mtd_set_of_node(&c->mtd, dev->of_node); - - dev_info(dev, "initializing on CS%d (0x%08lx), va %p, %s mode\n", - c->gpmc_cs, c->phys_base, c->onenand.base, - c->dma_chan ? "DMA" : "PIO"); - - r = onenand_scan(&c->mtd, 1); - if (r < 0) - goto err_release_dma; - - freq = omap2_onenand_get_freq(c->onenand.version_id); - if (freq > 0) { - switch (freq) { - case 104: - latency = 7; - break; - case 83: - latency = 6; - break; - case 66: - latency = 5; - break; - case 56: - latency = 4; - break; - default: /* 40 MHz or lower */ - latency = 3; - break; - } - - r = gpmc_omap_onenand_set_timings(dev, c->gpmc_cs, - freq, latency, &info); - if (r) - goto err_release_onenand; - - r = omap2_onenand_set_cfg(c, info.sync_read, info.sync_write, - latency, info.burst_len); - if (r) - goto err_release_onenand; - - if (info.sync_read || info.sync_write) - dev_info(dev, "optimized timings for %d MHz\n", freq); - } - - r = mtd_device_register(&c->mtd, NULL, 0); - if (r) - goto err_release_onenand; - - platform_set_drvdata(pdev, c); - - return 0; - -err_release_onenand: - onenand_release(&c->mtd); -err_release_dma: - if (c->dma_chan) - dma_release_channel(c->dma_chan); - - return r; -} - -static int omap2_onenand_remove(struct platform_device *pdev) -{ - struct omap2_onenand *c = dev_get_drvdata(&pdev->dev); - - onenand_release(&c->mtd); - if (c->dma_chan) - dma_release_channel(c->dma_chan); - omap2_onenand_shutdown(pdev); - - return 0; -} - -static const struct of_device_id omap2_onenand_id_table[] = { - { .compatible = "ti,omap2-onenand", }, - {}, -}; -MODULE_DEVICE_TABLE(of, omap2_onenand_id_table); - -static struct platform_driver omap2_onenand_driver = { - .probe = omap2_onenand_probe, - .remove = omap2_onenand_remove, - .shutdown = omap2_onenand_shutdown, - .driver = { - .name = DRIVER_NAME, - .of_match_table = omap2_onenand_id_table, - }, -}; - -module_platform_driver(omap2_onenand_driver); - -MODULE_ALIAS("platform:" DRIVER_NAME); -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Jarkko Lavinen "); -MODULE_DESCRIPTION("Glue layer for OneNAND flash on OMAP2 / OMAP3"); diff --git a/drivers/mtd/nand/onenand/onenand_omap2.c b/drivers/mtd/nand/onenand/onenand_omap2.c new file mode 100644 index 000000000000..aa9368bf7a0c --- /dev/null +++ b/drivers/mtd/nand/onenand/onenand_omap2.c @@ -0,0 +1,620 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * OneNAND driver for OMAP2 / OMAP3 + * + * Copyright © 2005-2006 Nokia Corporation + * + * Author: Jarkko Lavinen and Juha Yrjölä + * IRQ and DMA support written by Timo Teras + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRIVER_NAME "omap2-onenand" + +#define ONENAND_BUFRAM_SIZE (1024 * 5) + +struct omap2_onenand { + struct platform_device *pdev; + int gpmc_cs; + unsigned long phys_base; + struct gpio_desc *int_gpiod; + struct mtd_info mtd; + struct onenand_chip onenand; + struct completion irq_done; + struct completion dma_done; + struct dma_chan *dma_chan; +}; + +static void omap2_onenand_dma_complete_func(void *completion) +{ + complete(completion); +} + +static irqreturn_t omap2_onenand_interrupt(int irq, void *dev_id) +{ + struct omap2_onenand *c = dev_id; + + complete(&c->irq_done); + + return IRQ_HANDLED; +} + +static inline unsigned short read_reg(struct omap2_onenand *c, int reg) +{ + return readw(c->onenand.base + reg); +} + +static inline void write_reg(struct omap2_onenand *c, unsigned short value, + int reg) +{ + writew(value, c->onenand.base + reg); +} + +static int omap2_onenand_set_cfg(struct omap2_onenand *c, + bool sr, bool sw, + int latency, int burst_len) +{ + unsigned short reg = ONENAND_SYS_CFG1_RDY | ONENAND_SYS_CFG1_INT; + + reg |= latency << ONENAND_SYS_CFG1_BRL_SHIFT; + + switch (burst_len) { + case 0: /* continuous */ + break; + case 4: + reg |= ONENAND_SYS_CFG1_BL_4; + break; + case 8: + reg |= ONENAND_SYS_CFG1_BL_8; + break; + case 16: + reg |= ONENAND_SYS_CFG1_BL_16; + break; + case 32: + reg |= ONENAND_SYS_CFG1_BL_32; + break; + default: + return -EINVAL; + } + + if (latency > 5) + reg |= ONENAND_SYS_CFG1_HF; + if (latency > 7) + reg |= ONENAND_SYS_CFG1_VHF; + if (sr) + reg |= ONENAND_SYS_CFG1_SYNC_READ; + if (sw) + reg |= ONENAND_SYS_CFG1_SYNC_WRITE; + + write_reg(c, reg, ONENAND_REG_SYS_CFG1); + + return 0; +} + +static int omap2_onenand_get_freq(int ver) +{ + switch ((ver >> 4) & 0xf) { + case 0: + return 40; + case 1: + return 54; + case 2: + return 66; + case 3: + return 83; + case 4: + return 104; + } + + return -EINVAL; +} + +static void wait_err(char *msg, int state, unsigned int ctrl, unsigned int intr) +{ + printk(KERN_ERR "onenand_wait: %s! state %d ctrl 0x%04x intr 0x%04x\n", + msg, state, ctrl, intr); +} + +static void wait_warn(char *msg, int state, unsigned int ctrl, + unsigned int intr) +{ + printk(KERN_WARNING "onenand_wait: %s! state %d ctrl 0x%04x " + "intr 0x%04x\n", msg, state, ctrl, intr); +} + +static int omap2_onenand_wait(struct mtd_info *mtd, int state) +{ + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + struct onenand_chip *this = mtd->priv; + unsigned int intr = 0; + unsigned int ctrl, ctrl_mask; + unsigned long timeout; + u32 syscfg; + + if (state == FL_RESETTING || state == FL_PREPARING_ERASE || + state == FL_VERIFYING_ERASE) { + int i = 21; + unsigned int intr_flags = ONENAND_INT_MASTER; + + switch (state) { + case FL_RESETTING: + intr_flags |= ONENAND_INT_RESET; + break; + case FL_PREPARING_ERASE: + intr_flags |= ONENAND_INT_ERASE; + break; + case FL_VERIFYING_ERASE: + i = 101; + break; + } + + while (--i) { + udelay(1); + intr = read_reg(c, ONENAND_REG_INTERRUPT); + if (intr & ONENAND_INT_MASTER) + break; + } + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + if (ctrl & ONENAND_CTRL_ERROR) { + wait_err("controller error", state, ctrl, intr); + return -EIO; + } + if ((intr & intr_flags) == intr_flags) + return 0; + /* Continue in wait for interrupt branch */ + } + + if (state != FL_READING) { + int result; + + /* Turn interrupts on */ + syscfg = read_reg(c, ONENAND_REG_SYS_CFG1); + if (!(syscfg & ONENAND_SYS_CFG1_IOBE)) { + syscfg |= ONENAND_SYS_CFG1_IOBE; + write_reg(c, syscfg, ONENAND_REG_SYS_CFG1); + /* Add a delay to let GPIO settle */ + syscfg = read_reg(c, ONENAND_REG_SYS_CFG1); + } + + reinit_completion(&c->irq_done); + result = gpiod_get_value(c->int_gpiod); + if (result < 0) { + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + intr = read_reg(c, ONENAND_REG_INTERRUPT); + wait_err("gpio error", state, ctrl, intr); + return result; + } else if (result == 0) { + int retry_cnt = 0; +retry: + if (!wait_for_completion_io_timeout(&c->irq_done, + msecs_to_jiffies(20))) { + /* Timeout after 20ms */ + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + if (ctrl & ONENAND_CTRL_ONGO && + !this->ongoing) { + /* + * The operation seems to be still going + * so give it some more time. + */ + retry_cnt += 1; + if (retry_cnt < 3) + goto retry; + intr = read_reg(c, + ONENAND_REG_INTERRUPT); + wait_err("timeout", state, ctrl, intr); + return -EIO; + } + intr = read_reg(c, ONENAND_REG_INTERRUPT); + if ((intr & ONENAND_INT_MASTER) == 0) + wait_warn("timeout", state, ctrl, intr); + } + } + } else { + int retry_cnt = 0; + + /* Turn interrupts off */ + syscfg = read_reg(c, ONENAND_REG_SYS_CFG1); + syscfg &= ~ONENAND_SYS_CFG1_IOBE; + write_reg(c, syscfg, ONENAND_REG_SYS_CFG1); + + timeout = jiffies + msecs_to_jiffies(20); + while (1) { + if (time_before(jiffies, timeout)) { + intr = read_reg(c, ONENAND_REG_INTERRUPT); + if (intr & ONENAND_INT_MASTER) + break; + } else { + /* Timeout after 20ms */ + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + if (ctrl & ONENAND_CTRL_ONGO) { + /* + * The operation seems to be still going + * so give it some more time. + */ + retry_cnt += 1; + if (retry_cnt < 3) { + timeout = jiffies + + msecs_to_jiffies(20); + continue; + } + } + break; + } + } + } + + intr = read_reg(c, ONENAND_REG_INTERRUPT); + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + + if (intr & ONENAND_INT_READ) { + int ecc = read_reg(c, ONENAND_REG_ECC_STATUS); + + if (ecc) { + unsigned int addr1, addr8; + + addr1 = read_reg(c, ONENAND_REG_START_ADDRESS1); + addr8 = read_reg(c, ONENAND_REG_START_ADDRESS8); + if (ecc & ONENAND_ECC_2BIT_ALL) { + printk(KERN_ERR "onenand_wait: ECC error = " + "0x%04x, addr1 %#x, addr8 %#x\n", + ecc, addr1, addr8); + mtd->ecc_stats.failed++; + return -EBADMSG; + } else if (ecc & ONENAND_ECC_1BIT_ALL) { + printk(KERN_NOTICE "onenand_wait: correctable " + "ECC error = 0x%04x, addr1 %#x, " + "addr8 %#x\n", ecc, addr1, addr8); + mtd->ecc_stats.corrected++; + } + } + } else if (state == FL_READING) { + wait_err("timeout", state, ctrl, intr); + return -EIO; + } + + if (ctrl & ONENAND_CTRL_ERROR) { + wait_err("controller error", state, ctrl, intr); + if (ctrl & ONENAND_CTRL_LOCK) + printk(KERN_ERR "onenand_wait: " + "Device is write protected!!!\n"); + return -EIO; + } + + ctrl_mask = 0xFE9F; + if (this->ongoing) + ctrl_mask &= ~0x8000; + + if (ctrl & ctrl_mask) + wait_warn("unexpected controller status", state, ctrl, intr); + + return 0; +} + +static inline int omap2_onenand_bufferram_offset(struct mtd_info *mtd, int area) +{ + struct onenand_chip *this = mtd->priv; + + if (ONENAND_CURRENT_BUFFERRAM(this)) { + if (area == ONENAND_DATARAM) + return this->writesize; + if (area == ONENAND_SPARERAM) + return mtd->oobsize; + } + + return 0; +} + +static inline int omap2_onenand_dma_transfer(struct omap2_onenand *c, + dma_addr_t src, dma_addr_t dst, + size_t count) +{ + struct dma_async_tx_descriptor *tx; + dma_cookie_t cookie; + + tx = dmaengine_prep_dma_memcpy(c->dma_chan, dst, src, count, + DMA_CTRL_ACK | DMA_PREP_INTERRUPT); + if (!tx) { + dev_err(&c->pdev->dev, "Failed to prepare DMA memcpy\n"); + return -EIO; + } + + reinit_completion(&c->dma_done); + + tx->callback = omap2_onenand_dma_complete_func; + tx->callback_param = &c->dma_done; + + cookie = tx->tx_submit(tx); + if (dma_submit_error(cookie)) { + dev_err(&c->pdev->dev, "Failed to do DMA tx_submit\n"); + return -EIO; + } + + dma_async_issue_pending(c->dma_chan); + + if (!wait_for_completion_io_timeout(&c->dma_done, + msecs_to_jiffies(20))) { + dmaengine_terminate_sync(c->dma_chan); + return -ETIMEDOUT; + } + + return 0; +} + +static int omap2_onenand_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, + size_t count) +{ + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + struct onenand_chip *this = mtd->priv; + struct device *dev = &c->pdev->dev; + void *buf = (void *)buffer; + dma_addr_t dma_src, dma_dst; + int bram_offset, err; + size_t xtra; + + bram_offset = omap2_onenand_bufferram_offset(mtd, area) + area + offset; + /* + * If the buffer address is not DMA-able, len is not long enough to make + * DMA transfers profitable or panic_write() may be in an interrupt + * context fallback to PIO mode. + */ + if (!virt_addr_valid(buf) || bram_offset & 3 || (size_t)buf & 3 || + count < 384 || in_interrupt() || oops_in_progress) + goto out_copy; + + xtra = count & 3; + if (xtra) { + count -= xtra; + memcpy(buf + count, this->base + bram_offset + count, xtra); + } + + dma_dst = dma_map_single(dev, buf, count, DMA_FROM_DEVICE); + dma_src = c->phys_base + bram_offset; + + if (dma_mapping_error(dev, dma_dst)) { + dev_err(dev, "Couldn't DMA map a %d byte buffer\n", count); + goto out_copy; + } + + err = omap2_onenand_dma_transfer(c, dma_src, dma_dst, count); + dma_unmap_single(dev, dma_dst, count, DMA_FROM_DEVICE); + if (!err) + return 0; + + dev_err(dev, "timeout waiting for DMA\n"); + +out_copy: + memcpy(buf, this->base + bram_offset, count); + return 0; +} + +static int omap2_onenand_write_bufferram(struct mtd_info *mtd, int area, + const unsigned char *buffer, + int offset, size_t count) +{ + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + struct onenand_chip *this = mtd->priv; + struct device *dev = &c->pdev->dev; + void *buf = (void *)buffer; + dma_addr_t dma_src, dma_dst; + int bram_offset, err; + + bram_offset = omap2_onenand_bufferram_offset(mtd, area) + area + offset; + /* + * If the buffer address is not DMA-able, len is not long enough to make + * DMA transfers profitable or panic_write() may be in an interrupt + * context fallback to PIO mode. + */ + if (!virt_addr_valid(buf) || bram_offset & 3 || (size_t)buf & 3 || + count < 384 || in_interrupt() || oops_in_progress) + goto out_copy; + + dma_src = dma_map_single(dev, buf, count, DMA_TO_DEVICE); + dma_dst = c->phys_base + bram_offset; + if (dma_mapping_error(dev, dma_src)) { + dev_err(dev, "Couldn't DMA map a %d byte buffer\n", count); + goto out_copy; + } + + err = omap2_onenand_dma_transfer(c, dma_src, dma_dst, count); + dma_unmap_page(dev, dma_src, count, DMA_TO_DEVICE); + if (!err) + return 0; + + dev_err(dev, "timeout waiting for DMA\n"); + +out_copy: + memcpy(this->base + bram_offset, buf, count); + return 0; +} + +static void omap2_onenand_shutdown(struct platform_device *pdev) +{ + struct omap2_onenand *c = dev_get_drvdata(&pdev->dev); + + /* With certain content in the buffer RAM, the OMAP boot ROM code + * can recognize the flash chip incorrectly. Zero it out before + * soft reset. + */ + memset((__force void *)c->onenand.base, 0, ONENAND_BUFRAM_SIZE); +} + +static int omap2_onenand_probe(struct platform_device *pdev) +{ + u32 val; + dma_cap_mask_t mask; + int freq, latency, r; + struct resource *res; + struct omap2_onenand *c; + struct gpmc_onenand_info info; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "error getting memory resource\n"); + return -EINVAL; + } + + r = of_property_read_u32(np, "reg", &val); + if (r) { + dev_err(dev, "reg not found in DT\n"); + return r; + } + + c = devm_kzalloc(dev, sizeof(struct omap2_onenand), GFP_KERNEL); + if (!c) + return -ENOMEM; + + init_completion(&c->irq_done); + init_completion(&c->dma_done); + c->gpmc_cs = val; + c->phys_base = res->start; + + c->onenand.base = devm_ioremap_resource(dev, res); + if (IS_ERR(c->onenand.base)) + return PTR_ERR(c->onenand.base); + + c->int_gpiod = devm_gpiod_get_optional(dev, "int", GPIOD_IN); + if (IS_ERR(c->int_gpiod)) { + r = PTR_ERR(c->int_gpiod); + /* Just try again if this happens */ + if (r != -EPROBE_DEFER) + dev_err(dev, "error getting gpio: %d\n", r); + return r; + } + + if (c->int_gpiod) { + r = devm_request_irq(dev, gpiod_to_irq(c->int_gpiod), + omap2_onenand_interrupt, + IRQF_TRIGGER_RISING, "onenand", c); + if (r) + return r; + + c->onenand.wait = omap2_onenand_wait; + } + + dma_cap_zero(mask); + dma_cap_set(DMA_MEMCPY, mask); + + c->dma_chan = dma_request_channel(mask, NULL, NULL); + if (c->dma_chan) { + c->onenand.read_bufferram = omap2_onenand_read_bufferram; + c->onenand.write_bufferram = omap2_onenand_write_bufferram; + } + + c->pdev = pdev; + c->mtd.priv = &c->onenand; + c->mtd.dev.parent = dev; + mtd_set_of_node(&c->mtd, dev->of_node); + + dev_info(dev, "initializing on CS%d (0x%08lx), va %p, %s mode\n", + c->gpmc_cs, c->phys_base, c->onenand.base, + c->dma_chan ? "DMA" : "PIO"); + + r = onenand_scan(&c->mtd, 1); + if (r < 0) + goto err_release_dma; + + freq = omap2_onenand_get_freq(c->onenand.version_id); + if (freq > 0) { + switch (freq) { + case 104: + latency = 7; + break; + case 83: + latency = 6; + break; + case 66: + latency = 5; + break; + case 56: + latency = 4; + break; + default: /* 40 MHz or lower */ + latency = 3; + break; + } + + r = gpmc_omap_onenand_set_timings(dev, c->gpmc_cs, + freq, latency, &info); + if (r) + goto err_release_onenand; + + r = omap2_onenand_set_cfg(c, info.sync_read, info.sync_write, + latency, info.burst_len); + if (r) + goto err_release_onenand; + + if (info.sync_read || info.sync_write) + dev_info(dev, "optimized timings for %d MHz\n", freq); + } + + r = mtd_device_register(&c->mtd, NULL, 0); + if (r) + goto err_release_onenand; + + platform_set_drvdata(pdev, c); + + return 0; + +err_release_onenand: + onenand_release(&c->mtd); +err_release_dma: + if (c->dma_chan) + dma_release_channel(c->dma_chan); + + return r; +} + +static int omap2_onenand_remove(struct platform_device *pdev) +{ + struct omap2_onenand *c = dev_get_drvdata(&pdev->dev); + + onenand_release(&c->mtd); + if (c->dma_chan) + dma_release_channel(c->dma_chan); + omap2_onenand_shutdown(pdev); + + return 0; +} + +static const struct of_device_id omap2_onenand_id_table[] = { + { .compatible = "ti,omap2-onenand", }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap2_onenand_id_table); + +static struct platform_driver omap2_onenand_driver = { + .probe = omap2_onenand_probe, + .remove = omap2_onenand_remove, + .shutdown = omap2_onenand_shutdown, + .driver = { + .name = DRIVER_NAME, + .of_match_table = omap2_onenand_id_table, + }, +}; + +module_platform_driver(omap2_onenand_driver); + +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jarkko Lavinen "); +MODULE_DESCRIPTION("Glue layer for OneNAND flash on OMAP2 / OMAP3"); -- cgit