| /** |
| * @file |
| * @brief Provide Generic GPMC NAND implementation for OMAP platforms |
| * |
| * FileName: arch/arm/mach-omap/gpmc_nand.c |
| * |
| * GPMC has a NAND controller inbuilt. This provides a generic implementation |
| * for board files to register a nand device. drivers/nand/nand_base.c takes |
| * care of identifing the type of device, size etc. |
| * |
| * A typical device registration is as follows: |
| * |
| * @code |
| * static struct device_d my_nand_device = { |
| * .name = "gpmc_nand", |
| * .id = some identifier you need to show.. e.g. "gpmc_nand0" |
| * .map_base = GPMC base address |
| * .size = GPMC address map size. |
| * .platform_data = platform data - required - explained below |
| * }; |
| * platform data required: |
| * static struct gpmc_nand_platform_data nand_plat = { |
| * .cs = give the chip select of the device |
| * .device_width = what is the width of the device 8 or 16? |
| * .max_timeout = delay desired for operation |
| * .wait_mon_pin = do you use wait monitoring? if so wait pin |
| * .plat_options = platform options. |
| * NAND_HWECC_ENABLE/DISABLE - hw ecc enable/disable |
| * NAND_WAITPOL_LOW/HIGH - wait pin polarity |
| * .oob = if you would like to replace oob with a custom OOB. |
| * .nand_setup = if you would like a special setup function to be called |
| * .priv = any params you'd like to save(e.g. like nand_setup to use) |
| *}; |
| * then in your code, you'd device_register(&my_nand_device); |
| * @endcode |
| * |
| * Note: |
| * @li Enable CONFIG_NAND_OMAP_GPMC_HWECC in menuconfig to get H/w ECC support |
| * @li You may choose to register two "devices" for the same CS to get BOTH |
| * hwecc and swecc devices. |
| * @li You can choose to have your own OOB definition for compliance with ROM |
| * code organization - only if you dont want to use NAND's default oob layout. |
| * see GPMC_NAND_ECC_LP_x8_LAYOUT etc.. |
| * |
| * @see gpmc_nand_platform_data |
| * @warning Remember to initialize GPMC before initializing the nand dev. |
| */ |
| /* |
| * (C) Copyright 2008 |
| * Texas Instruments, <www.ti.com> |
| * Nishanth Menon <x0nishan@ti.com> |
| * |
| * Based on: |
| * drivers/mtd/nand/omap2.c from linux kernel |
| * |
| * Copyright (c) 2004 Texas Instruments, Jian Zhang <jzhang@ti.com> |
| * Copyright (c) 2004 Micron Technology Inc. |
| * Copyright (c) 2004 David Brownell |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| |
| #include <common.h> |
| #include <errno.h> |
| #include <init.h> |
| #include <driver.h> |
| #include <malloc.h> |
| #include <clock.h> |
| #include <linux/mtd/mtd.h> |
| #include <linux/mtd/nand.h> |
| #include <linux/mtd/nand_ecc.h> |
| #include <asm/io.h> |
| #include <mach/silicon.h> |
| #include <mach/gpmc.h> |
| #include <mach/gpmc_nand.h> |
| |
| int decode_bch(int select_4_8, unsigned char *ecc, unsigned int *err_loc); |
| |
| static char *ecc_mode_strings[] = { |
| "software", |
| "hamming_hw_romcode", |
| "bch4_hw", |
| "bch8_hw", |
| "bch8_hw_romcode", |
| }; |
| |
| /** internal structure maintained for nand information */ |
| struct gpmc_nand_info { |
| struct nand_hw_control controller; |
| struct device_d *pdev; |
| struct gpmc_nand_platform_data *pdata; |
| struct nand_chip nand; |
| struct mtd_info minfo; |
| int gpmc_cs; |
| void *gpmc_command; |
| void *gpmc_address; |
| void *gpmc_data; |
| unsigned long gpmc_base; |
| unsigned char wait_mon_mask; |
| uint64_t timeout; |
| unsigned inuse:1; |
| unsigned wait_pol:1; |
| unsigned char ecc_parity_pairs; |
| enum gpmc_ecc_mode ecc_mode; |
| }; |
| |
| /* Typical BOOTROM oob layouts-requires hwecc **/ |
| static struct nand_ecclayout omap_oobinfo; |
| /* Define some generic bad / good block scan pattern which are used |
| * while scanning a device for factory marked good / bad blocks |
| */ |
| static uint8_t scan_ff_pattern[] = { 0xff }; |
| static struct nand_bbt_descr bb_descrip_flashbased = { |
| .options = NAND_BBT_SCANEMPTY | NAND_BBT_SCANALLPAGES, |
| .offs = 0, |
| .len = 1, |
| .pattern = scan_ff_pattern, |
| }; |
| |
| /** Large Page x8 NAND device Layout */ |
| static struct nand_ecclayout ecc_lp_x8 = { |
| .eccbytes = 12, |
| .eccpos = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, |
| .oobfree = { |
| { |
| .offset = 60, |
| .length = 2, |
| } |
| } |
| }; |
| |
| /** Large Page x16 NAND device Layout */ |
| static struct nand_ecclayout ecc_lp_x16 = { |
| .eccbytes = 12, |
| .eccpos = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, |
| .oobfree = { |
| { |
| .offset = 60, |
| .length = 2, |
| } |
| } |
| }; |
| |
| /** Small Page x8 NAND device Layout */ |
| static struct nand_ecclayout ecc_sp_x8 = { |
| .eccbytes = 3, |
| .eccpos = {1, 2, 3}, |
| .oobfree = { |
| { |
| .offset = 14, |
| .length = 2, |
| } |
| } |
| }; |
| |
| /** Small Page x16 NAND device Layout */ |
| static struct nand_ecclayout ecc_sp_x16 = { |
| .eccbytes = 3, |
| .eccpos = {2, 3, 4}, |
| .oobfree = { |
| { |
| .offset = 14, |
| .length = 2 } |
| } |
| }; |
| |
| /** |
| * @brief calls the platform specific dev_ready functionds |
| * |
| * @param mtd - mtd info structure |
| * |
| * @return |
| */ |
| static int omap_dev_ready(struct mtd_info *mtd) |
| { |
| struct nand_chip *nand = (struct nand_chip *)(mtd->priv); |
| struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); |
| uint64_t start = get_time_ns(); |
| unsigned long comp; |
| |
| debug("mtd=%x", (unsigned int)mtd); |
| /* What do we mean by assert and de-assert? */ |
| comp = (oinfo->wait_pol == NAND_WAITPOL_HIGH) ? |
| oinfo->wait_mon_mask : 0x0; |
| while (1) { |
| /* Breakout condition */ |
| if (is_timeout(start, oinfo->timeout)) { |
| debug("timedout\n"); |
| return -ETIMEDOUT; |
| } |
| /* if the wait is released, we are good to go */ |
| if (comp == |
| (readl(oinfo->gpmc_base + GPMC_STATUS) && |
| oinfo->wait_mon_mask)) |
| break; |
| } |
| return 0; |
| } |
| |
| /** |
| * @brief This function will enable or disable the Write Protect feature on |
| * NAND device. GPMC has a single WP bit for all CS devices.. |
| * |
| * @param oinfo omap nand info |
| * @param mode 0-disable else enable |
| * |
| * @return none |
| */ |
| static void gpmc_nand_wp(struct gpmc_nand_info *oinfo, int mode) |
| { |
| unsigned long config = readl(oinfo->gpmc_base + GPMC_CFG); |
| |
| debug("mode=%x", mode); |
| if (mode) |
| config &= ~(NAND_WP_BIT); /* WP is ON */ |
| else |
| config |= (NAND_WP_BIT); /* WP is OFF */ |
| |
| writel(config, oinfo->gpmc_base + GPMC_CFG); |
| } |
| |
| /** |
| * @brief respond to hw event change request |
| * |
| * MTD layer uses NAND_CTRL_CLE etc to control selection of the latch |
| * we hoodwink by changing the R and W registers according to the state |
| * we are requested. |
| * |
| * @param mtd - mtd info structure |
| * @param cmd command mtd layer is requesting |
| * |
| * @return none |
| */ |
| static void omap_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) |
| { |
| struct nand_chip *nand = (struct nand_chip *)(mtd->priv); |
| struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); |
| debug("mtd=%x nand=%x cmd=%x ctrl = %x", (unsigned int)mtd, nand, |
| cmd, ctrl); |
| switch (ctrl) { |
| case NAND_CTRL_CHANGE | NAND_CTRL_CLE: |
| nand->IO_ADDR_W = oinfo->gpmc_command; |
| nand->IO_ADDR_R = oinfo->gpmc_data; |
| break; |
| |
| case NAND_CTRL_CHANGE | NAND_CTRL_ALE: |
| nand->IO_ADDR_W = oinfo->gpmc_address; |
| nand->IO_ADDR_R = oinfo->gpmc_data; |
| break; |
| |
| case NAND_CTRL_CHANGE | NAND_NCE: |
| nand->IO_ADDR_W = oinfo->gpmc_data; |
| nand->IO_ADDR_R = oinfo->gpmc_data; |
| break; |
| } |
| |
| if (cmd != NAND_CMD_NONE) |
| writeb(cmd, nand->IO_ADDR_W); |
| return; |
| } |
| |
| /** |
| * @brief This function will generate true ECC value, which can be used |
| * when correcting data read from NAND flash memory core |
| * |
| * @param ecc_buf buffer to store ecc code |
| * |
| * @return re-formatted ECC value |
| */ |
| static unsigned int gen_true_ecc(u8 *ecc_buf) |
| { |
| debug("ecc_buf=%x 1, 2 3 = %x %x %x", (unsigned int)ecc_buf, |
| ecc_buf[0], ecc_buf[1], ecc_buf[2]); |
| return ecc_buf[0] | (ecc_buf[1] << 16) | ((ecc_buf[2] & 0xF0) << 20) | |
| ((ecc_buf[2] & 0x0F) << 8); |
| } |
| |
| static int omap_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat, |
| uint8_t *ecc_code) |
| { |
| struct nand_chip *nand = (struct nand_chip *)(mtd->priv); |
| struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); |
| unsigned int reg; |
| unsigned int val1 = 0x0, val2 = 0x0; |
| unsigned int val3 = 0x0, val4 = 0x0; |
| int i; |
| int ecc_size = 8; |
| |
| switch (oinfo->ecc_mode) { |
| case OMAP_ECC_BCH4_CODE_HW: |
| ecc_size = 4; |
| /* fall through */ |
| case OMAP_ECC_BCH8_CODE_HW: |
| case OMAP_ECC_BCH8_CODE_HW_ROMCODE: |
| for (i = 0; i < 4; i++) { |
| /* |
| * Reading HW ECC_BCH_Results |
| * 0x240-0x24C, 0x250-0x25C, 0x260-0x26C, 0x270-0x27C |
| */ |
| reg = GPMC_ECC_BCH_RESULT_0 + (0x10 * i); |
| val1 = readl(oinfo->gpmc_base + reg); |
| val2 = readl(oinfo->gpmc_base + reg + 4); |
| if (ecc_size == 8) { |
| val3 = readl(oinfo->gpmc_base +reg + 8); |
| val4 = readl(oinfo->gpmc_base + reg + 12); |
| |
| *ecc_code++ = (val4 & 0xFF); |
| *ecc_code++ = ((val3 >> 24) & 0xFF); |
| *ecc_code++ = ((val3 >> 16) & 0xFF); |
| *ecc_code++ = ((val3 >> 8) & 0xFF); |
| *ecc_code++ = (val3 & 0xFF); |
| *ecc_code++ = ((val2 >> 24) & 0xFF); |
| } |
| *ecc_code++ = ((val2 >> 16) & 0xFF); |
| *ecc_code++ = ((val2 >> 8) & 0xFF); |
| *ecc_code++ = (val2 & 0xFF); |
| *ecc_code++ = ((val1 >> 24) & 0xFF); |
| *ecc_code++ = ((val1 >> 16) & 0xFF); |
| *ecc_code++ = ((val1 >> 8) & 0xFF); |
| *ecc_code++ = (val1 & 0xFF); |
| } |
| break; |
| case OMAP_ECC_HAMMING_CODE_HW_ROMCODE: |
| /* read ecc result */ |
| val1 = readl(oinfo->gpmc_base + GPMC_ECC1_RESULT); |
| *ecc_code++ = val1; /* P128e, ..., P1e */ |
| *ecc_code++ = val1 >> 16; /* P128o, ..., P1o */ |
| /* P2048o, P1024o, P512o, P256o, P2048e, P1024e, P512e, P256e */ |
| *ecc_code++ = ((val1 >> 8) & 0x0f) | ((val1 >> 20) & 0xf0); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Compares the ecc read from nand spare area with ECC |
| * registers values and corrects one bit error if it has occured |
| * Further details can be had from OMAP TRM and the following selected links: |
| * http://en.wikipedia.org/wiki/Hamming_code |
| * http://www.cs.utexas.edu/users/plaxton/c/337/05f/slides/ErrorCorrection-4.pdf |
| * |
| * @param mtd - mtd info structure |
| * @param dat page data |
| * @param read_ecc ecc readback |
| * @param calc_ecc calculated ecc (from reg) |
| * |
| * @return 0 if data is OK or corrected, else returns -1 |
| */ |
| static int omap_correct_data(struct mtd_info *mtd, uint8_t *dat, |
| uint8_t *read_ecc, uint8_t *calc_ecc) |
| { |
| unsigned int orig_ecc, new_ecc, res, hm; |
| unsigned short parity_bits, byte; |
| unsigned char bit; |
| struct nand_chip *nand = (struct nand_chip *)(mtd->priv); |
| struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); |
| int ecc_type = OMAP_ECC_BCH8_CODE_HW; |
| int i, j, eccsize, eccflag, count; |
| unsigned int err_loc[8]; |
| int blockCnt = 0; |
| int select_4_8; |
| |
| debug("mtd=%x dat=%x read_ecc=%x calc_ecc=%x", (unsigned int)mtd, |
| (unsigned int)dat, (unsigned int)read_ecc, |
| (unsigned int)calc_ecc); |
| |
| if ((nand->ecc.mode == NAND_ECC_HW) && |
| (nand->ecc.size == 2048)) |
| blockCnt = 4; |
| else |
| blockCnt = 1; |
| |
| switch (oinfo->ecc_mode) { |
| case OMAP_ECC_HAMMING_CODE_HW_ROMCODE: |
| if (read_ecc[0] == 0xff && read_ecc[1] == 0xff && |
| read_ecc[2] == 0xff && calc_ecc[0] == 0x0 && |
| calc_ecc[1] == 0x0 && calc_ecc[0] == 0x0) |
| break; |
| |
| /* Regenerate the orginal ECC */ |
| orig_ecc = gen_true_ecc(read_ecc); |
| new_ecc = gen_true_ecc(calc_ecc); |
| /* Get the XOR of real ecc */ |
| res = orig_ecc ^ new_ecc; |
| if (res) { |
| /* Get the hamming width */ |
| hm = hweight32(res); |
| /* Single bit errors can be corrected! */ |
| if (hm == oinfo->ecc_parity_pairs) { |
| /* Correctable data! */ |
| parity_bits = res >> 16; |
| bit = (parity_bits & 0x7); |
| byte = (parity_bits >> 3) & 0x1FF; |
| /* Flip the bit to correct */ |
| dat[byte] ^= (0x1 << bit); |
| } else if (hm == 1) { |
| printf("Ecc is wrong\n"); |
| /* ECC itself is corrupted */ |
| return 2; |
| } else { |
| printf("bad compare! failed\n"); |
| /* detected 2 bit error */ |
| return -1; |
| } |
| } |
| break; |
| case OMAP_ECC_BCH8_CODE_HW: |
| case OMAP_ECC_BCH8_CODE_HW_ROMCODE: |
| eccsize = 13; |
| select_4_8 = 1; |
| /* fall through */ |
| case OMAP_ECC_BCH4_CODE_HW: |
| if (ecc_type == OMAP_ECC_BCH4_CODE_HW) { |
| eccsize = 7; |
| select_4_8 = 0; |
| } |
| |
| omap_calculate_ecc(mtd, dat, calc_ecc); |
| for (i = 0; i < blockCnt; i++) { |
| /* check if any ecc error */ |
| eccflag = 0; |
| for (j = 0; (j < eccsize) && (eccflag == 0); j++) |
| if (calc_ecc[j] != 0) |
| eccflag = 1; |
| |
| if (eccflag == 1) { |
| eccflag = 0; |
| for (j = 0; (j < eccsize) && |
| (eccflag == 0); j++) |
| if (read_ecc[j] != 0xFF) |
| eccflag = 1; |
| } |
| |
| count = 0; |
| if (eccflag == 1) |
| count = decode_bch(select_4_8, calc_ecc, err_loc); |
| |
| for (j = 0; j < count; j++) { |
| if (err_loc[j] < 4096) |
| dat[err_loc[j] >> 3] ^= |
| 1 << (err_loc[j] & 7); |
| /* else, not interested to correct ecc */ |
| } |
| |
| calc_ecc = calc_ecc + eccsize; |
| read_ecc = read_ecc + eccsize; |
| dat += 512; |
| } |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static void omap_enable_hwecc(struct mtd_info *mtd, int mode) |
| { |
| struct nand_chip *nand = (struct nand_chip *)(mtd->priv); |
| struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); |
| unsigned int bch_mod = 0, bch_wrapmode = 0, eccsize1 = 0, eccsize0 = 0; |
| unsigned int ecc_conf_val = 0, ecc_size_conf_val = 0; |
| int dev_width = nand->options & NAND_BUSWIDTH_16 ? 1 : 0; |
| int ecc_size = nand->ecc.size; |
| int cs = 0; |
| |
| switch (oinfo->ecc_mode) { |
| case OMAP_ECC_BCH4_CODE_HW: |
| if (mode == NAND_ECC_READ) { |
| eccsize1 = 0xD; eccsize0 = 0x48; |
| bch_mod = 0; |
| bch_wrapmode = 0x09; |
| } else { |
| eccsize1 = 0x20; eccsize0 = 0x00; |
| bch_mod = 0; |
| bch_wrapmode = 0x06; |
| } |
| break; |
| case OMAP_ECC_BCH8_CODE_HW: |
| case OMAP_ECC_BCH8_CODE_HW_ROMCODE: |
| if (mode == NAND_ECC_READ) { |
| eccsize1 = 0x1A; eccsize0 = 0x18; |
| bch_mod = 1; |
| bch_wrapmode = 0x04; |
| } else { |
| eccsize1 = 0x20; eccsize0 = 0x00; |
| bch_mod = 1; |
| bch_wrapmode = 0x06; |
| } |
| break; |
| case OMAP_ECC_HAMMING_CODE_HW_ROMCODE: |
| eccsize1 = ((ecc_size >> 1) - 1) << 22; |
| break; |
| case OMAP_ECC_SOFT: |
| return; |
| } |
| |
| /* clear ecc and enable bits */ |
| if (oinfo->ecc_mode == OMAP_ECC_HAMMING_CODE_HW_ROMCODE) { |
| writel(0x00000101, oinfo->gpmc_base + GPMC_ECC_CONTROL); |
| /* Size 0 = 0xFF, Size1 is 0xFF - both are 512 bytes |
| * tell all regs to generate size0 sized regs |
| * we just have a single ECC engine for all CS |
| */ |
| ecc_size_conf_val = 0x3FCFF000; |
| ecc_conf_val = (dev_width << 7) | (cs << 1) | (0x1); |
| } else { |
| writel(0x1, oinfo->gpmc_base + GPMC_ECC_CONTROL); |
| ecc_size_conf_val = (eccsize1 << 22) | (eccsize0 << 12); |
| ecc_conf_val = ((0x01 << 16) | (bch_mod << 12) |
| | (bch_wrapmode << 8) | (dev_width << 7) |
| | (0x03 << 4) | (cs << 1) | (0x1)); |
| } |
| |
| writel(ecc_size_conf_val, oinfo->gpmc_base + GPMC_ECC_SIZE_CONFIG); |
| writel(ecc_conf_val, oinfo->gpmc_base + GPMC_ECC_CONFIG); |
| writel(0x00000101, oinfo->gpmc_base + GPMC_ECC_CONTROL); |
| } |
| |
| static int omap_gpmc_eccmode(struct gpmc_nand_info *oinfo, |
| enum gpmc_ecc_mode mode) |
| { |
| struct mtd_info *minfo = &oinfo->minfo; |
| struct nand_chip *nand = &oinfo->nand; |
| int offset; |
| int i, j; |
| |
| if (nand->options & NAND_BUSWIDTH_16) |
| nand->badblock_pattern = &bb_descrip_flashbased; |
| else |
| nand->badblock_pattern = NULL; |
| |
| if (oinfo->nand.options & NAND_BUSWIDTH_16) |
| offset = 2; |
| else |
| offset = 1; |
| |
| if (mode != OMAP_ECC_SOFT) { |
| nand->ecc.layout = &omap_oobinfo; |
| nand->ecc.calculate = omap_calculate_ecc; |
| nand->ecc.hwctl = omap_enable_hwecc; |
| nand->ecc.correct = omap_correct_data; |
| nand->ecc.read_page = NULL; |
| nand->ecc.write_page = NULL; |
| nand->ecc.read_oob = NULL; |
| nand->ecc.write_oob = NULL; |
| nand->ecc.mode = NAND_ECC_HW; |
| } |
| |
| switch (mode) { |
| case OMAP_ECC_HAMMING_CODE_HW_ROMCODE: |
| oinfo->nand.ecc.bytes = 3; |
| oinfo->nand.ecc.size = 512; |
| oinfo->ecc_parity_pairs = 12; |
| if (oinfo->nand.options & NAND_BUSWIDTH_16) { |
| offset = 2; |
| } else { |
| offset = 1; |
| oinfo->nand.badblock_pattern = &bb_descrip_flashbased; |
| } |
| omap_oobinfo.eccbytes = 3 * (minfo->oobsize / 16); |
| for (i = 0; i < omap_oobinfo.eccbytes; i++) |
| omap_oobinfo.eccpos[i] = i + offset; |
| omap_oobinfo.oobfree->offset = offset + omap_oobinfo.eccbytes; |
| omap_oobinfo.oobfree->length = minfo->oobsize - |
| offset - omap_oobinfo.eccbytes; |
| break; |
| case OMAP_ECC_BCH4_CODE_HW: |
| oinfo->nand.ecc.bytes = 4 * 7; |
| oinfo->nand.ecc.size = 4 * 512; |
| omap_oobinfo.oobfree->offset = offset; |
| omap_oobinfo.oobfree->length = minfo->oobsize - |
| offset - omap_oobinfo.eccbytes; |
| offset = minfo->oobsize - oinfo->nand.ecc.bytes; |
| for (i = 0; i < oinfo->nand.ecc.bytes; i++) |
| omap_oobinfo.eccpos[i] = i + offset; |
| break; |
| case OMAP_ECC_BCH8_CODE_HW: |
| oinfo->nand.ecc.bytes = 4 * 13; |
| oinfo->nand.ecc.size = 4 * 512; |
| omap_oobinfo.oobfree->offset = offset; |
| omap_oobinfo.oobfree->length = minfo->oobsize - |
| offset - omap_oobinfo.eccbytes; |
| offset = minfo->oobsize - oinfo->nand.ecc.bytes; |
| for (i = 0; i < oinfo->nand.ecc.bytes; i++) |
| omap_oobinfo.eccpos[i] = i + offset; |
| break; |
| case OMAP_ECC_BCH8_CODE_HW_ROMCODE: |
| /* |
| * Contradicting the datasheet the ecc checksum has to start |
| * at byte 2 in oob. I have no idea how the rom code can |
| * read this but it does. |
| */ |
| dev_warn(oinfo->pdev, "using rom loader ecc mode. " |
| "You can write properly but not read it back\n"); |
| |
| oinfo->nand.ecc.bytes = 4 * 13; |
| oinfo->nand.ecc.size = 4 * 512; |
| omap_oobinfo.oobfree->length = 0; |
| j = 0; |
| for (i = 2; i < 15; i++) |
| omap_oobinfo.eccpos[j++] = i; |
| for (i = 16; i < 29; i++) |
| omap_oobinfo.eccpos[j++] = i; |
| for (i = 30; i < 43; i++) |
| omap_oobinfo.eccpos[j++] = i; |
| for (i = 44; i < 57; i++) |
| omap_oobinfo.eccpos[j++] = i; |
| break; |
| case OMAP_ECC_SOFT: |
| nand->ecc.layout = NULL; |
| nand->ecc.mode = NAND_ECC_SOFT; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| oinfo->ecc_mode = mode; |
| |
| if (nand->buffers) |
| kfree(nand->buffers); |
| |
| /* second phase scan */ |
| if (nand_scan_tail(minfo)) |
| return -ENXIO; |
| |
| nand->options |= NAND_SKIP_BBTSCAN; |
| |
| return 0; |
| } |
| |
| static int omap_gpmc_eccmode_set(struct device_d *dev, struct param_d *param, const char *val) |
| { |
| struct gpmc_nand_info *oinfo = dev->priv; |
| int i; |
| |
| if (!val) |
| return 0; |
| |
| for (i = 0; i < ARRAY_SIZE(ecc_mode_strings); i++) |
| if (!strcmp(ecc_mode_strings[i], val)) |
| break; |
| |
| if (i == ARRAY_SIZE(ecc_mode_strings)) { |
| dev_err(dev, "invalid ecc mode '%s'\n", val); |
| printf("valid modes:\n"); |
| for (i = 0; i < ARRAY_SIZE(ecc_mode_strings); i++) |
| printf("%s\n", ecc_mode_strings[i]); |
| return -EINVAL; |
| } |
| |
| return omap_gpmc_eccmode(oinfo, i); |
| } |
| |
| /** |
| * @brief nand device probe. |
| * |
| * @param pdev -matching device |
| * |
| * @return -failure reason or give 0 |
| */ |
| static int gpmc_nand_probe(struct device_d *pdev) |
| { |
| struct gpmc_nand_info *oinfo; |
| struct gpmc_nand_platform_data *pdata; |
| struct nand_chip *nand; |
| struct mtd_info *minfo; |
| unsigned long cs_base; |
| int err; |
| struct nand_ecclayout *layout, *lsp, *llp; |
| |
| pdata = (struct gpmc_nand_platform_data *)pdev->platform_data; |
| if (pdata == NULL) { |
| dev_dbg(pdev, "platform data missing\n"); |
| return -ENODEV; |
| } |
| |
| oinfo = xzalloc(sizeof(*oinfo)); |
| |
| /* fill up my data structures */ |
| oinfo->pdev = pdev; |
| oinfo->pdata = pdata; |
| pdev->platform_data = (void *)oinfo; |
| pdev->priv = oinfo; |
| |
| nand = &oinfo->nand; |
| nand->priv = (void *)oinfo; |
| |
| minfo = &oinfo->minfo; |
| minfo->priv = (void *)nand; |
| |
| if (pdata->cs >= GPMC_NUM_CS) { |
| dev_dbg(pdev, "Invalid CS!\n"); |
| err = -EINVAL; |
| goto out_release_mem; |
| } |
| /* Setup register specific data */ |
| oinfo->gpmc_cs = pdata->cs; |
| oinfo->gpmc_base = pdev->map_base; |
| cs_base = oinfo->gpmc_base + GPMC_CONFIG1_0 + |
| (pdata->cs * GPMC_CONFIG_CS_SIZE); |
| oinfo->gpmc_command = (void *)(cs_base + GPMC_CS_NAND_COMMAND); |
| oinfo->gpmc_address = (void *)(cs_base + GPMC_CS_NAND_ADDRESS); |
| oinfo->gpmc_data = (void *)(cs_base + GPMC_CS_NAND_DATA); |
| oinfo->timeout = pdata->max_timeout; |
| debug("GPMC Details:\n" |
| "GPMC BASE=%x\n" |
| "CMD=%x\n" |
| "ADDRESS=%x\n" |
| "DATA=%x\n" |
| "CS_BASE=%x\n", |
| oinfo->gpmc_base, oinfo->gpmc_command, |
| oinfo->gpmc_address, oinfo->gpmc_data, cs_base); |
| |
| /* If we are 16 bit dev, our gpmc config tells us that */ |
| if ((readl(cs_base) & 0x3000) == 0x1000) { |
| dev_dbg(pdev, "16 bit dev\n"); |
| nand->options |= NAND_BUSWIDTH_16; |
| } |
| |
| /* Same data register for in and out */ |
| nand->IO_ADDR_W = nand->IO_ADDR_R = (void *)oinfo->gpmc_data; |
| /* |
| * If RDY/BSY line is connected to OMAP then use the omap ready |
| * function and the generic nand_wait function which reads the |
| * status register after monitoring the RDY/BSY line. Otherwise |
| * use a standard chip delay which is slightly more than tR |
| * (AC Timing) of the NAND device and read the status register |
| * until you get a failure or success |
| */ |
| if (pdata->wait_mon_pin > 4) { |
| dev_dbg(pdev, "Invalid wait monitoring pin\n"); |
| err = -EINVAL; |
| goto out_release_mem; |
| } |
| if (pdata->wait_mon_pin) { |
| /* Set up the wait monitoring mask |
| * This is GPMC_STATUS reg relevant */ |
| oinfo->wait_mon_mask = (0x1 << (pdata->wait_mon_pin - 1)) << 8; |
| oinfo->wait_pol = (pdata->plat_options & NAND_WAITPOL_MASK); |
| nand->dev_ready = omap_dev_ready; |
| nand->chip_delay = 0; |
| } else { |
| /* use the default nand_wait function */ |
| nand->chip_delay = 50; |
| } |
| |
| /* Use default cmdfunc */ |
| /* nand cmd control */ |
| nand->cmd_ctrl = omap_hwcontrol; |
| |
| /* Dont do a bbt scan at the start */ |
| nand->options |= NAND_SKIP_BBTSCAN; |
| |
| /* State my controller */ |
| nand->controller = &oinfo->controller; |
| |
| /* All information is ready.. now lets call setup, if present */ |
| if (pdata->nand_setup) { |
| err = pdata->nand_setup(pdata); |
| if (err) { |
| dev_dbg(pdev, "pdataform setup failed\n"); |
| goto out_release_mem; |
| } |
| } |
| /* Remove write protection */ |
| gpmc_nand_wp(oinfo, 0); |
| |
| /* we do not know what state of device we have is, so |
| * Send a reset to the device |
| * 8 bit write will work on 16 and 8 bit devices |
| */ |
| writeb(NAND_CMD_RESET, oinfo->gpmc_command); |
| mdelay(1); |
| |
| /* first scan to find the device and get the page size */ |
| if (nand_scan_ident(minfo, 1)) { |
| err = -ENXIO; |
| goto out_release_mem; |
| } |
| |
| switch (pdata->device_width) { |
| case 8: |
| lsp = &ecc_sp_x8; |
| llp = &ecc_lp_x8; |
| break; |
| case 16: |
| lsp = &ecc_sp_x16; |
| llp = &ecc_lp_x16; |
| break; |
| default: |
| err = -EINVAL; |
| goto out_release_mem; |
| } |
| |
| switch (minfo->writesize) { |
| case 512: |
| layout = lsp; |
| break; |
| case 2048: |
| layout = llp; |
| break; |
| default: |
| err = -EINVAL; |
| goto out_release_mem; |
| } |
| |
| nand->options |= NAND_SKIP_BBTSCAN; |
| omap_gpmc_eccmode(oinfo, pdata->ecc_mode); |
| |
| /* We are all set to register with the system now! */ |
| err = add_mtd_device(minfo); |
| if (err) { |
| dev_dbg(pdev, "device registration failed\n"); |
| goto out_release_mem; |
| } |
| |
| dev_add_param(pdev, "eccmode", omap_gpmc_eccmode_set, NULL, 0); |
| |
| return 0; |
| |
| out_release_mem: |
| if (oinfo) |
| free(oinfo); |
| |
| dev_dbg(pdev, "Failed!!\n"); |
| return err; |
| } |
| |
| /** GMPC nand driver -> device registered by platforms */ |
| static struct driver_d gpmc_nand_driver = { |
| .name = "gpmc_nand", |
| .probe = gpmc_nand_probe, |
| }; |
| |
| static int gpmc_nand_init(void) |
| { |
| return register_driver(&gpmc_nand_driver); |
| } |
| |
| device_initcall(gpmc_nand_init); |