| /* |
| * drivers/mtd/ambarella_nand.c |
| * |
| * History: |
| * 2008/04/11 - [Cao Rongrong & Chien-Yang Chen] created file |
| * 2009/01/04 - [Anthony Ginger] Port to 2.6.28 |
| * 2012/05/23 - [Ken He] Add the dma engine driver method support |
| * 2012/07/03 - [Ken He] use the individual FDMA for nand |
| * |
| * Copyright (C) 2004-2009, Ambarella, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/interrupt.h> |
| #include <linux/platform_device.h> |
| #include <linux/delay.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/semaphore.h> |
| #include <linux/slab.h> |
| #include <linux/bitrev.h> |
| #include <linux/bch.h> |
| #include <linux/io.h> |
| #include <linux/of.h> |
| #include <linux/of_mtd.h> |
| #include <linux/clk.h> |
| #include <linux/mtd/mtd.h> |
| #include <linux/mtd/nand.h> |
| #include <linux/mtd/nand_ecc.h> |
| #include <linux/mtd/partitions.h> |
| #include <plat/dma.h> |
| #include <plat/nand.h> |
| #include <plat/fio.h> |
| #include <plat/rct.h> |
| #include <plat/event.h> |
| |
| #define AMBARELLA_NAND_DMA_BUFFER_SIZE 4096 |
| |
| |
| struct ambarella_nand_info { |
| struct nand_chip chip; |
| struct mtd_info mtd; |
| struct nand_hw_control controller; |
| |
| struct device *dev; |
| wait_queue_head_t wq; |
| |
| unsigned char __iomem *regbase; |
| unsigned char __iomem *fdmaregbase; |
| u32 dmabase; |
| int suspend; |
| /* dma irq for transferring data between Nand and FIFO */ |
| int dma_irq; |
| int cmd_irq; |
| /* fdma irq for transferring data between FIFO and Dram */ |
| int fdma_irq; |
| u32 ecc_bits; |
| /* if or not support to read id in 5 cycles */ |
| bool id_cycles_5; |
| bool pllx2; |
| bool soft_ecc; |
| bool nand_wp; |
| |
| /* used for software BCH */ |
| struct bch_control *bch; |
| u32 *errloc; |
| u8 *bch_data; |
| u8 read_ecc_rev[13]; |
| u8 calc_ecc_rev[13]; |
| u8 soft_bch_extra_size; |
| |
| dma_addr_t dmaaddr; |
| u8 *dmabuf; |
| int dma_bufpos; |
| u32 dma_status; |
| u32 fio_dma_sta; |
| u32 fio_ecc_sta; |
| atomic_t irq_flag; |
| |
| /* saved column/page_addr during CMD_SEQIN */ |
| int seqin_column; |
| int seqin_page_addr; |
| |
| /* Operation parameters for nand controller register */ |
| int err_code; |
| u32 cmd; |
| u32 control_reg; |
| u32 addr_hi; |
| u32 addr; |
| u32 dst; |
| dma_addr_t buf_phys; |
| dma_addr_t spare_buf_phys; |
| u32 len; |
| u32 slen; |
| u32 area; |
| u32 ecc; |
| u32 timing[6]; |
| |
| struct notifier_block system_event; |
| struct semaphore system_event_sem; |
| |
| }; |
| |
| static struct nand_ecclayout amb_oobinfo_512 = { |
| .eccbytes = 5, |
| .eccpos = {6, 7, 8, 9, 10}, |
| .oobfree = {{0, 5}, {11, 5}} |
| }; |
| |
| static struct nand_ecclayout amb_oobinfo_2048 = { |
| .eccbytes = 20, |
| .eccpos = {8, 9, 10,11, 12, |
| 24, 25, 26, 27, 28, |
| 40, 41, 42, 43, 44, |
| 56, 57, 58, 59, 60}, |
| .oobfree = {{1, 7}, {13, 3}, |
| {17, 7}, {29, 3}, |
| {33, 7}, {45, 3}, |
| {49, 7}, {61, 3}} |
| }; |
| |
| static struct nand_ecclayout amb_oobinfo_2048_dsm_ecc6 = { |
| .eccbytes = 40, |
| .eccpos = {6, 7, 8, 9, 10, 11, 12, 13, 14, 15, |
| 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, |
| 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, |
| 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}, |
| .oobfree = {{1, 5}, {17, 5}, |
| {33, 5}, {49, 5}} |
| }; |
| |
| static struct nand_ecclayout amb_oobinfo_2048_dsm_ecc8 = { |
| .eccbytes = 52, |
| .eccpos = {19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, |
| 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, |
| 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, |
| 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127}, |
| .oobfree = {{2, 17}, {34, 17}, |
| {66, 17}, {98, 17}} |
| }; |
| |
| /* ==========================================================================*/ |
| #define NAND_TIMING_RSHIFT24BIT(x) (((x) & 0xff000000) >> 24) |
| #define NAND_TIMING_RSHIFT16BIT(x) (((x) & 0x00ff0000) >> 16) |
| #define NAND_TIMING_RSHIFT8BIT(x) (((x) & 0x0000ff00) >> 8) |
| #define NAND_TIMING_RSHIFT0BIT(x) (((x) & 0x000000ff) >> 0) |
| |
| #define NAND_TIMING_LSHIFT24BIT(x) ((x) << 24) |
| #define NAND_TIMING_LSHIFT16BIT(x) ((x) << 16) |
| #define NAND_TIMING_LSHIFT8BIT(x) ((x) << 8) |
| #define NAND_TIMING_LSHIFT0BIT(x) ((x) << 0) |
| |
| static int nand_timing_calc(u32 clk, int minmax, int val) |
| { |
| u32 x; |
| int n,r; |
| |
| x = val * clk; |
| n = x / 1000; |
| r = x % 1000; |
| |
| if (r != 0) |
| n++; |
| |
| if (minmax) |
| n--; |
| return n < 1 ? 1 : n; |
| } |
| |
| static inline int nand_amb_is_hw_bch(struct ambarella_nand_info *nand_info) |
| { |
| return !nand_info->soft_ecc && nand_info->ecc_bits > 1; |
| } |
| |
| static inline int nand_amb_is_sw_bch(struct ambarella_nand_info *nand_info) |
| { |
| return nand_info->soft_ecc && nand_info->ecc_bits > 1; |
| } |
| |
| static void nand_amb_corrected_recovery(struct ambarella_nand_info *nand_info) |
| { |
| u32 fio_ctr_reg, fio_dmactr_reg; |
| |
| /* FIO reset will just reset FIO registers, but will not affect |
| * Nand controller. */ |
| fio_ctr_reg = amba_readl(nand_info->regbase + FIO_CTR_OFFSET); |
| fio_dmactr_reg = amba_readl(nand_info->regbase + FIO_DMACTR_OFFSET); |
| ambarella_fio_rct_reset(); |
| amba_writel(nand_info->regbase + FIO_CTR_OFFSET, fio_ctr_reg); |
| amba_writel(nand_info->regbase + FIO_DMACTR_OFFSET, fio_dmactr_reg); |
| } |
| |
| static void nand_amb_enable_dsm(struct ambarella_nand_info *nand_info) |
| { |
| u32 fio_dsm_ctr = 0, fio_ctr_reg = 0, dma_dsm_ctr = 0; |
| fio_ctr_reg = amba_readl(nand_info->regbase + FIO_CTR_OFFSET); |
| |
| fio_dsm_ctr |= (FIO_DSM_EN | FIO_DSM_MAJP_2KB); |
| dma_dsm_ctr |= (DMA_DSM_EN | DMA_DSM_MAJP_2KB); |
| fio_ctr_reg |= FIO_CTR_RS; |
| fio_ctr_reg &= ~(FIO_CTR_CO | FIO_CTR_SE | FIO_CTR_ECC_6BIT |
| | FIO_CTR_ECC_8BIT); |
| |
| if (nand_info->ecc_bits == 6) { |
| fio_dsm_ctr |= FIO_DSM_SPJP_64B; |
| dma_dsm_ctr |= DMA_DSM_SPJP_64B; |
| } else { |
| u32 nand_ext_ctr_reg = 0; |
| fio_dsm_ctr |= FIO_DSM_SPJP_128B; |
| dma_dsm_ctr |= DMA_DSM_SPJP_128B; |
| |
| nand_ext_ctr_reg = amba_readl(nand_info->regbase + |
| FLASH_EX_CTR_OFFSET); |
| nand_ext_ctr_reg |= NAND_EXT_CTR_SP_2X; |
| amba_writel(nand_info->regbase + FLASH_EX_CTR_OFFSET, |
| nand_ext_ctr_reg); |
| } |
| |
| amba_writel(nand_info->regbase + FIO_CTR_OFFSET, fio_ctr_reg | FIO_CTR_RR); |
| amba_writel(nand_info->regbase + FIO_DSM_CTR_OFFSET, fio_dsm_ctr); |
| amba_writel(nand_info->regbase + FIO_CTR_OFFSET, fio_ctr_reg); |
| amba_writel(nand_info->fdmaregbase + FDMA_DSM_CTR_OFFSET, dma_dsm_ctr); |
| |
| } |
| |
| static void nand_amb_enable_bch(struct ambarella_nand_info *nand_info) |
| { |
| u32 fio_dsm_ctr = 0, fio_ctr_reg = 0, dma_dsm_ctr = 0; |
| fio_ctr_reg = amba_readl(nand_info->regbase + FIO_CTR_OFFSET); |
| |
| fio_dsm_ctr |= (FIO_DSM_EN | FIO_DSM_MAJP_2KB); |
| dma_dsm_ctr |= (DMA_DSM_EN | DMA_DSM_MAJP_2KB); |
| fio_ctr_reg |= (FIO_CTR_RS | FIO_CTR_CO | FIO_CTR_SKIP_BLANK); |
| |
| if (nand_info->ecc_bits == 6) { |
| fio_dsm_ctr |= FIO_DSM_SPJP_64B; |
| dma_dsm_ctr |= DMA_DSM_SPJP_64B; |
| fio_ctr_reg |= FIO_CTR_ECC_6BIT; |
| } else { |
| u32 nand_ext_ctr_reg = 0; |
| fio_dsm_ctr |= FIO_DSM_SPJP_128B; |
| dma_dsm_ctr |= DMA_DSM_SPJP_128B; |
| fio_ctr_reg |= FIO_CTR_ECC_8BIT; |
| nand_ext_ctr_reg = amba_readl(nand_info->regbase + |
| FLASH_EX_CTR_OFFSET); |
| nand_ext_ctr_reg |= NAND_EXT_CTR_SP_2X; |
| amba_writel(nand_info->regbase + FLASH_EX_CTR_OFFSET, |
| nand_ext_ctr_reg); |
| } |
| |
| amba_writel(nand_info->regbase + FIO_CTR_OFFSET, fio_ctr_reg | FIO_CTR_RR); |
| amba_writel(nand_info->regbase + FIO_DSM_CTR_OFFSET, fio_dsm_ctr); |
| amba_writel(nand_info->regbase + FIO_CTR_OFFSET, fio_ctr_reg); |
| amba_writel(nand_info->fdmaregbase + FDMA_DSM_CTR_OFFSET, dma_dsm_ctr); |
| |
| } |
| |
| static void nand_amb_disable_bch(struct ambarella_nand_info *nand_info) |
| { |
| u32 fio_ctr_reg = 0; |
| |
| fio_ctr_reg = amba_readl(nand_info->regbase + FIO_CTR_OFFSET); |
| /* Setup FIO Dual Space Mode Control Register */ |
| fio_ctr_reg |= FIO_CTR_RS; |
| fio_ctr_reg &= ~(FIO_CTR_CO | |
| FIO_CTR_ECC_6BIT | |
| FIO_CTR_ECC_8BIT); |
| |
| if (nand_info->ecc_bits == 8) { |
| u32 nand_ext_ctr_reg = 0; |
| nand_ext_ctr_reg = amba_readl(nand_info->regbase + |
| FLASH_EX_CTR_OFFSET); |
| nand_ext_ctr_reg &= ~NAND_EXT_CTR_SP_2X; |
| amba_writel(nand_info->regbase + FLASH_EX_CTR_OFFSET, |
| nand_ext_ctr_reg); |
| } |
| amba_writel(nand_info->regbase + FIO_CTR_OFFSET, fio_ctr_reg); |
| |
| amba_writel(nand_info->regbase + FIO_DSM_CTR_OFFSET, 0); |
| amba_writel(nand_info->fdmaregbase + FDMA_DSM_CTR_OFFSET, 0); |
| } |
| |
| static int count_zero_bits(u8 *buf, int size, int max_bits) |
| { |
| int i, zero_bits = 0; |
| |
| for (i = 0; i < size; i++) { |
| zero_bits += hweight8(~buf[i]); |
| if (zero_bits > max_bits) |
| break; |
| } |
| return zero_bits; |
| } |
| |
| static int nand_bch_check_blank_page(struct ambarella_nand_info *nand_info, int needset) |
| { |
| struct nand_chip *chip = &nand_info->chip; |
| int eccsteps = chip->ecc.steps; |
| int zeroflip = 0; |
| int oob_subset; |
| int zero_bits = 0; |
| u32 i; |
| u8 *bufpos; |
| u8 *bsp; |
| |
| bufpos = nand_info->dmabuf; |
| bsp = nand_info->dmabuf + nand_info->mtd.writesize; |
| oob_subset = nand_info->mtd.oobsize / eccsteps; |
| |
| for (i = 0; i < eccsteps; i++) { |
| zero_bits = count_zero_bits(bufpos, chip->ecc.size, |
| chip->ecc.strength); |
| if (zero_bits > chip->ecc.strength) |
| return -1; |
| |
| if (zero_bits) |
| zeroflip = 1; |
| |
| zero_bits += count_zero_bits(bsp, oob_subset, |
| chip->ecc.strength); |
| if (zero_bits > chip->ecc.strength) |
| return -1; |
| |
| bufpos += chip->ecc.size; |
| bsp += oob_subset; |
| } |
| |
| if (needset && zeroflip) |
| memset(nand_info->dmabuf, 0xff, nand_info->mtd.writesize); |
| |
| return zeroflip; |
| } |
| static void amb_nand_set_timing(struct ambarella_nand_info *nand_info) |
| { |
| u8 tcls, tals, tcs, tds; |
| u8 tclh, talh, tch, tdh; |
| u8 twp, twh, twb, trr; |
| u8 trp, treh, trb, tceh; |
| u8 trdelay, tclr, twhr, tir; |
| u8 tww, trhz, tar; |
| u32 i, t, clk, val; |
| |
| for (i = 0; i < ARRAY_SIZE(nand_info->timing); i++) { |
| if (nand_info->timing[i] != 0x0) |
| break; |
| } |
| /* if the timing is not setup by Amboot, we leave the timing unchanged */ |
| if (i == ARRAY_SIZE(nand_info->timing)) |
| return; |
| |
| clk = (clk_get_rate(clk_get(NULL, "gclk_core")) / 1000000); |
| if (nand_info->pllx2) |
| clk <<= 1; |
| |
| /* timing 0 */ |
| t = nand_info->timing[0]; |
| tcls = NAND_TIMING_RSHIFT24BIT(t); |
| tals = NAND_TIMING_RSHIFT16BIT(t); |
| tcs = NAND_TIMING_RSHIFT8BIT(t); |
| tds = NAND_TIMING_RSHIFT0BIT(t); |
| |
| tcls = nand_timing_calc(clk, 0, tcls); |
| tals = nand_timing_calc(clk, 0, tals); |
| tcs = nand_timing_calc(clk, 0, tcs); |
| tds = nand_timing_calc(clk, 0, tds); |
| |
| val = NAND_TIMING_LSHIFT24BIT(tcls) | |
| NAND_TIMING_LSHIFT16BIT(tals) | |
| NAND_TIMING_LSHIFT8BIT(tcs) | |
| NAND_TIMING_LSHIFT0BIT(tds); |
| |
| /* use default timing if gclk_core <= 96MHz */ |
| if (clk <= 96) |
| val = 0x20202020; |
| |
| amba_writel(nand_info->regbase + FLASH_TIM0_OFFSET, val); |
| |
| /* timing 1 */ |
| t = nand_info->timing[1]; |
| tclh = NAND_TIMING_RSHIFT24BIT(t); |
| talh = NAND_TIMING_RSHIFT16BIT(t); |
| tch = NAND_TIMING_RSHIFT8BIT(t); |
| tdh = NAND_TIMING_RSHIFT0BIT(t); |
| |
| tclh = nand_timing_calc(clk, 0, tclh); |
| talh = nand_timing_calc(clk, 0, talh); |
| tch = nand_timing_calc(clk, 0, tch); |
| tdh = nand_timing_calc(clk, 0, tdh); |
| |
| val = NAND_TIMING_LSHIFT24BIT(tclh) | |
| NAND_TIMING_LSHIFT16BIT(talh) | |
| NAND_TIMING_LSHIFT8BIT(tch) | |
| NAND_TIMING_LSHIFT0BIT(tdh); |
| |
| /* use default timing if gclk_core <= 96MHz */ |
| if (clk <= 96) |
| val = 0x20202020; |
| |
| amba_writel(nand_info->regbase + FLASH_TIM1_OFFSET, val); |
| |
| /* timing 2 */ |
| t = nand_info->timing[2]; |
| twp = NAND_TIMING_RSHIFT24BIT(t); |
| twh = NAND_TIMING_RSHIFT16BIT(t); |
| twb = NAND_TIMING_RSHIFT8BIT(t); |
| trr = NAND_TIMING_RSHIFT0BIT(t); |
| |
| twp = nand_timing_calc(clk, 0, twp); |
| twh = nand_timing_calc(clk, 0, twh); |
| twb = nand_timing_calc(clk, 1, twb); |
| trr = nand_timing_calc(clk, 0, trr); |
| |
| val = NAND_TIMING_LSHIFT24BIT(twp) | |
| NAND_TIMING_LSHIFT16BIT(twh) | |
| NAND_TIMING_LSHIFT8BIT(twb) | |
| NAND_TIMING_LSHIFT0BIT(trr); |
| |
| /* use default timing if gclk_core <= 96MHz */ |
| if (clk <= 96) |
| val = 0x20204020; |
| |
| amba_writel(nand_info->regbase + FLASH_TIM2_OFFSET, val); |
| |
| /* timing 3 */ |
| t = nand_info->timing[3]; |
| trp = NAND_TIMING_RSHIFT24BIT(t); |
| treh = NAND_TIMING_RSHIFT16BIT(t); |
| trb = NAND_TIMING_RSHIFT8BIT(t); |
| tceh = NAND_TIMING_RSHIFT0BIT(t); |
| |
| trp = nand_timing_calc(clk, 0, trp); |
| treh = nand_timing_calc(clk, 0, treh); |
| trb = nand_timing_calc(clk, 1, trb); |
| tceh = nand_timing_calc(clk, 1, tceh); |
| |
| val = NAND_TIMING_LSHIFT24BIT(trp) | |
| NAND_TIMING_LSHIFT16BIT(treh) | |
| NAND_TIMING_LSHIFT8BIT(trb) | |
| NAND_TIMING_LSHIFT0BIT(tceh); |
| |
| /* use default timing if gclk_core <= 96MHz */ |
| if (clk <= 96) |
| val = 0x20202020; |
| |
| amba_writel(nand_info->regbase + FLASH_TIM3_OFFSET, val); |
| |
| /* timing 4 */ |
| t = nand_info->timing[4]; |
| trdelay = NAND_TIMING_RSHIFT24BIT(t); |
| tclr = NAND_TIMING_RSHIFT16BIT(t); |
| twhr = NAND_TIMING_RSHIFT8BIT(t); |
| tir = NAND_TIMING_RSHIFT0BIT(t); |
| |
| trdelay = trp + treh; |
| tclr = nand_timing_calc(clk, 0, tclr); |
| twhr = nand_timing_calc(clk, 0, twhr); |
| tir = nand_timing_calc(clk, 0, tir); |
| |
| val = NAND_TIMING_LSHIFT24BIT(trdelay) | |
| NAND_TIMING_LSHIFT16BIT(tclr) | |
| NAND_TIMING_LSHIFT8BIT(twhr) | |
| NAND_TIMING_LSHIFT0BIT(tir); |
| |
| /* use default timing if gclk_core <= 96MHz */ |
| if (clk <= 96) |
| val = 0x20202020; |
| |
| amba_writel(nand_info->regbase + FLASH_TIM4_OFFSET, val); |
| |
| /* timing 5 */ |
| t = nand_info->timing[5]; |
| tww = NAND_TIMING_RSHIFT16BIT(t); |
| trhz = NAND_TIMING_RSHIFT8BIT(t); |
| tar = NAND_TIMING_RSHIFT0BIT(t); |
| |
| tww = nand_timing_calc(clk, 0, tww); |
| trhz = nand_timing_calc(clk, 1, trhz); |
| tar = nand_timing_calc(clk, 0, tar); |
| |
| |
| val = NAND_TIMING_LSHIFT16BIT(tww) | |
| NAND_TIMING_LSHIFT8BIT(trhz) | |
| NAND_TIMING_LSHIFT0BIT(tar); |
| |
| /* use default timing if gclk_core <= 96MHz */ |
| if (clk <= 96) |
| val = 0x20202020; |
| |
| amba_writel(nand_info->regbase + FLASH_TIM5_OFFSET, val); |
| } |
| |
| static int ambarella_nand_system_event(struct notifier_block *nb, |
| unsigned long val, void *data) |
| { |
| int errorCode = NOTIFY_OK; |
| struct ambarella_nand_info *nand_info; |
| |
| nand_info = container_of(nb, struct ambarella_nand_info, system_event); |
| |
| switch (val) { |
| case AMBA_EVENT_PRE_CPUFREQ: |
| pr_debug("%s: Pre Change\n", __func__); |
| down(&nand_info->system_event_sem); |
| break; |
| |
| case AMBA_EVENT_POST_CPUFREQ: |
| pr_debug("%s: Post Change\n", __func__); |
| amb_nand_set_timing(nand_info); |
| up(&nand_info->system_event_sem); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return errorCode; |
| } |
| |
| static irqreturn_t nand_fiocmd_isr_handler(int irq, void *dev_id) |
| { |
| irqreturn_t rval = IRQ_NONE; |
| struct ambarella_nand_info *nand_info; |
| u32 val; |
| |
| nand_info = (struct ambarella_nand_info *)dev_id; |
| |
| val = amba_readl(nand_info->regbase + FIO_STA_OFFSET); |
| |
| if (val & FIO_STA_FI) { |
| amba_writel(nand_info->regbase + FLASH_INT_OFFSET, 0x0); |
| |
| atomic_clear_mask(0x1, (unsigned long *)&nand_info->irq_flag); |
| wake_up(&nand_info->wq); |
| |
| rval = IRQ_HANDLED; |
| } |
| |
| return rval; |
| } |
| |
| /* this dma is used to transfer data between Nand and FIO FIFO. */ |
| static irqreturn_t nand_fiodma_isr_handler(int irq, void *dev_id) |
| { |
| struct ambarella_nand_info *nand_info; |
| u32 val, fio_dma_sta; |
| |
| nand_info = (struct ambarella_nand_info *)dev_id; |
| |
| val = amba_readl(nand_info->regbase + FIO_DMACTR_OFFSET); |
| |
| if ((val & (FIO_DMACTR_SD | FIO_DMACTR_CF | |
| FIO_DMACTR_XD | FIO_DMACTR_FL)) == FIO_DMACTR_FL) { |
| fio_dma_sta = amba_readl(nand_info->regbase + FIO_DMASTA_OFFSET); |
| /* dummy IRQ by S2 chip */ |
| if (fio_dma_sta == 0x0) |
| return IRQ_HANDLED; |
| |
| nand_info->fio_dma_sta = fio_dma_sta; |
| |
| amba_writel(nand_info->regbase + FIO_DMASTA_OFFSET, 0x0); |
| |
| if (nand_amb_is_hw_bch(nand_info)) { |
| nand_info->fio_ecc_sta = |
| amba_readl(nand_info->regbase + FIO_ECC_RPT_STA_OFFSET); |
| amba_writel(nand_info->regbase + FIO_ECC_RPT_STA_OFFSET, 0x0); |
| } |
| atomic_clear_mask(0x2, (unsigned long *)&nand_info->irq_flag); |
| wake_up(&nand_info->wq); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| /* this dma is used to transfer data between FIO FIFO and Memory. */ |
| static irqreturn_t ambarella_fdma_isr_handler(int irq, void *dev_id) |
| { |
| irqreturn_t rval = IRQ_NONE; |
| struct ambarella_nand_info *nand_info; |
| u32 int_src; |
| |
| nand_info = (struct ambarella_nand_info *)dev_id; |
| |
| int_src = amba_readl(nand_info->fdmaregbase + FDMA_INT_OFFSET); |
| |
| if (int_src & (1 << FIO_DMA_CHAN)) { |
| nand_info->dma_status = |
| amba_readl(nand_info->fdmaregbase + FDMA_STA_OFFSET); |
| amba_writel(nand_info->fdmaregbase + FDMA_STA_OFFSET, 0); |
| amba_writel(nand_info->fdmaregbase + FDMA_SPR_STA_OFFSET, 0); |
| |
| atomic_clear_mask(0x4, (unsigned long *)&nand_info->irq_flag); |
| wake_up(&nand_info->wq); |
| |
| rval = IRQ_HANDLED; |
| } |
| |
| return rval; |
| } |
| |
| static void nand_amb_setup_dma_devmem(struct ambarella_nand_info *nand_info) |
| { |
| u32 ctrl_val; |
| u32 size = 0; |
| |
| /* init and enable fdma to transfer data betwee FIFO and Memory */ |
| if (nand_info->len > 16) |
| ctrl_val = DMA_CHANX_CTR_WM | DMA_CHANX_CTR_NI | DMA_NODC_MN_BURST_SIZE; |
| else |
| ctrl_val = DMA_CHANX_CTR_WM | DMA_CHANX_CTR_NI | DMA_NODC_SP_BURST_SIZE; |
| |
| ctrl_val |= nand_info->len | DMA_CHANX_CTR_EN; |
| ctrl_val &= ~DMA_CHANX_CTR_D; |
| |
| /* Setup main external DMA engine transfer */ |
| amba_writel(nand_info->fdmaregbase + FDMA_STA_OFFSET, 0); |
| |
| amba_writel(nand_info->fdmaregbase + FDMA_SRC_OFFSET, nand_info->dmabase); |
| amba_writel(nand_info->fdmaregbase + FDMA_DST_OFFSET, nand_info->buf_phys); |
| |
| if (nand_info->ecc_bits > 1) { |
| /* Setup spare external DMA engine transfer */ |
| amba_writel(nand_info->fdmaregbase + FDMA_SPR_STA_OFFSET, 0x0); |
| amba_writel(nand_info->fdmaregbase + FDMA_SPR_SRC_OFFSET, nand_info->dmabase); |
| amba_writel(nand_info->fdmaregbase + FDMA_SPR_DST_OFFSET, nand_info->spare_buf_phys); |
| amba_writel(nand_info->fdmaregbase + FDMA_SPR_CNT_OFFSET, nand_info->slen); |
| } |
| |
| amba_writel(nand_info->fdmaregbase + FDMA_CTR_OFFSET, ctrl_val); |
| |
| /* init and enable fio-dma to transfer data between Nand and FIFO */ |
| amba_writel(nand_info->regbase + FIO_DMAADR_OFFSET, nand_info->addr); |
| |
| size = nand_info->len + nand_info->slen; |
| if (size > 16) { |
| ctrl_val = FIO_DMACTR_EN | |
| FIO_DMACTR_FL | |
| FIO_MN_BURST_SIZE | |
| size; |
| } else { |
| ctrl_val = FIO_DMACTR_EN | |
| FIO_DMACTR_FL | |
| FIO_SP_BURST_SIZE | |
| size; |
| } |
| amba_writel(nand_info->regbase + FIO_DMACTR_OFFSET, ctrl_val); |
| } |
| |
| static void nand_amb_setup_dma_memdev(struct ambarella_nand_info *nand_info) |
| { |
| u32 ctrl_val, dma_burst_val, fio_burst_val; |
| u32 size = 0; |
| |
| if (nand_info->ecc_bits > 1) { |
| dma_burst_val = DMA_NODC_MN_BURST_SIZE8; |
| fio_burst_val = FIO_MN_BURST_SIZE8; |
| } else { |
| dma_burst_val = DMA_NODC_MN_BURST_SIZE; |
| fio_burst_val = FIO_MN_BURST_SIZE; |
| } |
| |
| /* init and enable fdma to transfer data betwee FIFO and Memory */ |
| if (nand_info->len > 16) |
| ctrl_val = DMA_CHANX_CTR_RM | DMA_CHANX_CTR_NI | dma_burst_val; |
| else |
| ctrl_val = DMA_CHANX_CTR_RM | DMA_CHANX_CTR_NI | DMA_NODC_SP_BURST_SIZE; |
| |
| ctrl_val |= nand_info->len | DMA_CHANX_CTR_EN; |
| ctrl_val &= ~DMA_CHANX_CTR_D; |
| |
| /* Setup main external DMA engine transfer */ |
| amba_writel(nand_info->fdmaregbase + FDMA_STA_OFFSET, 0); |
| |
| amba_writel(nand_info->fdmaregbase + FDMA_SRC_OFFSET, nand_info->buf_phys); |
| amba_writel(nand_info->fdmaregbase + FDMA_DST_OFFSET, nand_info->dmabase); |
| |
| if (nand_info->ecc_bits > 1) { |
| /* Setup spare external DMA engine transfer */ |
| amba_writel(nand_info->fdmaregbase + FDMA_SPR_STA_OFFSET, 0x0); |
| amba_writel(nand_info->fdmaregbase + FDMA_SPR_SRC_OFFSET, nand_info->spare_buf_phys); |
| amba_writel(nand_info->fdmaregbase + FDMA_SPR_DST_OFFSET, nand_info->dmabase); |
| amba_writel(nand_info->fdmaregbase + FDMA_SPR_CNT_OFFSET, nand_info->slen); |
| } |
| |
| amba_writel(nand_info->fdmaregbase + FDMA_CTR_OFFSET, ctrl_val); |
| |
| /* init and enable fio-dma to transfer data between Nand and FIFO */ |
| amba_writel(nand_info->regbase + FIO_DMAADR_OFFSET, nand_info->addr); |
| |
| size = nand_info->len + nand_info->slen; |
| if (size > 16) { |
| ctrl_val = FIO_DMACTR_EN | FIO_DMACTR_FL | fio_burst_val | |
| FIO_DMACTR_RM | size; |
| } else { |
| ctrl_val = FIO_DMACTR_EN | FIO_DMACTR_FL | FIO_SP_BURST_SIZE | |
| FIO_DMACTR_RM | size; |
| } |
| amba_writel(nand_info->regbase + FIO_DMACTR_OFFSET, ctrl_val); |
| } |
| |
| static int nand_amb_request(struct ambarella_nand_info *nand_info) |
| { |
| int errorCode = 0; |
| u32 cmd; |
| u32 nand_ctr_reg = 0; |
| u32 nand_cmd_reg = 0; |
| u32 fio_ctr_reg = 0; |
| long timeout; |
| |
| if (unlikely(nand_info->suspend == 1)) { |
| dev_err(nand_info->dev, "%s: suspend!\n", __func__); |
| errorCode = -EPERM; |
| goto nand_amb_request_exit; |
| } |
| |
| cmd = nand_info->cmd; |
| |
| nand_ctr_reg = nand_info->control_reg | NAND_CTR_WAS; |
| |
| fio_select_lock(SELECT_FIO_FL); |
| |
| if ((nand_info->nand_wp) && |
| (cmd == NAND_AMB_CMD_ERASE || cmd == NAND_AMB_CMD_COPYBACK || |
| cmd == NAND_AMB_CMD_PROGRAM || cmd == NAND_AMB_CMD_READSTATUS)) |
| nand_ctr_reg &= ~NAND_CTR_WP; |
| |
| switch (cmd) { |
| case NAND_AMB_CMD_RESET: |
| nand_cmd_reg = NAND_AMB_CMD_RESET; |
| amba_writel(nand_info->regbase + FLASH_CMD_OFFSET, |
| nand_cmd_reg); |
| break; |
| |
| case NAND_AMB_CMD_READID: |
| nand_ctr_reg |= NAND_CTR_A(nand_info->addr_hi); |
| nand_cmd_reg = nand_info->addr | NAND_AMB_CMD_READID; |
| |
| if (nand_info->id_cycles_5) { |
| u32 nand_ext_ctr_reg = 0; |
| |
| nand_ext_ctr_reg = amba_readl(nand_info->regbase + FLASH_EX_CTR_OFFSET); |
| nand_ext_ctr_reg |= NAND_EXT_CTR_I5; |
| nand_ctr_reg &= ~(NAND_CTR_I4); |
| amba_writel(nand_info->regbase + FLASH_EX_CTR_OFFSET, |
| nand_ext_ctr_reg); |
| } |
| |
| amba_writel(nand_info->regbase + FLASH_CTR_OFFSET, |
| nand_ctr_reg); |
| amba_writel(nand_info->regbase + FLASH_CMD_OFFSET, |
| nand_cmd_reg); |
| break; |
| |
| case NAND_AMB_CMD_READSTATUS: |
| nand_ctr_reg |= NAND_CTR_A(nand_info->addr_hi); |
| nand_cmd_reg = nand_info->addr | NAND_AMB_CMD_READSTATUS; |
| amba_writel(nand_info->regbase + FLASH_CTR_OFFSET, |
| nand_ctr_reg); |
| amba_writel(nand_info->regbase + FLASH_CMD_OFFSET, |
| nand_cmd_reg); |
| break; |
| |
| case NAND_AMB_CMD_ERASE: |
| nand_ctr_reg |= NAND_CTR_A(nand_info->addr_hi); |
| nand_cmd_reg = nand_info->addr | NAND_AMB_CMD_ERASE; |
| amba_writel(nand_info->regbase + FLASH_CTR_OFFSET, |
| nand_ctr_reg); |
| amba_writel(nand_info->regbase + FLASH_CMD_OFFSET, |
| nand_cmd_reg); |
| break; |
| |
| case NAND_AMB_CMD_COPYBACK: |
| nand_ctr_reg |= NAND_CTR_A(nand_info->addr_hi); |
| nand_ctr_reg |= NAND_CTR_CE; |
| nand_cmd_reg = nand_info->addr | NAND_AMB_CMD_COPYBACK; |
| amba_writel(nand_info->regbase + FLASH_CFI_OFFSET, |
| nand_info->dst); |
| amba_writel(nand_info->regbase + FLASH_CTR_OFFSET, |
| nand_ctr_reg); |
| amba_writel(nand_info->regbase + FLASH_CMD_OFFSET, |
| nand_cmd_reg); |
| break; |
| |
| case NAND_AMB_CMD_READ: |
| nand_ctr_reg |= NAND_CTR_A(nand_info->addr_hi); |
| |
| if (nand_amb_is_hw_bch(nand_info)) { |
| /* Setup FIO DMA Control Register */ |
| nand_amb_enable_bch(nand_info); |
| /* in dual space mode,enable the SE bit */ |
| nand_ctr_reg |= NAND_CTR_SE; |
| |
| /* Clean Flash_IO_ecc_rpt_status Register */ |
| amba_writel(nand_info->regbase + FIO_ECC_RPT_STA_OFFSET, 0x0); |
| } else if (nand_amb_is_sw_bch(nand_info)) { |
| /* Setup FIO DMA Control Register */ |
| nand_amb_enable_dsm(nand_info); |
| /* in dual space mode,enable the SE bit */ |
| nand_ctr_reg |= NAND_CTR_SE; |
| } else { |
| if (nand_info->area == MAIN_ECC) |
| nand_ctr_reg |= (NAND_CTR_SE); |
| else if (nand_info->area == SPARE_ONLY || |
| nand_info->area == SPARE_ECC) |
| nand_ctr_reg |= (NAND_CTR_SE | NAND_CTR_SA); |
| |
| fio_ctr_reg = amba_readl(nand_info->regbase + FIO_CTR_OFFSET); |
| fio_ctr_reg &= ~(FIO_CTR_CO | FIO_CTR_RS); |
| |
| if (nand_info->area == SPARE_ONLY || |
| nand_info->area == SPARE_ECC || |
| nand_info->area == MAIN_ECC) |
| fio_ctr_reg |= (FIO_CTR_RS); |
| |
| switch (nand_info->ecc) { |
| case EC_MDSE: |
| nand_ctr_reg |= NAND_CTR_EC_SPARE; |
| fio_ctr_reg |= FIO_CTR_CO; |
| break; |
| case EC_MESD: |
| nand_ctr_reg |= NAND_CTR_EC_MAIN; |
| fio_ctr_reg |= FIO_CTR_CO; |
| break; |
| case EC_MESE: |
| nand_ctr_reg |= (NAND_CTR_EC_MAIN | NAND_CTR_EC_SPARE); |
| fio_ctr_reg |= FIO_CTR_CO; |
| break; |
| case EC_MDSD: |
| default: |
| break; |
| } |
| |
| amba_writel(nand_info->regbase + FIO_CTR_OFFSET, |
| fio_ctr_reg); |
| } |
| |
| amba_writel(nand_info->regbase + FLASH_CTR_OFFSET, nand_ctr_reg); |
| nand_amb_setup_dma_devmem(nand_info); |
| |
| break; |
| |
| case NAND_AMB_CMD_PROGRAM: |
| nand_ctr_reg |= NAND_CTR_A(nand_info->addr_hi); |
| |
| if (nand_amb_is_hw_bch(nand_info)) { |
| /* Setup FIO DMA Control Register */ |
| nand_amb_enable_bch(nand_info); |
| /* in dual space mode,enable the SE bit */ |
| nand_ctr_reg |= NAND_CTR_SE; |
| |
| /* Clean Flash_IO_ecc_rpt_status Register */ |
| amba_writel(nand_info->regbase + FIO_ECC_RPT_STA_OFFSET, 0x0); |
| } else if (nand_amb_is_sw_bch(nand_info)) { |
| /* Setup FIO DMA Control Register */ |
| nand_amb_enable_dsm(nand_info); |
| /* in dual space mode,enable the SE bit */ |
| nand_ctr_reg |= NAND_CTR_SE; |
| } else { |
| if (nand_info->area == MAIN_ECC) |
| nand_ctr_reg |= (NAND_CTR_SE); |
| else if (nand_info->area == SPARE_ONLY || |
| nand_info->area == SPARE_ECC) |
| nand_ctr_reg |= (NAND_CTR_SE | NAND_CTR_SA); |
| |
| fio_ctr_reg = amba_readl(nand_info->regbase + FIO_CTR_OFFSET); |
| fio_ctr_reg &= ~(FIO_CTR_CO | FIO_CTR_RS); |
| |
| if (nand_info->area == SPARE_ONLY || |
| nand_info->area == SPARE_ECC || |
| nand_info->area == MAIN_ECC) |
| fio_ctr_reg |= (FIO_CTR_RS); |
| |
| switch (nand_info->ecc) { |
| case EG_MDSE : |
| nand_ctr_reg |= NAND_CTR_EG_SPARE; |
| break; |
| case EG_MESD : |
| nand_ctr_reg |= NAND_CTR_EG_MAIN; |
| break; |
| case EG_MESE : |
| nand_ctr_reg |= (NAND_CTR_EG_MAIN | NAND_CTR_EG_SPARE); |
| break; |
| case EG_MDSD: |
| default: |
| break; |
| } |
| |
| amba_writel(nand_info->regbase + FIO_CTR_OFFSET, |
| fio_ctr_reg); |
| } |
| amba_writel(nand_info->regbase + FLASH_CTR_OFFSET, nand_ctr_reg); |
| nand_amb_setup_dma_memdev(nand_info); |
| |
| break; |
| |
| default: |
| dev_warn(nand_info->dev, |
| "%s: wrong command %d!\n", __func__, cmd); |
| errorCode = -EINVAL; |
| goto nand_amb_request_done; |
| break; |
| } |
| |
| if (cmd == NAND_AMB_CMD_READ || cmd == NAND_AMB_CMD_PROGRAM) { |
| timeout = wait_event_timeout(nand_info->wq, |
| atomic_read(&nand_info->irq_flag) == 0x0, 1 * HZ); |
| if (timeout <= 0) { |
| errorCode = -EBUSY; |
| dev_err(nand_info->dev, "%s: cmd=0x%x timeout 0x%08x\n", |
| __func__, cmd, atomic_read(&nand_info->irq_flag)); |
| } else { |
| dev_dbg(nand_info->dev, "%ld jiffies left.\n", timeout); |
| } |
| |
| if (nand_info->dma_status & (DMA_CHANX_STA_OE | DMA_CHANX_STA_ME | |
| DMA_CHANX_STA_BE | DMA_CHANX_STA_RWE | |
| DMA_CHANX_STA_AE)) { |
| dev_err(nand_info->dev, |
| "%s: Errors happend in DMA transaction %d!\n", |
| __func__, nand_info->dma_status); |
| errorCode = -EIO; |
| goto nand_amb_request_done; |
| } |
| |
| if (nand_amb_is_hw_bch(nand_info)) { |
| if (cmd == NAND_AMB_CMD_READ) { |
| if (nand_info->fio_ecc_sta & FIO_ECC_RPT_FAIL) { |
| int ret; |
| |
| /* Workaround for some chips which will |
| * report ECC failed for blank page. */ |
| if (FIO_SUPPORT_SKIP_BLANK_ECC) |
| ret = -1; |
| else |
| ret = nand_bch_check_blank_page(nand_info, 1); |
| |
| if (ret < 0) { |
| nand_info->mtd.ecc_stats.failed++; |
| dev_err(nand_info->dev, |
| "BCH corrected failed (0x%08x), addr is 0x[%x]!\n", |
| nand_info->fio_ecc_sta, nand_info->addr); |
| } |
| } else if (nand_info->fio_ecc_sta & FIO_ECC_RPT_ERR) { |
| unsigned int corrected; |
| corrected = 1; |
| if (NAND_ECC_RPT_NUM_SUPPORT) { |
| corrected = (nand_info->fio_ecc_sta >> 16) & 0x000F; |
| dev_info(nand_info->dev, "BCH correct [%d]bit in block[%d]\n", |
| corrected, (nand_info->fio_ecc_sta & 0x00007FFF)); |
| } else { |
| /* once bitflip and data corrected happened, BCH will keep on |
| * to report bitflip in following read operations, even though |
| * there is no bitflip happened really. So this is a workaround |
| * to get it back. */ |
| nand_amb_corrected_recovery(nand_info); |
| } |
| nand_info->mtd.ecc_stats.corrected += corrected; |
| } |
| } else if (cmd == NAND_AMB_CMD_PROGRAM) { |
| if (nand_info->fio_ecc_sta & FIO_ECC_RPT_FAIL) { |
| dev_err(nand_info->dev, |
| "BCH program program failed (0x%08x)!\n", |
| nand_info->fio_ecc_sta); |
| } |
| } |
| } |
| |
| if ((nand_info->fio_dma_sta & FIO_DMASTA_RE) |
| || (nand_info->fio_dma_sta & FIO_DMASTA_AE) |
| || !(nand_info->fio_dma_sta & FIO_DMASTA_DN)) { |
| u32 block_addr; |
| block_addr = nand_info->addr / |
| nand_info->mtd.erasesize * |
| nand_info->mtd.erasesize; |
| dev_err(nand_info->dev, |
| "%s: dma_status=0x%08x, cmd=0x%x, addr_hi=0x%x, " |
| "addr=0x%x, dst=0x%x, buf=0x%x, " |
| "len=0x%x, area=0x%x, ecc=0x%x, " |
| "block addr=0x%x!\n", |
| __func__, |
| nand_info->fio_dma_sta, |
| cmd, |
| nand_info->addr_hi, |
| nand_info->addr, |
| nand_info->dst, |
| nand_info->buf_phys, |
| nand_info->len, |
| nand_info->area, |
| nand_info->ecc, |
| block_addr); |
| errorCode = -EIO; |
| goto nand_amb_request_done; |
| } |
| } else { |
| /* just wait cmd irq, no care about both DMA irqs */ |
| timeout = wait_event_timeout(nand_info->wq, |
| (atomic_read(&nand_info->irq_flag) & 0x1) == 0x0, 1 * HZ); |
| if (timeout <= 0) { |
| errorCode = -EBUSY; |
| dev_err(nand_info->dev, "%s: cmd=0x%x timeout 0x%08x\n", |
| __func__, cmd, atomic_read(&nand_info->irq_flag)); |
| goto nand_amb_request_done; |
| } else { |
| dev_dbg(nand_info->dev, "%ld jiffies left.\n", timeout); |
| |
| if (cmd == NAND_AMB_CMD_READID) { |
| u32 id = amba_readl(nand_info->regbase + |
| FLASH_ID_OFFSET); |
| if (nand_info->id_cycles_5) { |
| u32 id_5 = amba_readl(nand_info->regbase + |
| FLASH_EX_ID_OFFSET); |
| nand_info->dmabuf[4] = (unsigned char) (id_5 & 0xFF); |
| } |
| nand_info->dmabuf[0] = (unsigned char) (id >> 24); |
| nand_info->dmabuf[1] = (unsigned char) (id >> 16); |
| nand_info->dmabuf[2] = (unsigned char) (id >> 8); |
| nand_info->dmabuf[3] = (unsigned char) id; |
| } else if (cmd == NAND_AMB_CMD_READSTATUS) { |
| *nand_info->dmabuf = amba_readl(nand_info->regbase + |
| FLASH_STA_OFFSET); |
| } |
| } |
| } |
| |
| nand_amb_request_done: |
| atomic_set(&nand_info->irq_flag, 0x7); |
| nand_info->dma_status = 0; |
| /* Avoid to flush previous error info */ |
| if (nand_info->err_code == 0) |
| nand_info->err_code = errorCode; |
| |
| if ((nand_info->nand_wp) && |
| (cmd == NAND_AMB_CMD_ERASE || cmd == NAND_AMB_CMD_COPYBACK || |
| cmd == NAND_AMB_CMD_PROGRAM || cmd == NAND_AMB_CMD_READSTATUS)) { |
| nand_ctr_reg |= NAND_CTR_WP; |
| amba_writel(nand_info->regbase + FLASH_CTR_OFFSET, nand_ctr_reg); |
| } |
| |
| if ((cmd == NAND_AMB_CMD_READ || cmd == NAND_AMB_CMD_PROGRAM) |
| && nand_amb_is_hw_bch(nand_info)) |
| nand_amb_disable_bch(nand_info); |
| |
| fio_unlock(SELECT_FIO_FL); |
| |
| nand_amb_request_exit: |
| return errorCode; |
| } |
| |
| int nand_amb_reset(struct ambarella_nand_info *nand_info) |
| { |
| nand_info->cmd = NAND_AMB_CMD_RESET; |
| |
| return nand_amb_request(nand_info); |
| } |
| |
| int nand_amb_read_id(struct ambarella_nand_info *nand_info) |
| { |
| nand_info->cmd = NAND_AMB_CMD_READID; |
| nand_info->addr_hi = 0; |
| nand_info->addr = 0; |
| |
| return nand_amb_request(nand_info); |
| } |
| |
| int nand_amb_read_status(struct ambarella_nand_info *nand_info) |
| { |
| nand_info->cmd = NAND_AMB_CMD_READSTATUS; |
| nand_info->addr_hi = 0; |
| nand_info->addr = 0; |
| |
| return nand_amb_request(nand_info); |
| } |
| |
| int nand_amb_erase(struct ambarella_nand_info *nand_info, u32 page_addr) |
| { |
| int errorCode = 0; |
| u32 addr_hi; |
| u32 addr; |
| u64 addr64; |
| |
| addr64 = (u64)(page_addr * nand_info->mtd.writesize); |
| addr_hi = (u32)(addr64 >> 32); |
| addr = (u32)addr64; |
| |
| nand_info->cmd = NAND_AMB_CMD_ERASE; |
| nand_info->addr_hi = addr_hi; |
| nand_info->addr = addr; |
| |
| /* Fix dual space mode bug */ |
| if (nand_info->ecc_bits > 1) |
| amba_writel(nand_info->regbase + FIO_DMAADR_OFFSET, nand_info->addr); |
| |
| errorCode = nand_amb_request(nand_info); |
| |
| return errorCode; |
| } |
| |
| int nand_amb_read_data(struct ambarella_nand_info *nand_info, |
| u32 page_addr, dma_addr_t buf_dma, u8 area) |
| { |
| int errorCode = 0; |
| u32 addr_hi; |
| u32 addr; |
| u32 len; |
| u64 addr64; |
| u8 ecc = 0; |
| |
| addr64 = (u64)(page_addr * nand_info->mtd.writesize); |
| addr_hi = (u32)(addr64 >> 32); |
| addr = (u32)addr64; |
| |
| switch (area) { |
| case MAIN_ONLY: |
| ecc = EC_MDSD; |
| len = nand_info->mtd.writesize; |
| break; |
| case MAIN_ECC: |
| ecc = EC_MESD; |
| len = nand_info->mtd.writesize; |
| break; |
| case SPARE_ONLY: |
| ecc = EC_MDSD; |
| len = nand_info->mtd.oobsize; |
| break; |
| case SPARE_ECC: |
| ecc = EC_MDSE; |
| len = nand_info->mtd.oobsize; |
| break; |
| default: |
| dev_err(nand_info->dev, "%s: Wrong area.\n", __func__); |
| errorCode = -EINVAL; |
| goto nand_amb_read_page_exit; |
| break; |
| } |
| |
| nand_info->slen = 0; |
| if (nand_info->ecc_bits > 1) { |
| /* when use BCH, the EG and EC should be 0 */ |
| ecc = 0; |
| len = nand_info->mtd.writesize; |
| nand_info->slen = nand_info->mtd.oobsize; |
| nand_info->spare_buf_phys = buf_dma + len; |
| } |
| |
| nand_info->cmd = NAND_AMB_CMD_READ; |
| nand_info->addr_hi = addr_hi; |
| nand_info->addr = addr; |
| nand_info->buf_phys = buf_dma; |
| nand_info->len = len; |
| nand_info->area = area; |
| nand_info->ecc = ecc; |
| |
| errorCode = nand_amb_request(nand_info); |
| |
| nand_amb_read_page_exit: |
| return errorCode; |
| } |
| |
| int nand_amb_write_data(struct ambarella_nand_info *nand_info, |
| u32 page_addr, dma_addr_t buf_dma, u8 area) |
| { |
| int errorCode = 0; |
| u32 addr_hi; |
| u32 addr; |
| u32 len; |
| u64 addr64; |
| u8 ecc; |
| |
| addr64 = (u64)(page_addr * nand_info->mtd.writesize); |
| addr_hi = (u32)(addr64 >> 32); |
| addr = (u32)addr64; |
| |
| switch (area) { |
| case MAIN_ONLY: |
| ecc = EG_MDSD; |
| len = nand_info->mtd.writesize; |
| break; |
| case MAIN_ECC: |
| ecc = EG_MESD; |
| len = nand_info->mtd.writesize; |
| break; |
| case SPARE_ONLY: |
| ecc = EG_MDSD; |
| len = nand_info->mtd.oobsize; |
| break; |
| case SPARE_ECC: |
| ecc = EG_MDSE; |
| len = nand_info->mtd.oobsize; |
| break; |
| default: |
| dev_err(nand_info->dev, "%s: Wrong area.\n", __func__); |
| errorCode = -EINVAL; |
| goto nand_amb_write_page_exit; |
| break; |
| } |
| |
| nand_info->slen = 0; |
| if (nand_info->ecc_bits > 1) { |
| /* when use BCH, the EG and EC should be 0 */ |
| ecc = 0; |
| len = nand_info->mtd.writesize; |
| nand_info->slen = nand_info->mtd.oobsize; |
| nand_info->spare_buf_phys = buf_dma + len; |
| } |
| nand_info->cmd = NAND_AMB_CMD_PROGRAM; |
| nand_info->addr_hi = addr_hi; |
| nand_info->addr = addr; |
| nand_info->buf_phys = buf_dma; |
| nand_info->len = len; |
| nand_info->area = area; |
| nand_info->ecc = ecc; |
| |
| errorCode = nand_amb_request(nand_info); |
| |
| nand_amb_write_page_exit: |
| return errorCode; |
| } |
| |
| |
| /* ==========================================================================*/ |
| static uint8_t amb_nand_read_byte(struct mtd_info *mtd) |
| { |
| struct ambarella_nand_info *nand_info; |
| uint8_t *data; |
| |
| nand_info = (struct ambarella_nand_info *)mtd->priv; |
| |
| data = nand_info->dmabuf + nand_info->dma_bufpos; |
| nand_info->dma_bufpos++; |
| |
| return *data; |
| } |
| |
| static u16 amb_nand_read_word(struct mtd_info *mtd) |
| { |
| struct ambarella_nand_info *nand_info; |
| u16 *data; |
| |
| nand_info = (struct ambarella_nand_info *)mtd->priv; |
| |
| data = (u16 *)(nand_info->dmabuf + nand_info->dma_bufpos); |
| nand_info->dma_bufpos += 2; |
| |
| return *data; |
| } |
| |
| static void amb_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) |
| { |
| struct ambarella_nand_info *nand_info; |
| |
| nand_info = (struct ambarella_nand_info *)mtd->priv; |
| |
| BUG_ON((nand_info->dma_bufpos + len) > AMBARELLA_NAND_DMA_BUFFER_SIZE); |
| |
| memcpy(buf, nand_info->dmabuf + nand_info->dma_bufpos, len); |
| nand_info->dma_bufpos += len; |
| } |
| |
| static void amb_nand_write_buf(struct mtd_info *mtd, |
| const uint8_t *buf, int len) |
| { |
| struct ambarella_nand_info *nand_info; |
| |
| nand_info = (struct ambarella_nand_info *)mtd->priv; |
| |
| BUG_ON((nand_info->dma_bufpos + len) > AMBARELLA_NAND_DMA_BUFFER_SIZE); |
| |
| memcpy(nand_info->dmabuf + nand_info->dma_bufpos, buf, len); |
| nand_info->dma_bufpos += len; |
| } |
| |
| static void amb_nand_select_chip(struct mtd_info *mtd, int chip) |
| { |
| struct ambarella_nand_info *nand_info; |
| |
| nand_info = (struct ambarella_nand_info *)mtd->priv; |
| |
| if (chip > 0) { |
| dev_err(nand_info->dev, |
| "%s: Multi-Chip isn't supported yet.\n", __func__); |
| } |
| } |
| |
| static void amb_nand_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl) |
| { |
| |
| } |
| |
| static int amb_nand_dev_ready(struct mtd_info *mtd) |
| { |
| struct nand_chip *chip = mtd->priv; |
| |
| chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); |
| |
| return (chip->read_byte(mtd) & NAND_STATUS_READY) ? 1 : 0; |
| } |
| |
| static int amb_nand_waitfunc(struct mtd_info *mtd, struct nand_chip *chip) |
| { |
| int status = 0; |
| struct ambarella_nand_info *nand_info; |
| |
| nand_info = (struct ambarella_nand_info *)mtd->priv; |
| |
| /* ambarella nand controller has waited for the command completion, |
| * but still need to check the nand chip's status |
| */ |
| if (nand_info->err_code) |
| status = NAND_STATUS_FAIL; |
| else { |
| chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); |
| status = chip->read_byte(mtd); |
| } |
| |
| return status; |
| } |
| |
| static void amb_nand_cmdfunc(struct mtd_info *mtd, unsigned command, |
| int column, int page_addr) |
| { |
| struct ambarella_nand_info *nand_info; |
| |
| nand_info = (struct ambarella_nand_info *)mtd->priv; |
| nand_info->err_code = 0; |
| |
| switch(command) { |
| case NAND_CMD_RESET: |
| nand_amb_reset(nand_info); |
| break; |
| case NAND_CMD_READID: |
| nand_info->dma_bufpos = 0; |
| nand_amb_read_id(nand_info); |
| break; |
| case NAND_CMD_STATUS: |
| nand_info->dma_bufpos = 0; |
| nand_amb_read_status(nand_info); |
| break; |
| case NAND_CMD_ERASE1: |
| nand_amb_erase(nand_info, page_addr); |
| break; |
| case NAND_CMD_ERASE2: |
| break; |
| case NAND_CMD_READOOB: |
| nand_info->dma_bufpos = column; |
| if (nand_info->ecc_bits > 1) { |
| u8 area = nand_info->soft_ecc ? MAIN_ONLY : MAIN_ECC; |
| nand_info->dma_bufpos = mtd->writesize; |
| nand_amb_read_data(nand_info, page_addr, |
| nand_info->dmaaddr, area); |
| } else { |
| nand_amb_read_data(nand_info, page_addr, |
| nand_info->dmaaddr, SPARE_ONLY); |
| } |
| break; |
| case NAND_CMD_READ0: |
| { |
| u8 area = nand_info->soft_ecc ? MAIN_ONLY : MAIN_ECC; |
| |
| nand_info->dma_bufpos = column; |
| nand_amb_read_data(nand_info, page_addr, nand_info->dmaaddr, area); |
| if (nand_info->ecc_bits == 1) |
| nand_amb_read_data(nand_info, page_addr, |
| nand_info->dmaaddr + mtd->writesize, SPARE_ONLY); |
| |
| break; |
| } |
| case NAND_CMD_SEQIN: |
| nand_info->dma_bufpos = column; |
| nand_info->seqin_column = column; |
| nand_info->seqin_page_addr = page_addr; |
| break; |
| case NAND_CMD_PAGEPROG: |
| { |
| u32 mn_area, sp_area, offset; |
| |
| mn_area = nand_info->soft_ecc ? MAIN_ONLY : MAIN_ECC; |
| sp_area = nand_amb_is_hw_bch(nand_info) ? SPARE_ECC : SPARE_ONLY; |
| offset = (nand_info->ecc_bits > 1) ? 0 : mtd->writesize; |
| |
| if (nand_info->seqin_column < mtd->writesize) { |
| nand_amb_write_data(nand_info, |
| nand_info->seqin_page_addr, |
| nand_info->dmaaddr, mn_area); |
| if (nand_info->soft_ecc && nand_info->ecc_bits == 1) { |
| nand_amb_write_data(nand_info, |
| nand_info->seqin_page_addr, |
| nand_info->dmaaddr + mtd->writesize, |
| sp_area); |
| } |
| } else { |
| nand_amb_write_data(nand_info, |
| nand_info->seqin_page_addr, |
| nand_info->dmaaddr + offset, |
| sp_area); |
| } |
| break; |
| } |
| default: |
| dev_err(nand_info->dev, "%s: 0x%x, %d, %d\n", |
| __func__, command, column, page_addr); |
| BUG(); |
| break; |
| } |
| } |
| |
| static void amb_nand_hwctl(struct mtd_info *mtd, int mode) |
| { |
| } |
| |
| static int amb_nand_calculate_ecc(struct mtd_info *mtd, |
| const u_char *buf, u_char *code) |
| { |
| struct ambarella_nand_info *nand_info; |
| |
| nand_info = (struct ambarella_nand_info *)mtd->priv; |
| |
| if (!nand_info->soft_ecc) { |
| memset(code, 0xff, nand_info->chip.ecc.bytes); |
| } else if (nand_info->ecc_bits == 1) { |
| nand_calculate_ecc(mtd, buf, code); |
| /* FIXME: the first two bytes ecc codes are swaped comparing |
| * to the ecc codes generated by our hardware, so we swap them |
| * here manually. But I don't know why they were swapped. */ |
| swap(code[0], code[1]); |
| } else { |
| u32 i, amb_eccsize; |
| |
| /* make it be compatible with hw bch */ |
| for (i = 0; i < nand_info->chip.ecc.size; i++) |
| nand_info->bch_data[i] = bitrev8(buf[i]); |
| |
| memset(code, 0, nand_info->chip.ecc.bytes); |
| |
| amb_eccsize = nand_info->chip.ecc.size + nand_info->soft_bch_extra_size; |
| encode_bch(nand_info->bch, nand_info->bch_data, amb_eccsize, code); |
| |
| /* make it be compatible with hw bch */ |
| for (i = 0; i < nand_info->chip.ecc.bytes; i++) |
| code[i] = bitrev8(code[i]); |
| } |
| |
| return 0; |
| } |
| |
| static int amb_nand_correct_data(struct mtd_info *mtd, u_char *buf, |
| u_char *read_ecc, u_char *calc_ecc) |
| { |
| struct ambarella_nand_info *nand_info; |
| int errorCode = 0; |
| |
| nand_info = (struct ambarella_nand_info *)mtd->priv; |
| |
| /* if we use hardware ecc, any errors include DMA error and |
| * FIO DMA error, we consider it as a ecc error which will tell |
| * the caller the read fail. We have distinguish all the errors, |
| * but the nand_read_ecc only check the return value by this |
| * function. */ |
| if (!nand_info->soft_ecc) |
| errorCode = nand_info->err_code; |
| else if (nand_info->ecc_bits == 1) |
| errorCode = nand_correct_data(mtd, buf, read_ecc, calc_ecc); |
| else { |
| struct nand_chip *chip = &nand_info->chip; |
| u32 *errloc = nand_info->errloc; |
| int amb_eccsize, i, count; |
| |
| for (i = 0; i < chip->ecc.bytes; i++) { |
| nand_info->read_ecc_rev[i] = bitrev8(read_ecc[i]); |
| nand_info->calc_ecc_rev[i] = bitrev8(calc_ecc[i]); |
| } |
| |
| amb_eccsize = chip->ecc.size + nand_info->soft_bch_extra_size; |
| count = decode_bch(nand_info->bch, NULL, |
| amb_eccsize, |
| nand_info->read_ecc_rev, |
| nand_info->calc_ecc_rev, |
| NULL, errloc); |
| if (count > 0) { |
| for (i = 0; i < count; i++) { |
| if (errloc[i] < (amb_eccsize * 8)) { |
| /* error is located in data, correct it */ |
| buf[errloc[i] >> 3] ^= (128 >> (errloc[i] & 7)); |
| } |
| /* else error in ecc, no action needed */ |
| |
| dev_dbg(nand_info->dev, |
| "corrected bitflip %u\n", errloc[i]); |
| } |
| } else if (count < 0) { |
| count = nand_bch_check_blank_page(nand_info , 0); |
| if (count < 0) |
| dev_err(nand_info->dev, "ecc unrecoverable error\n"); |
| else if (count > 0) |
| memset(buf, 0xff, chip->ecc.size); |
| } |
| |
| errorCode = count; |
| } |
| |
| return errorCode; |
| } |
| |
| static int amb_nand_write_oob_std(struct mtd_info *mtd, |
| struct nand_chip *chip, int page) |
| { |
| struct ambarella_nand_info *nand_info; |
| int i, status; |
| |
| nand_info = (struct ambarella_nand_info *)mtd->priv; |
| |
| /* Our nand controller will write the generated ECC code into spare |
| * area automatically, so we should mark the ECC code which located |
| * in the eccpos. |
| */ |
| if (!nand_info->soft_ecc) { |
| for (i = 0; i < chip->ecc.total; i++) |
| chip->oob_poi[chip->ecc.layout->eccpos[i]] = 0xFF; |
| } |
| |
| chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page); |
| chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); |
| chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); |
| status = chip->waitfunc(mtd, chip); |
| |
| return status & NAND_STATUS_FAIL ? -EIO : 0; |
| } |
| |
| /* |
| * The encoding sequence in a byte is "LSB first". |
| * |
| * For each 2K page, there will be 2048 byte main data (B0 ~ B2047) and 64 byte |
| * spare data (B2048 ~ B2111). Thus, each page is divided into 4 BCH blocks. |
| * For example, B0~B511 and B2048~B2063 are grouped as the first BCH block. |
| * B0 will be encoded first and B2053 will be encoded last. |
| * |
| * B2054 ~B2063 are used to store 10B parity data (precisely to say, 78 bits) |
| * The 2 dummy bits are filled as 0 and located at the msb of B2063. |
| */ |
| static int ambarella_nand_init_soft_bch(struct ambarella_nand_info *nand_info) |
| { |
| struct nand_chip *chip = &nand_info->chip; |
| u32 amb_eccsize, eccbytes, m, t; |
| |
| amb_eccsize = chip->ecc.size + nand_info->soft_bch_extra_size; |
| eccbytes = chip->ecc.bytes; |
| |
| m = fls(1 + 8 * amb_eccsize); |
| t = (eccbytes * 8) / m; |
| |
| nand_info->bch = init_bch(m, t, 0); |
| if (!nand_info->bch) |
| return -EINVAL; |
| |
| nand_info->errloc = devm_kzalloc(nand_info->dev, |
| t * sizeof(*nand_info->errloc), GFP_KERNEL); |
| if (!nand_info->errloc) |
| return -ENOMEM; |
| |
| nand_info->bch_data = devm_kzalloc(nand_info->dev, |
| amb_eccsize, GFP_KERNEL); |
| if (nand_info->bch_data == NULL) |
| return -ENOMEM; |
| |
| /* asumming the 6 bytes data in spare area are all 0xff, in other |
| * words, we don't support to write anything except for ECC code |
| * into spare are. */ |
| memset(nand_info->bch_data + chip->ecc.size, |
| 0xff, nand_info->soft_bch_extra_size); |
| |
| return 0; |
| } |
| |
| static void ambarella_nand_init_hw(struct ambarella_nand_info *nand_info) |
| { |
| /* reset FIO by RCT */ |
| fio_select_lock(SELECT_FIO_FL); |
| ambarella_fio_rct_reset(); |
| fio_unlock(SELECT_FIO_FL); |
| |
| /* When suspend/resume mode, before exit random read mode, |
| * we take time for make sure FIO reset well and |
| * some dma req finished. |
| */ |
| if (nand_info->suspend == 1) |
| mdelay(2); |
| /* Exit random read mode */ |
| amba_clrbitsl(nand_info->regbase + FIO_CTR_OFFSET, FIO_CTR_RR); |
| |
| /* init fdma to avoid dummy irq */ |
| amba_writel(nand_info->fdmaregbase + FDMA_STA_OFFSET, 0); |
| amba_writel(nand_info->fdmaregbase + FDMA_SPR_STA_OFFSET, 0); |
| amba_writel(nand_info->fdmaregbase + FDMA_CTR_OFFSET, |
| DMA_CHANX_CTR_WM | DMA_CHANX_CTR_RM | DMA_CHANX_CTR_NI); |
| |
| amb_nand_set_timing(nand_info); |
| } |
| |
| static int ambarella_nand_config_flash(struct ambarella_nand_info *nand_info) |
| { |
| int errorCode = 0; |
| |
| /* control_reg will be uesd when real operation to NAND is performed */ |
| |
| /* Calculate row address cycyle according to whether the page number |
| * of the nand is greater than 65536 */ |
| if ((nand_info->chip.chip_shift - nand_info->chip.page_shift) > 16) |
| nand_info->control_reg |= NAND_CTR_P3; |
| else |
| nand_info->control_reg &= ~NAND_CTR_P3; |
| |
| nand_info->control_reg &= ~NAND_CTR_SZ_8G; |
| switch (nand_info->chip.chipsize) { |
| case 8 * 1024 * 1024: |
| nand_info->control_reg |= NAND_CTR_SZ_64M; |
| break; |
| case 16 * 1024 * 1024: |
| nand_info->control_reg |= NAND_CTR_SZ_128M; |
| break; |
| case 32 * 1024 * 1024: |
| nand_info->control_reg |= NAND_CTR_SZ_256M; |
| break; |
| case 64 * 1024 * 1024: |
| nand_info->control_reg |= NAND_CTR_SZ_512M; |
| break; |
| case 128 * 1024 * 1024: |
| nand_info->control_reg |= NAND_CTR_SZ_1G; |
| break; |
| case 256 * 1024 * 1024: |
| nand_info->control_reg |= NAND_CTR_SZ_2G; |
| break; |
| case 512 * 1024 * 1024: |
| nand_info->control_reg |= NAND_CTR_SZ_4G; |
| break; |
| case 1024 * 1024 * 1024: |
| nand_info->control_reg |= NAND_CTR_SZ_8G; |
| break; |
| default: |
| dev_err(nand_info->dev, |
| "Unexpected NAND flash chipsize %lld. Aborting\n", |
| nand_info->chip.chipsize); |
| errorCode = -ENXIO; |
| break; |
| } |
| |
| return errorCode; |
| } |
| |
| static int ambarella_nand_init_chip(struct ambarella_nand_info *nand_info, |
| struct device_node *np) |
| { |
| struct nand_chip *chip = &nand_info->chip; |
| u32 poc = ambarella_get_poc(); |
| |
| /* if ecc is generated by software, the ecc bits num will |
| * be defined in FDT. */ |
| if (!nand_info->soft_ecc) { |
| if (of_find_property(np, "amb,no-bch", NULL)) { |
| nand_info->ecc_bits = 1; |
| } else if (poc & SYS_CONFIG_NAND_ECC_BCH_EN) { |
| if (poc & SYS_CONFIG_NAND_ECC_SPARE_2X) |
| nand_info->ecc_bits = 8; |
| else |
| nand_info->ecc_bits = 6; |
| } else { |
| nand_info->ecc_bits = 1; |
| } |
| } |
| |
| dev_info(nand_info->dev, "in %secc-[%d]bit mode\n", |
| nand_info->soft_ecc ? "soft " : "", nand_info->ecc_bits); |
| |
| nand_info->control_reg = 0; |
| if (poc & SYS_CONFIG_NAND_READ_CONFIRM) |
| nand_info->control_reg |= NAND_CTR_RC; |
| if (poc & SYS_CONFIG_NAND_PAGE_SIZE) |
| nand_info->control_reg |= (NAND_CTR_C2 | NAND_CTR_SZ_8G); |
| /* |
| * Always use P3 and I4 to support all NAND, |
| * but we will adjust them after read ID from NAND. */ |
| nand_info->control_reg |= (NAND_CTR_P3 | NAND_CTR_I4 | NAND_CTR_IE); |
| nand_info->id_cycles_5 = NAND_READ_ID5; |
| |
| if(nand_info->nand_wp) |
| nand_info->control_reg |= NAND_CTR_WP; |
| |
| chip->chip_delay = 0; |
| chip->controller = &nand_info->controller; |
| chip->read_byte = amb_nand_read_byte; |
| chip->read_word = amb_nand_read_word; |
| chip->write_buf = amb_nand_write_buf; |
| chip->read_buf = amb_nand_read_buf; |
| chip->select_chip = amb_nand_select_chip; |
| chip->cmd_ctrl = amb_nand_cmd_ctrl; |
| chip->dev_ready = amb_nand_dev_ready; |
| chip->waitfunc = amb_nand_waitfunc; |
| chip->cmdfunc = amb_nand_cmdfunc; |
| chip->options |= NAND_NO_SUBPAGE_WRITE; |
| if (of_get_nand_on_flash_bbt(np)) { |
| printk(KERN_INFO "ambarella_nand: Use On Flash BBT\n"); |
| chip->bbt_options |= NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB; |
| } |
| |
| nand_info->mtd.priv = chip; |
| nand_info->mtd.owner = THIS_MODULE; |
| |
| return 0; |
| } |
| |
| static int ambarella_nand_init_chipecc(struct ambarella_nand_info *nand_info) |
| { |
| struct nand_chip *chip = &nand_info->chip; |
| struct mtd_info *mtd = &nand_info->mtd; |
| int errorCode = 0; |
| |
| /* sanity check */ |
| BUG_ON(nand_info->ecc_bits != 1 |
| && nand_info->ecc_bits != 6 |
| && nand_info->ecc_bits != 8); |
| BUG_ON(mtd->writesize != 2048 && mtd->writesize != 512); |
| BUG_ON(nand_info->ecc_bits == 8 && mtd->oobsize < 128); |
| |
| chip->ecc.mode = NAND_ECC_HW; |
| chip->ecc.strength = nand_info->ecc_bits; |
| |
| switch (nand_info->ecc_bits) { |
| case 8: |
| chip->ecc.size = 512; |
| chip->ecc.bytes = 13; |
| chip->ecc.layout = &amb_oobinfo_2048_dsm_ecc8; |
| nand_info->soft_bch_extra_size = 19; |
| break; |
| case 6: |
| chip->ecc.size = 512; |
| chip->ecc.bytes = 10; |
| chip->ecc.layout = &amb_oobinfo_2048_dsm_ecc6; |
| nand_info->soft_bch_extra_size = 6; |
| break; |
| case 1: |
| chip->ecc.size = 512; |
| chip->ecc.bytes = 5; |
| if (mtd->writesize == 2048) |
| chip->ecc.layout = &amb_oobinfo_2048; |
| else |
| chip->ecc.layout = &amb_oobinfo_512; |
| break; |
| } |
| |
| if (nand_amb_is_sw_bch(nand_info)) { |
| errorCode = ambarella_nand_init_soft_bch(nand_info); |
| if (errorCode < 0) |
| return errorCode; |
| /* bootloader may have enabled hw BCH, we must disable it here */ |
| nand_amb_enable_dsm(nand_info); |
| } |
| |
| chip->ecc.hwctl = amb_nand_hwctl; |
| chip->ecc.calculate = amb_nand_calculate_ecc; |
| chip->ecc.correct = amb_nand_correct_data; |
| chip->ecc.write_oob = amb_nand_write_oob_std; |
| |
| return 0; |
| } |
| |
| static int ambarella_nand_get_resource( |
| struct ambarella_nand_info *nand_info, struct platform_device *pdev) |
| { |
| struct device_node *np = pdev->dev.of_node; |
| struct resource *res; |
| int errorCode = 0; |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (!res) { |
| dev_err(&pdev->dev, "No mem resource for fio_reg!\n"); |
| errorCode = -ENXIO; |
| goto nand_get_resource_err_exit; |
| } |
| |
| nand_info->regbase = |
| devm_ioremap(&pdev->dev, res->start, resource_size(res)); |
| if (!nand_info->regbase) { |
| dev_err(&pdev->dev, "devm_ioremap() failed\n"); |
| errorCode = -ENOMEM; |
| goto nand_get_resource_err_exit; |
| } |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
| if (!res) { |
| dev_err(&pdev->dev, "No mem resource for fdma_reg!\n"); |
| errorCode = -ENXIO; |
| goto nand_get_resource_err_exit; |
| } |
| |
| nand_info->fdmaregbase = |
| devm_ioremap(&pdev->dev, res->start, resource_size(res)); |
| if (!nand_info->fdmaregbase) { |
| dev_err(&pdev->dev, "devm_ioremap() failed\n"); |
| errorCode = -ENOMEM; |
| goto nand_get_resource_err_exit; |
| } |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 2); |
| if (!res) { |
| dev_err(&pdev->dev, "No mem resource for fifo base!\n"); |
| errorCode = -ENXIO; |
| goto nand_get_resource_err_exit; |
| } |
| nand_info->dmabase = res->start; |
| |
| nand_info->cmd_irq = platform_get_irq(pdev, 0); |
| if (nand_info->cmd_irq < 0) { |
| dev_err(&pdev->dev, "no irq for cmd_irq!\n"); |
| errorCode = -ENODEV; |
| goto nand_get_resource_err_exit; |
| } |
| |
| nand_info->dma_irq = platform_get_irq(pdev, 1); |
| if (nand_info->dma_irq < 0) { |
| dev_err(&pdev->dev, "no irq for dma_irq!\n"); |
| errorCode = -ENODEV; |
| goto nand_get_resource_err_exit; |
| } |
| |
| nand_info->fdma_irq = platform_get_irq(pdev, 2); |
| if (nand_info->fdma_irq < 0) { |
| dev_err(&pdev->dev, "no irq for fdma_irq!\n"); |
| errorCode = -ENODEV; |
| goto nand_get_resource_err_exit; |
| } |
| |
| nand_info->nand_wp = !!of_find_property(np, "amb,enable-wp", NULL); |
| |
| errorCode = of_property_read_u32_array(np, "amb,timing", |
| nand_info->timing, 6); |
| if (errorCode < 0) { |
| dev_dbg(&pdev->dev, "No timing defined!\n"); |
| memset(nand_info->timing, 0x0, sizeof(nand_info->timing)); |
| } |
| |
| nand_info->pllx2 = !!of_find_property(np, "amb,use-2x-pll", NULL); |
| |
| nand_info->ecc_bits = 0; |
| of_property_read_u32(np, "amb,soft-ecc", &nand_info->ecc_bits); |
| if (nand_info->ecc_bits > 0) |
| nand_info->soft_ecc = true; |
| |
| ambarella_nand_init_hw(nand_info); |
| |
| errorCode = request_irq(nand_info->cmd_irq, nand_fiocmd_isr_handler, |
| IRQF_SHARED | IRQF_TRIGGER_HIGH, |
| "fio_cmd_irq", nand_info); |
| if (errorCode < 0) { |
| dev_err(&pdev->dev, "Could not register fio_cmd_irq %d!\n", |
| nand_info->cmd_irq); |
| goto nand_get_resource_err_exit; |
| } |
| |
| errorCode = request_irq(nand_info->dma_irq, nand_fiodma_isr_handler, |
| IRQF_SHARED | IRQF_TRIGGER_HIGH, |
| "fio_dma_irq", nand_info); |
| if (errorCode < 0) { |
| dev_err(&pdev->dev, "Could not register fio_dma_irq %d!\n", |
| nand_info->dma_irq); |
| goto nand_get_resource_free_fiocmd_irq; |
| } |
| |
| errorCode = request_irq(nand_info->fdma_irq, ambarella_fdma_isr_handler, |
| IRQF_SHARED | IRQF_TRIGGER_HIGH, |
| "fdma_irq", nand_info); |
| if (errorCode < 0) { |
| dev_err(&pdev->dev, "Could not register fdma_irq %d!\n", |
| nand_info->dma_irq); |
| goto nand_get_resource_free_fiodma_irq; |
| } |
| |
| return 0; |
| |
| nand_get_resource_free_fiodma_irq: |
| free_irq(nand_info->dma_irq, nand_info); |
| |
| nand_get_resource_free_fiocmd_irq: |
| free_irq(nand_info->cmd_irq, nand_info); |
| |
| nand_get_resource_err_exit: |
| return errorCode; |
| } |
| |
| static void ambarella_nand_put_resource(struct ambarella_nand_info *nand_info) |
| { |
| free_irq(nand_info->fdma_irq, nand_info); |
| free_irq(nand_info->dma_irq, nand_info); |
| free_irq(nand_info->cmd_irq, nand_info); |
| } |
| |
| static int ambarella_nand_probe(struct platform_device *pdev) |
| { |
| int errorCode = 0; |
| struct ambarella_nand_info *nand_info; |
| struct mtd_info *mtd; |
| struct mtd_part_parser_data ppdata = {}; |
| |
| nand_info = kzalloc(sizeof(struct ambarella_nand_info), GFP_KERNEL); |
| if (nand_info == NULL) { |
| dev_err(&pdev->dev, "kzalloc for nand nand_info failed!\n"); |
| errorCode = - ENOMEM; |
| goto ambarella_nand_probe_exit; |
| } |
| |
| nand_info->dev = &pdev->dev; |
| spin_lock_init(&nand_info->controller.lock); |
| init_waitqueue_head(&nand_info->controller.wq); |
| init_waitqueue_head(&nand_info->wq); |
| sema_init(&nand_info->system_event_sem, 1); |
| atomic_set(&nand_info->irq_flag, 0x7); |
| |
| nand_info->dmabuf = dma_alloc_coherent(nand_info->dev, |
| AMBARELLA_NAND_DMA_BUFFER_SIZE, |
| &nand_info->dmaaddr, GFP_KERNEL); |
| if (nand_info->dmabuf == NULL) { |
| dev_err(&pdev->dev, "dma_alloc_coherent failed!\n"); |
| errorCode = -ENOMEM; |
| goto ambarella_nand_probe_free_info; |
| } |
| BUG_ON(nand_info->dmaaddr & 0x7); |
| |
| errorCode = ambarella_nand_get_resource(nand_info, pdev); |
| if (errorCode < 0) |
| goto ambarella_nand_probe_free_dma; |
| |
| ambarella_nand_init_chip(nand_info, pdev->dev.of_node); |
| |
| mtd = &nand_info->mtd; |
| errorCode = nand_scan_ident(mtd, 1, NULL); |
| if (errorCode) |
| goto ambarella_nand_probe_mtd_error; |
| |
| errorCode = ambarella_nand_init_chipecc(nand_info); |
| if (errorCode) |
| goto ambarella_nand_probe_mtd_error; |
| |
| errorCode = ambarella_nand_config_flash(nand_info); |
| if (errorCode) |
| goto ambarella_nand_probe_mtd_error; |
| |
| errorCode = nand_scan_tail(mtd); |
| if (errorCode) |
| goto ambarella_nand_probe_mtd_error; |
| |
| mtd->name = "amba_nand"; |
| |
| ppdata.of_node = pdev->dev.of_node; |
| errorCode = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0); |
| if (errorCode < 0) |
| goto ambarella_nand_probe_mtd_error; |
| |
| platform_set_drvdata(pdev, nand_info); |
| |
| nand_info->system_event.notifier_call = ambarella_nand_system_event; |
| ambarella_register_event_notifier(&nand_info->system_event); |
| |
| return 0; |
| |
| ambarella_nand_probe_mtd_error: |
| ambarella_nand_put_resource(nand_info); |
| |
| ambarella_nand_probe_free_dma: |
| dma_free_coherent(nand_info->dev, |
| AMBARELLA_NAND_DMA_BUFFER_SIZE, |
| nand_info->dmabuf, nand_info->dmaaddr); |
| |
| ambarella_nand_probe_free_info: |
| kfree(nand_info); |
| |
| ambarella_nand_probe_exit: |
| |
| return errorCode; |
| } |
| |
| static int ambarella_nand_remove(struct platform_device *pdev) |
| { |
| int errorCode = 0; |
| struct ambarella_nand_info *nand_info; |
| |
| nand_info = (struct ambarella_nand_info *)platform_get_drvdata(pdev); |
| |
| if (nand_info) { |
| ambarella_unregister_event_notifier(&nand_info->system_event); |
| |
| nand_release(&nand_info->mtd); |
| |
| ambarella_nand_put_resource(nand_info); |
| |
| dma_free_coherent(nand_info->dev, |
| AMBARELLA_NAND_DMA_BUFFER_SIZE, |
| nand_info->dmabuf, nand_info->dmaaddr); |
| |
| kfree(nand_info); |
| } |
| |
| return errorCode; |
| } |
| |
| #ifdef CONFIG_PM |
| static int ambarella_nand_suspend(struct platform_device *pdev, |
| pm_message_t state) |
| { |
| int errorCode = 0; |
| struct ambarella_nand_info *nand_info; |
| |
| nand_info = platform_get_drvdata(pdev); |
| nand_info->suspend = 1; |
| disable_irq(nand_info->dma_irq); |
| disable_irq(nand_info->cmd_irq); |
| |
| dev_dbg(&pdev->dev, "%s exit with %d @ %d\n", |
| __func__, errorCode, state.event); |
| |
| return errorCode; |
| } |
| |
| static int ambarella_nand_resume(struct platform_device *pdev) |
| { |
| int errorCode = 0; |
| struct ambarella_nand_info *nand_info; |
| |
| nand_info = platform_get_drvdata(pdev); |
| ambarella_nand_init_hw(nand_info); |
| nand_info->suspend = 0; |
| enable_irq(nand_info->dma_irq); |
| enable_irq(nand_info->cmd_irq); |
| |
| dev_dbg(&pdev->dev, "%s exit with %d\n", __func__, errorCode); |
| |
| return errorCode; |
| } |
| #endif |
| |
| static const struct of_device_id ambarella_nand_of_match[] = { |
| {.compatible = "ambarella,nand", }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, ambarella_nand_of_match); |
| |
| static struct platform_driver amb_nand_driver = { |
| .probe = ambarella_nand_probe, |
| .remove = ambarella_nand_remove, |
| #ifdef CONFIG_PM |
| .suspend = ambarella_nand_suspend, |
| .resume = ambarella_nand_resume, |
| #endif |
| .driver = { |
| .name = "ambarella-nand", |
| .owner = THIS_MODULE, |
| .of_match_table = ambarella_nand_of_match, |
| }, |
| }; |
| module_platform_driver(amb_nand_driver); |
| |
| MODULE_AUTHOR("Cao Rongrong & Chien-Yang Chen"); |
| MODULE_DESCRIPTION("Ambarella Media processor NAND Controller Driver"); |
| MODULE_LICENSE("GPL"); |
| |