* [PATCH v6 0/3] add the GPMI controller driver for IMX23/IMX28
@ 2011-04-20 2:45 ` Huang Shijie
0 siblings, 0 replies; 20+ messages in thread
From: Huang Shijie @ 2011-04-20 2:45 UTC (permalink / raw)
To: dedekind1; +Cc: Huang Shijie, linux-mtd, linux-arm-kernel
The general-purpose media interface(GPMI) controller is a flexible interface
to up to several NAND flashs.
The Bose Ray-Choudhury Hocquenghem(BCH) module is a hardware ECC accelerator.
With the help of BCH, the GPMI controller can choose to do the hardware ECC or
not.
This driver is a _pure_ MTD NAND controller driver now.
The driver depends on another patch set, you can find them at :
[1] http://lists.infradead.org/pipermail/linux-mtd/2011-April/035031.html
[2] http://lists.infradead.org/pipermail/linux-mtd/2011-April/035032.html
[3] http://lists.infradead.org/pipermail/linux-mtd/2011-April/035033.html
Test environment:
Using imx23 and imx28 boards with the rootfs in the nand flash.
The command line is:
# console=ttyAMA0,115200 mtdparts=gpmi-nfc-main:20m(boot),-(user) ubi.mtd=1 root=ubi0:rootfs0 rootfstype=ubifs gpmi_debug_init
v5 --> v6:
[0] split out the IMX23/IMX28 arch code to separate patches.
[1] fix bug : missing empty item in the end of platform_id array.
[2] inconsistent identation.
[3] others
v4 --> v5:
[0] rename the files.
[1] fix PM bug
[2] remove the rom_helper code, and move the necessary initialization code
to the main file.
[3] change the macros from CPU_IS_* to GPMI_IS_*
[4] remove the default partition layout init code. revert back the
partition parsing command line code.
[5] others
v3 --> v4:
[0] use the nand_ids{} as the nand database, drop my own database.
[1] remove the patch for DMA enginer, Shawn will submit his own version.
[2] use the platform_id to distinguish different Archs.
[3] fix the strange coding style.
[4] others.
v2 --> v3:
[0] merge the imx23 and imx28 into one file(including the header file).
[1] remove the unuse registers in the headers.
[2] fix DMA bugs
[3] add bus width field to nand_attr{}
[4] others
v1 --> v2:
[0] merge the common files into the gpmi-nfc-main.c
[1] change the code to get the clock.
[2] remove the timing in the nand_device_info{}
[3] fix DMA errors
[4] add the nand_device_info.[ch] to generic code
[5] use the chip->onfi_version for the ONFI nand
[6] useless init
[7] others
Huang Shijie (3):
MTD : add the common code for GPMI controller driver
MTD : add support for imx23 and imx28
MTD : add GPMI driver in the config and Makefile
drivers/mtd/nand/Kconfig | 11 +
drivers/mtd/nand/Makefile | 1 +
drivers/mtd/nand/gpmi-nfc/Makefile | 3 +
drivers/mtd/nand/gpmi-nfc/bch-regs.h | 88 ++
drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c | 2501 +++++++++++++++++++++++++++++++++
drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h | 488 +++++++
drivers/mtd/nand/gpmi-nfc/gpmi-regs.h | 163 +++
drivers/mtd/nand/gpmi-nfc/hal-mxs.c | 554 ++++++++
8 files changed, 3809 insertions(+), 0 deletions(-)
create mode 100644 drivers/mtd/nand/gpmi-nfc/Makefile
create mode 100644 drivers/mtd/nand/gpmi-nfc/bch-regs.h
create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c
create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h
create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-regs.h
create mode 100644 drivers/mtd/nand/gpmi-nfc/hal-mxs.c
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH v6 0/3] add the GPMI controller driver for IMX23/IMX28 @ 2011-04-20 2:45 ` Huang Shijie 0 siblings, 0 replies; 20+ messages in thread From: Huang Shijie @ 2011-04-20 2:45 UTC (permalink / raw) To: linux-arm-kernel The general-purpose media interface(GPMI) controller is a flexible interface to up to several NAND flashs. The Bose Ray-Choudhury Hocquenghem(BCH) module is a hardware ECC accelerator. With the help of BCH, the GPMI controller can choose to do the hardware ECC or not. This driver is a _pure_ MTD NAND controller driver now. The driver depends on another patch set, you can find them at : [1] http://lists.infradead.org/pipermail/linux-mtd/2011-April/035031.html [2] http://lists.infradead.org/pipermail/linux-mtd/2011-April/035032.html [3] http://lists.infradead.org/pipermail/linux-mtd/2011-April/035033.html Test environment: Using imx23 and imx28 boards with the rootfs in the nand flash. The command line is: # console=ttyAMA0,115200 mtdparts=gpmi-nfc-main:20m(boot),-(user) ubi.mtd=1 root=ubi0:rootfs0 rootfstype=ubifs gpmi_debug_init v5 --> v6: [0] split out the IMX23/IMX28 arch code to separate patches. [1] fix bug : missing empty item in the end of platform_id array. [2] inconsistent identation. [3] others v4 --> v5: [0] rename the files. [1] fix PM bug [2] remove the rom_helper code, and move the necessary initialization code to the main file. [3] change the macros from CPU_IS_* to GPMI_IS_* [4] remove the default partition layout init code. revert back the partition parsing command line code. [5] others v3 --> v4: [0] use the nand_ids{} as the nand database, drop my own database. [1] remove the patch for DMA enginer, Shawn will submit his own version. [2] use the platform_id to distinguish different Archs. [3] fix the strange coding style. [4] others. v2 --> v3: [0] merge the imx23 and imx28 into one file(including the header file). [1] remove the unuse registers in the headers. [2] fix DMA bugs [3] add bus width field to nand_attr{} [4] others v1 --> v2: [0] merge the common files into the gpmi-nfc-main.c [1] change the code to get the clock. [2] remove the timing in the nand_device_info{} [3] fix DMA errors [4] add the nand_device_info.[ch] to generic code [5] use the chip->onfi_version for the ONFI nand [6] useless init [7] others Huang Shijie (3): MTD : add the common code for GPMI controller driver MTD : add support for imx23 and imx28 MTD : add GPMI driver in the config and Makefile drivers/mtd/nand/Kconfig | 11 + drivers/mtd/nand/Makefile | 1 + drivers/mtd/nand/gpmi-nfc/Makefile | 3 + drivers/mtd/nand/gpmi-nfc/bch-regs.h | 88 ++ drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c | 2501 +++++++++++++++++++++++++++++++++ drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h | 488 +++++++ drivers/mtd/nand/gpmi-nfc/gpmi-regs.h | 163 +++ drivers/mtd/nand/gpmi-nfc/hal-mxs.c | 554 ++++++++ 8 files changed, 3809 insertions(+), 0 deletions(-) create mode 100644 drivers/mtd/nand/gpmi-nfc/Makefile create mode 100644 drivers/mtd/nand/gpmi-nfc/bch-regs.h create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-regs.h create mode 100644 drivers/mtd/nand/gpmi-nfc/hal-mxs.c ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 1/3] MTD : add the common code for GPMI controller driver 2011-04-20 2:45 ` Huang Shijie @ 2011-04-20 2:45 ` Huang Shijie -1 siblings, 0 replies; 20+ messages in thread From: Huang Shijie @ 2011-04-20 2:45 UTC (permalink / raw) To: dedekind1; +Cc: Huang Shijie, linux-mtd, linux-arm-kernel These files contain the common code for the GPMI driver. Signed-off-by: Huang Shijie <b32955@freescale.com> --- drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c | 2501 ++++++++++++++++++++++++++++++++++ drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h | 488 +++++++ 2 files changed, 2989 insertions(+), 0 deletions(-) create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h diff --git a/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c new file mode 100644 index 0000000..53d6915 --- /dev/null +++ b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c @@ -0,0 +1,2501 @@ +/* + * Freescale GPMI NFC NAND Flash Driver + * + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. + * Copyright (C) 2008 Embedded Alley Solutions, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include <linux/slab.h> +#include "gpmi-nfc.h" + +/* add our owner bbt descriptor */ +static uint8_t scan_ff_pattern[] = { 0xff }; +static struct nand_bbt_descr gpmi_bbt_descr = { + .options = 0, + .offs = 0, + .len = 1, + .pattern = scan_ff_pattern +}; + +/* debug control */ +int gpmi_debug; + +static ssize_t show_gpmi_debug(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", gpmi_debug); +} + +static ssize_t +store_gpmi_debug(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + const char *p = buf; + unsigned long v; + + if (strict_strtoul(p, 0, &v) < 0) + return size; + + gpmi_debug = v; + return size; +} + +static ssize_t show_ignorebad(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpmi_nfc_data *this = dev_get_drvdata(dev); + struct mil *mil = &this->mil; + + return sprintf(buf, "%d\n", mil->ignore_bad_block_marks); +} + +static ssize_t +store_ignorebad(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct gpmi_nfc_data *this = dev_get_drvdata(dev); + struct mil *mil = &this->mil; + const char *p = buf; + unsigned long v; + + if (strict_strtoul(p, 0, &v) < 0) + return size; + + if (v > 0) + v = 1; + + if (v != mil->ignore_bad_block_marks) { + if (v) { + /* + * This will cause the NAND Flash MTD code to believe + * that it never created a BBT and force it to call our + * block_bad function. + * + * See mil_block_bad for more details. + */ + mil->saved_bbt = mil->nand.bbt; + mil->nand.bbt = NULL; + } else { + /* + * Restore the NAND Flash MTD's pointer + * to its in-memory BBT. + */ + mil->nand.bbt = mil->saved_bbt; + } + mil->ignore_bad_block_marks = v; + } + return size; +} + +static DEVICE_ATTR(ignorebad, 0644, show_ignorebad, store_ignorebad); +static DEVICE_ATTR(gpmi_debug, 0644, show_gpmi_debug, store_gpmi_debug); +static struct device_attribute *device_attributes[] = { + &dev_attr_ignorebad, + &dev_attr_gpmi_debug, +}; + +static irqreturn_t bch_irq(int irq, void *cookie) +{ + struct gpmi_nfc_data *this = cookie; + struct nfc_hal *nfc = this->nfc; + + /* Clear the BCH interrupt */ + nfc->clear_bch(this); + + complete(&nfc->bch_done); + return IRQ_HANDLED; +} + +/* calculate the ECC strength by hand */ +static inline int get_ecc_strength(struct gpmi_nfc_data *this) +{ + struct mtd_info *mtd = &this->mil.mtd; + int ecc_strength = 0; + + switch (mtd->writesize) { + case 2048: + ecc_strength = 8; + break; + case 4096: + switch (mtd->oobsize) { + case 128: + ecc_strength = 8; + break; + case 224: + case 218: + ecc_strength = 16; + break; + } + break; + case 8192: + ecc_strength = 24; + break; + } + + return ecc_strength; +} + +static inline bool is_ddr_nand(struct nand_chip *chip) +{ + /* ONFI nand */ + if (chip->onfi_version != 0) + return true; + + /* TOGGLE nand */ + + return false; +} + +static inline int get_ecc_chunk_size(struct gpmi_nfc_data *this) +{ + struct nand_chip *chip = &this->mil.nand; + + /* the ONFI/TOGGLE nands use 1k ecc chunk size */ + if (is_ddr_nand(chip)) + return 1024; + + /* for historical reason */ + return 512; +} + +int common_nfc_set_geometry(struct gpmi_nfc_data *this) +{ + struct nfc_geometry *geo = &this->nfc_geometry; + struct mtd_info *mtd = &this->mil.mtd; + struct nand_chip *chip = &this->mil.nand; + unsigned int metadata_size; + unsigned int status_size; + unsigned int chunk_data_size_in_bits; + unsigned int chunk_ecc_size_in_bits; + unsigned int chunk_total_size_in_bits; + unsigned int block_mark_chunk_number; + unsigned int block_mark_chunk_bit_offset; + unsigned int block_mark_bit_offset; + + /* We only support BCH now. */ + geo->ecc_algorithm = "BCH"; + + /* + * We always choose a metadata size of 10. Don't try to make sense of + * it -- this is really only for historical compatibility. + */ + geo->metadata_size_in_bytes = 10; + + /* ECC chunks */ + geo->ecc_chunk_size_in_bytes = get_ecc_chunk_size(this); + + /* + * Compute the total number of ECC chunks in a page. This includes the + * slightly larger chunk at the beginning of the page, which contains + * both data and metadata. + */ + geo->ecc_chunk_count = mtd->writesize / geo->ecc_chunk_size_in_bytes; + + /* + * We use the same ECC strength for all chunks, including the first one. + */ + geo->ecc_strength = get_ecc_strength(this); + if (!geo->ecc_strength) { + log("Page size : %d, OOB : %d", mtd->writesize, mtd->oobsize); + return -EINVAL; + } + + /* Compute the page size, include page and oob. */ + geo->page_size_in_bytes = mtd->writesize + mtd->oobsize; + + /* + * ONFI/TOGGLE nand needs GF14, so re-calculate DMA page size. + * The ONFI nand must do the recalculation, + * else it will fail in DMA in some platform(such as imx50). + */ + if (is_ddr_nand(chip)) + geo->page_size_in_bytes = mtd->writesize + + geo->metadata_size_in_bytes + + (geo->ecc_strength * 14 * 8 / geo->ecc_chunk_count); + + geo->payload_size_in_bytes = mtd->writesize; + /* + * In principle, computing the auxiliary buffer geometry is NFC + * version-specific. However, at this writing, all versions share the + * same model, so this code can also be shared. + * + * The auxiliary buffer contains the metadata and the ECC status. The + * metadata is padded to the nearest 32-bit boundary. The ECC status + * contains one byte for every ECC chunk, and is also padded to the + * nearest 32-bit boundary. + */ + metadata_size = ALIGN(geo->metadata_size_in_bytes, 4); + status_size = ALIGN(geo->ecc_chunk_count, 4); + + geo->auxiliary_size_in_bytes = metadata_size + status_size; + geo->auxiliary_status_offset = metadata_size; + + /* Check if we're going to do block mark swapping. */ + if (!this->swap_block_mark) + return 0; + + /* + * If control arrives here, we're doing block mark swapping, so we need + * to compute the byte and bit offsets of the physical block mark within + * the ECC-based view of the page data. In principle, this isn't a + * difficult computation -- but it's very important and it's easy to get + * it wrong, so we do it carefully. + * + * Note that this calculation is simpler because we use the same ECC + * strength for all chunks, including the zero'th one, which contains + * the metadata. The calculation would be slightly more complicated + * otherwise. + * + * We start by computing the physical bit offset of the block mark. We + * then subtract the number of metadata and ECC bits appearing before + * the mark to arrive at its bit offset within the data alone. + */ + + /* Compute some important facts about chunk geometry. */ + chunk_data_size_in_bits = geo->ecc_chunk_size_in_bytes * 8; + + /* ONFI/TOGGLE nand needs GF14 */ + if (is_ddr_nand(chip)) + chunk_ecc_size_in_bits = geo->ecc_strength * 14; + else + chunk_ecc_size_in_bits = geo->ecc_strength * 13; + + chunk_total_size_in_bits = + chunk_data_size_in_bits + chunk_ecc_size_in_bits; + + /* Compute the bit offset of the block mark within the physical page. */ + block_mark_bit_offset = mtd->writesize * 8; + + /* Subtract the metadata bits. */ + block_mark_bit_offset -= geo->metadata_size_in_bytes * 8; + + /* + * Compute the chunk number (starting at zero) in which the block mark + * appears. + */ + block_mark_chunk_number = + block_mark_bit_offset / chunk_total_size_in_bits; + + /* + * Compute the bit offset of the block mark within its chunk, and + * validate it. + */ + block_mark_chunk_bit_offset = + block_mark_bit_offset - + (block_mark_chunk_number * chunk_total_size_in_bits); + + if (block_mark_chunk_bit_offset > chunk_data_size_in_bits) { + /* + * If control arrives here, the block mark actually appears in + * the ECC bits of this chunk. This wont' work. + */ + log("Unsupported page geometry (block mark in ECC): %u:%u", + mtd->writesize, mtd->oobsize); + return -EINVAL; + } + + /* + * Now that we know the chunk number in which the block mark appears, + * we can subtract all the ECC bits that appear before it. + */ + block_mark_bit_offset -= + block_mark_chunk_number * chunk_ecc_size_in_bits; + + /* + * We now know the absolute bit offset of the block mark within the + * ECC-based data. We can now compute the byte offset and the bit + * offset within the byte. + */ + geo->block_mark_byte_offset = block_mark_bit_offset / 8; + geo->block_mark_bit_offset = block_mark_bit_offset % 8; + + return 0; +} + +struct dma_chan *get_dma_chan(struct gpmi_nfc_data *this) +{ + int chip = this->mil.current_chip; + + BUG_ON(chip < 0); + return this->dma_chans[chip]; +} + +/* Can we use the upper's buffer directly for DMA? */ +void prepare_data_dma(struct gpmi_nfc_data *this, enum dma_data_direction dr) +{ + struct mil *mil = &this->mil; + struct scatterlist *sgl = &mil->data_sgl; + int ret; + + mil->direct_dma_map_ok = true; + + /* first try to map the upper buffer directly */ + sg_init_one(sgl, mil->upper_buf, mil->upper_len); + ret = dma_map_sg(this->dev, sgl, 1, dr); + if (ret == 0) { + /* We have to use our own DMA buffer. */ + sg_init_one(sgl, mil->data_buffer_dma, PAGE_SIZE); + + if (dr == DMA_TO_DEVICE) + memcpy(mil->data_buffer_dma, mil->upper_buf, + mil->upper_len); + + ret = dma_map_sg(this->dev, sgl, 1, dr); + BUG_ON(ret == 0); + + mil->direct_dma_map_ok = false; + } +} + +/* This will be called after the DMA operation is finished. */ +static void dma_irq_callback(void *param) +{ + struct gpmi_nfc_data *this = param; + struct nfc_hal *nfc = this->nfc; + struct mil *mil = &this->mil; + + complete(&nfc->dma_done); + + switch (this->dma_type) { + case DMA_FOR_COMMAND: + dma_unmap_sg(this->dev, &mil->cmd_sgl, 1, DMA_TO_DEVICE); + break; + + case DMA_FOR_READ_DATA: + dma_unmap_sg(this->dev, &mil->data_sgl, 1, DMA_FROM_DEVICE); + if (mil->direct_dma_map_ok == false) + memcpy(mil->upper_buf, (char *)mil->data_buffer_dma, + mil->upper_len); + break; + + case DMA_FOR_WRITE_DATA: + dma_unmap_sg(this->dev, &mil->data_sgl, 1, DMA_TO_DEVICE); + break; + + case DMA_FOR_READ_ECC_PAGE: + case DMA_FOR_WRITE_ECC_PAGE: + /* We have to wait the BCH interrupt to finish. */ + break; + + default: + BUG(); + } +} + +int start_dma_without_bch_irq(struct gpmi_nfc_data *this, + struct dma_async_tx_descriptor *desc) +{ + struct nfc_hal *nfc = this->nfc; + int err; + + init_completion(&nfc->dma_done); + + desc->callback = dma_irq_callback; + desc->callback_param = this; + dmaengine_submit(desc); + + /* Wait for the interrupt from the DMA block. */ + err = wait_for_completion_timeout(&nfc->dma_done, + msecs_to_jiffies(1000)); + err = (!err) ? -ETIMEDOUT : 0; + if (err) + log("DMA timeout!!!"); + return err; +} + +/* + * This function is used in BCH reading or BCH writing pages. + * It will wait for the BCH interrupt as long as ONE second. + * Actually, we must wait for two interrupts : + * [1] firstly the DMA interrupt and + * [2] secondly the BCH interrupt. + * + * @this: Per-device data structure. + * @desc: DMA channel + */ +int start_dma_with_bch_irq(struct gpmi_nfc_data *this, + struct dma_async_tx_descriptor *desc) +{ + struct nfc_hal *nfc = this->nfc; + int err; + + /* Prepare to receive an interrupt from the BCH block. */ + init_completion(&nfc->bch_done); + + /* start the DMA */ + start_dma_without_bch_irq(this, desc); + + /* Wait for the interrupt from the BCH block. */ + err = wait_for_completion_timeout(&nfc->bch_done, + msecs_to_jiffies(1000)); + err = (!err) ? -ETIMEDOUT : 0; + if (err) + log("bch timeout!!!"); + return err; +} + +/** + * ns_to_cycles - Converts time in nanoseconds to cycles. + * + * @ntime: The time, in nanoseconds. + * @period: The cycle period, in nanoseconds. + * @min: The minimum allowable number of cycles. + */ +static unsigned int ns_to_cycles(unsigned int time, + unsigned int period, unsigned int min) +{ + unsigned int k; + + /* + * Compute the minimum number of cycles that entirely contain the + * given time. + */ + k = (time + period - 1) / period; + return max(k, min); +} + +/** + * gpmi_compute_hardware_timing - Apply timing to current hardware conditions. + * + * @this: Per-device data. + * @hardware_timing: A pointer to a hardware timing structure that will receive + * the results of our calculations. + */ +int gpmi_nfc_compute_hardware_timing(struct gpmi_nfc_data *this, + struct gpmi_nfc_hardware_timing *hw) +{ + struct gpmi_nfc_platform_data *pdata = this->pdata; + struct nfc_hal *nfc = this->nfc; + struct nand_chip *nand = &this->mil.nand; + struct nand_timing target = nfc->timing; + bool improved_timing_is_available; + unsigned long clock_frequency_in_hz; + unsigned int clock_period_in_ns; + bool dll_use_half_periods; + unsigned int dll_delay_shift; + unsigned int max_sample_delay_in_ns; + unsigned int address_setup_in_cycles; + unsigned int data_setup_in_ns; + unsigned int data_setup_in_cycles; + unsigned int data_hold_in_cycles; + int ideal_sample_delay_in_ns; + unsigned int sample_delay_factor; + int tEYE; + unsigned int min_prop_delay_in_ns = pdata->min_prop_delay_in_ns; + unsigned int max_prop_delay_in_ns = pdata->max_prop_delay_in_ns; + + /* + * If there are multiple chips, we need to relax the timings to allow + * for signal distortion due to higher capacitance. + */ + if (nand->numchips > 2) { + target.data_setup_in_ns += 10; + target.data_hold_in_ns += 10; + target.address_setup_in_ns += 10; + } else if (nand->numchips > 1) { + target.data_setup_in_ns += 5; + target.data_hold_in_ns += 5; + target.address_setup_in_ns += 5; + } + + /* Check if improved timing information is available. */ + improved_timing_is_available = + (target.tREA_in_ns >= 0) && + (target.tRLOH_in_ns >= 0) && + (target.tRHOH_in_ns >= 0) ; + + /* Inspect the clock. */ + clock_frequency_in_hz = nfc->clock_frequency_in_hz; + clock_period_in_ns = 1000000000 / clock_frequency_in_hz; + + /* + * The NFC quantizes setup and hold parameters in terms of clock cycles. + * Here, we quantize the setup and hold timing parameters to the + * next-highest clock period to make sure we apply at least the + * specified times. + * + * For data setup and data hold, the hardware interprets a value of zero + * as the largest possible delay. This is not what's intended by a zero + * in the input parameter, so we impose a minimum of one cycle. + */ + data_setup_in_cycles = ns_to_cycles(target.data_setup_in_ns, + clock_period_in_ns, 1); + data_hold_in_cycles = ns_to_cycles(target.data_hold_in_ns, + clock_period_in_ns, 1); + address_setup_in_cycles = ns_to_cycles(target.address_setup_in_ns, + clock_period_in_ns, 0); + + /* + * The clock's period affects the sample delay in a number of ways: + * + * (1) The NFC HAL tells us the maximum clock period the sample delay + * DLL can tolerate. If the clock period is greater than half that + * maximum, we must configure the DLL to be driven by half periods. + * + * (2) We need to convert from an ideal sample delay, in ns, to a + * "sample delay factor," which the NFC uses. This factor depends on + * whether we're driving the DLL with full or half periods. + * Paraphrasing the reference manual: + * + * AD = SDF x 0.125 x RP + * + * where: + * + * AD is the applied delay, in ns. + * SDF is the sample delay factor, which is dimensionless. + * RP is the reference period, in ns, which is a full clock period + * if the DLL is being driven by full periods, or half that if + * the DLL is being driven by half periods. + * + * Let's re-arrange this in a way that's more useful to us: + * + * 8 + * SDF = AD x ---- + * RP + * + * The reference period is either the clock period or half that, so this + * is: + * + * 8 AD x DDF + * SDF = AD x ----- = -------- + * f x P P + * + * where: + * + * f is 1 or 1/2, depending on how we're driving the DLL. + * P is the clock period. + * DDF is the DLL Delay Factor, a dimensionless value that + * incorporates all the constants in the conversion. + * + * DDF will be either 8 or 16, both of which are powers of two. We can + * reduce the cost of this conversion by using bit shifts instead of + * multiplication or division. Thus: + * + * AD << DDS + * SDF = --------- + * P + * + * or + * + * AD = (SDF >> DDS) x P + * + * where: + * + * DDS is the DLL Delay Shift, the logarithm to base 2 of the DDF. + */ + if (clock_period_in_ns > (nfc->max_dll_clock_period_in_ns >> 1)) { + dll_use_half_periods = true; + dll_delay_shift = 3 + 1; + } else { + dll_use_half_periods = false; + dll_delay_shift = 3; + } + + /* + * Compute the maximum sample delay the NFC allows, under current + * conditions. If the clock is running too slowly, no sample delay is + * possible. + */ + if (clock_period_in_ns > nfc->max_dll_clock_period_in_ns) + max_sample_delay_in_ns = 0; + else { + /* + * Compute the delay implied by the largest sample delay factor + * the NFC allows. + */ + max_sample_delay_in_ns = + (nfc->max_sample_delay_factor * clock_period_in_ns) >> + dll_delay_shift; + + /* + * Check if the implied sample delay larger than the NFC + * actually allows. + */ + if (max_sample_delay_in_ns > nfc->max_dll_delay_in_ns) + max_sample_delay_in_ns = nfc->max_dll_delay_in_ns; + } + + /* + * Check if improved timing information is available. If not, we have to + * use a less-sophisticated algorithm. + */ + if (!improved_timing_is_available) { + /* + * Fold the read setup time required by the NFC into the ideal + * sample delay. + */ + ideal_sample_delay_in_ns = target.gpmi_sample_delay_in_ns + + nfc->internal_data_setup_in_ns; + + /* + * The ideal sample delay may be greater than the maximum + * allowed by the NFC. If so, we can trade off sample delay time + * for more data setup time. + * + * In each iteration of the following loop, we add a cycle to + * the data setup time and subtract a corresponding amount from + * the sample delay until we've satisified the constraints or + * can't do any better. + */ + while ((ideal_sample_delay_in_ns > max_sample_delay_in_ns) && + (data_setup_in_cycles < nfc->max_data_setup_cycles)) { + + data_setup_in_cycles++; + ideal_sample_delay_in_ns -= clock_period_in_ns; + + if (ideal_sample_delay_in_ns < 0) + ideal_sample_delay_in_ns = 0; + + } + + /* + * Compute the sample delay factor that corresponds most closely + * to the ideal sample delay. If the result is too large for the + * NFC, use the maximum value. + * + * Notice that we use the ns_to_cycles function to compute the + * sample delay factor. We do this because the form of the + * computation is the same as that for calculating cycles. + */ + sample_delay_factor = + ns_to_cycles( + ideal_sample_delay_in_ns << dll_delay_shift, + clock_period_in_ns, 0); + + if (sample_delay_factor > nfc->max_sample_delay_factor) + sample_delay_factor = nfc->max_sample_delay_factor; + + /* Skip to the part where we return our results. */ + goto return_results; + } + + /* + * If control arrives here, we have more detailed timing information, + * so we can use a better algorithm. + */ + + /* + * Fold the read setup time required by the NFC into the maximum + * propagation delay. + */ + max_prop_delay_in_ns += nfc->internal_data_setup_in_ns; + + /* + * Earlier, we computed the number of clock cycles required to satisfy + * the data setup time. Now, we need to know the actual nanoseconds. + */ + data_setup_in_ns = clock_period_in_ns * data_setup_in_cycles; + + /* + * Compute tEYE, the width of the data eye when reading from the NAND + * Flash. The eye width is fundamentally determined by the data setup + * time, perturbed by propagation delays and some characteristics of the + * NAND Flash device. + * + * start of the eye = max_prop_delay + tREA + * end of the eye = min_prop_delay + tRHOH + data_setup + */ + tEYE = (int)min_prop_delay_in_ns + (int)target.tRHOH_in_ns + + (int)data_setup_in_ns; + + tEYE -= (int)max_prop_delay_in_ns + (int)target.tREA_in_ns; + + /* + * The eye must be open. If it's not, we can try to open it by + * increasing its main forcer, the data setup time. + * + * In each iteration of the following loop, we increase the data setup + * time by a single clock cycle. We do this until either the eye is + * open or we run into NFC limits. + */ + while ((tEYE <= 0) && + (data_setup_in_cycles < nfc->max_data_setup_cycles)) { + /* Give a cycle to data setup. */ + data_setup_in_cycles++; + /* Synchronize the data setup time with the cycles. */ + data_setup_in_ns += clock_period_in_ns; + /* Adjust tEYE accordingly. */ + tEYE += clock_period_in_ns; + } + + /* + * When control arrives here, the eye is open. The ideal time to sample + * the data is in the center of the eye: + * + * end of the eye + start of the eye + * --------------------------------- - data_setup + * 2 + * + * After some algebra, this simplifies to the code immediately below. + */ + ideal_sample_delay_in_ns = + ((int)max_prop_delay_in_ns + + (int)target.tREA_in_ns + + (int)min_prop_delay_in_ns + + (int)target.tRHOH_in_ns - + (int)data_setup_in_ns) >> 1; + + /* + * The following figure illustrates some aspects of a NAND Flash read: + * + * + * __ _____________________________________ + * RDN \_________________/ + * + * <---- tEYE -----> + * /-----------------\ + * Read Data ----------------------------< >--------- + * \-----------------/ + * ^ ^ ^ ^ + * | | | | + * |<--Data Setup -->|<--Delay Time -->| | + * | | | | + * | | | + * | |<-- Quantized Delay Time -->| + * | | | + * + * + * We have some issues we must now address: + * + * (1) The *ideal* sample delay time must not be negative. If it is, we + * jam it to zero. + * + * (2) The *ideal* sample delay time must not be greater than that + * allowed by the NFC. If it is, we can increase the data setup + * time, which will reduce the delay between the end of the data + * setup and the center of the eye. It will also make the eye + * larger, which might help with the next issue... + * + * (3) The *quantized* sample delay time must not fall either before the + * eye opens or after it closes (the latter is the problem + * illustrated in the above figure). + */ + + /* Jam a negative ideal sample delay to zero. */ + if (ideal_sample_delay_in_ns < 0) + ideal_sample_delay_in_ns = 0; + + /* + * Extend the data setup as needed to reduce the ideal sample delay + * below the maximum permitted by the NFC. + */ + while ((ideal_sample_delay_in_ns > max_sample_delay_in_ns) && + (data_setup_in_cycles < nfc->max_data_setup_cycles)) { + + /* Give a cycle to data setup. */ + data_setup_in_cycles++; + /* Synchronize the data setup time with the cycles. */ + data_setup_in_ns += clock_period_in_ns; + /* Adjust tEYE accordingly. */ + tEYE += clock_period_in_ns; + + /* + * Decrease the ideal sample delay by one half cycle, to keep it + * in the middle of the eye. + */ + ideal_sample_delay_in_ns -= (clock_period_in_ns >> 1); + + /* Jam a negative ideal sample delay to zero. */ + if (ideal_sample_delay_in_ns < 0) + ideal_sample_delay_in_ns = 0; + } + + /* + * Compute the sample delay factor that corresponds to the ideal sample + * delay. If the result is too large, then use the maximum allowed + * value. + * + * Notice that we use the ns_to_cycles function to compute the sample + * delay factor. We do this because the form of the computation is the + * same as that for calculating cycles. + */ + sample_delay_factor = + ns_to_cycles(ideal_sample_delay_in_ns << dll_delay_shift, + clock_period_in_ns, 0); + + if (sample_delay_factor > nfc->max_sample_delay_factor) + sample_delay_factor = nfc->max_sample_delay_factor; + + /* + * These macros conveniently encapsulate a computation we'll use to + * continuously evaluate whether or not the data sample delay is inside + * the eye. + */ + #define IDEAL_DELAY ((int) ideal_sample_delay_in_ns) + + #define QUANTIZED_DELAY \ + ((int) ((sample_delay_factor * clock_period_in_ns) >> \ + dll_delay_shift)) + + #define DELAY_ERROR (abs(QUANTIZED_DELAY - IDEAL_DELAY)) + + #define SAMPLE_IS_NOT_WITHIN_THE_EYE (DELAY_ERROR > (tEYE >> 1)) + + /* + * While the quantized sample time falls outside the eye, reduce the + * sample delay or extend the data setup to move the sampling point back + * toward the eye. Do not allow the number of data setup cycles to + * exceed the maximum allowed by the NFC. + */ + while (SAMPLE_IS_NOT_WITHIN_THE_EYE && + (data_setup_in_cycles < nfc->max_data_setup_cycles)) { + /* + * If control arrives here, the quantized sample delay falls + * outside the eye. Check if it's before the eye opens, or after + * the eye closes. + */ + if (QUANTIZED_DELAY > IDEAL_DELAY) { + /* + * If control arrives here, the quantized sample delay + * falls after the eye closes. Decrease the quantized + * delay time and then go back to re-evaluate. + */ + if (sample_delay_factor != 0) + sample_delay_factor--; + continue; + } + + /* + * If control arrives here, the quantized sample delay falls + * before the eye opens. Shift the sample point by increasing + * data setup time. This will also make the eye larger. + */ + + /* Give a cycle to data setup. */ + data_setup_in_cycles++; + /* Synchronize the data setup time with the cycles. */ + data_setup_in_ns += clock_period_in_ns; + /* Adjust tEYE accordingly. */ + tEYE += clock_period_in_ns; + + /* + * Decrease the ideal sample delay by one half cycle, to keep it + * in the middle of the eye. + */ + ideal_sample_delay_in_ns -= (clock_period_in_ns >> 1); + + /* ...and one less period for the delay time. */ + ideal_sample_delay_in_ns -= clock_period_in_ns; + + /* Jam a negative ideal sample delay to zero. */ + if (ideal_sample_delay_in_ns < 0) + ideal_sample_delay_in_ns = 0; + + /* + * We have a new ideal sample delay, so re-compute the quantized + * delay. + */ + sample_delay_factor = + ns_to_cycles( + ideal_sample_delay_in_ns << dll_delay_shift, + clock_period_in_ns, 0); + + if (sample_delay_factor > nfc->max_sample_delay_factor) + sample_delay_factor = nfc->max_sample_delay_factor; + } + + /* Control arrives here when we're ready to return our results. */ +return_results: + hw->data_setup_in_cycles = data_setup_in_cycles; + hw->data_hold_in_cycles = data_hold_in_cycles; + hw->address_setup_in_cycles = address_setup_in_cycles; + hw->use_half_periods = dll_use_half_periods; + hw->sample_delay_factor = sample_delay_factor; + + /* Return success. */ + return 0; +} + +static int acquire_register_block(struct gpmi_nfc_data *this, + const char *resource_name, void **reg_block_base) +{ + struct platform_device *pdev = this->pdev; + struct resource *r; + void *p; + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, resource_name); + if (!r) { + log("Can't get resource information for '%s'", resource_name); + return -ENXIO; + } + + /* remap the register block */ + p = ioremap(r->start, resource_size(r)); + if (!p) { + log("Can't remap %s", resource_name); + return -ENOMEM; + } + + *reg_block_base = p; + return 0; +} + +static void release_register_block(struct gpmi_nfc_data *this, + void *reg_block_base) +{ + iounmap(reg_block_base); +} + +static int acquire_interrupt(struct gpmi_nfc_data *this, + const char *resource_name, + irq_handler_t interrupt_handler, int *lno, int *hno) +{ + struct platform_device *pdev = this->pdev; + struct resource *r; + int err; + + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, resource_name); + if (!r) { + log("Can't get resource information for '%s'", resource_name); + return -ENXIO; + } + + BUG_ON(r->start != r->end); + err = request_irq(r->start, interrupt_handler, 0, resource_name, this); + if (err) { + log("Can't own %s", resource_name); + return err; + } + + *lno = r->start; + *hno = r->end; + return 0; +} + +static void release_interrupt(struct gpmi_nfc_data *this, + int low_interrupt_number, int high_interrupt_number) +{ + int i; + for (i = low_interrupt_number; i <= high_interrupt_number; i++) + free_irq(i, this); +} + +static bool gpmi_dma_filter(struct dma_chan *chan, void *param) +{ + struct gpmi_nfc_data *this = param; + struct resource *r = this->private; + + if (!mxs_dma_is_apbh(chan)) + return false; + /* + * only catch the GPMI dma channels : + * for mx23 : MX23_DMA_GPMI0 ~ MX23_DMA_GPMI3 + * (These four channels share the same IRQ!) + * + * for mx28 : MX28_DMA_GPMI0 ~ MX28_DMA_GPMI7 + * (These eight channels share the same IRQ!) + */ + if (r->start <= chan->chan_id && chan->chan_id <= r->end) { + chan->private = &this->dma_data; + return true; + } + return false; +} + +static void release_dma_channels(struct gpmi_nfc_data *this) +{ + unsigned int i; + for (i = 0; i < DMA_CHANS; i++) + if (this->dma_chans[i]) { + dma_release_channel(this->dma_chans[i]); + this->dma_chans[i] = NULL; + } +} + +static int acquire_dma_channels(struct gpmi_nfc_data *this, + const char *resource_name, + unsigned *low_channel, unsigned *high_channel) +{ + struct platform_device *pdev = this->pdev; + struct resource *r, *r_dma; + unsigned int i; + + r = platform_get_resource_byname(pdev, IORESOURCE_DMA, resource_name); + r_dma = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + GPMI_NFC_DMA_INTERRUPT_RES_NAME); + if (!r || !r_dma) { + log("Can't get resource for DMA"); + return -ENXIO; + } + + /* used in gpmi_dma_filter() */ + this->private = r; + + for (i = r->start; i <= r->end; i++) { + dma_cap_mask_t mask; + struct dma_chan *dma_chan; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + /* get the DMA interrupt */ + this->dma_data.chan_irq = r_dma->start + + ((r_dma->start != r_dma->end) ? (i - r->start) : 0); + + dma_chan = dma_request_channel(mask, gpmi_dma_filter, this); + if (!dma_chan) + goto acquire_err; + /* fill the first empty item */ + this->dma_chans[i - r->start] = dma_chan; + } + + *low_channel = r->start; + *high_channel = r->end; + return 0; + +acquire_err: + log("Can't acquire DMA channel %u", i); + release_dma_channels(this); + return -EINVAL; +} + +static inline int acquire_clock(struct gpmi_nfc_data *this, struct clk **clock) +{ + struct clk *c; + + c = clk_get(&this->pdev->dev, NULL); + if (IS_ERR(c)) { + log("Can't own clock"); + return PTR_ERR(c); + } + *clock = c; + return 0; +} + +static void release_clock(struct gpmi_nfc_data *this, struct clk *clock) +{ + clk_put(clock); +} + +static int acquire_resources(struct gpmi_nfc_data *this) +{ + struct resources *resources = &this->resources; + int error; + + /* Attempt to acquire the GPMI register block. */ + error = acquire_register_block(this, + GPMI_NFC_GPMI_REGS_ADDR_RES_NAME, + &resources->gpmi_regs); + if (error) + goto exit_gpmi_regs; + + /* Attempt to acquire the BCH register block. */ + error = acquire_register_block(this, + GPMI_NFC_BCH_REGS_ADDR_RES_NAME, + &resources->bch_regs); + if (error) + goto exit_bch_regs; + + /* Attempt to acquire the BCH interrupt. */ + error = acquire_interrupt(this, + GPMI_NFC_BCH_INTERRUPT_RES_NAME, + bch_irq, + &resources->bch_low_interrupt, + &resources->bch_high_interrupt); + if (error) + goto exit_bch_interrupt; + + /* Attempt to acquire the DMA channels. */ + error = acquire_dma_channels(this, + GPMI_NFC_DMA_CHANNELS_RES_NAME, + &resources->dma_low_channel, + &resources->dma_high_channel); + if (error) + goto exit_dma_channels; + + /* Attempt to acquire our clock. */ + error = acquire_clock(this, &resources->clock); + if (error) + goto exit_clock; + return 0; + +exit_clock: + release_dma_channels(this); +exit_dma_channels: + release_interrupt(this, resources->bch_low_interrupt, + resources->bch_high_interrupt); +exit_bch_interrupt: + release_register_block(this, resources->bch_regs); +exit_bch_regs: + release_register_block(this, resources->gpmi_regs); +exit_gpmi_regs: + return error; +} + +static void release_resources(struct gpmi_nfc_data *this) +{ + struct resources *resources = &this->resources; + + release_clock(this, resources->clock); + release_register_block(this, resources->gpmi_regs); + release_register_block(this, resources->bch_regs); + release_interrupt(this, resources->bch_low_interrupt, + resources->bch_low_interrupt); + release_dma_channels(this); +} + +static void exit_nfc_hal(struct gpmi_nfc_data *this) +{ + if (this->nfc) + this->nfc->exit(this); +} + +static int set_up_nfc_hal(struct gpmi_nfc_data *this) +{ + struct nfc_hal *nfc = NULL; + int error; + + /* + * This structure contains the "safe" GPMI timing that should succeed + * with any NAND Flash device + * (although, with less-than-optimal performance). + */ + static struct nand_timing safe_timing = { + .data_setup_in_ns = 80, + .data_hold_in_ns = 60, + .address_setup_in_ns = 25, + .gpmi_sample_delay_in_ns = 6, + .tREA_in_ns = -1, + .tRLOH_in_ns = -1, + .tRHOH_in_ns = -1, + }; + + if (GPMI_IS_MX23(this) || GPMI_IS_MX28(this)) + nfc = &gpmi_nfc_hal_imx23_imx28; + + BUG_ON(nfc == NULL); + this->nfc = nfc; + + /* Initialize the NFC HAL. */ + error = nfc->init(this); + if (error) + return error; + + /* Set up safe timing. */ + nfc->set_timing(this, &safe_timing); + return 0; +} + +/* Creates/Removes sysfs files for this device.*/ +static void manage_sysfs_files(struct gpmi_nfc_data *this, int create) +{ + struct device *dev = this->dev; + struct device_attribute **attr; + unsigned int i; + int error; + + for (i = 0, attr = device_attributes; + i < ARRAY_SIZE(device_attributes); i++, attr++) { + + if (create) { + error = device_create_file(dev, *attr); + if (error) { + while (--attr >= device_attributes) + device_remove_file(dev, *attr); + return; + } + } else { + device_remove_file(dev, *attr); + } + } +} + +static int read_page_prepare(struct gpmi_nfc_data *this, + void *destination, unsigned length, + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, + void **use_virt, dma_addr_t *use_phys) +{ + struct device *dev = this->dev; + dma_addr_t destination_phys = ~0; + + if (virt_addr_valid(destination)) + destination_phys = dma_map_single(dev, (void *)destination, + length, DMA_FROM_DEVICE); + + if (dma_mapping_error(dev, destination_phys)) { + if (alt_size < length) { + log("Alternate buffer is too small for incoming I/O."); + return -ENOMEM; + } + + *use_virt = alt_virt; + *use_phys = alt_phys; + } else { + *use_virt = destination; + *use_phys = destination_phys; + } + return 0; +} + +static void read_page_end(struct gpmi_nfc_data *this, + void *destination, unsigned length, + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, + void *used_virt, dma_addr_t used_phys) +{ + struct device *dev = this->dev; + + if (used_virt == destination) + dma_unmap_single(dev, used_phys, length, DMA_FROM_DEVICE); + else + memcpy(destination, alt_virt, length); +} + +static int send_page_prepare(struct gpmi_nfc_data *this, + const void *source, unsigned length, + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, + const void **use_virt, dma_addr_t *use_phys) +{ + dma_addr_t source_phys = ~0; + struct device *dev = this->dev; + + if (virt_addr_valid(source)) + source_phys = dma_map_single(dev, + (void *)source, length, DMA_TO_DEVICE); + + if (dma_mapping_error(dev, source_phys)) { + if (alt_size < length) { + log("Alternate buffer is too small for outgoing I/O"); + return -ENOMEM; + } + + /* + * Copy the contents of the source buffer into the alternate + * buffer and set up the return values accordingly. + */ + memcpy(alt_virt, source, length); + + *use_virt = alt_virt; + *use_phys = alt_phys; + } else { + *use_virt = source; + *use_phys = source_phys; + } + return 0; +} + +static void send_page_end(struct gpmi_nfc_data *this, + const void *source, unsigned length, + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, + const void *used_virt, dma_addr_t used_phys) +{ + struct device *dev = this->dev; + if (used_virt == source) + dma_unmap_single(dev, used_phys, length, DMA_TO_DEVICE); +} + +static void mil_free_dma_buffer(struct gpmi_nfc_data *this) +{ + struct device *dev = this->dev; + struct mil *mil = &this->mil; + + if (mil->page_buffer_virt && virt_addr_valid(mil->page_buffer_virt)) + dma_free_coherent(dev, mil->page_buffer_size, + mil->page_buffer_virt, + mil->page_buffer_phys); + kfree(mil->cmd_buffer); + kfree(mil->data_buffer_dma); + + mil->cmd_buffer = NULL; + mil->data_buffer_dma = NULL; + mil->page_buffer_virt = NULL; + mil->page_buffer_size = 0; + mil->page_buffer_phys = ~0; +} + +/* Allocate the DMA buffers */ +static int mil_alloc_dma_buffer(struct gpmi_nfc_data *this) +{ + struct nfc_geometry *geo = &this->nfc_geometry; + struct device *dev = this->dev; + struct mil *mil = &this->mil; + + /* [1] Allocate a command buffer. PAGE_SIZE is enough. */ + mil->cmd_buffer = kzalloc(PAGE_SIZE, GFP_DMA); + if (mil->cmd_buffer == NULL) + goto error_alloc; + + /* [2] Allocate a read/write data buffer. PAGE_SIZE is enough. */ + mil->data_buffer_dma = kzalloc(PAGE_SIZE, GFP_DMA); + if (mil->data_buffer_dma == NULL) + goto error_alloc; + + /* + * [3] Allocate the page buffer. + * + * Both the payload buffer and the auxiliary buffer must appear on + * 32-bit boundaries. We presume the size of the payload buffer is a + * power of two and is much larger than four, which guarantees the + * auxiliary buffer will appear on a 32-bit boundary. + */ + mil->page_buffer_size = geo->payload_size_in_bytes + + geo->auxiliary_size_in_bytes; + + mil->page_buffer_virt = dma_alloc_coherent(dev, mil->page_buffer_size, + &mil->page_buffer_phys, GFP_DMA); + if (!mil->page_buffer_virt) + goto error_alloc; + + + /* Slice up the page buffer. */ + mil->payload_virt = mil->page_buffer_virt; + mil->payload_phys = mil->page_buffer_phys; + mil->auxiliary_virt = ((char *) mil->payload_virt) + + geo->payload_size_in_bytes; + mil->auxiliary_phys = mil->payload_phys + + geo->payload_size_in_bytes; + return 0; + +error_alloc: + mil_free_dma_buffer(this); + log("allocate DMA buffer error!!"); + return -ENOMEM; +} + +static void mil_cmd_ctrl(struct mtd_info *mtd, int data, unsigned int ctrl) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nfc_data *this = nand->priv; + struct mil *mil = &this->mil; + struct nfc_hal *nfc = this->nfc; + int error; + + /* + * Every operation begins with a command byte and a series of zero or + * more address bytes. These are distinguished by either the Address + * Latch Enable (ALE) or Command Latch Enable (CLE) signals being + * asserted. When MTD is ready to execute the command, it will deassert + * both latch enables. + * + * Rather than run a separate DMA operation for every single byte, we + * queue them up and run a single DMA operation for the entire series + * of command and data bytes. NAND_CMD_NONE means the END of the queue. + */ + if ((ctrl & (NAND_ALE | NAND_CLE))) { + if (data != NAND_CMD_NONE) + mil->cmd_buffer[mil->command_length++] = data; + return; + } + + if (!mil->command_length) + return; + + error = nfc->send_command(this); + if (error) + log("Chip: %u, Error %d", mil->current_chip, error); + + mil->command_length = 0; +} + +static int mil_dev_ready(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nfc_data *this = nand->priv; + struct nfc_hal *nfc = this->nfc; + struct mil *mil = &this->mil; + + return nfc->is_ready(this, mil->current_chip); +} + +static void mil_select_chip(struct mtd_info *mtd, int chip) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nfc_data *this = nand->priv; + struct nfc_hal *nfc = this->nfc; + struct mil *mil = &this->mil; + + if ((mil->current_chip < 0) && (chip >= 0)) + nfc->begin(this); + else if ((mil->current_chip >= 0) && (chip < 0)) + nfc->end(this); + else + ; + + mil->current_chip = chip; +} + +static void mil_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nfc_data *this = nand->priv; + struct nfc_hal *nfc = this->nfc; + struct mil *mil = &this->mil; + + logio(GPMI_DEBUG_READ); + /* save the info in mil{} for future */ + mil->upper_buf = buf; + mil->upper_len = len; + + nfc->read_data(this); +} + +static void mil_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nfc_data *this = nand->priv; + struct nfc_hal *nfc = this->nfc; + struct mil *mil = &this->mil; + + logio(GPMI_DEBUG_WRITE); + /* save the info in mil{} for future */ + mil->upper_buf = (uint8_t *)buf; + mil->upper_len = len; + + nfc->send_data(this); +} + +static uint8_t mil_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nfc_data *this = nand->priv; + struct mil *mil = &this->mil; + uint8_t *buf = mil->data_buffer_dma; + + mil_read_buf(mtd, buf, 1); + return buf[0]; +} + +/** + * mil_handle_block_mark_swapping() - Handles block mark swapping. + * + * Note that, when this function is called, it doesn't know whether it's + * swapping the block mark, or swapping it *back* -- but it doesn't matter + * because the the operation is the same. + * + * @this: Per-device data. + * @payload: A pointer to the payload buffer. + * @auxiliary: A pointer to the auxiliary buffer. + */ +static void mil_handle_block_mark_swapping(struct gpmi_nfc_data *this, + void *payload, void *auxiliary) +{ + struct nfc_geometry *nfc_geo = &this->nfc_geometry; + unsigned char *p; + unsigned char *a; + unsigned int bit; + unsigned char mask; + unsigned char from_data; + unsigned char from_oob; + + /* Check if we're doing block mark swapping. */ + if (!this->swap_block_mark) + return; + + /* + * If control arrives here, we're swapping. Make some convenience + * variables. + */ + bit = nfc_geo->block_mark_bit_offset; + p = payload + nfc_geo->block_mark_byte_offset; + a = auxiliary; + + /* + * Get the byte from the data area that overlays the block mark. Since + * the ECC engine applies its own view to the bits in the page, the + * physical block mark won't (in general) appear on a byte boundary in + * the data. + */ + from_data = (p[0] >> bit) | (p[1] << (8 - bit)); + + /* Get the byte from the OOB. */ + from_oob = a[0]; + + /* Swap them. */ + a[0] = from_data; + + mask = (0x1 << bit) - 1; + p[0] = (p[0] & mask) | (from_oob << bit); + + mask = ~0 << bit; + p[1] = (p[1] & mask) | (from_oob >> (8 - bit)); +} + +static int mil_ecc_read_page(struct mtd_info *mtd, struct nand_chip *nand, + uint8_t *buf, int page) +{ + struct gpmi_nfc_data *this = nand->priv; + struct nfc_hal *nfc = this->nfc; + struct nfc_geometry *nfc_geo = &this->nfc_geometry; + struct mil *mil = &this->mil; + void *payload_virt; + dma_addr_t payload_phys; + void *auxiliary_virt; + dma_addr_t auxiliary_phys; + unsigned int i; + unsigned char *status; + unsigned int failed; + unsigned int corrected; + int error; + + logio(GPMI_DEBUG_ECC_READ); + error = read_page_prepare(this, buf, mtd->writesize, + mil->payload_virt, mil->payload_phys, + nfc_geo->payload_size_in_bytes, + &payload_virt, &payload_phys); + if (error) { + log("Inadequate DMA buffer"); + error = -ENOMEM; + return error; + } + auxiliary_virt = mil->auxiliary_virt; + auxiliary_phys = mil->auxiliary_phys; + + /* ask the NFC */ + error = nfc->read_page(this, payload_phys, auxiliary_phys); + if (error) { + log("Error in ECC-based read: %d", error); + goto exit_nfc; + } + + /* handle the block mark swapping */ + mil_handle_block_mark_swapping(this, payload_virt, auxiliary_virt); + + /* Loop over status bytes, accumulating ECC status. */ + failed = 0; + corrected = 0; + status = auxiliary_virt + nfc_geo->auxiliary_status_offset; + + for (i = 0; i < nfc_geo->ecc_chunk_count; i++, status++) { + if ((*status == STATUS_GOOD) || (*status == STATUS_ERASED)) + continue; + + if (*status == STATUS_UNCORRECTABLE) { + failed++; + continue; + } + corrected += *status; + } + + /* + * Propagate ECC status to the owning MTD only when failed or + * corrected times nearly reaches our ECC correction threshold. + */ + if (failed || corrected >= (nfc_geo->ecc_strength - 1)) { + mtd->ecc_stats.failed += failed; + mtd->ecc_stats.corrected += corrected; + } + + /* + * It's time to deliver the OOB bytes. See mil_ecc_read_oob() for + * details about our policy for delivering the OOB. + * + * We fill the caller's buffer with set bits, and then copy the block + * mark to th caller's buffer. Note that, if block mark swapping was + * necessary, it has already been done, so we can rely on the first + * byte of the auxiliary buffer to contain the block mark. + */ + memset(nand->oob_poi, ~0, mtd->oobsize); + nand->oob_poi[0] = ((uint8_t *) auxiliary_virt)[0]; + +exit_nfc: + read_page_end(this, buf, mtd->writesize, + mil->payload_virt, mil->payload_phys, + nfc_geo->payload_size_in_bytes, + payload_virt, payload_phys); + return error; +} + +static void mil_ecc_write_page(struct mtd_info *mtd, + struct nand_chip *nand, const uint8_t *buf) +{ + struct gpmi_nfc_data *this = nand->priv; + struct nfc_hal *nfc = this->nfc; + struct nfc_geometry *nfc_geo = &this->nfc_geometry; + struct mil *mil = &this->mil; + const void *payload_virt; + dma_addr_t payload_phys; + const void *auxiliary_virt; + dma_addr_t auxiliary_phys; + int error; + + logio(GPMI_DEBUG_ECC_WRITE); + if (this->swap_block_mark) { + /* + * If control arrives here, we're doing block mark swapping. + * Since we can't modify the caller's buffers, we must copy them + * into our own. + */ + memcpy(mil->payload_virt, buf, mtd->writesize); + payload_virt = mil->payload_virt; + payload_phys = mil->payload_phys; + + memcpy(mil->auxiliary_virt, nand->oob_poi, + nfc_geo->auxiliary_size_in_bytes); + auxiliary_virt = mil->auxiliary_virt; + auxiliary_phys = mil->auxiliary_phys; + + /* Handle block mark swapping. */ + mil_handle_block_mark_swapping(this, + (void *) payload_virt, (void *) auxiliary_virt); + } else { + /* + * If control arrives here, we're not doing block mark swapping, + * so we can to try and use the caller's buffers. + */ + error = send_page_prepare(this, + buf, mtd->writesize, + mil->payload_virt, mil->payload_phys, + nfc_geo->payload_size_in_bytes, + &payload_virt, &payload_phys); + if (error) { + log("Inadequate payload DMA buffer"); + return; + } + + error = send_page_prepare(this, + nand->oob_poi, mtd->oobsize, + mil->auxiliary_virt, mil->auxiliary_phys, + nfc_geo->auxiliary_size_in_bytes, + &auxiliary_virt, &auxiliary_phys); + if (error) { + log("Inadequate auxiliary DMA buffer"); + goto exit_auxiliary; + } + } + + /* Ask the NFC. */ + error = nfc->send_page(this, payload_phys, auxiliary_phys); + if (error) + log("Error in ECC-based write: %d", error); + + if (!this->swap_block_mark) { + send_page_end(this, nand->oob_poi, mtd->oobsize, + mil->auxiliary_virt, mil->auxiliary_phys, + nfc_geo->auxiliary_size_in_bytes, + auxiliary_virt, auxiliary_phys); +exit_auxiliary: + send_page_end(this, buf, mtd->writesize, + mil->payload_virt, mil->payload_phys, + nfc_geo->payload_size_in_bytes, + payload_virt, payload_phys); + } +} + +static int mil_hook_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + register struct nand_chip *chip = mtd->priv; + struct gpmi_nfc_data *this = chip->priv; + struct mil *mil = &this->mil; + int ret; + + mil->marking_a_bad_block = true; + ret = mil->hooked_block_markbad(mtd, ofs); + mil->marking_a_bad_block = false; + return ret; +} + +/** + * mil_ecc_read_oob() - MTD Interface ecc.read_oob(). + * + * There are several places in this driver where we have to handle the OOB and + * block marks. This is the function where things are the most complicated, so + * this is where we try to explain it all. All the other places refer back to + * here. + * + * These are the rules, in order of decreasing importance: + * + * 1) Nothing the caller does can be allowed to imperil the block mark, so all + * write operations take measures to protect it. + * + * 2) In read operations, the first byte of the OOB we return must reflect the + * true state of the block mark, no matter where that block mark appears in + * the physical page. + * + * 3) ECC-based read operations return an OOB full of set bits (since we never + * allow ECC-based writes to the OOB, it doesn't matter what ECC-based reads + * return). + * + * 4) "Raw" read operations return a direct view of the physical bytes in the + * page, using the conventional definition of which bytes are data and which + * are OOB. This gives the caller a way to see the actual, physical bytes + * in the page, without the distortions applied by our ECC engine. + * + * + * What we do for this specific read operation depends on two questions: + * + * 1) Are we doing a "raw" read, or an ECC-based read? + * + * 2) Are we using block mark swapping or transcription? + * + * There are four cases, illustrated by the following Karnaugh map: + * + * | Raw | ECC-based | + * -------------+-------------------------+-------------------------+ + * | Read the conventional | | + * | OOB at the end of the | | + * Swapping | page and return it. It | | + * | contains exactly what | | + * | we want. | Read the block mark and | + * -------------+-------------------------+ return it in a buffer | + * | Read the conventional | full of set bits. | + * | OOB at the end of the | | + * | page and also the block | | + * Transcribing | mark in the metadata. | | + * | Copy the block mark | | + * | into the first byte of | | + * | the OOB. | | + * -------------+-------------------------+-------------------------+ + * + * Note that we break rule #4 in the Transcribing/Raw case because we're not + * giving an accurate view of the actual, physical bytes in the page (we're + * overwriting the block mark). That's OK because it's more important to follow + * rule #2. + * + * It turns out that knowing whether we want an "ECC-based" or "raw" read is not + * easy. When reading a page, for example, the NAND Flash MTD code calls our + * ecc.read_page or ecc.read_page_raw function. Thus, the fact that MTD wants an + * ECC-based or raw view of the page is implicit in which function it calls + * (there is a similar pair of ECC-based/raw functions for writing). + * + * Since MTD assumes the OOB is not covered by ECC, there is no pair of + * ECC-based/raw functions for reading or or writing the OOB. The fact that the + * caller wants an ECC-based or raw view of the page is not propagated down to + * this driver. + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + * @page: The page number to read. + * @sndcmd: Indicates this function should send a command to the chip before + * reading the out-of-band bytes. This is only false for small page + * chips that support auto-increment. + */ +static int mil_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *nand, + int page, int sndcmd) +{ + struct gpmi_nfc_data *this = nand->priv; + + /* clear the OOB buffer */ + memset(nand->oob_poi, ~0, mtd->oobsize); + + /* Read out the conventional OOB. */ + nand->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page); + nand->read_buf(mtd, nand->oob_poi, mtd->oobsize); + + /* + * Now, we want to make sure the block mark is correct. In the + * Swapping/Raw case, we already have it. Otherwise, we need to + * explicitly read it. + */ + if (!this->swap_block_mark) { + /* Read the block mark into the first byte of the OOB buffer. */ + nand->cmdfunc(mtd, NAND_CMD_READ0, 0, page); + nand->oob_poi[0] = nand->read_byte(mtd); + } + + /* + * Return true, indicating that the next call to this function must send + * a command. + */ + return true; +} + +static int mil_ecc_write_oob(struct mtd_info *mtd, + struct nand_chip *nand, int page) +{ + struct gpmi_nfc_data *this = nand->priv; + struct device *dev = this->dev; + struct mil *mil = &this->mil; + uint8_t *block_mark; + int block_mark_column; + int status; + int error = 0; + + /* Only marking a block bad is permitted to write the OOB. */ + if (!mil->marking_a_bad_block) { + dev_emerg(dev, "This driver doesn't support writing the OOB\n"); + WARN_ON(1); + error = -EIO; + goto exit; + } + + if (this->swap_block_mark) + block_mark_column = mtd->writesize; + else + block_mark_column = 0; + + /* Write the block mark. */ + block_mark = mil->data_buffer_dma; + block_mark[0] = 0; /* bad block marker */ + + nand->cmdfunc(mtd, NAND_CMD_SEQIN, block_mark_column, page); + nand->write_buf(mtd, block_mark, 1); + nand->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + status = nand->waitfunc(mtd, nand); + + /* Check if it worked. */ + if (status & NAND_STATUS_FAIL) + error = -EIO; +exit: + return error; +} + +/** + * mil_block_bad - Claims all blocks are good. + * + * In principle, this function is *only* called when the NAND Flash MTD system + * isn't allowed to keep an in-memory bad block table, so it is forced to ask + * the driver for bad block information. + * + * In fact, we permit the NAND Flash MTD system to have an in-memory BBT, so + * this function is *only* called when we take it away. + * + * We take away the in-memory BBT when the user sets the "ignorebad" parameter, + * which indicates that all blocks should be reported good. + * + * Thus, this function is only called when we want *all* blocks to look good, + * so it *always* return success. + * + * @mtd: Ignored. + * @ofs: Ignored. + * @getchip: Ignored. + */ +static int mil_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip) +{ + return 0; +} + +static int nand_boot_set_geometry(struct gpmi_nfc_data *this) +{ + struct boot_rom_geometry *geometry = &this->rom_geometry; + + /* + * Set the boot block stride size. + * + * In principle, we should be reading this from the OTP bits, since + * that's where the ROM is going to get it. In fact, we don't have any + * way to read the OTP bits, so we go with the default and hope for the + * best. + */ + geometry->stride_size_in_pages = 64; + + /* + * Set the search area stride exponent. + * + * In principle, we should be reading this from the OTP bits, since + * that's where the ROM is going to get it. In fact, we don't have any + * way to read the OTP bits, so we go with the default and hope for the + * best. + */ + geometry->search_area_stride_exponent = 2; + + if (gpmi_debug & GPMI_DEBUG_INIT) + log("stride size in page : %d, search areas : %d", + geometry->stride_size_in_pages, + geometry->search_area_stride_exponent); + return 0; +} + +static const char *fingerprint = "STMP"; +static int mx23_check_transcription_stamp(struct gpmi_nfc_data *this) +{ + struct boot_rom_geometry *rom_geo = &this->rom_geometry; + struct mil *mil = &this->mil; + struct mtd_info *mtd = &mil->mtd; + struct nand_chip *nand = &mil->nand; + unsigned int search_area_size_in_strides; + unsigned int stride; + unsigned int page; + loff_t byte; + uint8_t *buffer = nand->buffers->databuf; + int saved_chip_number; + int found_an_ncb_fingerprint = false; + + /* Compute the number of strides in a search area. */ + search_area_size_in_strides = 1 << rom_geo->search_area_stride_exponent; + + /* Select chip 0. */ + saved_chip_number = mil->current_chip; + nand->select_chip(mtd, 0); + + /* + * Loop through the first search area, looking for the NCB fingerprint. + */ + pr_info("Scanning for an NCB fingerprint...\n"); + + for (stride = 0; stride < search_area_size_in_strides; stride++) { + /* Compute the page and byte addresses. */ + page = stride * rom_geo->stride_size_in_pages; + byte = page * mtd->writesize; + + pr_info(" Looking for a fingerprint in page 0x%x\n", page); + + /* + * Read the NCB fingerprint. The fingerprint is four bytes long + * and starts in the 12th byte of the page. + */ + nand->cmdfunc(mtd, NAND_CMD_READ0, 12, page); + nand->read_buf(mtd, buffer, strlen(fingerprint)); + + /* Look for the fingerprint. */ + if (!memcmp(buffer, fingerprint, strlen(fingerprint))) { + found_an_ncb_fingerprint = true; + break; + } + + } + + /* Deselect chip 0. */ + nand->select_chip(mtd, saved_chip_number); + + if (found_an_ncb_fingerprint) + pr_info(" Found a fingerprint\n"); + else + pr_info(" No fingerprint found\n"); + return found_an_ncb_fingerprint; +} + +/* Writes a transcription stamp. */ +static int mx23_write_transcription_stamp(struct gpmi_nfc_data *this) +{ + struct device *dev = this->dev; + struct boot_rom_geometry *rom_geo = &this->rom_geometry; + struct mil *mil = &this->mil; + struct mtd_info *mtd = &mil->mtd; + struct nand_chip *nand = &mil->nand; + unsigned int block_size_in_pages; + unsigned int search_area_size_in_strides; + unsigned int search_area_size_in_pages; + unsigned int search_area_size_in_blocks; + unsigned int block; + unsigned int stride; + unsigned int page; + loff_t byte; + uint8_t *buffer = nand->buffers->databuf; + int saved_chip_number; + int status; + + /* Compute the search area geometry. */ + block_size_in_pages = mtd->erasesize / mtd->writesize; + search_area_size_in_strides = 1 << rom_geo->search_area_stride_exponent; + search_area_size_in_pages = search_area_size_in_strides * + rom_geo->stride_size_in_pages; + search_area_size_in_blocks = + (search_area_size_in_pages + (block_size_in_pages - 1)) / + block_size_in_pages; + + pr_info("-------------------------------------------\n"); + pr_info("Search Area Geometry\n"); + pr_info("-------------------------------------------\n"); + pr_info("Search Area Size in Blocks : %u", search_area_size_in_blocks); + pr_info("Search Area Size in Strides: %u", search_area_size_in_strides); + pr_info("Search Area Size in Pages : %u", search_area_size_in_pages); + + /* Select chip 0. */ + saved_chip_number = mil->current_chip; + nand->select_chip(mtd, 0); + + /* Loop over blocks in the first search area, erasing them. */ + pr_info("Erasing the search area...\n"); + + for (block = 0; block < search_area_size_in_blocks; block++) { + /* Compute the page address. */ + page = block * block_size_in_pages; + + /* Erase this block. */ + pr_info(" Erasing block 0x%x\n", block); + nand->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page); + nand->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1); + + /* Wait for the erase to finish. */ + status = nand->waitfunc(mtd, nand); + if (status & NAND_STATUS_FAIL) + dev_err(dev, "[%s] Erase failed.\n", __func__); + } + + /* Write the NCB fingerprint into the page buffer. */ + memset(buffer, ~0, mtd->writesize); + memset(nand->oob_poi, ~0, mtd->oobsize); + memcpy(buffer + 12, fingerprint, strlen(fingerprint)); + + /* Loop through the first search area, writing NCB fingerprints. */ + pr_info("Writing NCB fingerprints...\n"); + for (stride = 0; stride < search_area_size_in_strides; stride++) { + /* Compute the page and byte addresses. */ + page = stride * rom_geo->stride_size_in_pages; + byte = page * mtd->writesize; + + /* Write the first page of the current stride. */ + pr_info(" Writing an NCB fingerprint in page 0x%x\n", page); + nand->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); + nand->ecc.write_page_raw(mtd, nand, buffer); + nand->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + /* Wait for the write to finish. */ + status = nand->waitfunc(mtd, nand); + if (status & NAND_STATUS_FAIL) + dev_err(dev, "[%s] Write failed.\n", __func__); + } + + /* Deselect chip 0. */ + nand->select_chip(mtd, saved_chip_number); + return 0; +} + +int mx23_boot_init(struct gpmi_nfc_data *this) +{ + struct device *dev = this->dev; + struct mil *mil = &this->mil; + struct nand_chip *nand = &mil->nand; + struct mtd_info *mtd = &mil->mtd; + unsigned int block_count; + unsigned int block; + int chip; + int page; + loff_t byte; + uint8_t block_mark; + int error = 0; + + /* + * If control arrives here, we can't use block mark swapping, which + * means we're forced to use transcription. First, scan for the + * transcription stamp. If we find it, then we don't have to do + * anything -- the block marks are already transcribed. + */ + if (mx23_check_transcription_stamp(this)) + return 0; + + /* + * If control arrives here, we couldn't find a transcription stamp, so + * so we presume the block marks are in the conventional location. + */ + pr_info("Transcribing bad block marks...\n"); + + /* Compute the number of blocks in the entire medium. */ + block_count = nand->chipsize >> nand->phys_erase_shift; + + /* + * Loop over all the blocks in the medium, transcribing block marks as + * we go. + */ + for (block = 0; block < block_count; block++) { + /* + * Compute the chip, page and byte addresses for this block's + * conventional mark. + */ + chip = block >> (nand->chip_shift - nand->phys_erase_shift); + page = block << (nand->phys_erase_shift - nand->page_shift); + byte = block << nand->phys_erase_shift; + + /* Select the chip. */ + nand->select_chip(mtd, chip); + + /* Send the command to read the conventional block mark. */ + nand->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page); + + /* Read the conventional block mark. */ + block_mark = nand->read_byte(mtd); + + /* + * Check if the block is marked bad. If so, we need to mark it + * again, but this time the result will be a mark in the + * location where we transcribe block marks. + * + * Notice that we have to explicitly set the marking_a_bad_block + * member before we call through the block_markbad function + * pointer in the owning struct nand_chip. If we could call + * though the block_markbad function pointer in the owning + * struct mtd_info, which we have hooked, then this would be + * taken care of for us. Unfortunately, we can't because that + * higher-level code path will do things like consulting the + * in-memory bad block table -- which doesn't even exist yet! + * So, we have to call at a lower level and handle some details + * ourselves. + */ + if (block_mark != 0xff) { + pr_info("Transcribing mark in block %u\n", block); + mil->marking_a_bad_block = true; + error = nand->block_markbad(mtd, byte); + mil->marking_a_bad_block = false; + if (error) + dev_err(dev, "Failed to mark block bad with " + "error %d\n", error); + } + + /* Deselect the chip. */ + nand->select_chip(mtd, -1); + } + + /* Write the stamp that indicates we've transcribed the block marks. */ + mx23_write_transcription_stamp(this); + return 0; +} + +static int nand_boot_init(struct gpmi_nfc_data *this) +{ + nand_boot_set_geometry(this); + + /* This is ROM arch-specific initilization before the BBT scanning. */ + if (GPMI_IS_MX23(this)) + return mx23_boot_init(this); + return 0; +} + +static void show_nfc_geometry(struct nfc_geometry *geo) +{ + pr_info("---------------------------------------\n"); + pr_info(" NFC Geometry (used by BCH)\n"); + pr_info("---------------------------------------\n"); + pr_info("ECC Algorithm : %s\n", geo->ecc_algorithm); + pr_info("ECC Strength : %u\n", geo->ecc_strength); + pr_info("Page Size in Bytes : %u\n", geo->page_size_in_bytes); + pr_info("Metadata Size in Bytes : %u\n", geo->metadata_size_in_bytes); + pr_info("ECC Chunk Size in Bytes: %u\n", geo->ecc_chunk_size_in_bytes); + pr_info("ECC Chunk Count : %u\n", geo->ecc_chunk_count); + pr_info("Payload Size in Bytes : %u\n", geo->payload_size_in_bytes); + pr_info("Auxiliary Size in Bytes: %u\n", geo->auxiliary_size_in_bytes); + pr_info("Auxiliary Status Offset: %u\n", geo->auxiliary_status_offset); + pr_info("Block Mark Byte Offset : %u\n", geo->block_mark_byte_offset); + pr_info("Block Mark Bit Offset : %u\n", geo->block_mark_bit_offset); +} + +static int mil_set_geometry(struct gpmi_nfc_data *this) +{ + struct nfc_hal *nfc = this->nfc; + struct nfc_geometry *geo = &this->nfc_geometry; + int error; + + /* Free the temporary DMA memory for read ID case */ + mil_free_dma_buffer(this); + + /* Set up the NFC geometry which is used by BCH. */ + error = nfc->set_geometry(this); + if (error != 0) { + log("NFC set geometry error : %d", error); + return error; + } + if (gpmi_debug & GPMI_DEBUG_INIT) + show_nfc_geometry(geo); + + /* Alloc the new DMA buffers according to the pagesize and oobsize */ + return mil_alloc_dma_buffer(this); +} + +static int mil_pre_bbt_scan(struct gpmi_nfc_data *this) +{ + struct nand_chip *nand = &this->mil.nand; + struct mtd_info *mtd = &this->mil.mtd; + struct nand_ecclayout *layout = nand->ecc.layout; + struct nfc_hal *nfc = this->nfc; + int error; + + /* fix the ECC layout before the scanning */ + layout->eccbytes = 0; + layout->oobavail = mtd->oobsize; + layout->oobfree[0].offset = 0; + layout->oobfree[0].length = mtd->oobsize; + + mtd->oobavail = nand->ecc.layout->oobavail; + + /* Set up swap block-mark, must be set before the mil_set_geometry() */ + if (GPMI_IS_MX23(this)) + this->swap_block_mark = false; + else + this->swap_block_mark = true; + + /* Set up the medium geometry */ + error = mil_set_geometry(this); + if (error) + return error; + + /* extra init */ + if (nfc->extra_init) { + error = nfc->extra_init(this); + if (error != 0) + return error; + } + + /* NAND boot init, depends on the mil_set_geometry(). */ + return nand_boot_init(this); +} + +static int mil_scan_bbt(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nfc_data *this = nand->priv; + int error; + + /* Prepare for the BBT scan. */ + error = mil_pre_bbt_scan(this); + if (error) + return error; + + /* use the default BBT implementation */ + return nand_default_bbt(mtd); +} + +static const char *cmd_parse = "cmdlinepart"; +static int mil_partitions_init(struct gpmi_nfc_data *this) +{ + struct gpmi_nfc_platform_data *pdata = this->pdata; + struct mil *mil = &this->mil; + struct mtd_info *mtd = &mil->mtd; + + /* use the command line for simple partitions layout */ + mil->partition_count = parse_mtd_partitions(mtd, &cmd_parse, + &mil->partitions, 0); + if (mil->partition_count) + return add_mtd_partitions(mtd, mil->partitions, + mil->partition_count); + + /* The complicated partitions layout uses this. */ + if (pdata->partitions && pdata->partition_count > 0) + return add_mtd_partitions(mtd, pdata->partitions, + pdata->partition_count); + return 0; +} + +static void mil_partitions_exit(struct gpmi_nfc_data *this) +{ + struct mil *mil = &this->mil; + + if (mil->partition_count) { + struct mtd_info *mtd = &mil->mtd; + + del_mtd_partitions(mtd); + kfree(mil->partitions); + mil->partition_count = 0; + } +} + +/* Initializes the MTD Interface Layer */ +int gpmi_nfc_mil_init(struct gpmi_nfc_data *this) +{ + struct gpmi_nfc_platform_data *pdata = this->pdata; + struct mil *mil = &this->mil; + struct mtd_info *mtd = &mil->mtd; + struct nand_chip *nand = &mil->nand; + int error; + + /* Initialize MIL data */ + mil->current_chip = -1; + mil->command_length = 0; + mil->page_buffer_virt = NULL; + mil->page_buffer_phys = ~0; + mil->page_buffer_size = 0; + + /* Initialize the MTD data structures */ + mtd->priv = nand; + mtd->name = "gpmi-nfc-main"; + mtd->owner = THIS_MODULE; + nand->priv = this; + + /* Controls */ + nand->select_chip = mil_select_chip; + nand->cmd_ctrl = mil_cmd_ctrl; + nand->dev_ready = mil_dev_ready; + + /* + * Low-level I/O : + * We don't support a 16-bit NAND Flash bus, + * so we don't implement read_word. + */ + nand->read_byte = mil_read_byte; + nand->read_buf = mil_read_buf; + nand->write_buf = mil_write_buf; + + /* ECC-aware I/O */ + nand->ecc.read_page = mil_ecc_read_page; + nand->ecc.write_page = mil_ecc_write_page; + + /* High-level I/O */ + nand->ecc.read_oob = mil_ecc_read_oob; + nand->ecc.write_oob = mil_ecc_write_oob; + + /* Bad Block Management */ + nand->block_bad = mil_block_bad; + nand->scan_bbt = mil_scan_bbt; + nand->badblock_pattern = &gpmi_bbt_descr; + + /* Disallow partial page writes */ + nand->options |= NAND_NO_SUBPAGE_WRITE; + + /* + * Tell the NAND Flash MTD system that we'll be handling ECC with our + * own hardware. It turns out that we still have to fill in the ECC size + * because the MTD code will divide by it -- even though it doesn't + * actually care. + */ + nand->ecc.mode = NAND_ECC_HW; + nand->ecc.size = 1; + + /* use our layout */ + nand->ecc.layout = &mil->oob_layout; + + /* Allocate a temporary DMA buffer for reading ID in the nand_scan() */ + this->nfc_geometry.payload_size_in_bytes = 1024; + this->nfc_geometry.auxiliary_size_in_bytes = 128; + error = mil_alloc_dma_buffer(this); + if (error) + goto exit_dma_allocation; + + pr_info("Scanning for NAND Flash chips...\n"); + error = nand_scan(mtd, pdata->max_chip_count); + if (error) { + log("Chip scan failed"); + goto exit_nand_scan; + } + + /* Take over the management of the OOB */ + mil->hooked_block_markbad = mtd->block_markbad; + mtd->block_markbad = mil_hook_block_markbad; + + /* Construct partitions as necessary. */ + error = mil_partitions_init(this); + if (error) + goto exit_partitions; + return 0; + +exit_partitions: + nand_release(&mil->mtd); +exit_nand_scan: + mil_free_dma_buffer(this); +exit_dma_allocation: + return error; +} + +void gpmi_nfc_mil_exit(struct gpmi_nfc_data *this) +{ + struct mil *mil = &this->mil; + + mil_partitions_exit(this); + nand_release(&mil->mtd); + mil_free_dma_buffer(this); +} +static int gpmi_nfc_probe(struct platform_device *pdev) +{ + struct gpmi_nfc_platform_data *pdata = pdev->dev.platform_data; + struct gpmi_nfc_data *this; + int error; + + this = kzalloc(sizeof(*this), GFP_KERNEL); + if (!this) { + log("Failed to allocate per-device memory\n"); + return -ENOMEM; + } + + /* Set up our data structures. */ + platform_set_drvdata(pdev, this); + this->pdev = pdev; + this->dev = &pdev->dev; + this->pdata = pdata; + + /* Acquire the resources we need. */ + error = acquire_resources(this); + if (error) + goto exit_acquire_resources; + + /* Set up the NFC. */ + error = set_up_nfc_hal(this); + if (error) + goto exit_nfc_init; + + /* Initialize the MTD Interface Layer. */ + error = gpmi_nfc_mil_init(this); + if (error) + goto exit_mil_init; + + manage_sysfs_files(this, true); + return 0; + +exit_mil_init: + exit_nfc_hal(this); +exit_nfc_init: + release_resources(this); +exit_acquire_resources: + platform_set_drvdata(pdev, NULL); + kfree(this); + return error; +} + +static int __exit gpmi_nfc_remove(struct platform_device *pdev) +{ + struct gpmi_nfc_data *this = platform_get_drvdata(pdev); + + manage_sysfs_files(this, false); + gpmi_nfc_mil_exit(this); + exit_nfc_hal(this); + release_resources(this); + platform_set_drvdata(pdev, NULL); + kfree(this); + return 0; +} + +#ifdef CONFIG_PM +static int gpmi_nfc_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} +static int gpmi_nfc_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define gpmi_nfc_suspend NULL +#define gpmi_nfc_resume NULL +#endif + +static const struct platform_device_id gpmi_ids[] = { + { + .name = "imx23-gpmi-nfc", + .driver_data = IS_MX23, + }, { + .name = "imx28-gpmi-nfc", + .driver_data = IS_MX28, + }, {}, +}; + +/* This structure represents this driver to the platform management system. */ +static struct platform_driver gpmi_nfc_driver = { + .driver = { + .name = "gpmi-nfc", + }, + .probe = gpmi_nfc_probe, + .remove = __exit_p(gpmi_nfc_remove), + .suspend = gpmi_nfc_suspend, + .resume = gpmi_nfc_resume, + .id_table = gpmi_ids, +}; + +static int __init gpmi_nfc_init(void) +{ + int err; + + err = platform_driver_register(&gpmi_nfc_driver); + if (err == 0) + printk(KERN_INFO "GPMI NFC driver registered. (IMX)\n"); + else + pr_err("i.MX GPMI NFC driver registration failed\n"); + return err; +} + +static void __exit gpmi_nfc_exit(void) +{ + platform_driver_unregister(&gpmi_nfc_driver); +} + +static int __init gpmi_debug_setup(char *__unused) +{ + gpmi_debug = GPMI_DEBUG_INIT; + return 1; +} +__setup("gpmi_debug_init", gpmi_debug_setup); + +module_init(gpmi_nfc_init); +module_exit(gpmi_nfc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX GPMI NAND Flash Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h new file mode 100644 index 0000000..d0f363e --- /dev/null +++ b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h @@ -0,0 +1,488 @@ +/* + * Freescale GPMI NFC NAND Flash Driver + * + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. + * Copyright (C) 2008 Embedded Alley Solutions, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ +#ifndef __DRIVERS_MTD_NAND_GPMI_NFC_H +#define __DRIVERS_MTD_NAND_GPMI_NFC_H + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/concat.h> +#include <linux/dmaengine.h> +#include <asm/sizes.h> + +#include <mach/mxs.h> +#include <mach/common.h> +#include <mach/dma.h> +#include <mach/gpmi-nfc.h> +#include <mach/system.h> +#include <mach/clock.h> + +/** + * struct resources - The collection of resources the driver needs. + * + * @gpmi_regs: A pointer to the GPMI registers. + * @bch_regs: A pointer to the BCH registers. + * @bch_interrupt: The BCH interrupt number. + * @dma_low_channel: The low DMA channel. + * @dma_high_channel: The high DMA channel. + * @clock: A pointer to the struct clk for the NFC's clock. + */ +struct resources { + void *gpmi_regs; + void *bch_regs; + unsigned int bch_low_interrupt; + unsigned int bch_high_interrupt; + unsigned int dma_low_channel; + unsigned int dma_high_channel; + struct clk *clock; +}; + +/** + * struct mil - State for the MTD Interface Layer. + * + * @nand: The NAND Flash MTD data structure that represents + * the NAND Flash medium. + * @mtd: The MTD data structure that represents the NAND + * Flash medium. + * @oob_layout: A structure that describes how bytes are laid out + * in the OOB. + * @partitions: A pointer to a set of partitions. + * @partition_count: The number of partitions. + * @current_chip: The chip currently selected by the NAND Fash MTD + * code. A negative value indicates that no chip is + * selected. + * @command_length: The length of the command that appears in the + * command buffer (see cmd_virt, below). + * @ignore_bad_block_marks: Indicates we are ignoring bad block marks. + * @saved_bbt: A saved pointer to the in-memory NAND Flash MTD bad + * block table. See show_device_ignorebad() for more + * details. + * @marking_a_bad_block: Indicates the caller is marking a bad block. See + * mil_ecc_write_oob() for details. + * @hooked_block_markbad: A pointer to the block_markbad() function we + * we "hooked." See mil_ecc_write_oob() for details. + * @upper_buf: The buffer passed from upper layer. + * @upper_len: The buffer len passed from upper layer. + * @direct_dma_map_ok: Is the direct DMA map is good for the upper_buf? + * @cmd_sgl/cmd_buffer: For NAND command. + * @data_sgl/data_buffer_dma:For NAND DATA ops. + * @page_buffer_virt: A pointer to a DMA-coherent buffer we use for + * reading and writing pages. This buffer includes + * space for both the payload data and the auxiliary + * data (including status bytes, but not syndrome + * bytes). + * @page_buffer_phys: The physical address for the page_buffer_virt + * buffer. + * @page_buffer_size: The size of the page buffer. + * @payload_virt: A pointer to a location in the page buffer used + * for payload bytes. The size of this buffer is + * determined by struct nfc_geometry. + * @payload_phys: The physical address for payload_virt. + * @auxiliary_virt: A pointer to a location in the page buffer used + * for auxiliary bytes. The size of this buffer is + * determined by struct nfc_geometry. + * @auxiliary_phys: The physical address for auxiliary_virt. + */ +struct mil { + /* MTD Data Structures */ + struct nand_chip nand; + struct mtd_info mtd; + struct nand_ecclayout oob_layout; + + /* Partitions*/ + struct mtd_partition *partitions; + unsigned int partition_count; + + /* General-use Variables */ + int current_chip; + unsigned int command_length; + int ignore_bad_block_marks; + void *saved_bbt; + + /* MTD Function Pointer Hooks */ + int marking_a_bad_block; + int (*hooked_block_markbad)(struct mtd_info *mtd, + loff_t ofs); + + /* from upper layer */ + uint8_t *upper_buf; + int upper_len; + + /* DMA */ + bool direct_dma_map_ok; + + struct scatterlist cmd_sgl; + char *cmd_buffer; + + struct scatterlist data_sgl; + char *data_buffer_dma; + + void *page_buffer_virt; + dma_addr_t page_buffer_phys; + unsigned int page_buffer_size; + + void *payload_virt; + dma_addr_t payload_phys; + + void *auxiliary_virt; + dma_addr_t auxiliary_phys; +}; + +/** + * struct nfc_geometry - NFC geometry description. + * + * This structure describes the NFC's view of the medium geometry. + * + * @ecc_algorithm: The human-readable name of the ECC algorithm + * (e.g., "Reed-Solomon" or "BCH"). + * @ecc_strength: A number that describes the strength of the ECC + * algorithm. + * @page_size_in_bytes: The size, in bytes, of a physical page, including + * both data and OOB. + * @metadata_size_in_bytes: The size, in bytes, of the metadata. + * @ecc_chunk_size_in_bytes: The size, in bytes, of a single ECC chunk. Note + * the first chunk in the page includes both data and + * metadata, so it's a bit larger than this value. + * @ecc_chunk_count: The number of ECC chunks in the page, + * @payload_size_in_bytes: The size, in bytes, of the payload buffer. + * @auxiliary_size_in_bytes: The size, in bytes, of the auxiliary buffer. + * @auxiliary_status_offset: The offset into the auxiliary buffer at which + * the ECC status appears. + * @block_mark_byte_offset: The byte offset in the ECC-based page view at + * which the underlying physical block mark appears. + * @block_mark_bit_offset: The bit offset into the ECC-based page view at + * which the underlying physical block mark appears. + */ +struct nfc_geometry { + char *ecc_algorithm; + unsigned int ecc_strength; + unsigned int page_size_in_bytes; + unsigned int metadata_size_in_bytes; + unsigned int ecc_chunk_size_in_bytes; + unsigned int ecc_chunk_count; + unsigned int payload_size_in_bytes; + unsigned int auxiliary_size_in_bytes; + unsigned int auxiliary_status_offset; + unsigned int block_mark_byte_offset; + unsigned int block_mark_bit_offset; +}; + +/** + * struct boot_rom_geometry - Boot ROM geometry description. + * + * @stride_size_in_pages: The size of a boot block stride, in pages. + * @search_area_stride_exponent: The logarithm to base 2 of the size of a + * search area in boot block strides. + */ +struct boot_rom_geometry { + unsigned int stride_size_in_pages; + unsigned int search_area_stride_exponent; +}; + +/* DMA operations types */ +enum dma_ops_type { + DMA_FOR_COMMAND = 1, + DMA_FOR_READ_DATA, + DMA_FOR_WRITE_DATA, + DMA_FOR_READ_ECC_PAGE, + DMA_FOR_WRITE_ECC_PAGE +}; + +/** + * This structure contains the fundamental timing attributes for NAND. + * + * @data_setup_in_ns: The data setup time, in nanoseconds. Usually the + * maximum of tDS and tWP. A negative value + * indicates this characteristic isn't known. + * @data_hold_in_ns: The data hold time, in nanoseconds. Usually the + * maximum of tDH, tWH and tREH. A negative value + * indicates this characteristic isn't known. + * @address_setup_in_ns: The address setup time, in nanoseconds. Usually + * the maximum of tCLS, tCS and tALS. A negative + * value indicates this characteristic isn't known. + * @gpmi_sample_delay_in_ns: A GPMI-specific timing parameter. A negative value + * indicates this characteristic isn't known. + * @tREA_in_ns: tREA, in nanoseconds, from the data sheet. A + * negative value indicates this characteristic isn't + * known. + * @tRLOH_in_ns: tRLOH, in nanoseconds, from the data sheet. A + * negative value indicates this characteristic isn't + * known. + * @tRHOH_in_ns: tRHOH, in nanoseconds, from the data sheet. A + * negative value indicates this characteristic isn't + * known. + */ +struct nand_timing { + int8_t data_setup_in_ns; + int8_t data_hold_in_ns; + int8_t address_setup_in_ns; + int8_t gpmi_sample_delay_in_ns; + int8_t tREA_in_ns; + int8_t tRLOH_in_ns; + int8_t tRHOH_in_ns; +}; + +/** + * struct gpmi_nfc_data - i.MX NFC per-device data. + * + * @dev: A pointer to the owning struct device. + * @pdev: A pointer to the owning struct platform_device. + * @pdata: A pointer to the device's platform data. + * @resources: Information about system resources used by this driver. + * @device_info: A structure that contains detailed information about + * the NAND Flash device. + * @nfc: A pointer to a structure that represents the underlying + * NFC hardware. + * @nfc_geometry: A description of the medium geometry as viewed by the + * NFC. + * @swap_block_mark: Does it support the swap-block-mark feature? + * Boot ROM. + * @rom_geometry: A description of the medium geometry as viewed by the + * Boot ROM. + * @mil: A collection of information used by the MTD Interface + * Layer. + */ +struct gpmi_nfc_data { + /* System Interface */ + struct device *dev; + struct platform_device *pdev; + struct gpmi_nfc_platform_data *pdata; + + /* Resources */ + struct resources resources; + + /* Flash Hardware */ + struct nand_timing timing; + + /* NFC HAL */ + struct nfc_hal *nfc; + struct nfc_geometry nfc_geometry; + + /* NAND Boot issue */ + bool swap_block_mark; + struct boot_rom_geometry rom_geometry; + + /* MTD Interface Layer */ + struct mil mil; + + /* DMA channels */ +#define DMA_CHANS 8 + struct dma_chan *dma_chans[DMA_CHANS]; + struct mxs_dma_data dma_data; + enum dma_ops_type dma_type; + + /* private */ + void *private; +}; + +/** + * struct gpmi_nfc_hardware_timing - GPMI NFC hardware timing parameters. + * + * This structure contains timing information expressed in a form directly + * usable by the GPMI NFC hardware. + * + * @data_setup_in_cycles: The data setup time, in cycles. + * @data_hold_in_cycles: The data hold time, in cycles. + * @address_setup_in_cycles: The address setup time, in cycles. + * @use_half_periods: Indicates the clock is running slowly, so the + * NFC DLL should use half-periods. + * @sample_delay_factor: The sample delay factor. + */ +struct gpmi_nfc_hardware_timing { + uint8_t data_setup_in_cycles; + uint8_t data_hold_in_cycles; + uint8_t address_setup_in_cycles; + bool use_half_periods; + uint8_t sample_delay_factor; +}; + +/** + * struct nfc_hal - GPMI NFC HAL + * + * @description: description. + * @max_chip_count: The maximum number of chips the NFC can + * possibly support (this value is a constant for + * each NFC version). This may *not* be the actual + * number of chips connected. + * @max_data_setup_cycles: The maximum number of data setup cycles that + * can be expressed in the hardware. + * @internal_data_setup_in_ns: The time, in ns, that the NFC hardware requires + * for data read internal setup. In the Reference + * Manual, see the chapter "High-Speed NAND + * Timing" for more details. + * @max_sample_delay_factor: The maximum sample delay factor that can be + * expressed in the hardware. + * @max_dll_clock_period_in_ns: The maximum period of the GPMI clock that the + * sample delay DLL hardware can possibly work + * with (the DLL is unusable with longer periods). + * If the full-cycle period is greater than HALF + * this value, the DLL must be configured to use + * half-periods. + * @max_dll_delay_in_ns: The maximum amount of delay, in ns, that the + * DLL can implement. + * @dma_descriptors: A pool of DMA descriptors. + * @isr_dma_channel: The DMA channel with which the NFC HAL is + * working. We record this here so the ISR knows + * which DMA channel to acknowledge. + * @dma_done: The completion structure used for DMA + * interrupts. + * @bch_done: The completion structure used for BCH + * interrupts. + * @timing: The current timing configuration. + * @clock_frequency_in_hz: The clock frequency, in Hz, during the current + * I/O transaction. If no I/O transaction is in + * progress, this is the clock frequency during + * the most recent I/O transaction. + * @hardware_timing: The hardware timing configuration in effect + * during the current I/O transaction. If no I/O + * transaction is in progress, this is the + * hardware timing configuration during the most + * recent I/O transaction. + * @init: Initializes the NFC hardware and data + * structures. This function will be called after + * everything has been set up for communication + * with the NFC itself, but before the platform + * has set up off-chip communication. Thus, this + * function must not attempt to communicate with + * the NAND Flash hardware. + * @set_geometry: Configures the NFC hardware and data structures + * to match the physical NAND Flash geometry. + * @set_timing: Configures the NFC hardware and data structures + * to match the given NAND Flash bus timing. + * @get_timing: Returns the the clock frequency, in Hz, and + * the hardware timing configuration during the + * current I/O transaction. If no I/O transaction + * is in progress, this is the timing state during + * the most recent I/O transaction. + * @exit: Shuts down the NFC hardware and data + * structures. This function will be called after + * the platform has shut down off-chip + * communication but while communication with the + * NFC itself still works. + * @clear_bch: Clears a BCH interrupt (intended to be called + * by a more general interrupt handler to do + * device-specific clearing). + * @is_ready: Returns true if the given chip is ready. + * @begin: Begins an interaction with the NFC. This + * function must be called before *any* of the + * following functions so the NFC can prepare + * itself. + * @end: Ends interaction with the NFC. This function + * should be called to give the NFC a chance to, + * among other things, enter a lower-power state. + * @send_command: Sends the given buffer of command bytes. + * @send_data: Sends the given buffer of data bytes. + * @read_data: Reads data bytes into the given buffer. + * @send_page: Sends the given given data and OOB bytes, + * using the ECC engine. + * @read_page: Reads a page through the ECC engine and + * delivers the data and OOB bytes to the given + * buffers. + */ +struct nfc_hal { + /* Hardware attributes. */ + const char *description; + const unsigned int max_chip_count; + const unsigned int max_data_setup_cycles; + const unsigned int internal_data_setup_in_ns; + const unsigned int max_sample_delay_factor; + const unsigned int max_dll_clock_period_in_ns; + const unsigned int max_dll_delay_in_ns; + + int isr_dma_channel; + struct completion dma_done; + struct completion bch_done; + struct nand_timing timing; + unsigned long clock_frequency_in_hz; + + /* Configuration functions. */ + int (*init) (struct gpmi_nfc_data *); + int (*extra_init) (struct gpmi_nfc_data *); + int (*set_geometry)(struct gpmi_nfc_data *); + int (*set_timing) (struct gpmi_nfc_data *, + const struct nand_timing *); + void (*get_timing) (struct gpmi_nfc_data *, + unsigned long *clock_frequency_in_hz, + struct gpmi_nfc_hardware_timing *); + void (*exit) (struct gpmi_nfc_data *); + + /* Call these functions to begin and end I/O. */ + void (*begin) (struct gpmi_nfc_data *); + void (*end) (struct gpmi_nfc_data *); + + /* Call these I/O functions only between begin() and end(). */ + void (*clear_bch) (struct gpmi_nfc_data *); + int (*is_ready) (struct gpmi_nfc_data *, unsigned chip); + int (*send_command)(struct gpmi_nfc_data *); + int (*send_data) (struct gpmi_nfc_data *); + int (*read_data) (struct gpmi_nfc_data *); + int (*send_page) (struct gpmi_nfc_data *, + dma_addr_t payload, dma_addr_t auxiliary); + int (*read_page) (struct gpmi_nfc_data *, + dma_addr_t payload, dma_addr_t auxiliary); +}; + +/* NFC HAL Common Services */ +extern int common_nfc_set_geometry(struct gpmi_nfc_data *this); +extern int gpmi_nfc_compute_hardware_timing(struct gpmi_nfc_data *this, + struct gpmi_nfc_hardware_timing *hw); +extern struct dma_chan *get_dma_chan(struct gpmi_nfc_data *this); +extern void prepare_data_dma(struct gpmi_nfc_data *this, + enum dma_data_direction dr); +extern int start_dma_without_bch_irq(struct gpmi_nfc_data *this, + struct dma_async_tx_descriptor *desc); +extern int start_dma_with_bch_irq(struct gpmi_nfc_data *this, + struct dma_async_tx_descriptor *desc); +/* NFC HAL Structures */ +extern struct nfc_hal gpmi_nfc_hal_imx23_imx28; + +/* for log */ +extern int gpmi_debug; +#define GPMI_DEBUG_INIT 0x0001 +#define GPMI_DEBUG_READ 0x0002 +#define GPMI_DEBUG_WRITE 0x0004 +#define GPMI_DEBUG_ECC_READ 0x0008 +#define GPMI_DEBUG_ECC_WRITE 0x0010 + +#define log(a, ...) printk(KERN_INFO "[ %s : %.3d ] "a"\n", \ + __func__, __LINE__, ## __VA_ARGS__) +#define logio(level) \ + do { \ + if (gpmi_debug & level) \ + log(); \ + } while (0) + +/* BCH : Status Block Completion Codes */ +#define STATUS_GOOD 0x00 +#define STATUS_ERASED 0xff +#define STATUS_UNCORRECTABLE 0xfe + +/* Use the platform_id to distinguish different Archs. */ +#define IS_MX23 0x1 +#define IS_MX28 0x2 +#define GPMI_IS_MX23(x) ((x)->pdev->id_entry->driver_data == IS_MX23) +#define GPMI_IS_MX28(x) ((x)->pdev->id_entry->driver_data == IS_MX28) +#endif -- 1.7.0.4 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v6 1/3] MTD : add the common code for GPMI controller driver @ 2011-04-20 2:45 ` Huang Shijie 0 siblings, 0 replies; 20+ messages in thread From: Huang Shijie @ 2011-04-20 2:45 UTC (permalink / raw) To: linux-arm-kernel These files contain the common code for the GPMI driver. Signed-off-by: Huang Shijie <b32955@freescale.com> --- drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c | 2501 ++++++++++++++++++++++++++++++++++ drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h | 488 +++++++ 2 files changed, 2989 insertions(+), 0 deletions(-) create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h diff --git a/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c new file mode 100644 index 0000000..53d6915 --- /dev/null +++ b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c @@ -0,0 +1,2501 @@ +/* + * Freescale GPMI NFC NAND Flash Driver + * + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. + * Copyright (C) 2008 Embedded Alley Solutions, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include <linux/slab.h> +#include "gpmi-nfc.h" + +/* add our owner bbt descriptor */ +static uint8_t scan_ff_pattern[] = { 0xff }; +static struct nand_bbt_descr gpmi_bbt_descr = { + .options = 0, + .offs = 0, + .len = 1, + .pattern = scan_ff_pattern +}; + +/* debug control */ +int gpmi_debug; + +static ssize_t show_gpmi_debug(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", gpmi_debug); +} + +static ssize_t +store_gpmi_debug(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + const char *p = buf; + unsigned long v; + + if (strict_strtoul(p, 0, &v) < 0) + return size; + + gpmi_debug = v; + return size; +} + +static ssize_t show_ignorebad(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpmi_nfc_data *this = dev_get_drvdata(dev); + struct mil *mil = &this->mil; + + return sprintf(buf, "%d\n", mil->ignore_bad_block_marks); +} + +static ssize_t +store_ignorebad(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct gpmi_nfc_data *this = dev_get_drvdata(dev); + struct mil *mil = &this->mil; + const char *p = buf; + unsigned long v; + + if (strict_strtoul(p, 0, &v) < 0) + return size; + + if (v > 0) + v = 1; + + if (v != mil->ignore_bad_block_marks) { + if (v) { + /* + * This will cause the NAND Flash MTD code to believe + * that it never created a BBT and force it to call our + * block_bad function. + * + * See mil_block_bad for more details. + */ + mil->saved_bbt = mil->nand.bbt; + mil->nand.bbt = NULL; + } else { + /* + * Restore the NAND Flash MTD's pointer + * to its in-memory BBT. + */ + mil->nand.bbt = mil->saved_bbt; + } + mil->ignore_bad_block_marks = v; + } + return size; +} + +static DEVICE_ATTR(ignorebad, 0644, show_ignorebad, store_ignorebad); +static DEVICE_ATTR(gpmi_debug, 0644, show_gpmi_debug, store_gpmi_debug); +static struct device_attribute *device_attributes[] = { + &dev_attr_ignorebad, + &dev_attr_gpmi_debug, +}; + +static irqreturn_t bch_irq(int irq, void *cookie) +{ + struct gpmi_nfc_data *this = cookie; + struct nfc_hal *nfc = this->nfc; + + /* Clear the BCH interrupt */ + nfc->clear_bch(this); + + complete(&nfc->bch_done); + return IRQ_HANDLED; +} + +/* calculate the ECC strength by hand */ +static inline int get_ecc_strength(struct gpmi_nfc_data *this) +{ + struct mtd_info *mtd = &this->mil.mtd; + int ecc_strength = 0; + + switch (mtd->writesize) { + case 2048: + ecc_strength = 8; + break; + case 4096: + switch (mtd->oobsize) { + case 128: + ecc_strength = 8; + break; + case 224: + case 218: + ecc_strength = 16; + break; + } + break; + case 8192: + ecc_strength = 24; + break; + } + + return ecc_strength; +} + +static inline bool is_ddr_nand(struct nand_chip *chip) +{ + /* ONFI nand */ + if (chip->onfi_version != 0) + return true; + + /* TOGGLE nand */ + + return false; +} + +static inline int get_ecc_chunk_size(struct gpmi_nfc_data *this) +{ + struct nand_chip *chip = &this->mil.nand; + + /* the ONFI/TOGGLE nands use 1k ecc chunk size */ + if (is_ddr_nand(chip)) + return 1024; + + /* for historical reason */ + return 512; +} + +int common_nfc_set_geometry(struct gpmi_nfc_data *this) +{ + struct nfc_geometry *geo = &this->nfc_geometry; + struct mtd_info *mtd = &this->mil.mtd; + struct nand_chip *chip = &this->mil.nand; + unsigned int metadata_size; + unsigned int status_size; + unsigned int chunk_data_size_in_bits; + unsigned int chunk_ecc_size_in_bits; + unsigned int chunk_total_size_in_bits; + unsigned int block_mark_chunk_number; + unsigned int block_mark_chunk_bit_offset; + unsigned int block_mark_bit_offset; + + /* We only support BCH now. */ + geo->ecc_algorithm = "BCH"; + + /* + * We always choose a metadata size of 10. Don't try to make sense of + * it -- this is really only for historical compatibility. + */ + geo->metadata_size_in_bytes = 10; + + /* ECC chunks */ + geo->ecc_chunk_size_in_bytes = get_ecc_chunk_size(this); + + /* + * Compute the total number of ECC chunks in a page. This includes the + * slightly larger chunk at the beginning of the page, which contains + * both data and metadata. + */ + geo->ecc_chunk_count = mtd->writesize / geo->ecc_chunk_size_in_bytes; + + /* + * We use the same ECC strength for all chunks, including the first one. + */ + geo->ecc_strength = get_ecc_strength(this); + if (!geo->ecc_strength) { + log("Page size : %d, OOB : %d", mtd->writesize, mtd->oobsize); + return -EINVAL; + } + + /* Compute the page size, include page and oob. */ + geo->page_size_in_bytes = mtd->writesize + mtd->oobsize; + + /* + * ONFI/TOGGLE nand needs GF14, so re-calculate DMA page size. + * The ONFI nand must do the recalculation, + * else it will fail in DMA in some platform(such as imx50). + */ + if (is_ddr_nand(chip)) + geo->page_size_in_bytes = mtd->writesize + + geo->metadata_size_in_bytes + + (geo->ecc_strength * 14 * 8 / geo->ecc_chunk_count); + + geo->payload_size_in_bytes = mtd->writesize; + /* + * In principle, computing the auxiliary buffer geometry is NFC + * version-specific. However, at this writing, all versions share the + * same model, so this code can also be shared. + * + * The auxiliary buffer contains the metadata and the ECC status. The + * metadata is padded to the nearest 32-bit boundary. The ECC status + * contains one byte for every ECC chunk, and is also padded to the + * nearest 32-bit boundary. + */ + metadata_size = ALIGN(geo->metadata_size_in_bytes, 4); + status_size = ALIGN(geo->ecc_chunk_count, 4); + + geo->auxiliary_size_in_bytes = metadata_size + status_size; + geo->auxiliary_status_offset = metadata_size; + + /* Check if we're going to do block mark swapping. */ + if (!this->swap_block_mark) + return 0; + + /* + * If control arrives here, we're doing block mark swapping, so we need + * to compute the byte and bit offsets of the physical block mark within + * the ECC-based view of the page data. In principle, this isn't a + * difficult computation -- but it's very important and it's easy to get + * it wrong, so we do it carefully. + * + * Note that this calculation is simpler because we use the same ECC + * strength for all chunks, including the zero'th one, which contains + * the metadata. The calculation would be slightly more complicated + * otherwise. + * + * We start by computing the physical bit offset of the block mark. We + * then subtract the number of metadata and ECC bits appearing before + * the mark to arrive at its bit offset within the data alone. + */ + + /* Compute some important facts about chunk geometry. */ + chunk_data_size_in_bits = geo->ecc_chunk_size_in_bytes * 8; + + /* ONFI/TOGGLE nand needs GF14 */ + if (is_ddr_nand(chip)) + chunk_ecc_size_in_bits = geo->ecc_strength * 14; + else + chunk_ecc_size_in_bits = geo->ecc_strength * 13; + + chunk_total_size_in_bits = + chunk_data_size_in_bits + chunk_ecc_size_in_bits; + + /* Compute the bit offset of the block mark within the physical page. */ + block_mark_bit_offset = mtd->writesize * 8; + + /* Subtract the metadata bits. */ + block_mark_bit_offset -= geo->metadata_size_in_bytes * 8; + + /* + * Compute the chunk number (starting at zero) in which the block mark + * appears. + */ + block_mark_chunk_number = + block_mark_bit_offset / chunk_total_size_in_bits; + + /* + * Compute the bit offset of the block mark within its chunk, and + * validate it. + */ + block_mark_chunk_bit_offset = + block_mark_bit_offset - + (block_mark_chunk_number * chunk_total_size_in_bits); + + if (block_mark_chunk_bit_offset > chunk_data_size_in_bits) { + /* + * If control arrives here, the block mark actually appears in + * the ECC bits of this chunk. This wont' work. + */ + log("Unsupported page geometry (block mark in ECC): %u:%u", + mtd->writesize, mtd->oobsize); + return -EINVAL; + } + + /* + * Now that we know the chunk number in which the block mark appears, + * we can subtract all the ECC bits that appear before it. + */ + block_mark_bit_offset -= + block_mark_chunk_number * chunk_ecc_size_in_bits; + + /* + * We now know the absolute bit offset of the block mark within the + * ECC-based data. We can now compute the byte offset and the bit + * offset within the byte. + */ + geo->block_mark_byte_offset = block_mark_bit_offset / 8; + geo->block_mark_bit_offset = block_mark_bit_offset % 8; + + return 0; +} + +struct dma_chan *get_dma_chan(struct gpmi_nfc_data *this) +{ + int chip = this->mil.current_chip; + + BUG_ON(chip < 0); + return this->dma_chans[chip]; +} + +/* Can we use the upper's buffer directly for DMA? */ +void prepare_data_dma(struct gpmi_nfc_data *this, enum dma_data_direction dr) +{ + struct mil *mil = &this->mil; + struct scatterlist *sgl = &mil->data_sgl; + int ret; + + mil->direct_dma_map_ok = true; + + /* first try to map the upper buffer directly */ + sg_init_one(sgl, mil->upper_buf, mil->upper_len); + ret = dma_map_sg(this->dev, sgl, 1, dr); + if (ret == 0) { + /* We have to use our own DMA buffer. */ + sg_init_one(sgl, mil->data_buffer_dma, PAGE_SIZE); + + if (dr == DMA_TO_DEVICE) + memcpy(mil->data_buffer_dma, mil->upper_buf, + mil->upper_len); + + ret = dma_map_sg(this->dev, sgl, 1, dr); + BUG_ON(ret == 0); + + mil->direct_dma_map_ok = false; + } +} + +/* This will be called after the DMA operation is finished. */ +static void dma_irq_callback(void *param) +{ + struct gpmi_nfc_data *this = param; + struct nfc_hal *nfc = this->nfc; + struct mil *mil = &this->mil; + + complete(&nfc->dma_done); + + switch (this->dma_type) { + case DMA_FOR_COMMAND: + dma_unmap_sg(this->dev, &mil->cmd_sgl, 1, DMA_TO_DEVICE); + break; + + case DMA_FOR_READ_DATA: + dma_unmap_sg(this->dev, &mil->data_sgl, 1, DMA_FROM_DEVICE); + if (mil->direct_dma_map_ok == false) + memcpy(mil->upper_buf, (char *)mil->data_buffer_dma, + mil->upper_len); + break; + + case DMA_FOR_WRITE_DATA: + dma_unmap_sg(this->dev, &mil->data_sgl, 1, DMA_TO_DEVICE); + break; + + case DMA_FOR_READ_ECC_PAGE: + case DMA_FOR_WRITE_ECC_PAGE: + /* We have to wait the BCH interrupt to finish. */ + break; + + default: + BUG(); + } +} + +int start_dma_without_bch_irq(struct gpmi_nfc_data *this, + struct dma_async_tx_descriptor *desc) +{ + struct nfc_hal *nfc = this->nfc; + int err; + + init_completion(&nfc->dma_done); + + desc->callback = dma_irq_callback; + desc->callback_param = this; + dmaengine_submit(desc); + + /* Wait for the interrupt from the DMA block. */ + err = wait_for_completion_timeout(&nfc->dma_done, + msecs_to_jiffies(1000)); + err = (!err) ? -ETIMEDOUT : 0; + if (err) + log("DMA timeout!!!"); + return err; +} + +/* + * This function is used in BCH reading or BCH writing pages. + * It will wait for the BCH interrupt as long as ONE second. + * Actually, we must wait for two interrupts : + * [1] firstly the DMA interrupt and + * [2] secondly the BCH interrupt. + * + * @this: Per-device data structure. + * @desc: DMA channel + */ +int start_dma_with_bch_irq(struct gpmi_nfc_data *this, + struct dma_async_tx_descriptor *desc) +{ + struct nfc_hal *nfc = this->nfc; + int err; + + /* Prepare to receive an interrupt from the BCH block. */ + init_completion(&nfc->bch_done); + + /* start the DMA */ + start_dma_without_bch_irq(this, desc); + + /* Wait for the interrupt from the BCH block. */ + err = wait_for_completion_timeout(&nfc->bch_done, + msecs_to_jiffies(1000)); + err = (!err) ? -ETIMEDOUT : 0; + if (err) + log("bch timeout!!!"); + return err; +} + +/** + * ns_to_cycles - Converts time in nanoseconds to cycles. + * + * @ntime: The time, in nanoseconds. + * @period: The cycle period, in nanoseconds. + * @min: The minimum allowable number of cycles. + */ +static unsigned int ns_to_cycles(unsigned int time, + unsigned int period, unsigned int min) +{ + unsigned int k; + + /* + * Compute the minimum number of cycles that entirely contain the + * given time. + */ + k = (time + period - 1) / period; + return max(k, min); +} + +/** + * gpmi_compute_hardware_timing - Apply timing to current hardware conditions. + * + * @this: Per-device data. + * @hardware_timing: A pointer to a hardware timing structure that will receive + * the results of our calculations. + */ +int gpmi_nfc_compute_hardware_timing(struct gpmi_nfc_data *this, + struct gpmi_nfc_hardware_timing *hw) +{ + struct gpmi_nfc_platform_data *pdata = this->pdata; + struct nfc_hal *nfc = this->nfc; + struct nand_chip *nand = &this->mil.nand; + struct nand_timing target = nfc->timing; + bool improved_timing_is_available; + unsigned long clock_frequency_in_hz; + unsigned int clock_period_in_ns; + bool dll_use_half_periods; + unsigned int dll_delay_shift; + unsigned int max_sample_delay_in_ns; + unsigned int address_setup_in_cycles; + unsigned int data_setup_in_ns; + unsigned int data_setup_in_cycles; + unsigned int data_hold_in_cycles; + int ideal_sample_delay_in_ns; + unsigned int sample_delay_factor; + int tEYE; + unsigned int min_prop_delay_in_ns = pdata->min_prop_delay_in_ns; + unsigned int max_prop_delay_in_ns = pdata->max_prop_delay_in_ns; + + /* + * If there are multiple chips, we need to relax the timings to allow + * for signal distortion due to higher capacitance. + */ + if (nand->numchips > 2) { + target.data_setup_in_ns += 10; + target.data_hold_in_ns += 10; + target.address_setup_in_ns += 10; + } else if (nand->numchips > 1) { + target.data_setup_in_ns += 5; + target.data_hold_in_ns += 5; + target.address_setup_in_ns += 5; + } + + /* Check if improved timing information is available. */ + improved_timing_is_available = + (target.tREA_in_ns >= 0) && + (target.tRLOH_in_ns >= 0) && + (target.tRHOH_in_ns >= 0) ; + + /* Inspect the clock. */ + clock_frequency_in_hz = nfc->clock_frequency_in_hz; + clock_period_in_ns = 1000000000 / clock_frequency_in_hz; + + /* + * The NFC quantizes setup and hold parameters in terms of clock cycles. + * Here, we quantize the setup and hold timing parameters to the + * next-highest clock period to make sure we apply at least the + * specified times. + * + * For data setup and data hold, the hardware interprets a value of zero + * as the largest possible delay. This is not what's intended by a zero + * in the input parameter, so we impose a minimum of one cycle. + */ + data_setup_in_cycles = ns_to_cycles(target.data_setup_in_ns, + clock_period_in_ns, 1); + data_hold_in_cycles = ns_to_cycles(target.data_hold_in_ns, + clock_period_in_ns, 1); + address_setup_in_cycles = ns_to_cycles(target.address_setup_in_ns, + clock_period_in_ns, 0); + + /* + * The clock's period affects the sample delay in a number of ways: + * + * (1) The NFC HAL tells us the maximum clock period the sample delay + * DLL can tolerate. If the clock period is greater than half that + * maximum, we must configure the DLL to be driven by half periods. + * + * (2) We need to convert from an ideal sample delay, in ns, to a + * "sample delay factor," which the NFC uses. This factor depends on + * whether we're driving the DLL with full or half periods. + * Paraphrasing the reference manual: + * + * AD = SDF x 0.125 x RP + * + * where: + * + * AD is the applied delay, in ns. + * SDF is the sample delay factor, which is dimensionless. + * RP is the reference period, in ns, which is a full clock period + * if the DLL is being driven by full periods, or half that if + * the DLL is being driven by half periods. + * + * Let's re-arrange this in a way that's more useful to us: + * + * 8 + * SDF = AD x ---- + * RP + * + * The reference period is either the clock period or half that, so this + * is: + * + * 8 AD x DDF + * SDF = AD x ----- = -------- + * f x P P + * + * where: + * + * f is 1 or 1/2, depending on how we're driving the DLL. + * P is the clock period. + * DDF is the DLL Delay Factor, a dimensionless value that + * incorporates all the constants in the conversion. + * + * DDF will be either 8 or 16, both of which are powers of two. We can + * reduce the cost of this conversion by using bit shifts instead of + * multiplication or division. Thus: + * + * AD << DDS + * SDF = --------- + * P + * + * or + * + * AD = (SDF >> DDS) x P + * + * where: + * + * DDS is the DLL Delay Shift, the logarithm to base 2 of the DDF. + */ + if (clock_period_in_ns > (nfc->max_dll_clock_period_in_ns >> 1)) { + dll_use_half_periods = true; + dll_delay_shift = 3 + 1; + } else { + dll_use_half_periods = false; + dll_delay_shift = 3; + } + + /* + * Compute the maximum sample delay the NFC allows, under current + * conditions. If the clock is running too slowly, no sample delay is + * possible. + */ + if (clock_period_in_ns > nfc->max_dll_clock_period_in_ns) + max_sample_delay_in_ns = 0; + else { + /* + * Compute the delay implied by the largest sample delay factor + * the NFC allows. + */ + max_sample_delay_in_ns = + (nfc->max_sample_delay_factor * clock_period_in_ns) >> + dll_delay_shift; + + /* + * Check if the implied sample delay larger than the NFC + * actually allows. + */ + if (max_sample_delay_in_ns > nfc->max_dll_delay_in_ns) + max_sample_delay_in_ns = nfc->max_dll_delay_in_ns; + } + + /* + * Check if improved timing information is available. If not, we have to + * use a less-sophisticated algorithm. + */ + if (!improved_timing_is_available) { + /* + * Fold the read setup time required by the NFC into the ideal + * sample delay. + */ + ideal_sample_delay_in_ns = target.gpmi_sample_delay_in_ns + + nfc->internal_data_setup_in_ns; + + /* + * The ideal sample delay may be greater than the maximum + * allowed by the NFC. If so, we can trade off sample delay time + * for more data setup time. + * + * In each iteration of the following loop, we add a cycle to + * the data setup time and subtract a corresponding amount from + * the sample delay until we've satisified the constraints or + * can't do any better. + */ + while ((ideal_sample_delay_in_ns > max_sample_delay_in_ns) && + (data_setup_in_cycles < nfc->max_data_setup_cycles)) { + + data_setup_in_cycles++; + ideal_sample_delay_in_ns -= clock_period_in_ns; + + if (ideal_sample_delay_in_ns < 0) + ideal_sample_delay_in_ns = 0; + + } + + /* + * Compute the sample delay factor that corresponds most closely + * to the ideal sample delay. If the result is too large for the + * NFC, use the maximum value. + * + * Notice that we use the ns_to_cycles function to compute the + * sample delay factor. We do this because the form of the + * computation is the same as that for calculating cycles. + */ + sample_delay_factor = + ns_to_cycles( + ideal_sample_delay_in_ns << dll_delay_shift, + clock_period_in_ns, 0); + + if (sample_delay_factor > nfc->max_sample_delay_factor) + sample_delay_factor = nfc->max_sample_delay_factor; + + /* Skip to the part where we return our results. */ + goto return_results; + } + + /* + * If control arrives here, we have more detailed timing information, + * so we can use a better algorithm. + */ + + /* + * Fold the read setup time required by the NFC into the maximum + * propagation delay. + */ + max_prop_delay_in_ns += nfc->internal_data_setup_in_ns; + + /* + * Earlier, we computed the number of clock cycles required to satisfy + * the data setup time. Now, we need to know the actual nanoseconds. + */ + data_setup_in_ns = clock_period_in_ns * data_setup_in_cycles; + + /* + * Compute tEYE, the width of the data eye when reading from the NAND + * Flash. The eye width is fundamentally determined by the data setup + * time, perturbed by propagation delays and some characteristics of the + * NAND Flash device. + * + * start of the eye = max_prop_delay + tREA + * end of the eye = min_prop_delay + tRHOH + data_setup + */ + tEYE = (int)min_prop_delay_in_ns + (int)target.tRHOH_in_ns + + (int)data_setup_in_ns; + + tEYE -= (int)max_prop_delay_in_ns + (int)target.tREA_in_ns; + + /* + * The eye must be open. If it's not, we can try to open it by + * increasing its main forcer, the data setup time. + * + * In each iteration of the following loop, we increase the data setup + * time by a single clock cycle. We do this until either the eye is + * open or we run into NFC limits. + */ + while ((tEYE <= 0) && + (data_setup_in_cycles < nfc->max_data_setup_cycles)) { + /* Give a cycle to data setup. */ + data_setup_in_cycles++; + /* Synchronize the data setup time with the cycles. */ + data_setup_in_ns += clock_period_in_ns; + /* Adjust tEYE accordingly. */ + tEYE += clock_period_in_ns; + } + + /* + * When control arrives here, the eye is open. The ideal time to sample + * the data is in the center of the eye: + * + * end of the eye + start of the eye + * --------------------------------- - data_setup + * 2 + * + * After some algebra, this simplifies to the code immediately below. + */ + ideal_sample_delay_in_ns = + ((int)max_prop_delay_in_ns + + (int)target.tREA_in_ns + + (int)min_prop_delay_in_ns + + (int)target.tRHOH_in_ns - + (int)data_setup_in_ns) >> 1; + + /* + * The following figure illustrates some aspects of a NAND Flash read: + * + * + * __ _____________________________________ + * RDN \_________________/ + * + * <---- tEYE -----> + * /-----------------\ + * Read Data ----------------------------< >--------- + * \-----------------/ + * ^ ^ ^ ^ + * | | | | + * |<--Data Setup -->|<--Delay Time -->| | + * | | | | + * | | | + * | |<-- Quantized Delay Time -->| + * | | | + * + * + * We have some issues we must now address: + * + * (1) The *ideal* sample delay time must not be negative. If it is, we + * jam it to zero. + * + * (2) The *ideal* sample delay time must not be greater than that + * allowed by the NFC. If it is, we can increase the data setup + * time, which will reduce the delay between the end of the data + * setup and the center of the eye. It will also make the eye + * larger, which might help with the next issue... + * + * (3) The *quantized* sample delay time must not fall either before the + * eye opens or after it closes (the latter is the problem + * illustrated in the above figure). + */ + + /* Jam a negative ideal sample delay to zero. */ + if (ideal_sample_delay_in_ns < 0) + ideal_sample_delay_in_ns = 0; + + /* + * Extend the data setup as needed to reduce the ideal sample delay + * below the maximum permitted by the NFC. + */ + while ((ideal_sample_delay_in_ns > max_sample_delay_in_ns) && + (data_setup_in_cycles < nfc->max_data_setup_cycles)) { + + /* Give a cycle to data setup. */ + data_setup_in_cycles++; + /* Synchronize the data setup time with the cycles. */ + data_setup_in_ns += clock_period_in_ns; + /* Adjust tEYE accordingly. */ + tEYE += clock_period_in_ns; + + /* + * Decrease the ideal sample delay by one half cycle, to keep it + * in the middle of the eye. + */ + ideal_sample_delay_in_ns -= (clock_period_in_ns >> 1); + + /* Jam a negative ideal sample delay to zero. */ + if (ideal_sample_delay_in_ns < 0) + ideal_sample_delay_in_ns = 0; + } + + /* + * Compute the sample delay factor that corresponds to the ideal sample + * delay. If the result is too large, then use the maximum allowed + * value. + * + * Notice that we use the ns_to_cycles function to compute the sample + * delay factor. We do this because the form of the computation is the + * same as that for calculating cycles. + */ + sample_delay_factor = + ns_to_cycles(ideal_sample_delay_in_ns << dll_delay_shift, + clock_period_in_ns, 0); + + if (sample_delay_factor > nfc->max_sample_delay_factor) + sample_delay_factor = nfc->max_sample_delay_factor; + + /* + * These macros conveniently encapsulate a computation we'll use to + * continuously evaluate whether or not the data sample delay is inside + * the eye. + */ + #define IDEAL_DELAY ((int) ideal_sample_delay_in_ns) + + #define QUANTIZED_DELAY \ + ((int) ((sample_delay_factor * clock_period_in_ns) >> \ + dll_delay_shift)) + + #define DELAY_ERROR (abs(QUANTIZED_DELAY - IDEAL_DELAY)) + + #define SAMPLE_IS_NOT_WITHIN_THE_EYE (DELAY_ERROR > (tEYE >> 1)) + + /* + * While the quantized sample time falls outside the eye, reduce the + * sample delay or extend the data setup to move the sampling point back + * toward the eye. Do not allow the number of data setup cycles to + * exceed the maximum allowed by the NFC. + */ + while (SAMPLE_IS_NOT_WITHIN_THE_EYE && + (data_setup_in_cycles < nfc->max_data_setup_cycles)) { + /* + * If control arrives here, the quantized sample delay falls + * outside the eye. Check if it's before the eye opens, or after + * the eye closes. + */ + if (QUANTIZED_DELAY > IDEAL_DELAY) { + /* + * If control arrives here, the quantized sample delay + * falls after the eye closes. Decrease the quantized + * delay time and then go back to re-evaluate. + */ + if (sample_delay_factor != 0) + sample_delay_factor--; + continue; + } + + /* + * If control arrives here, the quantized sample delay falls + * before the eye opens. Shift the sample point by increasing + * data setup time. This will also make the eye larger. + */ + + /* Give a cycle to data setup. */ + data_setup_in_cycles++; + /* Synchronize the data setup time with the cycles. */ + data_setup_in_ns += clock_period_in_ns; + /* Adjust tEYE accordingly. */ + tEYE += clock_period_in_ns; + + /* + * Decrease the ideal sample delay by one half cycle, to keep it + * in the middle of the eye. + */ + ideal_sample_delay_in_ns -= (clock_period_in_ns >> 1); + + /* ...and one less period for the delay time. */ + ideal_sample_delay_in_ns -= clock_period_in_ns; + + /* Jam a negative ideal sample delay to zero. */ + if (ideal_sample_delay_in_ns < 0) + ideal_sample_delay_in_ns = 0; + + /* + * We have a new ideal sample delay, so re-compute the quantized + * delay. + */ + sample_delay_factor = + ns_to_cycles( + ideal_sample_delay_in_ns << dll_delay_shift, + clock_period_in_ns, 0); + + if (sample_delay_factor > nfc->max_sample_delay_factor) + sample_delay_factor = nfc->max_sample_delay_factor; + } + + /* Control arrives here when we're ready to return our results. */ +return_results: + hw->data_setup_in_cycles = data_setup_in_cycles; + hw->data_hold_in_cycles = data_hold_in_cycles; + hw->address_setup_in_cycles = address_setup_in_cycles; + hw->use_half_periods = dll_use_half_periods; + hw->sample_delay_factor = sample_delay_factor; + + /* Return success. */ + return 0; +} + +static int acquire_register_block(struct gpmi_nfc_data *this, + const char *resource_name, void **reg_block_base) +{ + struct platform_device *pdev = this->pdev; + struct resource *r; + void *p; + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, resource_name); + if (!r) { + log("Can't get resource information for '%s'", resource_name); + return -ENXIO; + } + + /* remap the register block */ + p = ioremap(r->start, resource_size(r)); + if (!p) { + log("Can't remap %s", resource_name); + return -ENOMEM; + } + + *reg_block_base = p; + return 0; +} + +static void release_register_block(struct gpmi_nfc_data *this, + void *reg_block_base) +{ + iounmap(reg_block_base); +} + +static int acquire_interrupt(struct gpmi_nfc_data *this, + const char *resource_name, + irq_handler_t interrupt_handler, int *lno, int *hno) +{ + struct platform_device *pdev = this->pdev; + struct resource *r; + int err; + + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, resource_name); + if (!r) { + log("Can't get resource information for '%s'", resource_name); + return -ENXIO; + } + + BUG_ON(r->start != r->end); + err = request_irq(r->start, interrupt_handler, 0, resource_name, this); + if (err) { + log("Can't own %s", resource_name); + return err; + } + + *lno = r->start; + *hno = r->end; + return 0; +} + +static void release_interrupt(struct gpmi_nfc_data *this, + int low_interrupt_number, int high_interrupt_number) +{ + int i; + for (i = low_interrupt_number; i <= high_interrupt_number; i++) + free_irq(i, this); +} + +static bool gpmi_dma_filter(struct dma_chan *chan, void *param) +{ + struct gpmi_nfc_data *this = param; + struct resource *r = this->private; + + if (!mxs_dma_is_apbh(chan)) + return false; + /* + * only catch the GPMI dma channels : + * for mx23 : MX23_DMA_GPMI0 ~ MX23_DMA_GPMI3 + * (These four channels share the same IRQ!) + * + * for mx28 : MX28_DMA_GPMI0 ~ MX28_DMA_GPMI7 + * (These eight channels share the same IRQ!) + */ + if (r->start <= chan->chan_id && chan->chan_id <= r->end) { + chan->private = &this->dma_data; + return true; + } + return false; +} + +static void release_dma_channels(struct gpmi_nfc_data *this) +{ + unsigned int i; + for (i = 0; i < DMA_CHANS; i++) + if (this->dma_chans[i]) { + dma_release_channel(this->dma_chans[i]); + this->dma_chans[i] = NULL; + } +} + +static int acquire_dma_channels(struct gpmi_nfc_data *this, + const char *resource_name, + unsigned *low_channel, unsigned *high_channel) +{ + struct platform_device *pdev = this->pdev; + struct resource *r, *r_dma; + unsigned int i; + + r = platform_get_resource_byname(pdev, IORESOURCE_DMA, resource_name); + r_dma = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + GPMI_NFC_DMA_INTERRUPT_RES_NAME); + if (!r || !r_dma) { + log("Can't get resource for DMA"); + return -ENXIO; + } + + /* used in gpmi_dma_filter() */ + this->private = r; + + for (i = r->start; i <= r->end; i++) { + dma_cap_mask_t mask; + struct dma_chan *dma_chan; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + /* get the DMA interrupt */ + this->dma_data.chan_irq = r_dma->start + + ((r_dma->start != r_dma->end) ? (i - r->start) : 0); + + dma_chan = dma_request_channel(mask, gpmi_dma_filter, this); + if (!dma_chan) + goto acquire_err; + /* fill the first empty item */ + this->dma_chans[i - r->start] = dma_chan; + } + + *low_channel = r->start; + *high_channel = r->end; + return 0; + +acquire_err: + log("Can't acquire DMA channel %u", i); + release_dma_channels(this); + return -EINVAL; +} + +static inline int acquire_clock(struct gpmi_nfc_data *this, struct clk **clock) +{ + struct clk *c; + + c = clk_get(&this->pdev->dev, NULL); + if (IS_ERR(c)) { + log("Can't own clock"); + return PTR_ERR(c); + } + *clock = c; + return 0; +} + +static void release_clock(struct gpmi_nfc_data *this, struct clk *clock) +{ + clk_put(clock); +} + +static int acquire_resources(struct gpmi_nfc_data *this) +{ + struct resources *resources = &this->resources; + int error; + + /* Attempt to acquire the GPMI register block. */ + error = acquire_register_block(this, + GPMI_NFC_GPMI_REGS_ADDR_RES_NAME, + &resources->gpmi_regs); + if (error) + goto exit_gpmi_regs; + + /* Attempt to acquire the BCH register block. */ + error = acquire_register_block(this, + GPMI_NFC_BCH_REGS_ADDR_RES_NAME, + &resources->bch_regs); + if (error) + goto exit_bch_regs; + + /* Attempt to acquire the BCH interrupt. */ + error = acquire_interrupt(this, + GPMI_NFC_BCH_INTERRUPT_RES_NAME, + bch_irq, + &resources->bch_low_interrupt, + &resources->bch_high_interrupt); + if (error) + goto exit_bch_interrupt; + + /* Attempt to acquire the DMA channels. */ + error = acquire_dma_channels(this, + GPMI_NFC_DMA_CHANNELS_RES_NAME, + &resources->dma_low_channel, + &resources->dma_high_channel); + if (error) + goto exit_dma_channels; + + /* Attempt to acquire our clock. */ + error = acquire_clock(this, &resources->clock); + if (error) + goto exit_clock; + return 0; + +exit_clock: + release_dma_channels(this); +exit_dma_channels: + release_interrupt(this, resources->bch_low_interrupt, + resources->bch_high_interrupt); +exit_bch_interrupt: + release_register_block(this, resources->bch_regs); +exit_bch_regs: + release_register_block(this, resources->gpmi_regs); +exit_gpmi_regs: + return error; +} + +static void release_resources(struct gpmi_nfc_data *this) +{ + struct resources *resources = &this->resources; + + release_clock(this, resources->clock); + release_register_block(this, resources->gpmi_regs); + release_register_block(this, resources->bch_regs); + release_interrupt(this, resources->bch_low_interrupt, + resources->bch_low_interrupt); + release_dma_channels(this); +} + +static void exit_nfc_hal(struct gpmi_nfc_data *this) +{ + if (this->nfc) + this->nfc->exit(this); +} + +static int set_up_nfc_hal(struct gpmi_nfc_data *this) +{ + struct nfc_hal *nfc = NULL; + int error; + + /* + * This structure contains the "safe" GPMI timing that should succeed + * with any NAND Flash device + * (although, with less-than-optimal performance). + */ + static struct nand_timing safe_timing = { + .data_setup_in_ns = 80, + .data_hold_in_ns = 60, + .address_setup_in_ns = 25, + .gpmi_sample_delay_in_ns = 6, + .tREA_in_ns = -1, + .tRLOH_in_ns = -1, + .tRHOH_in_ns = -1, + }; + + if (GPMI_IS_MX23(this) || GPMI_IS_MX28(this)) + nfc = &gpmi_nfc_hal_imx23_imx28; + + BUG_ON(nfc == NULL); + this->nfc = nfc; + + /* Initialize the NFC HAL. */ + error = nfc->init(this); + if (error) + return error; + + /* Set up safe timing. */ + nfc->set_timing(this, &safe_timing); + return 0; +} + +/* Creates/Removes sysfs files for this device.*/ +static void manage_sysfs_files(struct gpmi_nfc_data *this, int create) +{ + struct device *dev = this->dev; + struct device_attribute **attr; + unsigned int i; + int error; + + for (i = 0, attr = device_attributes; + i < ARRAY_SIZE(device_attributes); i++, attr++) { + + if (create) { + error = device_create_file(dev, *attr); + if (error) { + while (--attr >= device_attributes) + device_remove_file(dev, *attr); + return; + } + } else { + device_remove_file(dev, *attr); + } + } +} + +static int read_page_prepare(struct gpmi_nfc_data *this, + void *destination, unsigned length, + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, + void **use_virt, dma_addr_t *use_phys) +{ + struct device *dev = this->dev; + dma_addr_t destination_phys = ~0; + + if (virt_addr_valid(destination)) + destination_phys = dma_map_single(dev, (void *)destination, + length, DMA_FROM_DEVICE); + + if (dma_mapping_error(dev, destination_phys)) { + if (alt_size < length) { + log("Alternate buffer is too small for incoming I/O."); + return -ENOMEM; + } + + *use_virt = alt_virt; + *use_phys = alt_phys; + } else { + *use_virt = destination; + *use_phys = destination_phys; + } + return 0; +} + +static void read_page_end(struct gpmi_nfc_data *this, + void *destination, unsigned length, + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, + void *used_virt, dma_addr_t used_phys) +{ + struct device *dev = this->dev; + + if (used_virt == destination) + dma_unmap_single(dev, used_phys, length, DMA_FROM_DEVICE); + else + memcpy(destination, alt_virt, length); +} + +static int send_page_prepare(struct gpmi_nfc_data *this, + const void *source, unsigned length, + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, + const void **use_virt, dma_addr_t *use_phys) +{ + dma_addr_t source_phys = ~0; + struct device *dev = this->dev; + + if (virt_addr_valid(source)) + source_phys = dma_map_single(dev, + (void *)source, length, DMA_TO_DEVICE); + + if (dma_mapping_error(dev, source_phys)) { + if (alt_size < length) { + log("Alternate buffer is too small for outgoing I/O"); + return -ENOMEM; + } + + /* + * Copy the contents of the source buffer into the alternate + * buffer and set up the return values accordingly. + */ + memcpy(alt_virt, source, length); + + *use_virt = alt_virt; + *use_phys = alt_phys; + } else { + *use_virt = source; + *use_phys = source_phys; + } + return 0; +} + +static void send_page_end(struct gpmi_nfc_data *this, + const void *source, unsigned length, + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, + const void *used_virt, dma_addr_t used_phys) +{ + struct device *dev = this->dev; + if (used_virt == source) + dma_unmap_single(dev, used_phys, length, DMA_TO_DEVICE); +} + +static void mil_free_dma_buffer(struct gpmi_nfc_data *this) +{ + struct device *dev = this->dev; + struct mil *mil = &this->mil; + + if (mil->page_buffer_virt && virt_addr_valid(mil->page_buffer_virt)) + dma_free_coherent(dev, mil->page_buffer_size, + mil->page_buffer_virt, + mil->page_buffer_phys); + kfree(mil->cmd_buffer); + kfree(mil->data_buffer_dma); + + mil->cmd_buffer = NULL; + mil->data_buffer_dma = NULL; + mil->page_buffer_virt = NULL; + mil->page_buffer_size = 0; + mil->page_buffer_phys = ~0; +} + +/* Allocate the DMA buffers */ +static int mil_alloc_dma_buffer(struct gpmi_nfc_data *this) +{ + struct nfc_geometry *geo = &this->nfc_geometry; + struct device *dev = this->dev; + struct mil *mil = &this->mil; + + /* [1] Allocate a command buffer. PAGE_SIZE is enough. */ + mil->cmd_buffer = kzalloc(PAGE_SIZE, GFP_DMA); + if (mil->cmd_buffer == NULL) + goto error_alloc; + + /* [2] Allocate a read/write data buffer. PAGE_SIZE is enough. */ + mil->data_buffer_dma = kzalloc(PAGE_SIZE, GFP_DMA); + if (mil->data_buffer_dma == NULL) + goto error_alloc; + + /* + * [3] Allocate the page buffer. + * + * Both the payload buffer and the auxiliary buffer must appear on + * 32-bit boundaries. We presume the size of the payload buffer is a + * power of two and is much larger than four, which guarantees the + * auxiliary buffer will appear on a 32-bit boundary. + */ + mil->page_buffer_size = geo->payload_size_in_bytes + + geo->auxiliary_size_in_bytes; + + mil->page_buffer_virt = dma_alloc_coherent(dev, mil->page_buffer_size, + &mil->page_buffer_phys, GFP_DMA); + if (!mil->page_buffer_virt) + goto error_alloc; + + + /* Slice up the page buffer. */ + mil->payload_virt = mil->page_buffer_virt; + mil->payload_phys = mil->page_buffer_phys; + mil->auxiliary_virt = ((char *) mil->payload_virt) + + geo->payload_size_in_bytes; + mil->auxiliary_phys = mil->payload_phys + + geo->payload_size_in_bytes; + return 0; + +error_alloc: + mil_free_dma_buffer(this); + log("allocate DMA buffer error!!"); + return -ENOMEM; +} + +static void mil_cmd_ctrl(struct mtd_info *mtd, int data, unsigned int ctrl) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nfc_data *this = nand->priv; + struct mil *mil = &this->mil; + struct nfc_hal *nfc = this->nfc; + int error; + + /* + * Every operation begins with a command byte and a series of zero or + * more address bytes. These are distinguished by either the Address + * Latch Enable (ALE) or Command Latch Enable (CLE) signals being + * asserted. When MTD is ready to execute the command, it will deassert + * both latch enables. + * + * Rather than run a separate DMA operation for every single byte, we + * queue them up and run a single DMA operation for the entire series + * of command and data bytes. NAND_CMD_NONE means the END of the queue. + */ + if ((ctrl & (NAND_ALE | NAND_CLE))) { + if (data != NAND_CMD_NONE) + mil->cmd_buffer[mil->command_length++] = data; + return; + } + + if (!mil->command_length) + return; + + error = nfc->send_command(this); + if (error) + log("Chip: %u, Error %d", mil->current_chip, error); + + mil->command_length = 0; +} + +static int mil_dev_ready(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nfc_data *this = nand->priv; + struct nfc_hal *nfc = this->nfc; + struct mil *mil = &this->mil; + + return nfc->is_ready(this, mil->current_chip); +} + +static void mil_select_chip(struct mtd_info *mtd, int chip) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nfc_data *this = nand->priv; + struct nfc_hal *nfc = this->nfc; + struct mil *mil = &this->mil; + + if ((mil->current_chip < 0) && (chip >= 0)) + nfc->begin(this); + else if ((mil->current_chip >= 0) && (chip < 0)) + nfc->end(this); + else + ; + + mil->current_chip = chip; +} + +static void mil_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nfc_data *this = nand->priv; + struct nfc_hal *nfc = this->nfc; + struct mil *mil = &this->mil; + + logio(GPMI_DEBUG_READ); + /* save the info in mil{} for future */ + mil->upper_buf = buf; + mil->upper_len = len; + + nfc->read_data(this); +} + +static void mil_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nfc_data *this = nand->priv; + struct nfc_hal *nfc = this->nfc; + struct mil *mil = &this->mil; + + logio(GPMI_DEBUG_WRITE); + /* save the info in mil{} for future */ + mil->upper_buf = (uint8_t *)buf; + mil->upper_len = len; + + nfc->send_data(this); +} + +static uint8_t mil_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nfc_data *this = nand->priv; + struct mil *mil = &this->mil; + uint8_t *buf = mil->data_buffer_dma; + + mil_read_buf(mtd, buf, 1); + return buf[0]; +} + +/** + * mil_handle_block_mark_swapping() - Handles block mark swapping. + * + * Note that, when this function is called, it doesn't know whether it's + * swapping the block mark, or swapping it *back* -- but it doesn't matter + * because the the operation is the same. + * + * @this: Per-device data. + * @payload: A pointer to the payload buffer. + * @auxiliary: A pointer to the auxiliary buffer. + */ +static void mil_handle_block_mark_swapping(struct gpmi_nfc_data *this, + void *payload, void *auxiliary) +{ + struct nfc_geometry *nfc_geo = &this->nfc_geometry; + unsigned char *p; + unsigned char *a; + unsigned int bit; + unsigned char mask; + unsigned char from_data; + unsigned char from_oob; + + /* Check if we're doing block mark swapping. */ + if (!this->swap_block_mark) + return; + + /* + * If control arrives here, we're swapping. Make some convenience + * variables. + */ + bit = nfc_geo->block_mark_bit_offset; + p = payload + nfc_geo->block_mark_byte_offset; + a = auxiliary; + + /* + * Get the byte from the data area that overlays the block mark. Since + * the ECC engine applies its own view to the bits in the page, the + * physical block mark won't (in general) appear on a byte boundary in + * the data. + */ + from_data = (p[0] >> bit) | (p[1] << (8 - bit)); + + /* Get the byte from the OOB. */ + from_oob = a[0]; + + /* Swap them. */ + a[0] = from_data; + + mask = (0x1 << bit) - 1; + p[0] = (p[0] & mask) | (from_oob << bit); + + mask = ~0 << bit; + p[1] = (p[1] & mask) | (from_oob >> (8 - bit)); +} + +static int mil_ecc_read_page(struct mtd_info *mtd, struct nand_chip *nand, + uint8_t *buf, int page) +{ + struct gpmi_nfc_data *this = nand->priv; + struct nfc_hal *nfc = this->nfc; + struct nfc_geometry *nfc_geo = &this->nfc_geometry; + struct mil *mil = &this->mil; + void *payload_virt; + dma_addr_t payload_phys; + void *auxiliary_virt; + dma_addr_t auxiliary_phys; + unsigned int i; + unsigned char *status; + unsigned int failed; + unsigned int corrected; + int error; + + logio(GPMI_DEBUG_ECC_READ); + error = read_page_prepare(this, buf, mtd->writesize, + mil->payload_virt, mil->payload_phys, + nfc_geo->payload_size_in_bytes, + &payload_virt, &payload_phys); + if (error) { + log("Inadequate DMA buffer"); + error = -ENOMEM; + return error; + } + auxiliary_virt = mil->auxiliary_virt; + auxiliary_phys = mil->auxiliary_phys; + + /* ask the NFC */ + error = nfc->read_page(this, payload_phys, auxiliary_phys); + if (error) { + log("Error in ECC-based read: %d", error); + goto exit_nfc; + } + + /* handle the block mark swapping */ + mil_handle_block_mark_swapping(this, payload_virt, auxiliary_virt); + + /* Loop over status bytes, accumulating ECC status. */ + failed = 0; + corrected = 0; + status = auxiliary_virt + nfc_geo->auxiliary_status_offset; + + for (i = 0; i < nfc_geo->ecc_chunk_count; i++, status++) { + if ((*status == STATUS_GOOD) || (*status == STATUS_ERASED)) + continue; + + if (*status == STATUS_UNCORRECTABLE) { + failed++; + continue; + } + corrected += *status; + } + + /* + * Propagate ECC status to the owning MTD only when failed or + * corrected times nearly reaches our ECC correction threshold. + */ + if (failed || corrected >= (nfc_geo->ecc_strength - 1)) { + mtd->ecc_stats.failed += failed; + mtd->ecc_stats.corrected += corrected; + } + + /* + * It's time to deliver the OOB bytes. See mil_ecc_read_oob() for + * details about our policy for delivering the OOB. + * + * We fill the caller's buffer with set bits, and then copy the block + * mark to th caller's buffer. Note that, if block mark swapping was + * necessary, it has already been done, so we can rely on the first + * byte of the auxiliary buffer to contain the block mark. + */ + memset(nand->oob_poi, ~0, mtd->oobsize); + nand->oob_poi[0] = ((uint8_t *) auxiliary_virt)[0]; + +exit_nfc: + read_page_end(this, buf, mtd->writesize, + mil->payload_virt, mil->payload_phys, + nfc_geo->payload_size_in_bytes, + payload_virt, payload_phys); + return error; +} + +static void mil_ecc_write_page(struct mtd_info *mtd, + struct nand_chip *nand, const uint8_t *buf) +{ + struct gpmi_nfc_data *this = nand->priv; + struct nfc_hal *nfc = this->nfc; + struct nfc_geometry *nfc_geo = &this->nfc_geometry; + struct mil *mil = &this->mil; + const void *payload_virt; + dma_addr_t payload_phys; + const void *auxiliary_virt; + dma_addr_t auxiliary_phys; + int error; + + logio(GPMI_DEBUG_ECC_WRITE); + if (this->swap_block_mark) { + /* + * If control arrives here, we're doing block mark swapping. + * Since we can't modify the caller's buffers, we must copy them + * into our own. + */ + memcpy(mil->payload_virt, buf, mtd->writesize); + payload_virt = mil->payload_virt; + payload_phys = mil->payload_phys; + + memcpy(mil->auxiliary_virt, nand->oob_poi, + nfc_geo->auxiliary_size_in_bytes); + auxiliary_virt = mil->auxiliary_virt; + auxiliary_phys = mil->auxiliary_phys; + + /* Handle block mark swapping. */ + mil_handle_block_mark_swapping(this, + (void *) payload_virt, (void *) auxiliary_virt); + } else { + /* + * If control arrives here, we're not doing block mark swapping, + * so we can to try and use the caller's buffers. + */ + error = send_page_prepare(this, + buf, mtd->writesize, + mil->payload_virt, mil->payload_phys, + nfc_geo->payload_size_in_bytes, + &payload_virt, &payload_phys); + if (error) { + log("Inadequate payload DMA buffer"); + return; + } + + error = send_page_prepare(this, + nand->oob_poi, mtd->oobsize, + mil->auxiliary_virt, mil->auxiliary_phys, + nfc_geo->auxiliary_size_in_bytes, + &auxiliary_virt, &auxiliary_phys); + if (error) { + log("Inadequate auxiliary DMA buffer"); + goto exit_auxiliary; + } + } + + /* Ask the NFC. */ + error = nfc->send_page(this, payload_phys, auxiliary_phys); + if (error) + log("Error in ECC-based write: %d", error); + + if (!this->swap_block_mark) { + send_page_end(this, nand->oob_poi, mtd->oobsize, + mil->auxiliary_virt, mil->auxiliary_phys, + nfc_geo->auxiliary_size_in_bytes, + auxiliary_virt, auxiliary_phys); +exit_auxiliary: + send_page_end(this, buf, mtd->writesize, + mil->payload_virt, mil->payload_phys, + nfc_geo->payload_size_in_bytes, + payload_virt, payload_phys); + } +} + +static int mil_hook_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + register struct nand_chip *chip = mtd->priv; + struct gpmi_nfc_data *this = chip->priv; + struct mil *mil = &this->mil; + int ret; + + mil->marking_a_bad_block = true; + ret = mil->hooked_block_markbad(mtd, ofs); + mil->marking_a_bad_block = false; + return ret; +} + +/** + * mil_ecc_read_oob() - MTD Interface ecc.read_oob(). + * + * There are several places in this driver where we have to handle the OOB and + * block marks. This is the function where things are the most complicated, so + * this is where we try to explain it all. All the other places refer back to + * here. + * + * These are the rules, in order of decreasing importance: + * + * 1) Nothing the caller does can be allowed to imperil the block mark, so all + * write operations take measures to protect it. + * + * 2) In read operations, the first byte of the OOB we return must reflect the + * true state of the block mark, no matter where that block mark appears in + * the physical page. + * + * 3) ECC-based read operations return an OOB full of set bits (since we never + * allow ECC-based writes to the OOB, it doesn't matter what ECC-based reads + * return). + * + * 4) "Raw" read operations return a direct view of the physical bytes in the + * page, using the conventional definition of which bytes are data and which + * are OOB. This gives the caller a way to see the actual, physical bytes + * in the page, without the distortions applied by our ECC engine. + * + * + * What we do for this specific read operation depends on two questions: + * + * 1) Are we doing a "raw" read, or an ECC-based read? + * + * 2) Are we using block mark swapping or transcription? + * + * There are four cases, illustrated by the following Karnaugh map: + * + * | Raw | ECC-based | + * -------------+-------------------------+-------------------------+ + * | Read the conventional | | + * | OOB at the end of the | | + * Swapping | page and return it. It | | + * | contains exactly what | | + * | we want. | Read the block mark and | + * -------------+-------------------------+ return it in a buffer | + * | Read the conventional | full of set bits. | + * | OOB at the end of the | | + * | page and also the block | | + * Transcribing | mark in the metadata. | | + * | Copy the block mark | | + * | into the first byte of | | + * | the OOB. | | + * -------------+-------------------------+-------------------------+ + * + * Note that we break rule #4 in the Transcribing/Raw case because we're not + * giving an accurate view of the actual, physical bytes in the page (we're + * overwriting the block mark). That's OK because it's more important to follow + * rule #2. + * + * It turns out that knowing whether we want an "ECC-based" or "raw" read is not + * easy. When reading a page, for example, the NAND Flash MTD code calls our + * ecc.read_page or ecc.read_page_raw function. Thus, the fact that MTD wants an + * ECC-based or raw view of the page is implicit in which function it calls + * (there is a similar pair of ECC-based/raw functions for writing). + * + * Since MTD assumes the OOB is not covered by ECC, there is no pair of + * ECC-based/raw functions for reading or or writing the OOB. The fact that the + * caller wants an ECC-based or raw view of the page is not propagated down to + * this driver. + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + * @page: The page number to read. + * @sndcmd: Indicates this function should send a command to the chip before + * reading the out-of-band bytes. This is only false for small page + * chips that support auto-increment. + */ +static int mil_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *nand, + int page, int sndcmd) +{ + struct gpmi_nfc_data *this = nand->priv; + + /* clear the OOB buffer */ + memset(nand->oob_poi, ~0, mtd->oobsize); + + /* Read out the conventional OOB. */ + nand->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page); + nand->read_buf(mtd, nand->oob_poi, mtd->oobsize); + + /* + * Now, we want to make sure the block mark is correct. In the + * Swapping/Raw case, we already have it. Otherwise, we need to + * explicitly read it. + */ + if (!this->swap_block_mark) { + /* Read the block mark into the first byte of the OOB buffer. */ + nand->cmdfunc(mtd, NAND_CMD_READ0, 0, page); + nand->oob_poi[0] = nand->read_byte(mtd); + } + + /* + * Return true, indicating that the next call to this function must send + * a command. + */ + return true; +} + +static int mil_ecc_write_oob(struct mtd_info *mtd, + struct nand_chip *nand, int page) +{ + struct gpmi_nfc_data *this = nand->priv; + struct device *dev = this->dev; + struct mil *mil = &this->mil; + uint8_t *block_mark; + int block_mark_column; + int status; + int error = 0; + + /* Only marking a block bad is permitted to write the OOB. */ + if (!mil->marking_a_bad_block) { + dev_emerg(dev, "This driver doesn't support writing the OOB\n"); + WARN_ON(1); + error = -EIO; + goto exit; + } + + if (this->swap_block_mark) + block_mark_column = mtd->writesize; + else + block_mark_column = 0; + + /* Write the block mark. */ + block_mark = mil->data_buffer_dma; + block_mark[0] = 0; /* bad block marker */ + + nand->cmdfunc(mtd, NAND_CMD_SEQIN, block_mark_column, page); + nand->write_buf(mtd, block_mark, 1); + nand->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + status = nand->waitfunc(mtd, nand); + + /* Check if it worked. */ + if (status & NAND_STATUS_FAIL) + error = -EIO; +exit: + return error; +} + +/** + * mil_block_bad - Claims all blocks are good. + * + * In principle, this function is *only* called when the NAND Flash MTD system + * isn't allowed to keep an in-memory bad block table, so it is forced to ask + * the driver for bad block information. + * + * In fact, we permit the NAND Flash MTD system to have an in-memory BBT, so + * this function is *only* called when we take it away. + * + * We take away the in-memory BBT when the user sets the "ignorebad" parameter, + * which indicates that all blocks should be reported good. + * + * Thus, this function is only called when we want *all* blocks to look good, + * so it *always* return success. + * + * @mtd: Ignored. + * @ofs: Ignored. + * @getchip: Ignored. + */ +static int mil_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip) +{ + return 0; +} + +static int nand_boot_set_geometry(struct gpmi_nfc_data *this) +{ + struct boot_rom_geometry *geometry = &this->rom_geometry; + + /* + * Set the boot block stride size. + * + * In principle, we should be reading this from the OTP bits, since + * that's where the ROM is going to get it. In fact, we don't have any + * way to read the OTP bits, so we go with the default and hope for the + * best. + */ + geometry->stride_size_in_pages = 64; + + /* + * Set the search area stride exponent. + * + * In principle, we should be reading this from the OTP bits, since + * that's where the ROM is going to get it. In fact, we don't have any + * way to read the OTP bits, so we go with the default and hope for the + * best. + */ + geometry->search_area_stride_exponent = 2; + + if (gpmi_debug & GPMI_DEBUG_INIT) + log("stride size in page : %d, search areas : %d", + geometry->stride_size_in_pages, + geometry->search_area_stride_exponent); + return 0; +} + +static const char *fingerprint = "STMP"; +static int mx23_check_transcription_stamp(struct gpmi_nfc_data *this) +{ + struct boot_rom_geometry *rom_geo = &this->rom_geometry; + struct mil *mil = &this->mil; + struct mtd_info *mtd = &mil->mtd; + struct nand_chip *nand = &mil->nand; + unsigned int search_area_size_in_strides; + unsigned int stride; + unsigned int page; + loff_t byte; + uint8_t *buffer = nand->buffers->databuf; + int saved_chip_number; + int found_an_ncb_fingerprint = false; + + /* Compute the number of strides in a search area. */ + search_area_size_in_strides = 1 << rom_geo->search_area_stride_exponent; + + /* Select chip 0. */ + saved_chip_number = mil->current_chip; + nand->select_chip(mtd, 0); + + /* + * Loop through the first search area, looking for the NCB fingerprint. + */ + pr_info("Scanning for an NCB fingerprint...\n"); + + for (stride = 0; stride < search_area_size_in_strides; stride++) { + /* Compute the page and byte addresses. */ + page = stride * rom_geo->stride_size_in_pages; + byte = page * mtd->writesize; + + pr_info(" Looking for a fingerprint in page 0x%x\n", page); + + /* + * Read the NCB fingerprint. The fingerprint is four bytes long + * and starts in the 12th byte of the page. + */ + nand->cmdfunc(mtd, NAND_CMD_READ0, 12, page); + nand->read_buf(mtd, buffer, strlen(fingerprint)); + + /* Look for the fingerprint. */ + if (!memcmp(buffer, fingerprint, strlen(fingerprint))) { + found_an_ncb_fingerprint = true; + break; + } + + } + + /* Deselect chip 0. */ + nand->select_chip(mtd, saved_chip_number); + + if (found_an_ncb_fingerprint) + pr_info(" Found a fingerprint\n"); + else + pr_info(" No fingerprint found\n"); + return found_an_ncb_fingerprint; +} + +/* Writes a transcription stamp. */ +static int mx23_write_transcription_stamp(struct gpmi_nfc_data *this) +{ + struct device *dev = this->dev; + struct boot_rom_geometry *rom_geo = &this->rom_geometry; + struct mil *mil = &this->mil; + struct mtd_info *mtd = &mil->mtd; + struct nand_chip *nand = &mil->nand; + unsigned int block_size_in_pages; + unsigned int search_area_size_in_strides; + unsigned int search_area_size_in_pages; + unsigned int search_area_size_in_blocks; + unsigned int block; + unsigned int stride; + unsigned int page; + loff_t byte; + uint8_t *buffer = nand->buffers->databuf; + int saved_chip_number; + int status; + + /* Compute the search area geometry. */ + block_size_in_pages = mtd->erasesize / mtd->writesize; + search_area_size_in_strides = 1 << rom_geo->search_area_stride_exponent; + search_area_size_in_pages = search_area_size_in_strides * + rom_geo->stride_size_in_pages; + search_area_size_in_blocks = + (search_area_size_in_pages + (block_size_in_pages - 1)) / + block_size_in_pages; + + pr_info("-------------------------------------------\n"); + pr_info("Search Area Geometry\n"); + pr_info("-------------------------------------------\n"); + pr_info("Search Area Size in Blocks : %u", search_area_size_in_blocks); + pr_info("Search Area Size in Strides: %u", search_area_size_in_strides); + pr_info("Search Area Size in Pages : %u", search_area_size_in_pages); + + /* Select chip 0. */ + saved_chip_number = mil->current_chip; + nand->select_chip(mtd, 0); + + /* Loop over blocks in the first search area, erasing them. */ + pr_info("Erasing the search area...\n"); + + for (block = 0; block < search_area_size_in_blocks; block++) { + /* Compute the page address. */ + page = block * block_size_in_pages; + + /* Erase this block. */ + pr_info(" Erasing block 0x%x\n", block); + nand->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page); + nand->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1); + + /* Wait for the erase to finish. */ + status = nand->waitfunc(mtd, nand); + if (status & NAND_STATUS_FAIL) + dev_err(dev, "[%s] Erase failed.\n", __func__); + } + + /* Write the NCB fingerprint into the page buffer. */ + memset(buffer, ~0, mtd->writesize); + memset(nand->oob_poi, ~0, mtd->oobsize); + memcpy(buffer + 12, fingerprint, strlen(fingerprint)); + + /* Loop through the first search area, writing NCB fingerprints. */ + pr_info("Writing NCB fingerprints...\n"); + for (stride = 0; stride < search_area_size_in_strides; stride++) { + /* Compute the page and byte addresses. */ + page = stride * rom_geo->stride_size_in_pages; + byte = page * mtd->writesize; + + /* Write the first page of the current stride. */ + pr_info(" Writing an NCB fingerprint in page 0x%x\n", page); + nand->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); + nand->ecc.write_page_raw(mtd, nand, buffer); + nand->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + /* Wait for the write to finish. */ + status = nand->waitfunc(mtd, nand); + if (status & NAND_STATUS_FAIL) + dev_err(dev, "[%s] Write failed.\n", __func__); + } + + /* Deselect chip 0. */ + nand->select_chip(mtd, saved_chip_number); + return 0; +} + +int mx23_boot_init(struct gpmi_nfc_data *this) +{ + struct device *dev = this->dev; + struct mil *mil = &this->mil; + struct nand_chip *nand = &mil->nand; + struct mtd_info *mtd = &mil->mtd; + unsigned int block_count; + unsigned int block; + int chip; + int page; + loff_t byte; + uint8_t block_mark; + int error = 0; + + /* + * If control arrives here, we can't use block mark swapping, which + * means we're forced to use transcription. First, scan for the + * transcription stamp. If we find it, then we don't have to do + * anything -- the block marks are already transcribed. + */ + if (mx23_check_transcription_stamp(this)) + return 0; + + /* + * If control arrives here, we couldn't find a transcription stamp, so + * so we presume the block marks are in the conventional location. + */ + pr_info("Transcribing bad block marks...\n"); + + /* Compute the number of blocks in the entire medium. */ + block_count = nand->chipsize >> nand->phys_erase_shift; + + /* + * Loop over all the blocks in the medium, transcribing block marks as + * we go. + */ + for (block = 0; block < block_count; block++) { + /* + * Compute the chip, page and byte addresses for this block's + * conventional mark. + */ + chip = block >> (nand->chip_shift - nand->phys_erase_shift); + page = block << (nand->phys_erase_shift - nand->page_shift); + byte = block << nand->phys_erase_shift; + + /* Select the chip. */ + nand->select_chip(mtd, chip); + + /* Send the command to read the conventional block mark. */ + nand->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page); + + /* Read the conventional block mark. */ + block_mark = nand->read_byte(mtd); + + /* + * Check if the block is marked bad. If so, we need to mark it + * again, but this time the result will be a mark in the + * location where we transcribe block marks. + * + * Notice that we have to explicitly set the marking_a_bad_block + * member before we call through the block_markbad function + * pointer in the owning struct nand_chip. If we could call + * though the block_markbad function pointer in the owning + * struct mtd_info, which we have hooked, then this would be + * taken care of for us. Unfortunately, we can't because that + * higher-level code path will do things like consulting the + * in-memory bad block table -- which doesn't even exist yet! + * So, we have to call at a lower level and handle some details + * ourselves. + */ + if (block_mark != 0xff) { + pr_info("Transcribing mark in block %u\n", block); + mil->marking_a_bad_block = true; + error = nand->block_markbad(mtd, byte); + mil->marking_a_bad_block = false; + if (error) + dev_err(dev, "Failed to mark block bad with " + "error %d\n", error); + } + + /* Deselect the chip. */ + nand->select_chip(mtd, -1); + } + + /* Write the stamp that indicates we've transcribed the block marks. */ + mx23_write_transcription_stamp(this); + return 0; +} + +static int nand_boot_init(struct gpmi_nfc_data *this) +{ + nand_boot_set_geometry(this); + + /* This is ROM arch-specific initilization before the BBT scanning. */ + if (GPMI_IS_MX23(this)) + return mx23_boot_init(this); + return 0; +} + +static void show_nfc_geometry(struct nfc_geometry *geo) +{ + pr_info("---------------------------------------\n"); + pr_info(" NFC Geometry (used by BCH)\n"); + pr_info("---------------------------------------\n"); + pr_info("ECC Algorithm : %s\n", geo->ecc_algorithm); + pr_info("ECC Strength : %u\n", geo->ecc_strength); + pr_info("Page Size in Bytes : %u\n", geo->page_size_in_bytes); + pr_info("Metadata Size in Bytes : %u\n", geo->metadata_size_in_bytes); + pr_info("ECC Chunk Size in Bytes: %u\n", geo->ecc_chunk_size_in_bytes); + pr_info("ECC Chunk Count : %u\n", geo->ecc_chunk_count); + pr_info("Payload Size in Bytes : %u\n", geo->payload_size_in_bytes); + pr_info("Auxiliary Size in Bytes: %u\n", geo->auxiliary_size_in_bytes); + pr_info("Auxiliary Status Offset: %u\n", geo->auxiliary_status_offset); + pr_info("Block Mark Byte Offset : %u\n", geo->block_mark_byte_offset); + pr_info("Block Mark Bit Offset : %u\n", geo->block_mark_bit_offset); +} + +static int mil_set_geometry(struct gpmi_nfc_data *this) +{ + struct nfc_hal *nfc = this->nfc; + struct nfc_geometry *geo = &this->nfc_geometry; + int error; + + /* Free the temporary DMA memory for read ID case */ + mil_free_dma_buffer(this); + + /* Set up the NFC geometry which is used by BCH. */ + error = nfc->set_geometry(this); + if (error != 0) { + log("NFC set geometry error : %d", error); + return error; + } + if (gpmi_debug & GPMI_DEBUG_INIT) + show_nfc_geometry(geo); + + /* Alloc the new DMA buffers according to the pagesize and oobsize */ + return mil_alloc_dma_buffer(this); +} + +static int mil_pre_bbt_scan(struct gpmi_nfc_data *this) +{ + struct nand_chip *nand = &this->mil.nand; + struct mtd_info *mtd = &this->mil.mtd; + struct nand_ecclayout *layout = nand->ecc.layout; + struct nfc_hal *nfc = this->nfc; + int error; + + /* fix the ECC layout before the scanning */ + layout->eccbytes = 0; + layout->oobavail = mtd->oobsize; + layout->oobfree[0].offset = 0; + layout->oobfree[0].length = mtd->oobsize; + + mtd->oobavail = nand->ecc.layout->oobavail; + + /* Set up swap block-mark, must be set before the mil_set_geometry() */ + if (GPMI_IS_MX23(this)) + this->swap_block_mark = false; + else + this->swap_block_mark = true; + + /* Set up the medium geometry */ + error = mil_set_geometry(this); + if (error) + return error; + + /* extra init */ + if (nfc->extra_init) { + error = nfc->extra_init(this); + if (error != 0) + return error; + } + + /* NAND boot init, depends on the mil_set_geometry(). */ + return nand_boot_init(this); +} + +static int mil_scan_bbt(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nfc_data *this = nand->priv; + int error; + + /* Prepare for the BBT scan. */ + error = mil_pre_bbt_scan(this); + if (error) + return error; + + /* use the default BBT implementation */ + return nand_default_bbt(mtd); +} + +static const char *cmd_parse = "cmdlinepart"; +static int mil_partitions_init(struct gpmi_nfc_data *this) +{ + struct gpmi_nfc_platform_data *pdata = this->pdata; + struct mil *mil = &this->mil; + struct mtd_info *mtd = &mil->mtd; + + /* use the command line for simple partitions layout */ + mil->partition_count = parse_mtd_partitions(mtd, &cmd_parse, + &mil->partitions, 0); + if (mil->partition_count) + return add_mtd_partitions(mtd, mil->partitions, + mil->partition_count); + + /* The complicated partitions layout uses this. */ + if (pdata->partitions && pdata->partition_count > 0) + return add_mtd_partitions(mtd, pdata->partitions, + pdata->partition_count); + return 0; +} + +static void mil_partitions_exit(struct gpmi_nfc_data *this) +{ + struct mil *mil = &this->mil; + + if (mil->partition_count) { + struct mtd_info *mtd = &mil->mtd; + + del_mtd_partitions(mtd); + kfree(mil->partitions); + mil->partition_count = 0; + } +} + +/* Initializes the MTD Interface Layer */ +int gpmi_nfc_mil_init(struct gpmi_nfc_data *this) +{ + struct gpmi_nfc_platform_data *pdata = this->pdata; + struct mil *mil = &this->mil; + struct mtd_info *mtd = &mil->mtd; + struct nand_chip *nand = &mil->nand; + int error; + + /* Initialize MIL data */ + mil->current_chip = -1; + mil->command_length = 0; + mil->page_buffer_virt = NULL; + mil->page_buffer_phys = ~0; + mil->page_buffer_size = 0; + + /* Initialize the MTD data structures */ + mtd->priv = nand; + mtd->name = "gpmi-nfc-main"; + mtd->owner = THIS_MODULE; + nand->priv = this; + + /* Controls */ + nand->select_chip = mil_select_chip; + nand->cmd_ctrl = mil_cmd_ctrl; + nand->dev_ready = mil_dev_ready; + + /* + * Low-level I/O : + * We don't support a 16-bit NAND Flash bus, + * so we don't implement read_word. + */ + nand->read_byte = mil_read_byte; + nand->read_buf = mil_read_buf; + nand->write_buf = mil_write_buf; + + /* ECC-aware I/O */ + nand->ecc.read_page = mil_ecc_read_page; + nand->ecc.write_page = mil_ecc_write_page; + + /* High-level I/O */ + nand->ecc.read_oob = mil_ecc_read_oob; + nand->ecc.write_oob = mil_ecc_write_oob; + + /* Bad Block Management */ + nand->block_bad = mil_block_bad; + nand->scan_bbt = mil_scan_bbt; + nand->badblock_pattern = &gpmi_bbt_descr; + + /* Disallow partial page writes */ + nand->options |= NAND_NO_SUBPAGE_WRITE; + + /* + * Tell the NAND Flash MTD system that we'll be handling ECC with our + * own hardware. It turns out that we still have to fill in the ECC size + * because the MTD code will divide by it -- even though it doesn't + * actually care. + */ + nand->ecc.mode = NAND_ECC_HW; + nand->ecc.size = 1; + + /* use our layout */ + nand->ecc.layout = &mil->oob_layout; + + /* Allocate a temporary DMA buffer for reading ID in the nand_scan() */ + this->nfc_geometry.payload_size_in_bytes = 1024; + this->nfc_geometry.auxiliary_size_in_bytes = 128; + error = mil_alloc_dma_buffer(this); + if (error) + goto exit_dma_allocation; + + pr_info("Scanning for NAND Flash chips...\n"); + error = nand_scan(mtd, pdata->max_chip_count); + if (error) { + log("Chip scan failed"); + goto exit_nand_scan; + } + + /* Take over the management of the OOB */ + mil->hooked_block_markbad = mtd->block_markbad; + mtd->block_markbad = mil_hook_block_markbad; + + /* Construct partitions as necessary. */ + error = mil_partitions_init(this); + if (error) + goto exit_partitions; + return 0; + +exit_partitions: + nand_release(&mil->mtd); +exit_nand_scan: + mil_free_dma_buffer(this); +exit_dma_allocation: + return error; +} + +void gpmi_nfc_mil_exit(struct gpmi_nfc_data *this) +{ + struct mil *mil = &this->mil; + + mil_partitions_exit(this); + nand_release(&mil->mtd); + mil_free_dma_buffer(this); +} +static int gpmi_nfc_probe(struct platform_device *pdev) +{ + struct gpmi_nfc_platform_data *pdata = pdev->dev.platform_data; + struct gpmi_nfc_data *this; + int error; + + this = kzalloc(sizeof(*this), GFP_KERNEL); + if (!this) { + log("Failed to allocate per-device memory\n"); + return -ENOMEM; + } + + /* Set up our data structures. */ + platform_set_drvdata(pdev, this); + this->pdev = pdev; + this->dev = &pdev->dev; + this->pdata = pdata; + + /* Acquire the resources we need. */ + error = acquire_resources(this); + if (error) + goto exit_acquire_resources; + + /* Set up the NFC. */ + error = set_up_nfc_hal(this); + if (error) + goto exit_nfc_init; + + /* Initialize the MTD Interface Layer. */ + error = gpmi_nfc_mil_init(this); + if (error) + goto exit_mil_init; + + manage_sysfs_files(this, true); + return 0; + +exit_mil_init: + exit_nfc_hal(this); +exit_nfc_init: + release_resources(this); +exit_acquire_resources: + platform_set_drvdata(pdev, NULL); + kfree(this); + return error; +} + +static int __exit gpmi_nfc_remove(struct platform_device *pdev) +{ + struct gpmi_nfc_data *this = platform_get_drvdata(pdev); + + manage_sysfs_files(this, false); + gpmi_nfc_mil_exit(this); + exit_nfc_hal(this); + release_resources(this); + platform_set_drvdata(pdev, NULL); + kfree(this); + return 0; +} + +#ifdef CONFIG_PM +static int gpmi_nfc_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} +static int gpmi_nfc_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define gpmi_nfc_suspend NULL +#define gpmi_nfc_resume NULL +#endif + +static const struct platform_device_id gpmi_ids[] = { + { + .name = "imx23-gpmi-nfc", + .driver_data = IS_MX23, + }, { + .name = "imx28-gpmi-nfc", + .driver_data = IS_MX28, + }, {}, +}; + +/* This structure represents this driver to the platform management system. */ +static struct platform_driver gpmi_nfc_driver = { + .driver = { + .name = "gpmi-nfc", + }, + .probe = gpmi_nfc_probe, + .remove = __exit_p(gpmi_nfc_remove), + .suspend = gpmi_nfc_suspend, + .resume = gpmi_nfc_resume, + .id_table = gpmi_ids, +}; + +static int __init gpmi_nfc_init(void) +{ + int err; + + err = platform_driver_register(&gpmi_nfc_driver); + if (err == 0) + printk(KERN_INFO "GPMI NFC driver registered. (IMX)\n"); + else + pr_err("i.MX GPMI NFC driver registration failed\n"); + return err; +} + +static void __exit gpmi_nfc_exit(void) +{ + platform_driver_unregister(&gpmi_nfc_driver); +} + +static int __init gpmi_debug_setup(char *__unused) +{ + gpmi_debug = GPMI_DEBUG_INIT; + return 1; +} +__setup("gpmi_debug_init", gpmi_debug_setup); + +module_init(gpmi_nfc_init); +module_exit(gpmi_nfc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX GPMI NAND Flash Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h new file mode 100644 index 0000000..d0f363e --- /dev/null +++ b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h @@ -0,0 +1,488 @@ +/* + * Freescale GPMI NFC NAND Flash Driver + * + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. + * Copyright (C) 2008 Embedded Alley Solutions, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ +#ifndef __DRIVERS_MTD_NAND_GPMI_NFC_H +#define __DRIVERS_MTD_NAND_GPMI_NFC_H + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/concat.h> +#include <linux/dmaengine.h> +#include <asm/sizes.h> + +#include <mach/mxs.h> +#include <mach/common.h> +#include <mach/dma.h> +#include <mach/gpmi-nfc.h> +#include <mach/system.h> +#include <mach/clock.h> + +/** + * struct resources - The collection of resources the driver needs. + * + * @gpmi_regs: A pointer to the GPMI registers. + * @bch_regs: A pointer to the BCH registers. + * @bch_interrupt: The BCH interrupt number. + * @dma_low_channel: The low DMA channel. + * @dma_high_channel: The high DMA channel. + * @clock: A pointer to the struct clk for the NFC's clock. + */ +struct resources { + void *gpmi_regs; + void *bch_regs; + unsigned int bch_low_interrupt; + unsigned int bch_high_interrupt; + unsigned int dma_low_channel; + unsigned int dma_high_channel; + struct clk *clock; +}; + +/** + * struct mil - State for the MTD Interface Layer. + * + * @nand: The NAND Flash MTD data structure that represents + * the NAND Flash medium. + * @mtd: The MTD data structure that represents the NAND + * Flash medium. + * @oob_layout: A structure that describes how bytes are laid out + * in the OOB. + * @partitions: A pointer to a set of partitions. + * @partition_count: The number of partitions. + * @current_chip: The chip currently selected by the NAND Fash MTD + * code. A negative value indicates that no chip is + * selected. + * @command_length: The length of the command that appears in the + * command buffer (see cmd_virt, below). + * @ignore_bad_block_marks: Indicates we are ignoring bad block marks. + * @saved_bbt: A saved pointer to the in-memory NAND Flash MTD bad + * block table. See show_device_ignorebad() for more + * details. + * @marking_a_bad_block: Indicates the caller is marking a bad block. See + * mil_ecc_write_oob() for details. + * @hooked_block_markbad: A pointer to the block_markbad() function we + * we "hooked." See mil_ecc_write_oob() for details. + * @upper_buf: The buffer passed from upper layer. + * @upper_len: The buffer len passed from upper layer. + * @direct_dma_map_ok: Is the direct DMA map is good for the upper_buf? + * @cmd_sgl/cmd_buffer: For NAND command. + * @data_sgl/data_buffer_dma:For NAND DATA ops. + * @page_buffer_virt: A pointer to a DMA-coherent buffer we use for + * reading and writing pages. This buffer includes + * space for both the payload data and the auxiliary + * data (including status bytes, but not syndrome + * bytes). + * @page_buffer_phys: The physical address for the page_buffer_virt + * buffer. + * @page_buffer_size: The size of the page buffer. + * @payload_virt: A pointer to a location in the page buffer used + * for payload bytes. The size of this buffer is + * determined by struct nfc_geometry. + * @payload_phys: The physical address for payload_virt. + * @auxiliary_virt: A pointer to a location in the page buffer used + * for auxiliary bytes. The size of this buffer is + * determined by struct nfc_geometry. + * @auxiliary_phys: The physical address for auxiliary_virt. + */ +struct mil { + /* MTD Data Structures */ + struct nand_chip nand; + struct mtd_info mtd; + struct nand_ecclayout oob_layout; + + /* Partitions*/ + struct mtd_partition *partitions; + unsigned int partition_count; + + /* General-use Variables */ + int current_chip; + unsigned int command_length; + int ignore_bad_block_marks; + void *saved_bbt; + + /* MTD Function Pointer Hooks */ + int marking_a_bad_block; + int (*hooked_block_markbad)(struct mtd_info *mtd, + loff_t ofs); + + /* from upper layer */ + uint8_t *upper_buf; + int upper_len; + + /* DMA */ + bool direct_dma_map_ok; + + struct scatterlist cmd_sgl; + char *cmd_buffer; + + struct scatterlist data_sgl; + char *data_buffer_dma; + + void *page_buffer_virt; + dma_addr_t page_buffer_phys; + unsigned int page_buffer_size; + + void *payload_virt; + dma_addr_t payload_phys; + + void *auxiliary_virt; + dma_addr_t auxiliary_phys; +}; + +/** + * struct nfc_geometry - NFC geometry description. + * + * This structure describes the NFC's view of the medium geometry. + * + * @ecc_algorithm: The human-readable name of the ECC algorithm + * (e.g., "Reed-Solomon" or "BCH"). + * @ecc_strength: A number that describes the strength of the ECC + * algorithm. + * @page_size_in_bytes: The size, in bytes, of a physical page, including + * both data and OOB. + * @metadata_size_in_bytes: The size, in bytes, of the metadata. + * @ecc_chunk_size_in_bytes: The size, in bytes, of a single ECC chunk. Note + * the first chunk in the page includes both data and + * metadata, so it's a bit larger than this value. + * @ecc_chunk_count: The number of ECC chunks in the page, + * @payload_size_in_bytes: The size, in bytes, of the payload buffer. + * @auxiliary_size_in_bytes: The size, in bytes, of the auxiliary buffer. + * @auxiliary_status_offset: The offset into the auxiliary buffer at which + * the ECC status appears. + * @block_mark_byte_offset: The byte offset in the ECC-based page view at + * which the underlying physical block mark appears. + * @block_mark_bit_offset: The bit offset into the ECC-based page view at + * which the underlying physical block mark appears. + */ +struct nfc_geometry { + char *ecc_algorithm; + unsigned int ecc_strength; + unsigned int page_size_in_bytes; + unsigned int metadata_size_in_bytes; + unsigned int ecc_chunk_size_in_bytes; + unsigned int ecc_chunk_count; + unsigned int payload_size_in_bytes; + unsigned int auxiliary_size_in_bytes; + unsigned int auxiliary_status_offset; + unsigned int block_mark_byte_offset; + unsigned int block_mark_bit_offset; +}; + +/** + * struct boot_rom_geometry - Boot ROM geometry description. + * + * @stride_size_in_pages: The size of a boot block stride, in pages. + * @search_area_stride_exponent: The logarithm to base 2 of the size of a + * search area in boot block strides. + */ +struct boot_rom_geometry { + unsigned int stride_size_in_pages; + unsigned int search_area_stride_exponent; +}; + +/* DMA operations types */ +enum dma_ops_type { + DMA_FOR_COMMAND = 1, + DMA_FOR_READ_DATA, + DMA_FOR_WRITE_DATA, + DMA_FOR_READ_ECC_PAGE, + DMA_FOR_WRITE_ECC_PAGE +}; + +/** + * This structure contains the fundamental timing attributes for NAND. + * + * @data_setup_in_ns: The data setup time, in nanoseconds. Usually the + * maximum of tDS and tWP. A negative value + * indicates this characteristic isn't known. + * @data_hold_in_ns: The data hold time, in nanoseconds. Usually the + * maximum of tDH, tWH and tREH. A negative value + * indicates this characteristic isn't known. + * @address_setup_in_ns: The address setup time, in nanoseconds. Usually + * the maximum of tCLS, tCS and tALS. A negative + * value indicates this characteristic isn't known. + * @gpmi_sample_delay_in_ns: A GPMI-specific timing parameter. A negative value + * indicates this characteristic isn't known. + * @tREA_in_ns: tREA, in nanoseconds, from the data sheet. A + * negative value indicates this characteristic isn't + * known. + * @tRLOH_in_ns: tRLOH, in nanoseconds, from the data sheet. A + * negative value indicates this characteristic isn't + * known. + * @tRHOH_in_ns: tRHOH, in nanoseconds, from the data sheet. A + * negative value indicates this characteristic isn't + * known. + */ +struct nand_timing { + int8_t data_setup_in_ns; + int8_t data_hold_in_ns; + int8_t address_setup_in_ns; + int8_t gpmi_sample_delay_in_ns; + int8_t tREA_in_ns; + int8_t tRLOH_in_ns; + int8_t tRHOH_in_ns; +}; + +/** + * struct gpmi_nfc_data - i.MX NFC per-device data. + * + * @dev: A pointer to the owning struct device. + * @pdev: A pointer to the owning struct platform_device. + * @pdata: A pointer to the device's platform data. + * @resources: Information about system resources used by this driver. + * @device_info: A structure that contains detailed information about + * the NAND Flash device. + * @nfc: A pointer to a structure that represents the underlying + * NFC hardware. + * @nfc_geometry: A description of the medium geometry as viewed by the + * NFC. + * @swap_block_mark: Does it support the swap-block-mark feature? + * Boot ROM. + * @rom_geometry: A description of the medium geometry as viewed by the + * Boot ROM. + * @mil: A collection of information used by the MTD Interface + * Layer. + */ +struct gpmi_nfc_data { + /* System Interface */ + struct device *dev; + struct platform_device *pdev; + struct gpmi_nfc_platform_data *pdata; + + /* Resources */ + struct resources resources; + + /* Flash Hardware */ + struct nand_timing timing; + + /* NFC HAL */ + struct nfc_hal *nfc; + struct nfc_geometry nfc_geometry; + + /* NAND Boot issue */ + bool swap_block_mark; + struct boot_rom_geometry rom_geometry; + + /* MTD Interface Layer */ + struct mil mil; + + /* DMA channels */ +#define DMA_CHANS 8 + struct dma_chan *dma_chans[DMA_CHANS]; + struct mxs_dma_data dma_data; + enum dma_ops_type dma_type; + + /* private */ + void *private; +}; + +/** + * struct gpmi_nfc_hardware_timing - GPMI NFC hardware timing parameters. + * + * This structure contains timing information expressed in a form directly + * usable by the GPMI NFC hardware. + * + * @data_setup_in_cycles: The data setup time, in cycles. + * @data_hold_in_cycles: The data hold time, in cycles. + * @address_setup_in_cycles: The address setup time, in cycles. + * @use_half_periods: Indicates the clock is running slowly, so the + * NFC DLL should use half-periods. + * @sample_delay_factor: The sample delay factor. + */ +struct gpmi_nfc_hardware_timing { + uint8_t data_setup_in_cycles; + uint8_t data_hold_in_cycles; + uint8_t address_setup_in_cycles; + bool use_half_periods; + uint8_t sample_delay_factor; +}; + +/** + * struct nfc_hal - GPMI NFC HAL + * + * @description: description. + * @max_chip_count: The maximum number of chips the NFC can + * possibly support (this value is a constant for + * each NFC version). This may *not* be the actual + * number of chips connected. + * @max_data_setup_cycles: The maximum number of data setup cycles that + * can be expressed in the hardware. + * @internal_data_setup_in_ns: The time, in ns, that the NFC hardware requires + * for data read internal setup. In the Reference + * Manual, see the chapter "High-Speed NAND + * Timing" for more details. + * @max_sample_delay_factor: The maximum sample delay factor that can be + * expressed in the hardware. + * @max_dll_clock_period_in_ns: The maximum period of the GPMI clock that the + * sample delay DLL hardware can possibly work + * with (the DLL is unusable with longer periods). + * If the full-cycle period is greater than HALF + * this value, the DLL must be configured to use + * half-periods. + * @max_dll_delay_in_ns: The maximum amount of delay, in ns, that the + * DLL can implement. + * @dma_descriptors: A pool of DMA descriptors. + * @isr_dma_channel: The DMA channel with which the NFC HAL is + * working. We record this here so the ISR knows + * which DMA channel to acknowledge. + * @dma_done: The completion structure used for DMA + * interrupts. + * @bch_done: The completion structure used for BCH + * interrupts. + * @timing: The current timing configuration. + * @clock_frequency_in_hz: The clock frequency, in Hz, during the current + * I/O transaction. If no I/O transaction is in + * progress, this is the clock frequency during + * the most recent I/O transaction. + * @hardware_timing: The hardware timing configuration in effect + * during the current I/O transaction. If no I/O + * transaction is in progress, this is the + * hardware timing configuration during the most + * recent I/O transaction. + * @init: Initializes the NFC hardware and data + * structures. This function will be called after + * everything has been set up for communication + * with the NFC itself, but before the platform + * has set up off-chip communication. Thus, this + * function must not attempt to communicate with + * the NAND Flash hardware. + * @set_geometry: Configures the NFC hardware and data structures + * to match the physical NAND Flash geometry. + * @set_timing: Configures the NFC hardware and data structures + * to match the given NAND Flash bus timing. + * @get_timing: Returns the the clock frequency, in Hz, and + * the hardware timing configuration during the + * current I/O transaction. If no I/O transaction + * is in progress, this is the timing state during + * the most recent I/O transaction. + * @exit: Shuts down the NFC hardware and data + * structures. This function will be called after + * the platform has shut down off-chip + * communication but while communication with the + * NFC itself still works. + * @clear_bch: Clears a BCH interrupt (intended to be called + * by a more general interrupt handler to do + * device-specific clearing). + * @is_ready: Returns true if the given chip is ready. + * @begin: Begins an interaction with the NFC. This + * function must be called before *any* of the + * following functions so the NFC can prepare + * itself. + * @end: Ends interaction with the NFC. This function + * should be called to give the NFC a chance to, + * among other things, enter a lower-power state. + * @send_command: Sends the given buffer of command bytes. + * @send_data: Sends the given buffer of data bytes. + * @read_data: Reads data bytes into the given buffer. + * @send_page: Sends the given given data and OOB bytes, + * using the ECC engine. + * @read_page: Reads a page through the ECC engine and + * delivers the data and OOB bytes to the given + * buffers. + */ +struct nfc_hal { + /* Hardware attributes. */ + const char *description; + const unsigned int max_chip_count; + const unsigned int max_data_setup_cycles; + const unsigned int internal_data_setup_in_ns; + const unsigned int max_sample_delay_factor; + const unsigned int max_dll_clock_period_in_ns; + const unsigned int max_dll_delay_in_ns; + + int isr_dma_channel; + struct completion dma_done; + struct completion bch_done; + struct nand_timing timing; + unsigned long clock_frequency_in_hz; + + /* Configuration functions. */ + int (*init) (struct gpmi_nfc_data *); + int (*extra_init) (struct gpmi_nfc_data *); + int (*set_geometry)(struct gpmi_nfc_data *); + int (*set_timing) (struct gpmi_nfc_data *, + const struct nand_timing *); + void (*get_timing) (struct gpmi_nfc_data *, + unsigned long *clock_frequency_in_hz, + struct gpmi_nfc_hardware_timing *); + void (*exit) (struct gpmi_nfc_data *); + + /* Call these functions to begin and end I/O. */ + void (*begin) (struct gpmi_nfc_data *); + void (*end) (struct gpmi_nfc_data *); + + /* Call these I/O functions only between begin() and end(). */ + void (*clear_bch) (struct gpmi_nfc_data *); + int (*is_ready) (struct gpmi_nfc_data *, unsigned chip); + int (*send_command)(struct gpmi_nfc_data *); + int (*send_data) (struct gpmi_nfc_data *); + int (*read_data) (struct gpmi_nfc_data *); + int (*send_page) (struct gpmi_nfc_data *, + dma_addr_t payload, dma_addr_t auxiliary); + int (*read_page) (struct gpmi_nfc_data *, + dma_addr_t payload, dma_addr_t auxiliary); +}; + +/* NFC HAL Common Services */ +extern int common_nfc_set_geometry(struct gpmi_nfc_data *this); +extern int gpmi_nfc_compute_hardware_timing(struct gpmi_nfc_data *this, + struct gpmi_nfc_hardware_timing *hw); +extern struct dma_chan *get_dma_chan(struct gpmi_nfc_data *this); +extern void prepare_data_dma(struct gpmi_nfc_data *this, + enum dma_data_direction dr); +extern int start_dma_without_bch_irq(struct gpmi_nfc_data *this, + struct dma_async_tx_descriptor *desc); +extern int start_dma_with_bch_irq(struct gpmi_nfc_data *this, + struct dma_async_tx_descriptor *desc); +/* NFC HAL Structures */ +extern struct nfc_hal gpmi_nfc_hal_imx23_imx28; + +/* for log */ +extern int gpmi_debug; +#define GPMI_DEBUG_INIT 0x0001 +#define GPMI_DEBUG_READ 0x0002 +#define GPMI_DEBUG_WRITE 0x0004 +#define GPMI_DEBUG_ECC_READ 0x0008 +#define GPMI_DEBUG_ECC_WRITE 0x0010 + +#define log(a, ...) printk(KERN_INFO "[ %s : %.3d ] "a"\n", \ + __func__, __LINE__, ## __VA_ARGS__) +#define logio(level) \ + do { \ + if (gpmi_debug & level) \ + log(); \ + } while (0) + +/* BCH : Status Block Completion Codes */ +#define STATUS_GOOD 0x00 +#define STATUS_ERASED 0xff +#define STATUS_UNCORRECTABLE 0xfe + +/* Use the platform_id to distinguish different Archs. */ +#define IS_MX23 0x1 +#define IS_MX28 0x2 +#define GPMI_IS_MX23(x) ((x)->pdev->id_entry->driver_data == IS_MX23) +#define GPMI_IS_MX28(x) ((x)->pdev->id_entry->driver_data == IS_MX28) +#endif -- 1.7.0.4 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v6 1/3] MTD : add the common code for GPMI controller driver 2011-04-20 2:45 ` Huang Shijie @ 2011-04-21 7:49 ` Lothar Waßmann -1 siblings, 0 replies; 20+ messages in thread From: Lothar Waßmann @ 2011-04-21 7:49 UTC (permalink / raw) To: Huang Shijie; +Cc: linux-mtd, linux-arm-kernel, dedekind1 Huang Shijie writes: > These files contain the common code for the GPMI driver. > > Signed-off-by: Huang Shijie <b32955@freescale.com> > --- > drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c | 2501 ++++++++++++++++++++++++++++++++++ > drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h | 488 +++++++ > 2 files changed, 2989 insertions(+), 0 deletions(-) > create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c > create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h > > diff --git a/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c > new file mode 100644 > index 0000000..53d6915 > --- /dev/null > +++ b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c > @@ -0,0 +1,2501 @@ > +/* > + * Freescale GPMI NFC NAND Flash Driver > + * > + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. > + * Copyright (C) 2008 Embedded Alley Solutions, Inc. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * 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., > + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. > + */ > +#include <linux/slab.h> > +#include "gpmi-nfc.h" > + > +/* add our owner bbt descriptor */ > +static uint8_t scan_ff_pattern[] = { 0xff }; > +static struct nand_bbt_descr gpmi_bbt_descr = { > + .options = 0, > + .offs = 0, > + .len = 1, > + .pattern = scan_ff_pattern > +}; > + > +/* debug control */ > +int gpmi_debug; > + This could be a module_param. > +static ssize_t show_gpmi_debug(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + return sprintf(buf, "%d\n", gpmi_debug); > +} > + > +static ssize_t > +store_gpmi_debug(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t size) > +{ > + const char *p = buf; > + unsigned long v; > + > + if (strict_strtoul(p, 0, &v) < 0) > + return size; > + > + gpmi_debug = v; > + return size; > +} > + > +static ssize_t show_ignorebad(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct gpmi_nfc_data *this = dev_get_drvdata(dev); > + struct mil *mil = &this->mil; > + > + return sprintf(buf, "%d\n", mil->ignore_bad_block_marks); > +} > + > +static ssize_t > +store_ignorebad(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t size) > +{ > + struct gpmi_nfc_data *this = dev_get_drvdata(dev); > + struct mil *mil = &this->mil; > + const char *p = buf; > + unsigned long v; > + > + if (strict_strtoul(p, 0, &v) < 0) > + return size; > return -EINVAL would be more appropriate here. > + > + if (v > 0) > + v = 1; > + > + if (v != mil->ignore_bad_block_marks) { > These lines could be reduced to: if (!v == mil->ignore_bad_block_marks) { > + if (v) { > + /* > + * This will cause the NAND Flash MTD code to believe > + * that it never created a BBT and force it to call our > + * block_bad function. > + * > + * See mil_block_bad for more details. > + */ > + mil->saved_bbt = mil->nand.bbt; > + mil->nand.bbt = NULL; > + } else { > + /* > + * Restore the NAND Flash MTD's pointer > + * to its in-memory BBT. > + */ > + mil->nand.bbt = mil->saved_bbt; > + } > + mil->ignore_bad_block_marks = v; > + } > + return size; > +} > + [...] > +/* This will be called after the DMA operation is finished. */ > +static void dma_irq_callback(void *param) > +{ > + struct gpmi_nfc_data *this = param; > + struct nfc_hal *nfc = this->nfc; > + struct mil *mil = &this->mil; > + > + complete(&nfc->dma_done); > + > + switch (this->dma_type) { > + case DMA_FOR_COMMAND: > + dma_unmap_sg(this->dev, &mil->cmd_sgl, 1, DMA_TO_DEVICE); > + break; > + > + case DMA_FOR_READ_DATA: > + dma_unmap_sg(this->dev, &mil->data_sgl, 1, DMA_FROM_DEVICE); > + if (mil->direct_dma_map_ok == false) > + memcpy(mil->upper_buf, (char *)mil->data_buffer_dma, > pointless cast. [...] > +static int acquire_dma_channels(struct gpmi_nfc_data *this, > + const char *resource_name, > + unsigned *low_channel, unsigned *high_channel) > +{ > + struct platform_device *pdev = this->pdev; struct gpmi_nfc_platform_data *pdata = pdev->dev.platform_data; see below. > + struct resource *r, *r_dma; > + unsigned int i; > + > + r = platform_get_resource_byname(pdev, IORESOURCE_DMA, resource_name); > + r_dma = platform_get_resource_byname(pdev, IORESOURCE_IRQ, > + GPMI_NFC_DMA_INTERRUPT_RES_NAME); > + if (!r || !r_dma) { > + log("Can't get resource for DMA"); > + return -ENXIO; > + } > + > + /* used in gpmi_dma_filter() */ > + this->private = r; > + > + for (i = r->start; i <= r->end; i++) { > + dma_cap_mask_t mask; > + struct dma_chan *dma_chan; > + if (i - r->start >= pdata->max_chip_count) break; > + dma_cap_zero(mask); > + dma_cap_set(DMA_SLAVE, mask); > + > + /* get the DMA interrupt */ > + this->dma_data.chan_irq = r_dma->start + > + ((r_dma->start != r_dma->end) ? (i - r->start) : 0); > + > + dma_chan = dma_request_channel(mask, gpmi_dma_filter, this); > + if (!dma_chan) > + goto acquire_err; > + /* fill the first empty item */ > + this->dma_chans[i - r->start] = dma_chan; > + } > + > + *low_channel = r->start; > + *high_channel = r->end; *high_channel = i; This will acquire only those DMA channels that are actually needed. [...] > +static int read_page_prepare(struct gpmi_nfc_data *this, > + void *destination, unsigned length, > + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, > + void **use_virt, dma_addr_t *use_phys) > +{ > + struct device *dev = this->dev; > + dma_addr_t destination_phys = ~0; > + > + if (virt_addr_valid(destination)) > + destination_phys = dma_map_single(dev, (void *)destination, > pointless cast. [...] > +static int mil_alloc_dma_buffer(struct gpmi_nfc_data *this) > +{ > + struct nfc_geometry *geo = &this->nfc_geometry; > + struct device *dev = this->dev; > + struct mil *mil = &this->mil; > + > + /* [1] Allocate a command buffer. PAGE_SIZE is enough. */ > + mil->cmd_buffer = kzalloc(PAGE_SIZE, GFP_DMA); > + if (mil->cmd_buffer == NULL) > + goto error_alloc; > + > + /* [2] Allocate a read/write data buffer. PAGE_SIZE is enough. */ > + mil->data_buffer_dma = kzalloc(PAGE_SIZE, GFP_DMA); > + if (mil->data_buffer_dma == NULL) > + goto error_alloc; > + > + /* > + * [3] Allocate the page buffer. > + * > + * Both the payload buffer and the auxiliary buffer must appear on > + * 32-bit boundaries. We presume the size of the payload buffer is a > + * power of two and is much larger than four, which guarantees the > + * auxiliary buffer will appear on a 32-bit boundary. > + */ > + mil->page_buffer_size = geo->payload_size_in_bytes + > + geo->auxiliary_size_in_bytes; > + > + mil->page_buffer_virt = dma_alloc_coherent(dev, mil->page_buffer_size, > + &mil->page_buffer_phys, GFP_DMA); > + if (!mil->page_buffer_virt) > + goto error_alloc; > + > + > + /* Slice up the page buffer. */ > + mil->payload_virt = mil->page_buffer_virt; > + mil->payload_phys = mil->page_buffer_phys; > + mil->auxiliary_virt = ((char *) mil->payload_virt) + > unnecessary cast. [...] The functions from here thru gpmi_nfc_probe could be marked __devinit, so they can be discarded after boot. > +static int nand_boot_set_geometry(struct gpmi_nfc_data *this) > +{ > + struct boot_rom_geometry *geometry = &this->rom_geometry; > + > + /* > + * Set the boot block stride size. > + * > + * In principle, we should be reading this from the OTP bits, since > + * that's where the ROM is going to get it. In fact, we don't have any > + * way to read the OTP bits, so we go with the default and hope for the > + * best. > + */ > + geometry->stride_size_in_pages = 64; > + > + /* > + * Set the search area stride exponent. > + * > + * In principle, we should be reading this from the OTP bits, since > + * that's where the ROM is going to get it. In fact, we don't have any > + * way to read the OTP bits, so we go with the default and hope for the > + * best. > + */ > + geometry->search_area_stride_exponent = 2; > + > + if (gpmi_debug & GPMI_DEBUG_INIT) > + log("stride size in page : %d, search areas : %d", > + geometry->stride_size_in_pages, > + geometry->search_area_stride_exponent); > + return 0; > +} > + > +static const char *fingerprint = "STMP"; > +static int mx23_check_transcription_stamp(struct gpmi_nfc_data *this) > +{ > + struct boot_rom_geometry *rom_geo = &this->rom_geometry; > + struct mil *mil = &this->mil; > + struct mtd_info *mtd = &mil->mtd; > + struct nand_chip *nand = &mil->nand; > + unsigned int search_area_size_in_strides; > + unsigned int stride; > + unsigned int page; > + loff_t byte; > + uint8_t *buffer = nand->buffers->databuf; > + int saved_chip_number; > + int found_an_ncb_fingerprint = false; > + > + /* Compute the number of strides in a search area. */ > + search_area_size_in_strides = 1 << rom_geo->search_area_stride_exponent; > + > + /* Select chip 0. */ > + saved_chip_number = mil->current_chip; > + nand->select_chip(mtd, 0); > + > + /* > + * Loop through the first search area, looking for the NCB fingerprint. > + */ > + pr_info("Scanning for an NCB fingerprint...\n"); > + > + for (stride = 0; stride < search_area_size_in_strides; stride++) { > + /* Compute the page and byte addresses. */ > + page = stride * rom_geo->stride_size_in_pages; > + byte = page * mtd->writesize; > + > + pr_info(" Looking for a fingerprint in page 0x%x\n", page); > + > + /* > + * Read the NCB fingerprint. The fingerprint is four bytes long > + * and starts in the 12th byte of the page. > + */ > + nand->cmdfunc(mtd, NAND_CMD_READ0, 12, page); > + nand->read_buf(mtd, buffer, strlen(fingerprint)); > + > + /* Look for the fingerprint. */ > + if (!memcmp(buffer, fingerprint, strlen(fingerprint))) { > + found_an_ncb_fingerprint = true; > + break; > + } > + > + } > + > + /* Deselect chip 0. */ > + nand->select_chip(mtd, saved_chip_number); > + > + if (found_an_ncb_fingerprint) > + pr_info(" Found a fingerprint\n"); > + else > + pr_info(" No fingerprint found\n"); > + return found_an_ncb_fingerprint; > +} > + > +/* Writes a transcription stamp. */ > +static int mx23_write_transcription_stamp(struct gpmi_nfc_data *this) > +{ > + struct device *dev = this->dev; > + struct boot_rom_geometry *rom_geo = &this->rom_geometry; > + struct mil *mil = &this->mil; > + struct mtd_info *mtd = &mil->mtd; > + struct nand_chip *nand = &mil->nand; > + unsigned int block_size_in_pages; > + unsigned int search_area_size_in_strides; > + unsigned int search_area_size_in_pages; > + unsigned int search_area_size_in_blocks; > + unsigned int block; > + unsigned int stride; > + unsigned int page; > + loff_t byte; > + uint8_t *buffer = nand->buffers->databuf; > + int saved_chip_number; > + int status; > + > + /* Compute the search area geometry. */ > + block_size_in_pages = mtd->erasesize / mtd->writesize; > + search_area_size_in_strides = 1 << rom_geo->search_area_stride_exponent; > + search_area_size_in_pages = search_area_size_in_strides * > + rom_geo->stride_size_in_pages; > + search_area_size_in_blocks = > + (search_area_size_in_pages + (block_size_in_pages - 1)) / > + block_size_in_pages; > + > + pr_info("-------------------------------------------\n"); > + pr_info("Search Area Geometry\n"); > + pr_info("-------------------------------------------\n"); > + pr_info("Search Area Size in Blocks : %u", search_area_size_in_blocks); > + pr_info("Search Area Size in Strides: %u", search_area_size_in_strides); > + pr_info("Search Area Size in Pages : %u", search_area_size_in_pages); > + > + /* Select chip 0. */ > + saved_chip_number = mil->current_chip; > + nand->select_chip(mtd, 0); > + > + /* Loop over blocks in the first search area, erasing them. */ > + pr_info("Erasing the search area...\n"); > + > + for (block = 0; block < search_area_size_in_blocks; block++) { > + /* Compute the page address. */ > + page = block * block_size_in_pages; > + > + /* Erase this block. */ > + pr_info(" Erasing block 0x%x\n", block); > + nand->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page); > + nand->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1); > + > + /* Wait for the erase to finish. */ > + status = nand->waitfunc(mtd, nand); > + if (status & NAND_STATUS_FAIL) > + dev_err(dev, "[%s] Erase failed.\n", __func__); > + } > + > + /* Write the NCB fingerprint into the page buffer. */ > + memset(buffer, ~0, mtd->writesize); > + memset(nand->oob_poi, ~0, mtd->oobsize); > + memcpy(buffer + 12, fingerprint, strlen(fingerprint)); > + > + /* Loop through the first search area, writing NCB fingerprints. */ > + pr_info("Writing NCB fingerprints...\n"); > + for (stride = 0; stride < search_area_size_in_strides; stride++) { > + /* Compute the page and byte addresses. */ > + page = stride * rom_geo->stride_size_in_pages; > + byte = page * mtd->writesize; > + > + /* Write the first page of the current stride. */ > + pr_info(" Writing an NCB fingerprint in page 0x%x\n", page); > + nand->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); > + nand->ecc.write_page_raw(mtd, nand, buffer); > + nand->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); > + > + /* Wait for the write to finish. */ > + status = nand->waitfunc(mtd, nand); > + if (status & NAND_STATUS_FAIL) > + dev_err(dev, "[%s] Write failed.\n", __func__); > + } > + > + /* Deselect chip 0. */ > + nand->select_chip(mtd, saved_chip_number); > + return 0; > +} > + > +int mx23_boot_init(struct gpmi_nfc_data *this) > +{ > + struct device *dev = this->dev; > + struct mil *mil = &this->mil; > + struct nand_chip *nand = &mil->nand; > + struct mtd_info *mtd = &mil->mtd; > + unsigned int block_count; > + unsigned int block; > + int chip; > + int page; > + loff_t byte; > + uint8_t block_mark; > + int error = 0; > + > + /* > + * If control arrives here, we can't use block mark swapping, which > + * means we're forced to use transcription. First, scan for the > + * transcription stamp. If we find it, then we don't have to do > + * anything -- the block marks are already transcribed. > + */ > + if (mx23_check_transcription_stamp(this)) > + return 0; > + > + /* > + * If control arrives here, we couldn't find a transcription stamp, so > + * so we presume the block marks are in the conventional location. > + */ > + pr_info("Transcribing bad block marks...\n"); > + > + /* Compute the number of blocks in the entire medium. */ > + block_count = nand->chipsize >> nand->phys_erase_shift; > + > + /* > + * Loop over all the blocks in the medium, transcribing block marks as > + * we go. > + */ > + for (block = 0; block < block_count; block++) { > + /* > + * Compute the chip, page and byte addresses for this block's > + * conventional mark. > + */ > + chip = block >> (nand->chip_shift - nand->phys_erase_shift); > + page = block << (nand->phys_erase_shift - nand->page_shift); > + byte = block << nand->phys_erase_shift; > + > + /* Select the chip. */ > + nand->select_chip(mtd, chip); > + > + /* Send the command to read the conventional block mark. */ > + nand->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page); > + > + /* Read the conventional block mark. */ > + block_mark = nand->read_byte(mtd); > + > + /* > + * Check if the block is marked bad. If so, we need to mark it > + * again, but this time the result will be a mark in the > + * location where we transcribe block marks. > + * > + * Notice that we have to explicitly set the marking_a_bad_block > + * member before we call through the block_markbad function > + * pointer in the owning struct nand_chip. If we could call > + * though the block_markbad function pointer in the owning > + * struct mtd_info, which we have hooked, then this would be > + * taken care of for us. Unfortunately, we can't because that > + * higher-level code path will do things like consulting the > + * in-memory bad block table -- which doesn't even exist yet! > + * So, we have to call at a lower level and handle some details > + * ourselves. > + */ > + if (block_mark != 0xff) { > + pr_info("Transcribing mark in block %u\n", block); > + mil->marking_a_bad_block = true; > + error = nand->block_markbad(mtd, byte); > + mil->marking_a_bad_block = false; > + if (error) > + dev_err(dev, "Failed to mark block bad with " > + "error %d\n", error); > + } > + > + /* Deselect the chip. */ > + nand->select_chip(mtd, -1); > + } > + > + /* Write the stamp that indicates we've transcribed the block marks. */ > + mx23_write_transcription_stamp(this); > + return 0; > +} > + > +static int nand_boot_init(struct gpmi_nfc_data *this) > +{ > + nand_boot_set_geometry(this); > + > + /* This is ROM arch-specific initilization before the BBT scanning. */ > + if (GPMI_IS_MX23(this)) > + return mx23_boot_init(this); > + return 0; > +} > + > +static void show_nfc_geometry(struct nfc_geometry *geo) > +{ > + pr_info("---------------------------------------\n"); > + pr_info(" NFC Geometry (used by BCH)\n"); > + pr_info("---------------------------------------\n"); > + pr_info("ECC Algorithm : %s\n", geo->ecc_algorithm); > + pr_info("ECC Strength : %u\n", geo->ecc_strength); > + pr_info("Page Size in Bytes : %u\n", geo->page_size_in_bytes); > + pr_info("Metadata Size in Bytes : %u\n", geo->metadata_size_in_bytes); > + pr_info("ECC Chunk Size in Bytes: %u\n", geo->ecc_chunk_size_in_bytes); > + pr_info("ECC Chunk Count : %u\n", geo->ecc_chunk_count); > + pr_info("Payload Size in Bytes : %u\n", geo->payload_size_in_bytes); > + pr_info("Auxiliary Size in Bytes: %u\n", geo->auxiliary_size_in_bytes); > + pr_info("Auxiliary Status Offset: %u\n", geo->auxiliary_status_offset); > + pr_info("Block Mark Byte Offset : %u\n", geo->block_mark_byte_offset); > + pr_info("Block Mark Bit Offset : %u\n", geo->block_mark_bit_offset); > +} > + > +static int mil_set_geometry(struct gpmi_nfc_data *this) > +{ > + struct nfc_hal *nfc = this->nfc; > + struct nfc_geometry *geo = &this->nfc_geometry; > + int error; > + > + /* Free the temporary DMA memory for read ID case */ > + mil_free_dma_buffer(this); > + > + /* Set up the NFC geometry which is used by BCH. */ > + error = nfc->set_geometry(this); > + if (error != 0) { > + log("NFC set geometry error : %d", error); > + return error; > + } > + if (gpmi_debug & GPMI_DEBUG_INIT) > + show_nfc_geometry(geo); > + > + /* Alloc the new DMA buffers according to the pagesize and oobsize */ > + return mil_alloc_dma_buffer(this); > +} > + > +static int mil_pre_bbt_scan(struct gpmi_nfc_data *this) > +{ > + struct nand_chip *nand = &this->mil.nand; > + struct mtd_info *mtd = &this->mil.mtd; > + struct nand_ecclayout *layout = nand->ecc.layout; > + struct nfc_hal *nfc = this->nfc; > + int error; > + > + /* fix the ECC layout before the scanning */ > + layout->eccbytes = 0; > + layout->oobavail = mtd->oobsize; > + layout->oobfree[0].offset = 0; > + layout->oobfree[0].length = mtd->oobsize; > + > + mtd->oobavail = nand->ecc.layout->oobavail; > + > + /* Set up swap block-mark, must be set before the mil_set_geometry() */ > + if (GPMI_IS_MX23(this)) > + this->swap_block_mark = false; > + else > + this->swap_block_mark = true; > + > + /* Set up the medium geometry */ > + error = mil_set_geometry(this); > + if (error) > + return error; > + > + /* extra init */ > + if (nfc->extra_init) { > + error = nfc->extra_init(this); > + if (error != 0) > + return error; > + } > + > + /* NAND boot init, depends on the mil_set_geometry(). */ > + return nand_boot_init(this); > +} > + > +static int mil_scan_bbt(struct mtd_info *mtd) > +{ > + struct nand_chip *nand = mtd->priv; > + struct gpmi_nfc_data *this = nand->priv; > + int error; > + > + /* Prepare for the BBT scan. */ > + error = mil_pre_bbt_scan(this); > + if (error) > + return error; > + > + /* use the default BBT implementation */ > + return nand_default_bbt(mtd); > +} > + > +static const char *cmd_parse = "cmdlinepart"; > This should be a NULL terminated list of strings: static const char *cmd_parse[] = { "cmdlinepart", NULL }; > +static int mil_partitions_init(struct gpmi_nfc_data *this) > +{ > + struct gpmi_nfc_platform_data *pdata = this->pdata; > + struct mil *mil = &this->mil; > + struct mtd_info *mtd = &mil->mtd; > + > + /* use the command line for simple partitions layout */ > + mil->partition_count = parse_mtd_partitions(mtd, &cmd_parse, > + &mil->partitions, 0); > + if (mil->partition_count) > + return add_mtd_partitions(mtd, mil->partitions, > + mil->partition_count); > + > + /* The complicated partitions layout uses this. */ > + if (pdata->partitions && pdata->partition_count > 0) > + return add_mtd_partitions(mtd, pdata->partitions, > + pdata->partition_count); > + return 0; How about: return mtd_add_device(mtd); so you will get the whole flash registered in case there are no partitions defined. > +#ifdef CONFIG_PM > +static int gpmi_nfc_suspend(struct platform_device *pdev, pm_message_t state) > +{ > + return 0; > +} > +static int gpmi_nfc_resume(struct platform_device *pdev) > +{ > + return 0; > +} > +#else > +#define gpmi_nfc_suspend NULL > +#define gpmi_nfc_resume NULL > +#endif > + There is no point in adding empty suspend/resume functions. Furthermore you should use dev_pm_ops. Lothar Waßmann -- ___________________________________________________________ Ka-Ro electronics GmbH | Pascalstraße 22 | D - 52076 Aachen Phone: +49 2408 1402-0 | Fax: +49 2408 1402-10 Geschäftsführer: Matthias Kaussen Handelsregistereintrag: Amtsgericht Aachen, HRB 4996 www.karo-electronics.de | info@karo-electronics.de ___________________________________________________________ ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 1/3] MTD : add the common code for GPMI controller driver @ 2011-04-21 7:49 ` Lothar Waßmann 0 siblings, 0 replies; 20+ messages in thread From: Lothar Waßmann @ 2011-04-21 7:49 UTC (permalink / raw) To: linux-arm-kernel Huang Shijie writes: > These files contain the common code for the GPMI driver. > > Signed-off-by: Huang Shijie <b32955@freescale.com> > --- > drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c | 2501 ++++++++++++++++++++++++++++++++++ > drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h | 488 +++++++ > 2 files changed, 2989 insertions(+), 0 deletions(-) > create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c > create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h > > diff --git a/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c > new file mode 100644 > index 0000000..53d6915 > --- /dev/null > +++ b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c > @@ -0,0 +1,2501 @@ > +/* > + * Freescale GPMI NFC NAND Flash Driver > + * > + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. > + * Copyright (C) 2008 Embedded Alley Solutions, Inc. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * 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., > + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. > + */ > +#include <linux/slab.h> > +#include "gpmi-nfc.h" > + > +/* add our owner bbt descriptor */ > +static uint8_t scan_ff_pattern[] = { 0xff }; > +static struct nand_bbt_descr gpmi_bbt_descr = { > + .options = 0, > + .offs = 0, > + .len = 1, > + .pattern = scan_ff_pattern > +}; > + > +/* debug control */ > +int gpmi_debug; > + This could be a module_param. > +static ssize_t show_gpmi_debug(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + return sprintf(buf, "%d\n", gpmi_debug); > +} > + > +static ssize_t > +store_gpmi_debug(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t size) > +{ > + const char *p = buf; > + unsigned long v; > + > + if (strict_strtoul(p, 0, &v) < 0) > + return size; > + > + gpmi_debug = v; > + return size; > +} > + > +static ssize_t show_ignorebad(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct gpmi_nfc_data *this = dev_get_drvdata(dev); > + struct mil *mil = &this->mil; > + > + return sprintf(buf, "%d\n", mil->ignore_bad_block_marks); > +} > + > +static ssize_t > +store_ignorebad(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t size) > +{ > + struct gpmi_nfc_data *this = dev_get_drvdata(dev); > + struct mil *mil = &this->mil; > + const char *p = buf; > + unsigned long v; > + > + if (strict_strtoul(p, 0, &v) < 0) > + return size; > return -EINVAL would be more appropriate here. > + > + if (v > 0) > + v = 1; > + > + if (v != mil->ignore_bad_block_marks) { > These lines could be reduced to: if (!v == mil->ignore_bad_block_marks) { > + if (v) { > + /* > + * This will cause the NAND Flash MTD code to believe > + * that it never created a BBT and force it to call our > + * block_bad function. > + * > + * See mil_block_bad for more details. > + */ > + mil->saved_bbt = mil->nand.bbt; > + mil->nand.bbt = NULL; > + } else { > + /* > + * Restore the NAND Flash MTD's pointer > + * to its in-memory BBT. > + */ > + mil->nand.bbt = mil->saved_bbt; > + } > + mil->ignore_bad_block_marks = v; > + } > + return size; > +} > + [...] > +/* This will be called after the DMA operation is finished. */ > +static void dma_irq_callback(void *param) > +{ > + struct gpmi_nfc_data *this = param; > + struct nfc_hal *nfc = this->nfc; > + struct mil *mil = &this->mil; > + > + complete(&nfc->dma_done); > + > + switch (this->dma_type) { > + case DMA_FOR_COMMAND: > + dma_unmap_sg(this->dev, &mil->cmd_sgl, 1, DMA_TO_DEVICE); > + break; > + > + case DMA_FOR_READ_DATA: > + dma_unmap_sg(this->dev, &mil->data_sgl, 1, DMA_FROM_DEVICE); > + if (mil->direct_dma_map_ok == false) > + memcpy(mil->upper_buf, (char *)mil->data_buffer_dma, > pointless cast. [...] > +static int acquire_dma_channels(struct gpmi_nfc_data *this, > + const char *resource_name, > + unsigned *low_channel, unsigned *high_channel) > +{ > + struct platform_device *pdev = this->pdev; struct gpmi_nfc_platform_data *pdata = pdev->dev.platform_data; see below. > + struct resource *r, *r_dma; > + unsigned int i; > + > + r = platform_get_resource_byname(pdev, IORESOURCE_DMA, resource_name); > + r_dma = platform_get_resource_byname(pdev, IORESOURCE_IRQ, > + GPMI_NFC_DMA_INTERRUPT_RES_NAME); > + if (!r || !r_dma) { > + log("Can't get resource for DMA"); > + return -ENXIO; > + } > + > + /* used in gpmi_dma_filter() */ > + this->private = r; > + > + for (i = r->start; i <= r->end; i++) { > + dma_cap_mask_t mask; > + struct dma_chan *dma_chan; > + if (i - r->start >= pdata->max_chip_count) break; > + dma_cap_zero(mask); > + dma_cap_set(DMA_SLAVE, mask); > + > + /* get the DMA interrupt */ > + this->dma_data.chan_irq = r_dma->start + > + ((r_dma->start != r_dma->end) ? (i - r->start) : 0); > + > + dma_chan = dma_request_channel(mask, gpmi_dma_filter, this); > + if (!dma_chan) > + goto acquire_err; > + /* fill the first empty item */ > + this->dma_chans[i - r->start] = dma_chan; > + } > + > + *low_channel = r->start; > + *high_channel = r->end; *high_channel = i; This will acquire only those DMA channels that are actually needed. [...] > +static int read_page_prepare(struct gpmi_nfc_data *this, > + void *destination, unsigned length, > + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, > + void **use_virt, dma_addr_t *use_phys) > +{ > + struct device *dev = this->dev; > + dma_addr_t destination_phys = ~0; > + > + if (virt_addr_valid(destination)) > + destination_phys = dma_map_single(dev, (void *)destination, > pointless cast. [...] > +static int mil_alloc_dma_buffer(struct gpmi_nfc_data *this) > +{ > + struct nfc_geometry *geo = &this->nfc_geometry; > + struct device *dev = this->dev; > + struct mil *mil = &this->mil; > + > + /* [1] Allocate a command buffer. PAGE_SIZE is enough. */ > + mil->cmd_buffer = kzalloc(PAGE_SIZE, GFP_DMA); > + if (mil->cmd_buffer == NULL) > + goto error_alloc; > + > + /* [2] Allocate a read/write data buffer. PAGE_SIZE is enough. */ > + mil->data_buffer_dma = kzalloc(PAGE_SIZE, GFP_DMA); > + if (mil->data_buffer_dma == NULL) > + goto error_alloc; > + > + /* > + * [3] Allocate the page buffer. > + * > + * Both the payload buffer and the auxiliary buffer must appear on > + * 32-bit boundaries. We presume the size of the payload buffer is a > + * power of two and is much larger than four, which guarantees the > + * auxiliary buffer will appear on a 32-bit boundary. > + */ > + mil->page_buffer_size = geo->payload_size_in_bytes + > + geo->auxiliary_size_in_bytes; > + > + mil->page_buffer_virt = dma_alloc_coherent(dev, mil->page_buffer_size, > + &mil->page_buffer_phys, GFP_DMA); > + if (!mil->page_buffer_virt) > + goto error_alloc; > + > + > + /* Slice up the page buffer. */ > + mil->payload_virt = mil->page_buffer_virt; > + mil->payload_phys = mil->page_buffer_phys; > + mil->auxiliary_virt = ((char *) mil->payload_virt) + > unnecessary cast. [...] The functions from here thru gpmi_nfc_probe could be marked __devinit, so they can be discarded after boot. > +static int nand_boot_set_geometry(struct gpmi_nfc_data *this) > +{ > + struct boot_rom_geometry *geometry = &this->rom_geometry; > + > + /* > + * Set the boot block stride size. > + * > + * In principle, we should be reading this from the OTP bits, since > + * that's where the ROM is going to get it. In fact, we don't have any > + * way to read the OTP bits, so we go with the default and hope for the > + * best. > + */ > + geometry->stride_size_in_pages = 64; > + > + /* > + * Set the search area stride exponent. > + * > + * In principle, we should be reading this from the OTP bits, since > + * that's where the ROM is going to get it. In fact, we don't have any > + * way to read the OTP bits, so we go with the default and hope for the > + * best. > + */ > + geometry->search_area_stride_exponent = 2; > + > + if (gpmi_debug & GPMI_DEBUG_INIT) > + log("stride size in page : %d, search areas : %d", > + geometry->stride_size_in_pages, > + geometry->search_area_stride_exponent); > + return 0; > +} > + > +static const char *fingerprint = "STMP"; > +static int mx23_check_transcription_stamp(struct gpmi_nfc_data *this) > +{ > + struct boot_rom_geometry *rom_geo = &this->rom_geometry; > + struct mil *mil = &this->mil; > + struct mtd_info *mtd = &mil->mtd; > + struct nand_chip *nand = &mil->nand; > + unsigned int search_area_size_in_strides; > + unsigned int stride; > + unsigned int page; > + loff_t byte; > + uint8_t *buffer = nand->buffers->databuf; > + int saved_chip_number; > + int found_an_ncb_fingerprint = false; > + > + /* Compute the number of strides in a search area. */ > + search_area_size_in_strides = 1 << rom_geo->search_area_stride_exponent; > + > + /* Select chip 0. */ > + saved_chip_number = mil->current_chip; > + nand->select_chip(mtd, 0); > + > + /* > + * Loop through the first search area, looking for the NCB fingerprint. > + */ > + pr_info("Scanning for an NCB fingerprint...\n"); > + > + for (stride = 0; stride < search_area_size_in_strides; stride++) { > + /* Compute the page and byte addresses. */ > + page = stride * rom_geo->stride_size_in_pages; > + byte = page * mtd->writesize; > + > + pr_info(" Looking for a fingerprint in page 0x%x\n", page); > + > + /* > + * Read the NCB fingerprint. The fingerprint is four bytes long > + * and starts in the 12th byte of the page. > + */ > + nand->cmdfunc(mtd, NAND_CMD_READ0, 12, page); > + nand->read_buf(mtd, buffer, strlen(fingerprint)); > + > + /* Look for the fingerprint. */ > + if (!memcmp(buffer, fingerprint, strlen(fingerprint))) { > + found_an_ncb_fingerprint = true; > + break; > + } > + > + } > + > + /* Deselect chip 0. */ > + nand->select_chip(mtd, saved_chip_number); > + > + if (found_an_ncb_fingerprint) > + pr_info(" Found a fingerprint\n"); > + else > + pr_info(" No fingerprint found\n"); > + return found_an_ncb_fingerprint; > +} > + > +/* Writes a transcription stamp. */ > +static int mx23_write_transcription_stamp(struct gpmi_nfc_data *this) > +{ > + struct device *dev = this->dev; > + struct boot_rom_geometry *rom_geo = &this->rom_geometry; > + struct mil *mil = &this->mil; > + struct mtd_info *mtd = &mil->mtd; > + struct nand_chip *nand = &mil->nand; > + unsigned int block_size_in_pages; > + unsigned int search_area_size_in_strides; > + unsigned int search_area_size_in_pages; > + unsigned int search_area_size_in_blocks; > + unsigned int block; > + unsigned int stride; > + unsigned int page; > + loff_t byte; > + uint8_t *buffer = nand->buffers->databuf; > + int saved_chip_number; > + int status; > + > + /* Compute the search area geometry. */ > + block_size_in_pages = mtd->erasesize / mtd->writesize; > + search_area_size_in_strides = 1 << rom_geo->search_area_stride_exponent; > + search_area_size_in_pages = search_area_size_in_strides * > + rom_geo->stride_size_in_pages; > + search_area_size_in_blocks = > + (search_area_size_in_pages + (block_size_in_pages - 1)) / > + block_size_in_pages; > + > + pr_info("-------------------------------------------\n"); > + pr_info("Search Area Geometry\n"); > + pr_info("-------------------------------------------\n"); > + pr_info("Search Area Size in Blocks : %u", search_area_size_in_blocks); > + pr_info("Search Area Size in Strides: %u", search_area_size_in_strides); > + pr_info("Search Area Size in Pages : %u", search_area_size_in_pages); > + > + /* Select chip 0. */ > + saved_chip_number = mil->current_chip; > + nand->select_chip(mtd, 0); > + > + /* Loop over blocks in the first search area, erasing them. */ > + pr_info("Erasing the search area...\n"); > + > + for (block = 0; block < search_area_size_in_blocks; block++) { > + /* Compute the page address. */ > + page = block * block_size_in_pages; > + > + /* Erase this block. */ > + pr_info(" Erasing block 0x%x\n", block); > + nand->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page); > + nand->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1); > + > + /* Wait for the erase to finish. */ > + status = nand->waitfunc(mtd, nand); > + if (status & NAND_STATUS_FAIL) > + dev_err(dev, "[%s] Erase failed.\n", __func__); > + } > + > + /* Write the NCB fingerprint into the page buffer. */ > + memset(buffer, ~0, mtd->writesize); > + memset(nand->oob_poi, ~0, mtd->oobsize); > + memcpy(buffer + 12, fingerprint, strlen(fingerprint)); > + > + /* Loop through the first search area, writing NCB fingerprints. */ > + pr_info("Writing NCB fingerprints...\n"); > + for (stride = 0; stride < search_area_size_in_strides; stride++) { > + /* Compute the page and byte addresses. */ > + page = stride * rom_geo->stride_size_in_pages; > + byte = page * mtd->writesize; > + > + /* Write the first page of the current stride. */ > + pr_info(" Writing an NCB fingerprint in page 0x%x\n", page); > + nand->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); > + nand->ecc.write_page_raw(mtd, nand, buffer); > + nand->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); > + > + /* Wait for the write to finish. */ > + status = nand->waitfunc(mtd, nand); > + if (status & NAND_STATUS_FAIL) > + dev_err(dev, "[%s] Write failed.\n", __func__); > + } > + > + /* Deselect chip 0. */ > + nand->select_chip(mtd, saved_chip_number); > + return 0; > +} > + > +int mx23_boot_init(struct gpmi_nfc_data *this) > +{ > + struct device *dev = this->dev; > + struct mil *mil = &this->mil; > + struct nand_chip *nand = &mil->nand; > + struct mtd_info *mtd = &mil->mtd; > + unsigned int block_count; > + unsigned int block; > + int chip; > + int page; > + loff_t byte; > + uint8_t block_mark; > + int error = 0; > + > + /* > + * If control arrives here, we can't use block mark swapping, which > + * means we're forced to use transcription. First, scan for the > + * transcription stamp. If we find it, then we don't have to do > + * anything -- the block marks are already transcribed. > + */ > + if (mx23_check_transcription_stamp(this)) > + return 0; > + > + /* > + * If control arrives here, we couldn't find a transcription stamp, so > + * so we presume the block marks are in the conventional location. > + */ > + pr_info("Transcribing bad block marks...\n"); > + > + /* Compute the number of blocks in the entire medium. */ > + block_count = nand->chipsize >> nand->phys_erase_shift; > + > + /* > + * Loop over all the blocks in the medium, transcribing block marks as > + * we go. > + */ > + for (block = 0; block < block_count; block++) { > + /* > + * Compute the chip, page and byte addresses for this block's > + * conventional mark. > + */ > + chip = block >> (nand->chip_shift - nand->phys_erase_shift); > + page = block << (nand->phys_erase_shift - nand->page_shift); > + byte = block << nand->phys_erase_shift; > + > + /* Select the chip. */ > + nand->select_chip(mtd, chip); > + > + /* Send the command to read the conventional block mark. */ > + nand->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page); > + > + /* Read the conventional block mark. */ > + block_mark = nand->read_byte(mtd); > + > + /* > + * Check if the block is marked bad. If so, we need to mark it > + * again, but this time the result will be a mark in the > + * location where we transcribe block marks. > + * > + * Notice that we have to explicitly set the marking_a_bad_block > + * member before we call through the block_markbad function > + * pointer in the owning struct nand_chip. If we could call > + * though the block_markbad function pointer in the owning > + * struct mtd_info, which we have hooked, then this would be > + * taken care of for us. Unfortunately, we can't because that > + * higher-level code path will do things like consulting the > + * in-memory bad block table -- which doesn't even exist yet! > + * So, we have to call at a lower level and handle some details > + * ourselves. > + */ > + if (block_mark != 0xff) { > + pr_info("Transcribing mark in block %u\n", block); > + mil->marking_a_bad_block = true; > + error = nand->block_markbad(mtd, byte); > + mil->marking_a_bad_block = false; > + if (error) > + dev_err(dev, "Failed to mark block bad with " > + "error %d\n", error); > + } > + > + /* Deselect the chip. */ > + nand->select_chip(mtd, -1); > + } > + > + /* Write the stamp that indicates we've transcribed the block marks. */ > + mx23_write_transcription_stamp(this); > + return 0; > +} > + > +static int nand_boot_init(struct gpmi_nfc_data *this) > +{ > + nand_boot_set_geometry(this); > + > + /* This is ROM arch-specific initilization before the BBT scanning. */ > + if (GPMI_IS_MX23(this)) > + return mx23_boot_init(this); > + return 0; > +} > + > +static void show_nfc_geometry(struct nfc_geometry *geo) > +{ > + pr_info("---------------------------------------\n"); > + pr_info(" NFC Geometry (used by BCH)\n"); > + pr_info("---------------------------------------\n"); > + pr_info("ECC Algorithm : %s\n", geo->ecc_algorithm); > + pr_info("ECC Strength : %u\n", geo->ecc_strength); > + pr_info("Page Size in Bytes : %u\n", geo->page_size_in_bytes); > + pr_info("Metadata Size in Bytes : %u\n", geo->metadata_size_in_bytes); > + pr_info("ECC Chunk Size in Bytes: %u\n", geo->ecc_chunk_size_in_bytes); > + pr_info("ECC Chunk Count : %u\n", geo->ecc_chunk_count); > + pr_info("Payload Size in Bytes : %u\n", geo->payload_size_in_bytes); > + pr_info("Auxiliary Size in Bytes: %u\n", geo->auxiliary_size_in_bytes); > + pr_info("Auxiliary Status Offset: %u\n", geo->auxiliary_status_offset); > + pr_info("Block Mark Byte Offset : %u\n", geo->block_mark_byte_offset); > + pr_info("Block Mark Bit Offset : %u\n", geo->block_mark_bit_offset); > +} > + > +static int mil_set_geometry(struct gpmi_nfc_data *this) > +{ > + struct nfc_hal *nfc = this->nfc; > + struct nfc_geometry *geo = &this->nfc_geometry; > + int error; > + > + /* Free the temporary DMA memory for read ID case */ > + mil_free_dma_buffer(this); > + > + /* Set up the NFC geometry which is used by BCH. */ > + error = nfc->set_geometry(this); > + if (error != 0) { > + log("NFC set geometry error : %d", error); > + return error; > + } > + if (gpmi_debug & GPMI_DEBUG_INIT) > + show_nfc_geometry(geo); > + > + /* Alloc the new DMA buffers according to the pagesize and oobsize */ > + return mil_alloc_dma_buffer(this); > +} > + > +static int mil_pre_bbt_scan(struct gpmi_nfc_data *this) > +{ > + struct nand_chip *nand = &this->mil.nand; > + struct mtd_info *mtd = &this->mil.mtd; > + struct nand_ecclayout *layout = nand->ecc.layout; > + struct nfc_hal *nfc = this->nfc; > + int error; > + > + /* fix the ECC layout before the scanning */ > + layout->eccbytes = 0; > + layout->oobavail = mtd->oobsize; > + layout->oobfree[0].offset = 0; > + layout->oobfree[0].length = mtd->oobsize; > + > + mtd->oobavail = nand->ecc.layout->oobavail; > + > + /* Set up swap block-mark, must be set before the mil_set_geometry() */ > + if (GPMI_IS_MX23(this)) > + this->swap_block_mark = false; > + else > + this->swap_block_mark = true; > + > + /* Set up the medium geometry */ > + error = mil_set_geometry(this); > + if (error) > + return error; > + > + /* extra init */ > + if (nfc->extra_init) { > + error = nfc->extra_init(this); > + if (error != 0) > + return error; > + } > + > + /* NAND boot init, depends on the mil_set_geometry(). */ > + return nand_boot_init(this); > +} > + > +static int mil_scan_bbt(struct mtd_info *mtd) > +{ > + struct nand_chip *nand = mtd->priv; > + struct gpmi_nfc_data *this = nand->priv; > + int error; > + > + /* Prepare for the BBT scan. */ > + error = mil_pre_bbt_scan(this); > + if (error) > + return error; > + > + /* use the default BBT implementation */ > + return nand_default_bbt(mtd); > +} > + > +static const char *cmd_parse = "cmdlinepart"; > This should be a NULL terminated list of strings: static const char *cmd_parse[] = { "cmdlinepart", NULL }; > +static int mil_partitions_init(struct gpmi_nfc_data *this) > +{ > + struct gpmi_nfc_platform_data *pdata = this->pdata; > + struct mil *mil = &this->mil; > + struct mtd_info *mtd = &mil->mtd; > + > + /* use the command line for simple partitions layout */ > + mil->partition_count = parse_mtd_partitions(mtd, &cmd_parse, > + &mil->partitions, 0); > + if (mil->partition_count) > + return add_mtd_partitions(mtd, mil->partitions, > + mil->partition_count); > + > + /* The complicated partitions layout uses this. */ > + if (pdata->partitions && pdata->partition_count > 0) > + return add_mtd_partitions(mtd, pdata->partitions, > + pdata->partition_count); > + return 0; How about: return mtd_add_device(mtd); so you will get the whole flash registered in case there are no partitions defined. > +#ifdef CONFIG_PM > +static int gpmi_nfc_suspend(struct platform_device *pdev, pm_message_t state) > +{ > + return 0; > +} > +static int gpmi_nfc_resume(struct platform_device *pdev) > +{ > + return 0; > +} > +#else > +#define gpmi_nfc_suspend NULL > +#define gpmi_nfc_resume NULL > +#endif > + There is no point in adding empty suspend/resume functions. Furthermore you should use dev_pm_ops. Lothar Wa?mann -- ___________________________________________________________ Ka-Ro electronics GmbH | Pascalstra?e 22 | D - 52076 Aachen Phone: +49 2408 1402-0 | Fax: +49 2408 1402-10 Gesch?ftsf?hrer: Matthias Kaussen Handelsregistereintrag: Amtsgericht Aachen, HRB 4996 www.karo-electronics.de | info at karo-electronics.de ___________________________________________________________ ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v6 1/3] MTD : add the common code for GPMI controller driver 2011-04-21 7:49 ` Lothar Waßmann @ 2011-04-22 9:39 ` Huang Shijie -1 siblings, 0 replies; 20+ messages in thread From: Huang Shijie @ 2011-04-22 9:39 UTC (permalink / raw) To: Lothar Waßmann; +Cc: linux-mtd, linux-arm-kernel, dedekind1 Hi: > Huang Shijie writes: >> These files contain the common code for the GPMI driver. >> >> Signed-off-by: Huang Shijie<b32955@freescale.com> >> --- >> drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c | 2501 ++++++++++++++++++++++++++++++++++ >> drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h | 488 +++++++ >> 2 files changed, 2989 insertions(+), 0 deletions(-) >> create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c >> create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h >> >> diff --git a/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c >> new file mode 100644 >> index 0000000..53d6915 >> --- /dev/null >> +++ b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c >> @@ -0,0 +1,2501 @@ >> +/* >> + * Freescale GPMI NFC NAND Flash Driver >> + * >> + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. >> + * Copyright (C) 2008 Embedded Alley Solutions, Inc. >> + * >> + * This program is free software; you can redistribute it and/or modify >> + * it under the terms of the GNU General Public License as published by >> + * the Free Software Foundation; either version 2 of the License, or >> + * (at your option) any later version. >> + * >> + * 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., >> + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >> + */ >> +#include<linux/slab.h> >> +#include "gpmi-nfc.h" >> + >> +/* add our owner bbt descriptor */ >> +static uint8_t scan_ff_pattern[] = { 0xff }; >> +static struct nand_bbt_descr gpmi_bbt_descr = { >> + .options = 0, >> + .offs = 0, >> + .len = 1, >> + .pattern = scan_ff_pattern >> +}; >> + >> +/* debug control */ >> +int gpmi_debug; >> + > This could be a module_param. > thanks. >> +static ssize_t show_gpmi_debug(struct device *dev, >> + struct device_attribute *attr, char *buf) >> +{ >> + return sprintf(buf, "%d\n", gpmi_debug); >> +} >> + >> +static ssize_t >> +store_gpmi_debug(struct device *dev, struct device_attribute *attr, >> + const char *buf, size_t size) >> +{ >> + const char *p = buf; >> + unsigned long v; >> + >> + if (strict_strtoul(p, 0,&v)< 0) >> + return size; >> + >> + gpmi_debug = v; >> + return size; >> +} >> + >> +static ssize_t show_ignorebad(struct device *dev, >> + struct device_attribute *attr, char *buf) >> +{ >> + struct gpmi_nfc_data *this = dev_get_drvdata(dev); >> + struct mil *mil =&this->mil; >> + >> + return sprintf(buf, "%d\n", mil->ignore_bad_block_marks); >> +} >> + >> +static ssize_t >> +store_ignorebad(struct device *dev, struct device_attribute *attr, >> + const char *buf, size_t size) >> +{ >> + struct gpmi_nfc_data *this = dev_get_drvdata(dev); >> + struct mil *mil =&this->mil; >> + const char *p = buf; >> + unsigned long v; >> + >> + if (strict_strtoul(p, 0,&v)< 0) >> + return size; >> > return -EINVAL would be more appropriate here. > >> + >> + if (v> 0) >> + v = 1; >> + >> + if (v != mil->ignore_bad_block_marks) { >> > These lines could be reduced to: > if (!v == mil->ignore_bad_block_marks) { > :) >> + if (v) { >> + /* >> + * This will cause the NAND Flash MTD code to believe >> + * that it never created a BBT and force it to call our >> + * block_bad function. >> + * >> + * See mil_block_bad for more details. >> + */ >> + mil->saved_bbt = mil->nand.bbt; >> + mil->nand.bbt = NULL; >> + } else { >> + /* >> + * Restore the NAND Flash MTD's pointer >> + * to its in-memory BBT. >> + */ >> + mil->nand.bbt = mil->saved_bbt; >> + } >> + mil->ignore_bad_block_marks = v; >> + } >> + return size; >> +} >> + > [...] >> +/* This will be called after the DMA operation is finished. */ >> +static void dma_irq_callback(void *param) >> +{ >> + struct gpmi_nfc_data *this = param; >> + struct nfc_hal *nfc = this->nfc; >> + struct mil *mil =&this->mil; >> + >> + complete(&nfc->dma_done); >> + >> + switch (this->dma_type) { >> + case DMA_FOR_COMMAND: >> + dma_unmap_sg(this->dev,&mil->cmd_sgl, 1, DMA_TO_DEVICE); >> + break; >> + >> + case DMA_FOR_READ_DATA: >> + dma_unmap_sg(this->dev,&mil->data_sgl, 1, DMA_FROM_DEVICE); >> + if (mil->direct_dma_map_ok == false) >> + memcpy(mil->upper_buf, (char *)mil->data_buffer_dma, >> > pointless cast. thanks. > [...] >> +static int acquire_dma_channels(struct gpmi_nfc_data *this, >> + const char *resource_name, >> + unsigned *low_channel, unsigned *high_channel) >> +{ >> + struct platform_device *pdev = this->pdev; > struct gpmi_nfc_platform_data *pdata = pdev->dev.platform_data; > see below. > >> + struct resource *r, *r_dma; >> + unsigned int i; >> + >> + r = platform_get_resource_byname(pdev, IORESOURCE_DMA, resource_name); >> + r_dma = platform_get_resource_byname(pdev, IORESOURCE_IRQ, >> + GPMI_NFC_DMA_INTERRUPT_RES_NAME); >> + if (!r || !r_dma) { >> + log("Can't get resource for DMA"); >> + return -ENXIO; >> + } >> + >> + /* used in gpmi_dma_filter() */ >> + this->private = r; >> + >> + for (i = r->start; i<= r->end; i++) { >> + dma_cap_mask_t mask; >> + struct dma_chan *dma_chan; >> + > if (i - r->start>= pdata->max_chip_count) > break; > This is a good idea. it can limits the real dma channels. >> + dma_cap_zero(mask); >> + dma_cap_set(DMA_SLAVE, mask); >> + >> + /* get the DMA interrupt */ >> + this->dma_data.chan_irq = r_dma->start + >> + ((r_dma->start != r_dma->end) ? (i - r->start) : 0); >> + >> + dma_chan = dma_request_channel(mask, gpmi_dma_filter, this); >> + if (!dma_chan) >> + goto acquire_err; >> + /* fill the first empty item */ >> + this->dma_chans[i - r->start] = dma_chan; >> + } >> + >> + *low_channel = r->start; >> + *high_channel = r->end; > *high_channel = i; > > This will acquire only those DMA channels that are actually needed. > > [...] >> +static int read_page_prepare(struct gpmi_nfc_data *this, >> + void *destination, unsigned length, >> + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, >> + void **use_virt, dma_addr_t *use_phys) >> +{ >> + struct device *dev = this->dev; >> + dma_addr_t destination_phys = ~0; >> + >> + if (virt_addr_valid(destination)) >> + destination_phys = dma_map_single(dev, (void *)destination, >> > pointless cast. > > [...] >> +static int mil_alloc_dma_buffer(struct gpmi_nfc_data *this) >> +{ >> + struct nfc_geometry *geo =&this->nfc_geometry; >> + struct device *dev = this->dev; >> + struct mil *mil =&this->mil; >> + >> + /* [1] Allocate a command buffer. PAGE_SIZE is enough. */ >> + mil->cmd_buffer = kzalloc(PAGE_SIZE, GFP_DMA); >> + if (mil->cmd_buffer == NULL) >> + goto error_alloc; >> + >> + /* [2] Allocate a read/write data buffer. PAGE_SIZE is enough. */ >> + mil->data_buffer_dma = kzalloc(PAGE_SIZE, GFP_DMA); >> + if (mil->data_buffer_dma == NULL) >> + goto error_alloc; >> + >> + /* >> + * [3] Allocate the page buffer. >> + * >> + * Both the payload buffer and the auxiliary buffer must appear on >> + * 32-bit boundaries. We presume the size of the payload buffer is a >> + * power of two and is much larger than four, which guarantees the >> + * auxiliary buffer will appear on a 32-bit boundary. >> + */ >> + mil->page_buffer_size = geo->payload_size_in_bytes + >> + geo->auxiliary_size_in_bytes; >> + >> + mil->page_buffer_virt = dma_alloc_coherent(dev, mil->page_buffer_size, >> + &mil->page_buffer_phys, GFP_DMA); >> + if (!mil->page_buffer_virt) >> + goto error_alloc; >> + >> + >> + /* Slice up the page buffer. */ >> + mil->payload_virt = mil->page_buffer_virt; >> + mil->payload_phys = mil->page_buffer_phys; >> + mil->auxiliary_virt = ((char *) mil->payload_virt) + >> > unnecessary cast. > > [...] > > The functions from here thru gpmi_nfc_probe could be marked __devinit, > so they can be discarded after boot. >> +static int nand_boot_set_geometry(struct gpmi_nfc_data *this) >> +{ >> + struct boot_rom_geometry *geometry =&this->rom_geometry; >> + >> + /* >> + * Set the boot block stride size. >> + * >> + * In principle, we should be reading this from the OTP bits, since >> + * that's where the ROM is going to get it. In fact, we don't have any >> + * way to read the OTP bits, so we go with the default and hope for the >> + * best. >> + */ >> + geometry->stride_size_in_pages = 64; >> + >> + /* >> + * Set the search area stride exponent. >> + * >> + * In principle, we should be reading this from the OTP bits, since >> + * that's where the ROM is going to get it. In fact, we don't have any >> + * way to read the OTP bits, so we go with the default and hope for the >> + * best. >> + */ >> + geometry->search_area_stride_exponent = 2; >> + >> + if (gpmi_debug& GPMI_DEBUG_INIT) >> + log("stride size in page : %d, search areas : %d", >> + geometry->stride_size_in_pages, >> + geometry->search_area_stride_exponent); >> + return 0; >> +} >> + >> +static const char *fingerprint = "STMP"; >> +static int mx23_check_transcription_stamp(struct gpmi_nfc_data *this) >> +{ >> + struct boot_rom_geometry *rom_geo =&this->rom_geometry; >> + struct mil *mil =&this->mil; >> + struct mtd_info *mtd =&mil->mtd; >> + struct nand_chip *nand =&mil->nand; >> + unsigned int search_area_size_in_strides; >> + unsigned int stride; >> + unsigned int page; >> + loff_t byte; >> + uint8_t *buffer = nand->buffers->databuf; >> + int saved_chip_number; >> + int found_an_ncb_fingerprint = false; >> + >> + /* Compute the number of strides in a search area. */ >> + search_area_size_in_strides = 1<< rom_geo->search_area_stride_exponent; >> + >> + /* Select chip 0. */ >> + saved_chip_number = mil->current_chip; >> + nand->select_chip(mtd, 0); >> + >> + /* >> + * Loop through the first search area, looking for the NCB fingerprint. >> + */ >> + pr_info("Scanning for an NCB fingerprint...\n"); >> + >> + for (stride = 0; stride< search_area_size_in_strides; stride++) { >> + /* Compute the page and byte addresses. */ >> + page = stride * rom_geo->stride_size_in_pages; >> + byte = page * mtd->writesize; >> + >> + pr_info(" Looking for a fingerprint in page 0x%x\n", page); >> + >> + /* >> + * Read the NCB fingerprint. The fingerprint is four bytes long >> + * and starts in the 12th byte of the page. >> + */ >> + nand->cmdfunc(mtd, NAND_CMD_READ0, 12, page); >> + nand->read_buf(mtd, buffer, strlen(fingerprint)); >> + >> + /* Look for the fingerprint. */ >> + if (!memcmp(buffer, fingerprint, strlen(fingerprint))) { >> + found_an_ncb_fingerprint = true; >> + break; >> + } >> + >> + } >> + >> + /* Deselect chip 0. */ >> + nand->select_chip(mtd, saved_chip_number); >> + >> + if (found_an_ncb_fingerprint) >> + pr_info(" Found a fingerprint\n"); >> + else >> + pr_info(" No fingerprint found\n"); >> + return found_an_ncb_fingerprint; >> +} >> + >> +/* Writes a transcription stamp. */ >> +static int mx23_write_transcription_stamp(struct gpmi_nfc_data *this) >> +{ >> + struct device *dev = this->dev; >> + struct boot_rom_geometry *rom_geo =&this->rom_geometry; >> + struct mil *mil =&this->mil; >> + struct mtd_info *mtd =&mil->mtd; >> + struct nand_chip *nand =&mil->nand; >> + unsigned int block_size_in_pages; >> + unsigned int search_area_size_in_strides; >> + unsigned int search_area_size_in_pages; >> + unsigned int search_area_size_in_blocks; >> + unsigned int block; >> + unsigned int stride; >> + unsigned int page; >> + loff_t byte; >> + uint8_t *buffer = nand->buffers->databuf; >> + int saved_chip_number; >> + int status; >> + >> + /* Compute the search area geometry. */ >> + block_size_in_pages = mtd->erasesize / mtd->writesize; >> + search_area_size_in_strides = 1<< rom_geo->search_area_stride_exponent; >> + search_area_size_in_pages = search_area_size_in_strides * >> + rom_geo->stride_size_in_pages; >> + search_area_size_in_blocks = >> + (search_area_size_in_pages + (block_size_in_pages - 1)) / >> + block_size_in_pages; >> + >> + pr_info("-------------------------------------------\n"); >> + pr_info("Search Area Geometry\n"); >> + pr_info("-------------------------------------------\n"); >> + pr_info("Search Area Size in Blocks : %u", search_area_size_in_blocks); >> + pr_info("Search Area Size in Strides: %u", search_area_size_in_strides); >> + pr_info("Search Area Size in Pages : %u", search_area_size_in_pages); >> + >> + /* Select chip 0. */ >> + saved_chip_number = mil->current_chip; >> + nand->select_chip(mtd, 0); >> + >> + /* Loop over blocks in the first search area, erasing them. */ >> + pr_info("Erasing the search area...\n"); >> + >> + for (block = 0; block< search_area_size_in_blocks; block++) { >> + /* Compute the page address. */ >> + page = block * block_size_in_pages; >> + >> + /* Erase this block. */ >> + pr_info(" Erasing block 0x%x\n", block); >> + nand->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page); >> + nand->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1); >> + >> + /* Wait for the erase to finish. */ >> + status = nand->waitfunc(mtd, nand); >> + if (status& NAND_STATUS_FAIL) >> + dev_err(dev, "[%s] Erase failed.\n", __func__); >> + } >> + >> + /* Write the NCB fingerprint into the page buffer. */ >> + memset(buffer, ~0, mtd->writesize); >> + memset(nand->oob_poi, ~0, mtd->oobsize); >> + memcpy(buffer + 12, fingerprint, strlen(fingerprint)); >> + >> + /* Loop through the first search area, writing NCB fingerprints. */ >> + pr_info("Writing NCB fingerprints...\n"); >> + for (stride = 0; stride< search_area_size_in_strides; stride++) { >> + /* Compute the page and byte addresses. */ >> + page = stride * rom_geo->stride_size_in_pages; >> + byte = page * mtd->writesize; >> + >> + /* Write the first page of the current stride. */ >> + pr_info(" Writing an NCB fingerprint in page 0x%x\n", page); >> + nand->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); >> + nand->ecc.write_page_raw(mtd, nand, buffer); >> + nand->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); >> + >> + /* Wait for the write to finish. */ >> + status = nand->waitfunc(mtd, nand); >> + if (status& NAND_STATUS_FAIL) >> + dev_err(dev, "[%s] Write failed.\n", __func__); >> + } >> + >> + /* Deselect chip 0. */ >> + nand->select_chip(mtd, saved_chip_number); >> + return 0; >> +} >> + >> +int mx23_boot_init(struct gpmi_nfc_data *this) >> +{ >> + struct device *dev = this->dev; >> + struct mil *mil =&this->mil; >> + struct nand_chip *nand =&mil->nand; >> + struct mtd_info *mtd =&mil->mtd; >> + unsigned int block_count; >> + unsigned int block; >> + int chip; >> + int page; >> + loff_t byte; >> + uint8_t block_mark; >> + int error = 0; >> + >> + /* >> + * If control arrives here, we can't use block mark swapping, which >> + * means we're forced to use transcription. First, scan for the >> + * transcription stamp. If we find it, then we don't have to do >> + * anything -- the block marks are already transcribed. >> + */ >> + if (mx23_check_transcription_stamp(this)) >> + return 0; >> + >> + /* >> + * If control arrives here, we couldn't find a transcription stamp, so >> + * so we presume the block marks are in the conventional location. >> + */ >> + pr_info("Transcribing bad block marks...\n"); >> + >> + /* Compute the number of blocks in the entire medium. */ >> + block_count = nand->chipsize>> nand->phys_erase_shift; >> + >> + /* >> + * Loop over all the blocks in the medium, transcribing block marks as >> + * we go. >> + */ >> + for (block = 0; block< block_count; block++) { >> + /* >> + * Compute the chip, page and byte addresses for this block's >> + * conventional mark. >> + */ >> + chip = block>> (nand->chip_shift - nand->phys_erase_shift); >> + page = block<< (nand->phys_erase_shift - nand->page_shift); >> + byte = block<< nand->phys_erase_shift; >> + >> + /* Select the chip. */ >> + nand->select_chip(mtd, chip); >> + >> + /* Send the command to read the conventional block mark. */ >> + nand->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page); >> + >> + /* Read the conventional block mark. */ >> + block_mark = nand->read_byte(mtd); >> + >> + /* >> + * Check if the block is marked bad. If so, we need to mark it >> + * again, but this time the result will be a mark in the >> + * location where we transcribe block marks. >> + * >> + * Notice that we have to explicitly set the marking_a_bad_block >> + * member before we call through the block_markbad function >> + * pointer in the owning struct nand_chip. If we could call >> + * though the block_markbad function pointer in the owning >> + * struct mtd_info, which we have hooked, then this would be >> + * taken care of for us. Unfortunately, we can't because that >> + * higher-level code path will do things like consulting the >> + * in-memory bad block table -- which doesn't even exist yet! >> + * So, we have to call at a lower level and handle some details >> + * ourselves. >> + */ >> + if (block_mark != 0xff) { >> + pr_info("Transcribing mark in block %u\n", block); >> + mil->marking_a_bad_block = true; >> + error = nand->block_markbad(mtd, byte); >> + mil->marking_a_bad_block = false; >> + if (error) >> + dev_err(dev, "Failed to mark block bad with " >> + "error %d\n", error); >> + } >> + >> + /* Deselect the chip. */ >> + nand->select_chip(mtd, -1); >> + } >> + >> + /* Write the stamp that indicates we've transcribed the block marks. */ >> + mx23_write_transcription_stamp(this); >> + return 0; >> +} >> + >> +static int nand_boot_init(struct gpmi_nfc_data *this) >> +{ >> + nand_boot_set_geometry(this); >> + >> + /* This is ROM arch-specific initilization before the BBT scanning. */ >> + if (GPMI_IS_MX23(this)) >> + return mx23_boot_init(this); >> + return 0; >> +} >> + >> +static void show_nfc_geometry(struct nfc_geometry *geo) >> +{ >> + pr_info("---------------------------------------\n"); >> + pr_info(" NFC Geometry (used by BCH)\n"); >> + pr_info("---------------------------------------\n"); >> + pr_info("ECC Algorithm : %s\n", geo->ecc_algorithm); >> + pr_info("ECC Strength : %u\n", geo->ecc_strength); >> + pr_info("Page Size in Bytes : %u\n", geo->page_size_in_bytes); >> + pr_info("Metadata Size in Bytes : %u\n", geo->metadata_size_in_bytes); >> + pr_info("ECC Chunk Size in Bytes: %u\n", geo->ecc_chunk_size_in_bytes); >> + pr_info("ECC Chunk Count : %u\n", geo->ecc_chunk_count); >> + pr_info("Payload Size in Bytes : %u\n", geo->payload_size_in_bytes); >> + pr_info("Auxiliary Size in Bytes: %u\n", geo->auxiliary_size_in_bytes); >> + pr_info("Auxiliary Status Offset: %u\n", geo->auxiliary_status_offset); >> + pr_info("Block Mark Byte Offset : %u\n", geo->block_mark_byte_offset); >> + pr_info("Block Mark Bit Offset : %u\n", geo->block_mark_bit_offset); >> +} >> + >> +static int mil_set_geometry(struct gpmi_nfc_data *this) >> +{ >> + struct nfc_hal *nfc = this->nfc; >> + struct nfc_geometry *geo =&this->nfc_geometry; >> + int error; >> + >> + /* Free the temporary DMA memory for read ID case */ >> + mil_free_dma_buffer(this); >> + >> + /* Set up the NFC geometry which is used by BCH. */ >> + error = nfc->set_geometry(this); >> + if (error != 0) { >> + log("NFC set geometry error : %d", error); >> + return error; >> + } >> + if (gpmi_debug& GPMI_DEBUG_INIT) >> + show_nfc_geometry(geo); >> + >> + /* Alloc the new DMA buffers according to the pagesize and oobsize */ >> + return mil_alloc_dma_buffer(this); >> +} >> + >> +static int mil_pre_bbt_scan(struct gpmi_nfc_data *this) >> +{ >> + struct nand_chip *nand =&this->mil.nand; >> + struct mtd_info *mtd =&this->mil.mtd; >> + struct nand_ecclayout *layout = nand->ecc.layout; >> + struct nfc_hal *nfc = this->nfc; >> + int error; >> + >> + /* fix the ECC layout before the scanning */ >> + layout->eccbytes = 0; >> + layout->oobavail = mtd->oobsize; >> + layout->oobfree[0].offset = 0; >> + layout->oobfree[0].length = mtd->oobsize; >> + >> + mtd->oobavail = nand->ecc.layout->oobavail; >> + >> + /* Set up swap block-mark, must be set before the mil_set_geometry() */ >> + if (GPMI_IS_MX23(this)) >> + this->swap_block_mark = false; >> + else >> + this->swap_block_mark = true; >> + >> + /* Set up the medium geometry */ >> + error = mil_set_geometry(this); >> + if (error) >> + return error; >> + >> + /* extra init */ >> + if (nfc->extra_init) { >> + error = nfc->extra_init(this); >> + if (error != 0) >> + return error; >> + } >> + >> + /* NAND boot init, depends on the mil_set_geometry(). */ >> + return nand_boot_init(this); >> +} >> + >> +static int mil_scan_bbt(struct mtd_info *mtd) >> +{ >> + struct nand_chip *nand = mtd->priv; >> + struct gpmi_nfc_data *this = nand->priv; >> + int error; >> + >> + /* Prepare for the BBT scan. */ >> + error = mil_pre_bbt_scan(this); >> + if (error) >> + return error; >> + >> + /* use the default BBT implementation */ >> + return nand_default_bbt(mtd); >> +} >> + >> +static const char *cmd_parse = "cmdlinepart"; >> > This should be a NULL terminated list of strings: > static const char *cmd_parse[] = { "cmdlinepart", NULL }; > thanks a lot. >> +static int mil_partitions_init(struct gpmi_nfc_data *this) >> +{ >> + struct gpmi_nfc_platform_data *pdata = this->pdata; >> + struct mil *mil =&this->mil; >> + struct mtd_info *mtd =&mil->mtd; >> + >> + /* use the command line for simple partitions layout */ >> + mil->partition_count = parse_mtd_partitions(mtd,&cmd_parse, >> + &mil->partitions, 0); >> + if (mil->partition_count) >> + return add_mtd_partitions(mtd, mil->partitions, >> + mil->partition_count); >> + >> + /* The complicated partitions layout uses this. */ >> + if (pdata->partitions&& pdata->partition_count> 0) >> + return add_mtd_partitions(mtd, pdata->partitions, >> + pdata->partition_count); >> + return 0; > How about: > return mtd_add_device(mtd); > so you will get the whole flash registered in case there are no > partitions defined. > no problem. >> +#ifdef CONFIG_PM >> +static int gpmi_nfc_suspend(struct platform_device *pdev, pm_message_t state) >> +{ >> + return 0; >> +} >> +static int gpmi_nfc_resume(struct platform_device *pdev) >> +{ >> + return 0; >> +} >> +#else >> +#define gpmi_nfc_suspend NULL >> +#define gpmi_nfc_resume NULL >> +#endif >> + > There is no point in adding empty suspend/resume functions. > Furthermore you should use dev_pm_ops. > Ok. I think the gpmi needs to do nothing when suspend. Best Regards Huang shijie > Lothar Waßmann ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 1/3] MTD : add the common code for GPMI controller driver @ 2011-04-22 9:39 ` Huang Shijie 0 siblings, 0 replies; 20+ messages in thread From: Huang Shijie @ 2011-04-22 9:39 UTC (permalink / raw) To: linux-arm-kernel Hi: > Huang Shijie writes: >> These files contain the common code for the GPMI driver. >> >> Signed-off-by: Huang Shijie<b32955@freescale.com> >> --- >> drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c | 2501 ++++++++++++++++++++++++++++++++++ >> drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h | 488 +++++++ >> 2 files changed, 2989 insertions(+), 0 deletions(-) >> create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c >> create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h >> >> diff --git a/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c >> new file mode 100644 >> index 0000000..53d6915 >> --- /dev/null >> +++ b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c >> @@ -0,0 +1,2501 @@ >> +/* >> + * Freescale GPMI NFC NAND Flash Driver >> + * >> + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. >> + * Copyright (C) 2008 Embedded Alley Solutions, Inc. >> + * >> + * This program is free software; you can redistribute it and/or modify >> + * it under the terms of the GNU General Public License as published by >> + * the Free Software Foundation; either version 2 of the License, or >> + * (at your option) any later version. >> + * >> + * 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., >> + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >> + */ >> +#include<linux/slab.h> >> +#include "gpmi-nfc.h" >> + >> +/* add our owner bbt descriptor */ >> +static uint8_t scan_ff_pattern[] = { 0xff }; >> +static struct nand_bbt_descr gpmi_bbt_descr = { >> + .options = 0, >> + .offs = 0, >> + .len = 1, >> + .pattern = scan_ff_pattern >> +}; >> + >> +/* debug control */ >> +int gpmi_debug; >> + > This could be a module_param. > thanks. >> +static ssize_t show_gpmi_debug(struct device *dev, >> + struct device_attribute *attr, char *buf) >> +{ >> + return sprintf(buf, "%d\n", gpmi_debug); >> +} >> + >> +static ssize_t >> +store_gpmi_debug(struct device *dev, struct device_attribute *attr, >> + const char *buf, size_t size) >> +{ >> + const char *p = buf; >> + unsigned long v; >> + >> + if (strict_strtoul(p, 0,&v)< 0) >> + return size; >> + >> + gpmi_debug = v; >> + return size; >> +} >> + >> +static ssize_t show_ignorebad(struct device *dev, >> + struct device_attribute *attr, char *buf) >> +{ >> + struct gpmi_nfc_data *this = dev_get_drvdata(dev); >> + struct mil *mil =&this->mil; >> + >> + return sprintf(buf, "%d\n", mil->ignore_bad_block_marks); >> +} >> + >> +static ssize_t >> +store_ignorebad(struct device *dev, struct device_attribute *attr, >> + const char *buf, size_t size) >> +{ >> + struct gpmi_nfc_data *this = dev_get_drvdata(dev); >> + struct mil *mil =&this->mil; >> + const char *p = buf; >> + unsigned long v; >> + >> + if (strict_strtoul(p, 0,&v)< 0) >> + return size; >> > return -EINVAL would be more appropriate here. > >> + >> + if (v> 0) >> + v = 1; >> + >> + if (v != mil->ignore_bad_block_marks) { >> > These lines could be reduced to: > if (!v == mil->ignore_bad_block_marks) { > :) >> + if (v) { >> + /* >> + * This will cause the NAND Flash MTD code to believe >> + * that it never created a BBT and force it to call our >> + * block_bad function. >> + * >> + * See mil_block_bad for more details. >> + */ >> + mil->saved_bbt = mil->nand.bbt; >> + mil->nand.bbt = NULL; >> + } else { >> + /* >> + * Restore the NAND Flash MTD's pointer >> + * to its in-memory BBT. >> + */ >> + mil->nand.bbt = mil->saved_bbt; >> + } >> + mil->ignore_bad_block_marks = v; >> + } >> + return size; >> +} >> + > [...] >> +/* This will be called after the DMA operation is finished. */ >> +static void dma_irq_callback(void *param) >> +{ >> + struct gpmi_nfc_data *this = param; >> + struct nfc_hal *nfc = this->nfc; >> + struct mil *mil =&this->mil; >> + >> + complete(&nfc->dma_done); >> + >> + switch (this->dma_type) { >> + case DMA_FOR_COMMAND: >> + dma_unmap_sg(this->dev,&mil->cmd_sgl, 1, DMA_TO_DEVICE); >> + break; >> + >> + case DMA_FOR_READ_DATA: >> + dma_unmap_sg(this->dev,&mil->data_sgl, 1, DMA_FROM_DEVICE); >> + if (mil->direct_dma_map_ok == false) >> + memcpy(mil->upper_buf, (char *)mil->data_buffer_dma, >> > pointless cast. thanks. > [...] >> +static int acquire_dma_channels(struct gpmi_nfc_data *this, >> + const char *resource_name, >> + unsigned *low_channel, unsigned *high_channel) >> +{ >> + struct platform_device *pdev = this->pdev; > struct gpmi_nfc_platform_data *pdata = pdev->dev.platform_data; > see below. > >> + struct resource *r, *r_dma; >> + unsigned int i; >> + >> + r = platform_get_resource_byname(pdev, IORESOURCE_DMA, resource_name); >> + r_dma = platform_get_resource_byname(pdev, IORESOURCE_IRQ, >> + GPMI_NFC_DMA_INTERRUPT_RES_NAME); >> + if (!r || !r_dma) { >> + log("Can't get resource for DMA"); >> + return -ENXIO; >> + } >> + >> + /* used in gpmi_dma_filter() */ >> + this->private = r; >> + >> + for (i = r->start; i<= r->end; i++) { >> + dma_cap_mask_t mask; >> + struct dma_chan *dma_chan; >> + > if (i - r->start>= pdata->max_chip_count) > break; > This is a good idea. it can limits the real dma channels. >> + dma_cap_zero(mask); >> + dma_cap_set(DMA_SLAVE, mask); >> + >> + /* get the DMA interrupt */ >> + this->dma_data.chan_irq = r_dma->start + >> + ((r_dma->start != r_dma->end) ? (i - r->start) : 0); >> + >> + dma_chan = dma_request_channel(mask, gpmi_dma_filter, this); >> + if (!dma_chan) >> + goto acquire_err; >> + /* fill the first empty item */ >> + this->dma_chans[i - r->start] = dma_chan; >> + } >> + >> + *low_channel = r->start; >> + *high_channel = r->end; > *high_channel = i; > > This will acquire only those DMA channels that are actually needed. > > [...] >> +static int read_page_prepare(struct gpmi_nfc_data *this, >> + void *destination, unsigned length, >> + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, >> + void **use_virt, dma_addr_t *use_phys) >> +{ >> + struct device *dev = this->dev; >> + dma_addr_t destination_phys = ~0; >> + >> + if (virt_addr_valid(destination)) >> + destination_phys = dma_map_single(dev, (void *)destination, >> > pointless cast. > > [...] >> +static int mil_alloc_dma_buffer(struct gpmi_nfc_data *this) >> +{ >> + struct nfc_geometry *geo =&this->nfc_geometry; >> + struct device *dev = this->dev; >> + struct mil *mil =&this->mil; >> + >> + /* [1] Allocate a command buffer. PAGE_SIZE is enough. */ >> + mil->cmd_buffer = kzalloc(PAGE_SIZE, GFP_DMA); >> + if (mil->cmd_buffer == NULL) >> + goto error_alloc; >> + >> + /* [2] Allocate a read/write data buffer. PAGE_SIZE is enough. */ >> + mil->data_buffer_dma = kzalloc(PAGE_SIZE, GFP_DMA); >> + if (mil->data_buffer_dma == NULL) >> + goto error_alloc; >> + >> + /* >> + * [3] Allocate the page buffer. >> + * >> + * Both the payload buffer and the auxiliary buffer must appear on >> + * 32-bit boundaries. We presume the size of the payload buffer is a >> + * power of two and is much larger than four, which guarantees the >> + * auxiliary buffer will appear on a 32-bit boundary. >> + */ >> + mil->page_buffer_size = geo->payload_size_in_bytes + >> + geo->auxiliary_size_in_bytes; >> + >> + mil->page_buffer_virt = dma_alloc_coherent(dev, mil->page_buffer_size, >> + &mil->page_buffer_phys, GFP_DMA); >> + if (!mil->page_buffer_virt) >> + goto error_alloc; >> + >> + >> + /* Slice up the page buffer. */ >> + mil->payload_virt = mil->page_buffer_virt; >> + mil->payload_phys = mil->page_buffer_phys; >> + mil->auxiliary_virt = ((char *) mil->payload_virt) + >> > unnecessary cast. > > [...] > > The functions from here thru gpmi_nfc_probe could be marked __devinit, > so they can be discarded after boot. >> +static int nand_boot_set_geometry(struct gpmi_nfc_data *this) >> +{ >> + struct boot_rom_geometry *geometry =&this->rom_geometry; >> + >> + /* >> + * Set the boot block stride size. >> + * >> + * In principle, we should be reading this from the OTP bits, since >> + * that's where the ROM is going to get it. In fact, we don't have any >> + * way to read the OTP bits, so we go with the default and hope for the >> + * best. >> + */ >> + geometry->stride_size_in_pages = 64; >> + >> + /* >> + * Set the search area stride exponent. >> + * >> + * In principle, we should be reading this from the OTP bits, since >> + * that's where the ROM is going to get it. In fact, we don't have any >> + * way to read the OTP bits, so we go with the default and hope for the >> + * best. >> + */ >> + geometry->search_area_stride_exponent = 2; >> + >> + if (gpmi_debug& GPMI_DEBUG_INIT) >> + log("stride size in page : %d, search areas : %d", >> + geometry->stride_size_in_pages, >> + geometry->search_area_stride_exponent); >> + return 0; >> +} >> + >> +static const char *fingerprint = "STMP"; >> +static int mx23_check_transcription_stamp(struct gpmi_nfc_data *this) >> +{ >> + struct boot_rom_geometry *rom_geo =&this->rom_geometry; >> + struct mil *mil =&this->mil; >> + struct mtd_info *mtd =&mil->mtd; >> + struct nand_chip *nand =&mil->nand; >> + unsigned int search_area_size_in_strides; >> + unsigned int stride; >> + unsigned int page; >> + loff_t byte; >> + uint8_t *buffer = nand->buffers->databuf; >> + int saved_chip_number; >> + int found_an_ncb_fingerprint = false; >> + >> + /* Compute the number of strides in a search area. */ >> + search_area_size_in_strides = 1<< rom_geo->search_area_stride_exponent; >> + >> + /* Select chip 0. */ >> + saved_chip_number = mil->current_chip; >> + nand->select_chip(mtd, 0); >> + >> + /* >> + * Loop through the first search area, looking for the NCB fingerprint. >> + */ >> + pr_info("Scanning for an NCB fingerprint...\n"); >> + >> + for (stride = 0; stride< search_area_size_in_strides; stride++) { >> + /* Compute the page and byte addresses. */ >> + page = stride * rom_geo->stride_size_in_pages; >> + byte = page * mtd->writesize; >> + >> + pr_info(" Looking for a fingerprint in page 0x%x\n", page); >> + >> + /* >> + * Read the NCB fingerprint. The fingerprint is four bytes long >> + * and starts in the 12th byte of the page. >> + */ >> + nand->cmdfunc(mtd, NAND_CMD_READ0, 12, page); >> + nand->read_buf(mtd, buffer, strlen(fingerprint)); >> + >> + /* Look for the fingerprint. */ >> + if (!memcmp(buffer, fingerprint, strlen(fingerprint))) { >> + found_an_ncb_fingerprint = true; >> + break; >> + } >> + >> + } >> + >> + /* Deselect chip 0. */ >> + nand->select_chip(mtd, saved_chip_number); >> + >> + if (found_an_ncb_fingerprint) >> + pr_info(" Found a fingerprint\n"); >> + else >> + pr_info(" No fingerprint found\n"); >> + return found_an_ncb_fingerprint; >> +} >> + >> +/* Writes a transcription stamp. */ >> +static int mx23_write_transcription_stamp(struct gpmi_nfc_data *this) >> +{ >> + struct device *dev = this->dev; >> + struct boot_rom_geometry *rom_geo =&this->rom_geometry; >> + struct mil *mil =&this->mil; >> + struct mtd_info *mtd =&mil->mtd; >> + struct nand_chip *nand =&mil->nand; >> + unsigned int block_size_in_pages; >> + unsigned int search_area_size_in_strides; >> + unsigned int search_area_size_in_pages; >> + unsigned int search_area_size_in_blocks; >> + unsigned int block; >> + unsigned int stride; >> + unsigned int page; >> + loff_t byte; >> + uint8_t *buffer = nand->buffers->databuf; >> + int saved_chip_number; >> + int status; >> + >> + /* Compute the search area geometry. */ >> + block_size_in_pages = mtd->erasesize / mtd->writesize; >> + search_area_size_in_strides = 1<< rom_geo->search_area_stride_exponent; >> + search_area_size_in_pages = search_area_size_in_strides * >> + rom_geo->stride_size_in_pages; >> + search_area_size_in_blocks = >> + (search_area_size_in_pages + (block_size_in_pages - 1)) / >> + block_size_in_pages; >> + >> + pr_info("-------------------------------------------\n"); >> + pr_info("Search Area Geometry\n"); >> + pr_info("-------------------------------------------\n"); >> + pr_info("Search Area Size in Blocks : %u", search_area_size_in_blocks); >> + pr_info("Search Area Size in Strides: %u", search_area_size_in_strides); >> + pr_info("Search Area Size in Pages : %u", search_area_size_in_pages); >> + >> + /* Select chip 0. */ >> + saved_chip_number = mil->current_chip; >> + nand->select_chip(mtd, 0); >> + >> + /* Loop over blocks in the first search area, erasing them. */ >> + pr_info("Erasing the search area...\n"); >> + >> + for (block = 0; block< search_area_size_in_blocks; block++) { >> + /* Compute the page address. */ >> + page = block * block_size_in_pages; >> + >> + /* Erase this block. */ >> + pr_info(" Erasing block 0x%x\n", block); >> + nand->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page); >> + nand->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1); >> + >> + /* Wait for the erase to finish. */ >> + status = nand->waitfunc(mtd, nand); >> + if (status& NAND_STATUS_FAIL) >> + dev_err(dev, "[%s] Erase failed.\n", __func__); >> + } >> + >> + /* Write the NCB fingerprint into the page buffer. */ >> + memset(buffer, ~0, mtd->writesize); >> + memset(nand->oob_poi, ~0, mtd->oobsize); >> + memcpy(buffer + 12, fingerprint, strlen(fingerprint)); >> + >> + /* Loop through the first search area, writing NCB fingerprints. */ >> + pr_info("Writing NCB fingerprints...\n"); >> + for (stride = 0; stride< search_area_size_in_strides; stride++) { >> + /* Compute the page and byte addresses. */ >> + page = stride * rom_geo->stride_size_in_pages; >> + byte = page * mtd->writesize; >> + >> + /* Write the first page of the current stride. */ >> + pr_info(" Writing an NCB fingerprint in page 0x%x\n", page); >> + nand->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); >> + nand->ecc.write_page_raw(mtd, nand, buffer); >> + nand->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); >> + >> + /* Wait for the write to finish. */ >> + status = nand->waitfunc(mtd, nand); >> + if (status& NAND_STATUS_FAIL) >> + dev_err(dev, "[%s] Write failed.\n", __func__); >> + } >> + >> + /* Deselect chip 0. */ >> + nand->select_chip(mtd, saved_chip_number); >> + return 0; >> +} >> + >> +int mx23_boot_init(struct gpmi_nfc_data *this) >> +{ >> + struct device *dev = this->dev; >> + struct mil *mil =&this->mil; >> + struct nand_chip *nand =&mil->nand; >> + struct mtd_info *mtd =&mil->mtd; >> + unsigned int block_count; >> + unsigned int block; >> + int chip; >> + int page; >> + loff_t byte; >> + uint8_t block_mark; >> + int error = 0; >> + >> + /* >> + * If control arrives here, we can't use block mark swapping, which >> + * means we're forced to use transcription. First, scan for the >> + * transcription stamp. If we find it, then we don't have to do >> + * anything -- the block marks are already transcribed. >> + */ >> + if (mx23_check_transcription_stamp(this)) >> + return 0; >> + >> + /* >> + * If control arrives here, we couldn't find a transcription stamp, so >> + * so we presume the block marks are in the conventional location. >> + */ >> + pr_info("Transcribing bad block marks...\n"); >> + >> + /* Compute the number of blocks in the entire medium. */ >> + block_count = nand->chipsize>> nand->phys_erase_shift; >> + >> + /* >> + * Loop over all the blocks in the medium, transcribing block marks as >> + * we go. >> + */ >> + for (block = 0; block< block_count; block++) { >> + /* >> + * Compute the chip, page and byte addresses for this block's >> + * conventional mark. >> + */ >> + chip = block>> (nand->chip_shift - nand->phys_erase_shift); >> + page = block<< (nand->phys_erase_shift - nand->page_shift); >> + byte = block<< nand->phys_erase_shift; >> + >> + /* Select the chip. */ >> + nand->select_chip(mtd, chip); >> + >> + /* Send the command to read the conventional block mark. */ >> + nand->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page); >> + >> + /* Read the conventional block mark. */ >> + block_mark = nand->read_byte(mtd); >> + >> + /* >> + * Check if the block is marked bad. If so, we need to mark it >> + * again, but this time the result will be a mark in the >> + * location where we transcribe block marks. >> + * >> + * Notice that we have to explicitly set the marking_a_bad_block >> + * member before we call through the block_markbad function >> + * pointer in the owning struct nand_chip. If we could call >> + * though the block_markbad function pointer in the owning >> + * struct mtd_info, which we have hooked, then this would be >> + * taken care of for us. Unfortunately, we can't because that >> + * higher-level code path will do things like consulting the >> + * in-memory bad block table -- which doesn't even exist yet! >> + * So, we have to call at a lower level and handle some details >> + * ourselves. >> + */ >> + if (block_mark != 0xff) { >> + pr_info("Transcribing mark in block %u\n", block); >> + mil->marking_a_bad_block = true; >> + error = nand->block_markbad(mtd, byte); >> + mil->marking_a_bad_block = false; >> + if (error) >> + dev_err(dev, "Failed to mark block bad with " >> + "error %d\n", error); >> + } >> + >> + /* Deselect the chip. */ >> + nand->select_chip(mtd, -1); >> + } >> + >> + /* Write the stamp that indicates we've transcribed the block marks. */ >> + mx23_write_transcription_stamp(this); >> + return 0; >> +} >> + >> +static int nand_boot_init(struct gpmi_nfc_data *this) >> +{ >> + nand_boot_set_geometry(this); >> + >> + /* This is ROM arch-specific initilization before the BBT scanning. */ >> + if (GPMI_IS_MX23(this)) >> + return mx23_boot_init(this); >> + return 0; >> +} >> + >> +static void show_nfc_geometry(struct nfc_geometry *geo) >> +{ >> + pr_info("---------------------------------------\n"); >> + pr_info(" NFC Geometry (used by BCH)\n"); >> + pr_info("---------------------------------------\n"); >> + pr_info("ECC Algorithm : %s\n", geo->ecc_algorithm); >> + pr_info("ECC Strength : %u\n", geo->ecc_strength); >> + pr_info("Page Size in Bytes : %u\n", geo->page_size_in_bytes); >> + pr_info("Metadata Size in Bytes : %u\n", geo->metadata_size_in_bytes); >> + pr_info("ECC Chunk Size in Bytes: %u\n", geo->ecc_chunk_size_in_bytes); >> + pr_info("ECC Chunk Count : %u\n", geo->ecc_chunk_count); >> + pr_info("Payload Size in Bytes : %u\n", geo->payload_size_in_bytes); >> + pr_info("Auxiliary Size in Bytes: %u\n", geo->auxiliary_size_in_bytes); >> + pr_info("Auxiliary Status Offset: %u\n", geo->auxiliary_status_offset); >> + pr_info("Block Mark Byte Offset : %u\n", geo->block_mark_byte_offset); >> + pr_info("Block Mark Bit Offset : %u\n", geo->block_mark_bit_offset); >> +} >> + >> +static int mil_set_geometry(struct gpmi_nfc_data *this) >> +{ >> + struct nfc_hal *nfc = this->nfc; >> + struct nfc_geometry *geo =&this->nfc_geometry; >> + int error; >> + >> + /* Free the temporary DMA memory for read ID case */ >> + mil_free_dma_buffer(this); >> + >> + /* Set up the NFC geometry which is used by BCH. */ >> + error = nfc->set_geometry(this); >> + if (error != 0) { >> + log("NFC set geometry error : %d", error); >> + return error; >> + } >> + if (gpmi_debug& GPMI_DEBUG_INIT) >> + show_nfc_geometry(geo); >> + >> + /* Alloc the new DMA buffers according to the pagesize and oobsize */ >> + return mil_alloc_dma_buffer(this); >> +} >> + >> +static int mil_pre_bbt_scan(struct gpmi_nfc_data *this) >> +{ >> + struct nand_chip *nand =&this->mil.nand; >> + struct mtd_info *mtd =&this->mil.mtd; >> + struct nand_ecclayout *layout = nand->ecc.layout; >> + struct nfc_hal *nfc = this->nfc; >> + int error; >> + >> + /* fix the ECC layout before the scanning */ >> + layout->eccbytes = 0; >> + layout->oobavail = mtd->oobsize; >> + layout->oobfree[0].offset = 0; >> + layout->oobfree[0].length = mtd->oobsize; >> + >> + mtd->oobavail = nand->ecc.layout->oobavail; >> + >> + /* Set up swap block-mark, must be set before the mil_set_geometry() */ >> + if (GPMI_IS_MX23(this)) >> + this->swap_block_mark = false; >> + else >> + this->swap_block_mark = true; >> + >> + /* Set up the medium geometry */ >> + error = mil_set_geometry(this); >> + if (error) >> + return error; >> + >> + /* extra init */ >> + if (nfc->extra_init) { >> + error = nfc->extra_init(this); >> + if (error != 0) >> + return error; >> + } >> + >> + /* NAND boot init, depends on the mil_set_geometry(). */ >> + return nand_boot_init(this); >> +} >> + >> +static int mil_scan_bbt(struct mtd_info *mtd) >> +{ >> + struct nand_chip *nand = mtd->priv; >> + struct gpmi_nfc_data *this = nand->priv; >> + int error; >> + >> + /* Prepare for the BBT scan. */ >> + error = mil_pre_bbt_scan(this); >> + if (error) >> + return error; >> + >> + /* use the default BBT implementation */ >> + return nand_default_bbt(mtd); >> +} >> + >> +static const char *cmd_parse = "cmdlinepart"; >> > This should be a NULL terminated list of strings: > static const char *cmd_parse[] = { "cmdlinepart", NULL }; > thanks a lot. >> +static int mil_partitions_init(struct gpmi_nfc_data *this) >> +{ >> + struct gpmi_nfc_platform_data *pdata = this->pdata; >> + struct mil *mil =&this->mil; >> + struct mtd_info *mtd =&mil->mtd; >> + >> + /* use the command line for simple partitions layout */ >> + mil->partition_count = parse_mtd_partitions(mtd,&cmd_parse, >> + &mil->partitions, 0); >> + if (mil->partition_count) >> + return add_mtd_partitions(mtd, mil->partitions, >> + mil->partition_count); >> + >> + /* The complicated partitions layout uses this. */ >> + if (pdata->partitions&& pdata->partition_count> 0) >> + return add_mtd_partitions(mtd, pdata->partitions, >> + pdata->partition_count); >> + return 0; > How about: > return mtd_add_device(mtd); > so you will get the whole flash registered in case there are no > partitions defined. > no problem. >> +#ifdef CONFIG_PM >> +static int gpmi_nfc_suspend(struct platform_device *pdev, pm_message_t state) >> +{ >> + return 0; >> +} >> +static int gpmi_nfc_resume(struct platform_device *pdev) >> +{ >> + return 0; >> +} >> +#else >> +#define gpmi_nfc_suspend NULL >> +#define gpmi_nfc_resume NULL >> +#endif >> + > There is no point in adding empty suspend/resume functions. > Furthermore you should use dev_pm_ops. > Ok. I think the gpmi needs to do nothing when suspend. Best Regards Huang shijie > Lothar Wa?mann ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v6 1/3] MTD : add the common code for GPMI controller driver 2011-04-21 7:49 ` Lothar Waßmann @ 2011-04-26 2:52 ` Huang Shijie -1 siblings, 0 replies; 20+ messages in thread From: Huang Shijie @ 2011-04-26 2:52 UTC (permalink / raw) To: Lothar Waßmann; +Cc: linux-mtd, linux-arm-kernel, dedekind1 Hi: > Huang Shijie writes: >> These files contain the common code for the GPMI driver. >> >> Signed-off-by: Huang Shijie<b32955@freescale.com> >> --- >> drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c | 2501 ++++++++++++++++++++++++++++++++++ >> drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h | 488 +++++++ >> 2 files changed, 2989 insertions(+), 0 deletions(-) >> create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c >> create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h >> >> diff --git a/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c >> new file mode 100644 >> index 0000000..53d6915 >> --- /dev/null >> +++ b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c >> @@ -0,0 +1,2501 @@ >> +/* >> + * Freescale GPMI NFC NAND Flash Driver >> + * >> + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. >> + * Copyright (C) 2008 Embedded Alley Solutions, Inc. >> + * >> + * This program is free software; you can redistribute it and/or modify >> + * it under the terms of the GNU General Public License as published by >> + * the Free Software Foundation; either version 2 of the License, or >> + * (at your option) any later version. >> + * >> + * 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., >> + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >> + */ >> +#include<linux/slab.h> >> +#include "gpmi-nfc.h" >> + >> +/* add our owner bbt descriptor */ >> +static uint8_t scan_ff_pattern[] = { 0xff }; >> +static struct nand_bbt_descr gpmi_bbt_descr = { >> + .options = 0, >> + .offs = 0, >> + .len = 1, >> + .pattern = scan_ff_pattern >> +}; >> + >> +/* debug control */ >> +int gpmi_debug; >> + > This could be a module_param. > I think the module_param is not easy to use. I want to know the log when the gpmi driver is running, and change the log level as soon as my need changs. So I prefer to use the origin method. Best Regards Huang Shijie ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 1/3] MTD : add the common code for GPMI controller driver @ 2011-04-26 2:52 ` Huang Shijie 0 siblings, 0 replies; 20+ messages in thread From: Huang Shijie @ 2011-04-26 2:52 UTC (permalink / raw) To: linux-arm-kernel Hi: > Huang Shijie writes: >> These files contain the common code for the GPMI driver. >> >> Signed-off-by: Huang Shijie<b32955@freescale.com> >> --- >> drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c | 2501 ++++++++++++++++++++++++++++++++++ >> drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h | 488 +++++++ >> 2 files changed, 2989 insertions(+), 0 deletions(-) >> create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c >> create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-nfc.h >> >> diff --git a/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c >> new file mode 100644 >> index 0000000..53d6915 >> --- /dev/null >> +++ b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc.c >> @@ -0,0 +1,2501 @@ >> +/* >> + * Freescale GPMI NFC NAND Flash Driver >> + * >> + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. >> + * Copyright (C) 2008 Embedded Alley Solutions, Inc. >> + * >> + * This program is free software; you can redistribute it and/or modify >> + * it under the terms of the GNU General Public License as published by >> + * the Free Software Foundation; either version 2 of the License, or >> + * (at your option) any later version. >> + * >> + * 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., >> + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >> + */ >> +#include<linux/slab.h> >> +#include "gpmi-nfc.h" >> + >> +/* add our owner bbt descriptor */ >> +static uint8_t scan_ff_pattern[] = { 0xff }; >> +static struct nand_bbt_descr gpmi_bbt_descr = { >> + .options = 0, >> + .offs = 0, >> + .len = 1, >> + .pattern = scan_ff_pattern >> +}; >> + >> +/* debug control */ >> +int gpmi_debug; >> + > This could be a module_param. > I think the module_param is not easy to use. I want to know the log when the gpmi driver is running, and change the log level as soon as my need changs. So I prefer to use the origin method. Best Regards Huang Shijie ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v6 1/3] MTD : add the common code for GPMI controller driver 2011-04-26 2:52 ` Huang Shijie @ 2011-04-26 10:09 ` Lothar Waßmann -1 siblings, 0 replies; 20+ messages in thread From: Lothar Waßmann @ 2011-04-26 10:09 UTC (permalink / raw) To: Huang Shijie; +Cc: linux-mtd, linux-arm-kernel, dedekind1 Hi, Huang Shijie writes: > Hi: > > Huang Shijie writes: > >> +/* debug control */ > >> +int gpmi_debug; > >> + > > This could be a module_param. > > > I think the module_param is not easy to use. > I want to know the log when the gpmi driver is running, and change the > log level as soon > as my need changs. > > So I prefer to use the origin method. > > |static int debug; |module_param(debug, int, S_IRUGO | S_IWUSR); is all you need to do exactly what you want. You can write to /sys/module/imx28-gpmi-nfc/parameters/debug at any time to change the debug level. Lothar Waßmann -- ___________________________________________________________ Ka-Ro electronics GmbH | Pascalstraße 22 | D - 52076 Aachen Phone: +49 2408 1402-0 | Fax: +49 2408 1402-10 Geschäftsführer: Matthias Kaussen Handelsregistereintrag: Amtsgericht Aachen, HRB 4996 www.karo-electronics.de | info@karo-electronics.de ___________________________________________________________ ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 1/3] MTD : add the common code for GPMI controller driver @ 2011-04-26 10:09 ` Lothar Waßmann 0 siblings, 0 replies; 20+ messages in thread From: Lothar Waßmann @ 2011-04-26 10:09 UTC (permalink / raw) To: linux-arm-kernel Hi, Huang Shijie writes: > Hi: > > Huang Shijie writes: > >> +/* debug control */ > >> +int gpmi_debug; > >> + > > This could be a module_param. > > > I think the module_param is not easy to use. > I want to know the log when the gpmi driver is running, and change the > log level as soon > as my need changs. > > So I prefer to use the origin method. > > |static int debug; |module_param(debug, int, S_IRUGO | S_IWUSR); is all you need to do exactly what you want. You can write to /sys/module/imx28-gpmi-nfc/parameters/debug at any time to change the debug level. Lothar Wa?mann -- ___________________________________________________________ Ka-Ro electronics GmbH | Pascalstra?e 22 | D - 52076 Aachen Phone: +49 2408 1402-0 | Fax: +49 2408 1402-10 Gesch?ftsf?hrer: Matthias Kaussen Handelsregistereintrag: Amtsgericht Aachen, HRB 4996 www.karo-electronics.de | info at karo-electronics.de ___________________________________________________________ ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v6 1/3] MTD : add the common code for GPMI controller driver 2011-04-20 2:45 ` Huang Shijie @ 2011-04-28 19:20 ` Russell King - ARM Linux -1 siblings, 0 replies; 20+ messages in thread From: Russell King - ARM Linux @ 2011-04-28 19:20 UTC (permalink / raw) To: Huang Shijie; +Cc: linux-mtd, linux-arm-kernel, dedekind1 On Wed, Apr 20, 2011 at 10:45:47AM +0800, Huang Shijie wrote: > +static inline int acquire_clock(struct gpmi_nfc_data *this, struct clk **clock) > +{ > + struct clk *c; > + > + c = clk_get(&this->pdev->dev, NULL); > + if (IS_ERR(c)) { > + log("Can't own clock"); > + return PTR_ERR(c); > + } > + *clock = c; > + return 0; > +} > + > +static void release_clock(struct gpmi_nfc_data *this, struct clk *clock) > +{ > + clk_put(clock); > +} Do you really need to abstract like this? It appears to just complicate the code unnecessarily. > +#define log(a, ...) printk(KERN_INFO "[ %s : %.3d ] "a"\n", \ > + __func__, __LINE__, ## __VA_ARGS__) Also, I don't like this log() function. You can do the same with: #define pr_fmt(fmt) "[ %s : %.3d ] " fmt, __func__, __LINE__ and use pr_info() instead. Note that it's generally frowned upon to hide the \n inside the pr_fmt(). ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 1/3] MTD : add the common code for GPMI controller driver @ 2011-04-28 19:20 ` Russell King - ARM Linux 0 siblings, 0 replies; 20+ messages in thread From: Russell King - ARM Linux @ 2011-04-28 19:20 UTC (permalink / raw) To: linux-arm-kernel On Wed, Apr 20, 2011 at 10:45:47AM +0800, Huang Shijie wrote: > +static inline int acquire_clock(struct gpmi_nfc_data *this, struct clk **clock) > +{ > + struct clk *c; > + > + c = clk_get(&this->pdev->dev, NULL); > + if (IS_ERR(c)) { > + log("Can't own clock"); > + return PTR_ERR(c); > + } > + *clock = c; > + return 0; > +} > + > +static void release_clock(struct gpmi_nfc_data *this, struct clk *clock) > +{ > + clk_put(clock); > +} Do you really need to abstract like this? It appears to just complicate the code unnecessarily. > +#define log(a, ...) printk(KERN_INFO "[ %s : %.3d ] "a"\n", \ > + __func__, __LINE__, ## __VA_ARGS__) Also, I don't like this log() function. You can do the same with: #define pr_fmt(fmt) "[ %s : %.3d ] " fmt, __func__, __LINE__ and use pr_info() instead. Note that it's generally frowned upon to hide the \n inside the pr_fmt(). ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v6 1/3] MTD : add the common code for GPMI controller driver 2011-04-28 19:20 ` Russell King - ARM Linux @ 2011-04-29 1:42 ` Huang Shijie -1 siblings, 0 replies; 20+ messages in thread From: Huang Shijie @ 2011-04-29 1:42 UTC (permalink / raw) To: Russell King - ARM Linux; +Cc: linux-mtd, linux-arm-kernel, dedekind1 hi: > On Wed, Apr 20, 2011 at 10:45:47AM +0800, Huang Shijie wrote: >> +static inline int acquire_clock(struct gpmi_nfc_data *this, struct clk **clock) >> +{ >> + struct clk *c; >> + >> + c = clk_get(&this->pdev->dev, NULL); >> + if (IS_ERR(c)) { >> + log("Can't own clock"); >> + return PTR_ERR(c); >> + } >> + *clock = c; >> + return 0; >> +} >> + >> +static void release_clock(struct gpmi_nfc_data *this, struct clk *clock) >> +{ >> + clk_put(clock); >> +} > Do you really need to abstract like this? It appears to just complicate > the code unnecessarily. > ok, I can remove the two functions. >> +#define log(a, ...) printk(KERN_INFO "[ %s : %.3d ] "a"\n", \ >> + __func__, __LINE__, ## __VA_ARGS__) > Also, I don't like this log() function. You can do the same with: > > #define pr_fmt(fmt) "[ %s : %.3d ] " fmt, __func__, __LINE__ > > and use pr_info() instead. Note that it's generally frowned upon to > hide the \n inside the pr_fmt(). > ok, I will change it. Thanks . Best Regards Huang Shijie ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 1/3] MTD : add the common code for GPMI controller driver @ 2011-04-29 1:42 ` Huang Shijie 0 siblings, 0 replies; 20+ messages in thread From: Huang Shijie @ 2011-04-29 1:42 UTC (permalink / raw) To: linux-arm-kernel hi: > On Wed, Apr 20, 2011 at 10:45:47AM +0800, Huang Shijie wrote: >> +static inline int acquire_clock(struct gpmi_nfc_data *this, struct clk **clock) >> +{ >> + struct clk *c; >> + >> + c = clk_get(&this->pdev->dev, NULL); >> + if (IS_ERR(c)) { >> + log("Can't own clock"); >> + return PTR_ERR(c); >> + } >> + *clock = c; >> + return 0; >> +} >> + >> +static void release_clock(struct gpmi_nfc_data *this, struct clk *clock) >> +{ >> + clk_put(clock); >> +} > Do you really need to abstract like this? It appears to just complicate > the code unnecessarily. > ok, I can remove the two functions. >> +#define log(a, ...) printk(KERN_INFO "[ %s : %.3d ] "a"\n", \ >> + __func__, __LINE__, ## __VA_ARGS__) > Also, I don't like this log() function. You can do the same with: > > #define pr_fmt(fmt) "[ %s : %.3d ] " fmt, __func__, __LINE__ > > and use pr_info() instead. Note that it's generally frowned upon to > hide the \n inside the pr_fmt(). > ok, I will change it. Thanks . Best Regards Huang Shijie ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 2/3] MTD : add support for imx23 and imx28 2011-04-20 2:45 ` Huang Shijie @ 2011-04-20 2:45 ` Huang Shijie -1 siblings, 0 replies; 20+ messages in thread From: Huang Shijie @ 2011-04-20 2:45 UTC (permalink / raw) To: dedekind1; +Cc: Huang Shijie, linux-mtd, linux-arm-kernel Add the implementation code for imx23 and imx28. The imx23 and imx28 share most of the same code. Signed-off-by: Huang Shijie <b32955@freescale.com> --- drivers/mtd/nand/gpmi-nfc/bch-regs.h | 88 ++++++ drivers/mtd/nand/gpmi-nfc/gpmi-regs.h | 163 ++++++++++ drivers/mtd/nand/gpmi-nfc/hal-mxs.c | 554 +++++++++++++++++++++++++++++++++ 3 files changed, 805 insertions(+), 0 deletions(-) create mode 100644 drivers/mtd/nand/gpmi-nfc/bch-regs.h create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-regs.h create mode 100644 drivers/mtd/nand/gpmi-nfc/hal-mxs.c diff --git a/drivers/mtd/nand/gpmi-nfc/bch-regs.h b/drivers/mtd/nand/gpmi-nfc/bch-regs.h new file mode 100644 index 0000000..90af8cf --- /dev/null +++ b/drivers/mtd/nand/gpmi-nfc/bch-regs.h @@ -0,0 +1,88 @@ +/* + * Freescale GPMI NFC NAND Flash Driver + * + * Copyright 2008-2011 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#ifndef __GPMI_NFC_BCH_REGS_H +#define __GPMI_NFC_BCH_REGS_H + +/*============================================================================*/ +#define HW_BCH_CTRL 0x00000000 +#define HW_BCH_CTRL_SET 0x00000004 +#define HW_BCH_CTRL_CLR 0x00000008 +#define HW_BCH_CTRL_TOG 0x0000000c + +#define BM_BCH_CTRL_COMPLETE_IRQ_EN (1 << 8) +#define BM_BCH_CTRL_COMPLETE_IRQ (1 << 0) + +/*============================================================================*/ +#define HW_BCH_STATUS0 0x00000010 +#define HW_BCH_MODE 0x00000020 +#define HW_BCH_ENCODEPTR 0x00000030 +#define HW_BCH_DATAPTR 0x00000040 +#define HW_BCH_METAPTR 0x00000050 +#define HW_BCH_LAYOUTSELECT 0x00000070 + +/*============================================================================*/ +#define HW_BCH_FLASH0LAYOUT0 0x00000080 + +#define BP_BCH_FLASH0LAYOUT0_NBLOCKS 24 +#define BM_BCH_FLASH0LAYOUT0_NBLOCKS (0xff << BP_BCH_FLASH0LAYOUT0_NBLOCKS) +#define BF_BCH_FLASH0LAYOUT0_NBLOCKS(v) \ + (((v) << BP_BCH_FLASH0LAYOUT0_NBLOCKS) & BM_BCH_FLASH0LAYOUT0_NBLOCKS) + +#define BP_BCH_FLASH0LAYOUT0_META_SIZE 16 +#define BM_BCH_FLASH0LAYOUT0_META_SIZE (0xff << BP_BCH_FLASH0LAYOUT0_META_SIZE) +#define BF_BCH_FLASH0LAYOUT0_META_SIZE(v) \ + (((v) << BP_BCH_FLASH0LAYOUT0_META_SIZE)\ + & BM_BCH_FLASH0LAYOUT0_META_SIZE) + +#define BP_BCH_FLASH0LAYOUT0_ECC0 12 +#define BM_BCH_FLASH0LAYOUT0_ECC0 (0xf << BP_BCH_FLASH0LAYOUT0_ECC0) +#define BF_BCH_FLASH0LAYOUT0_ECC0(v) \ + (((v) << BP_BCH_FLASH0LAYOUT0_ECC0) & BM_BCH_FLASH0LAYOUT0_ECC0) + +#define BP_BCH_FLASH0LAYOUT0_DATA0_SIZE 0 +#define BM_BCH_FLASH0LAYOUT0_DATA0_SIZE \ + (0xfff << BP_BCH_FLASH0LAYOUT0_DATA0_SIZE) +#define BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(v) \ + (((v) << BP_BCH_FLASH0LAYOUT0_DATA0_SIZE)\ + & BM_BCH_FLASH0LAYOUT0_DATA0_SIZE) + +/*============================================================================*/ +#define HW_BCH_FLASH0LAYOUT1 0x00000090 + +#define BP_BCH_FLASH0LAYOUT1_PAGE_SIZE 16 +#define BM_BCH_FLASH0LAYOUT1_PAGE_SIZE \ + (0xffff << BP_BCH_FLASH0LAYOUT1_PAGE_SIZE) +#define BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(v) \ + (((v) << BP_BCH_FLASH0LAYOUT1_PAGE_SIZE) \ + & BM_BCH_FLASH0LAYOUT1_PAGE_SIZE) + +#define BP_BCH_FLASH0LAYOUT1_ECCN 12 +#define BM_BCH_FLASH0LAYOUT1_ECCN (0xf << BP_BCH_FLASH0LAYOUT1_ECCN) +#define BF_BCH_FLASH0LAYOUT1_ECCN(v) \ + (((v) << BP_BCH_FLASH0LAYOUT1_ECCN) & BM_BCH_FLASH0LAYOUT1_ECCN) + +#define BP_BCH_FLASH0LAYOUT1_DATAN_SIZE 0 +#define BM_BCH_FLASH0LAYOUT1_DATAN_SIZE \ + (0xfff << BP_BCH_FLASH0LAYOUT1_DATAN_SIZE) +#define BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(v) \ + (((v) << BP_BCH_FLASH0LAYOUT1_DATAN_SIZE) \ + & BM_BCH_FLASH0LAYOUT1_DATAN_SIZE) +#endif diff --git a/drivers/mtd/nand/gpmi-nfc/gpmi-regs.h b/drivers/mtd/nand/gpmi-nfc/gpmi-regs.h new file mode 100644 index 0000000..d012c76 --- /dev/null +++ b/drivers/mtd/nand/gpmi-nfc/gpmi-regs.h @@ -0,0 +1,163 @@ +/* + * Freescale GPMI NFC NAND Flash Driver + * + * Copyright 2008-2011 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#ifndef __GPMI_NFC_GPMI_REGS_H +#define __GPMI_NFC_GPMI_REGS_H + +/*============================================================================*/ +#define HW_GPMI_CTRL0 0x00000000 +#define HW_GPMI_CTRL0_SET 0x00000004 +#define HW_GPMI_CTRL0_CLR 0x00000008 +#define HW_GPMI_CTRL0_TOG 0x0000000c + +#define BP_GPMI_CTRL0_COMMAND_MODE 24 +#define BM_GPMI_CTRL0_COMMAND_MODE (3 << BP_GPMI_CTRL0_COMMAND_MODE) +#define BF_GPMI_CTRL0_COMMAND_MODE(v) \ + (((v) << BP_GPMI_CTRL0_COMMAND_MODE) & BM_GPMI_CTRL0_COMMAND_MODE) +#define BV_GPMI_CTRL0_COMMAND_MODE__WRITE 0x0 +#define BV_GPMI_CTRL0_COMMAND_MODE__READ 0x1 +#define BV_GPMI_CTRL0_COMMAND_MODE__READ_AND_COMPARE 0x2 +#define BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY 0x3 + +#define BM_GPMI_CTRL0_WORD_LENGTH (1 << 23) +#define BV_GPMI_CTRL0_WORD_LENGTH__16_BIT 0x0 +#define BV_GPMI_CTRL0_WORD_LENGTH__8_BIT 0x1 + +/* different in CS between imx23 and imx28 */ +#define BP_GPMI_CTRL0_CS 20 +#define MX23_BM_GPMI_CTRL0_CS (3 << BP_GPMI_CTRL0_CS) +#define MX28_BM_GPMI_CTRL0_CS (7 << BP_GPMI_CTRL0_CS) +#define BF_GPMI_CTRL0_CS(v, x) (((v) << 20) & (GPMI_IS_MX23((x)) \ + ? MX23_BM_GPMI_CTRL0_CS \ + : MX28_BM_GPMI_CTRL0_CS)) + +#define BP_GPMI_CTRL0_ADDRESS 17 +#define BM_GPMI_CTRL0_ADDRESS (3 << BP_GPMI_CTRL0_ADDRESS) +#define BF_GPMI_CTRL0_ADDRESS(v) \ + (((v) << BP_GPMI_CTRL0_ADDRESS) & BM_GPMI_CTRL0_ADDRESS) +#define BV_GPMI_CTRL0_ADDRESS__NAND_DATA 0x0 +#define BV_GPMI_CTRL0_ADDRESS__NAND_CLE 0x1 +#define BV_GPMI_CTRL0_ADDRESS__NAND_ALE 0x2 + +#define BM_GPMI_CTRL0_ADDRESS_INCREMENT (1 << 16) +#define BV_GPMI_CTRL0_ADDRESS_INCREMENT__DISABLED 0x0 +#define BV_GPMI_CTRL0_ADDRESS_INCREMENT__ENABLED 0x1 + +#define BP_GPMI_CTRL0_XFER_COUNT 0 +#define BM_GPMI_CTRL0_XFER_COUNT (0xffff << BP_GPMI_CTRL0_XFER_COUNT) +#define BF_GPMI_CTRL0_XFER_COUNT(v) \ + (((v) << BP_GPMI_CTRL0_XFER_COUNT) & BM_GPMI_CTRL0_XFER_COUNT) + +/*============================================================================*/ +#define HW_GPMI_COMPARE 0x00000010 +/*============================================================================*/ +#define HW_GPMI_ECCCTRL 0x00000020 +#define HW_GPMI_ECCCTRL_SET 0x00000024 +#define HW_GPMI_ECCCTRL_CLR 0x00000028 +#define HW_GPMI_ECCCTRL_TOG 0x0000002c + +#define BP_GPMI_ECCCTRL_ECC_CMD 13 +#define BM_GPMI_ECCCTRL_ECC_CMD (3 << BP_GPMI_ECCCTRL_ECC_CMD) +#define BF_GPMI_ECCCTRL_ECC_CMD(v) \ + (((v) << BP_GPMI_ECCCTRL_ECC_CMD) & BM_GPMI_ECCCTRL_ECC_CMD) +#define BV_GPMI_ECCCTRL_ECC_CMD__BCH_DECODE 0x0 +#define BV_GPMI_ECCCTRL_ECC_CMD__BCH_ENCODE 0x1 + +#define BM_GPMI_ECCCTRL_ENABLE_ECC (1 << 12) +#define BV_GPMI_ECCCTRL_ENABLE_ECC__ENABLE 0x1 +#define BV_GPMI_ECCCTRL_ENABLE_ECC__DISABLE 0x0 + +#define BP_GPMI_ECCCTRL_BUFFER_MASK 0 +#define BM_GPMI_ECCCTRL_BUFFER_MASK (0x1ff << BP_GPMI_ECCCTRL_BUFFER_MASK) +#define BF_GPMI_ECCCTRL_BUFFER_MASK(v) \ + (((v) << BP_GPMI_ECCCTRL_BUFFER_MASK) & BM_GPMI_ECCCTRL_BUFFER_MASK) +#define BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY 0x100 +#define BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE 0x1FF + +/*============================================================================*/ +#define HW_GPMI_ECCCOUNT 0x00000030 +#define HW_GPMI_PAYLOAD 0x00000040 +#define HW_GPMI_AUXILIARY 0x00000050 +/*============================================================================*/ +#define HW_GPMI_CTRL1 0x00000060 +#define HW_GPMI_CTRL1_SET 0x00000064 +#define HW_GPMI_CTRL1_CLR 0x00000068 +#define HW_GPMI_CTRL1_TOG 0x0000006c + +#define BM_GPMI_CTRL1_BCH_MODE (1 << 18) + +#define BP_GPMI_CTRL1_DLL_ENABLE 17 +#define BM_GPMI_CTRL1_DLL_ENABLE (1 << BP_GPMI_CTRL1_DLL_ENABLE) + +#define BP_GPMI_CTRL1_HALF_PERIOD 16 +#define BM_GPMI_CTRL1_HALF_PERIOD (1 << BP_GPMI_CTRL1_HALF_PERIOD) + +#define BP_GPMI_CTRL1_RDN_DELAY 12 +#define BM_GPMI_CTRL1_RDN_DELAY (0xf << BP_GPMI_CTRL1_RDN_DELAY) +#define BF_GPMI_CTRL1_RDN_DELAY(v) \ + (((v) << BP_GPMI_CTRL1_RDN_DELAY) & BM_GPMI_CTRL1_RDN_DELAY) + +#define BM_GPMI_CTRL1_DEV_RESET (1 << 3) +#define BV_GPMI_CTRL1_DEV_RESET__ENABLED 0x0 +#define BV_GPMI_CTRL1_DEV_RESET__DISABLED 0x1 + +#define BM_GPMI_CTRL1_ATA_IRQRDY_POLARITY (1 << 2) +#define BV_GPMI_CTRL1_ATA_IRQRDY_POLARITY__ACTIVELOW 0x0 +#define BV_GPMI_CTRL1_ATA_IRQRDY_POLARITY__ACTIVEHIGH 0x1 + +#define BM_GPMI_CTRL1_CAMERA_MODE (1 << 1) +#define BV_GPMI_CTRL1_GPMI_MODE__NAND 0x0 +#define BV_GPMI_CTRL1_GPMI_MODE__ATA 0x1 + +#define BM_GPMI_CTRL1_GPMI_MODE (1 << 0) + +/*============================================================================*/ +#define HW_GPMI_TIMING0 0x00000070 + +#define BP_GPMI_TIMING0_ADDRESS_SETUP 16 +#define BM_GPMI_TIMING0_ADDRESS_SETUP (0xff << BP_GPMI_TIMING0_ADDRESS_SETUP) +#define BF_GPMI_TIMING0_ADDRESS_SETUP(v) \ + (((v) << BP_GPMI_TIMING0_ADDRESS_SETUP) & BM_GPMI_TIMING0_ADDRESS_SETUP) + +#define BP_GPMI_TIMING0_DATA_HOLD 8 +#define BM_GPMI_TIMING0_DATA_HOLD (0xff << BP_GPMI_TIMING0_DATA_HOLD) +#define BF_GPMI_TIMING0_DATA_HOLD(v) \ + (((v) << BP_GPMI_TIMING0_DATA_HOLD) & BM_GPMI_TIMING0_DATA_HOLD) + +#define BP_GPMI_TIMING0_DATA_SETUP 0 +#define BM_GPMI_TIMING0_DATA_SETUP (0xff << BP_GPMI_TIMING0_DATA_SETUP) +#define BF_GPMI_TIMING0_DATA_SETUP(v) \ + (((v) << BP_GPMI_TIMING0_DATA_SETUP) & BM_GPMI_TIMING0_DATA_SETUP) + +/*============================================================================*/ +#define HW_GPMI_TIMING1 0x00000080 +#define HW_GPMI_TIMING2 0x00000090 +#define HW_GPMI_DATA 0x000000a0 +/*============================ MX28 uses this to detect READY ================*/ +#define HW_GPMI_STAT 0x000000b0 +#define MX28_BP_GPMI_STAT_READY_BUSY 24 +#define MX28_BM_GPMI_STAT_READY_BUSY (0xff << MX28_BP_GPMI_STAT_READY_BUSY) +#define MX28_BF_GPMI_STAT_READY_BUSY(v) \ + (((v) << MX28_BP_GPMI_STAT_READY_BUSY) & MX28_BM_GPMI_STAT_READY_BUSY) +/*============================ MX23 uses this to detect READY ================*/ +#define HW_GPMI_DEBUG 0x000000c0 +#define MX23_BP_GPMI_DEBUG_READY0 28 +#define MX23_BM_GPMI_DEBUG_READY0 (1 << MX23_BP_GPMI_DEBUG_READY0) +#endif diff --git a/drivers/mtd/nand/gpmi-nfc/hal-mxs.c b/drivers/mtd/nand/gpmi-nfc/hal-mxs.c new file mode 100644 index 0000000..8b0181e --- /dev/null +++ b/drivers/mtd/nand/gpmi-nfc/hal-mxs.c @@ -0,0 +1,554 @@ +/* + * Freescale GPMI NFC NAND Flash Driver + * + * Copyright (C) 2008-2011 Freescale Semiconductor, Inc. + * Copyright (C) 2008 Embedded Alley Solutions, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "gpmi-nfc.h" +#include "gpmi-regs.h" +#include "bch-regs.h" + +static int init_hal(struct gpmi_nfc_data *this) +{ + struct resources *resources = &this->resources; + + /* Enable the clock */ + clk_enable(resources->clock); + + /* Reset the GPMI block. */ + mxs_reset_block(resources->gpmi_regs); + + /* Choose NAND mode. */ + __raw_writel(BM_GPMI_CTRL1_GPMI_MODE, + resources->gpmi_regs + HW_GPMI_CTRL1_CLR); + + /* Set the IRQ polarity. */ + __raw_writel(BM_GPMI_CTRL1_ATA_IRQRDY_POLARITY, + resources->gpmi_regs + HW_GPMI_CTRL1_SET); + + /* Disable write protection. */ + __raw_writel(BM_GPMI_CTRL1_DEV_RESET, + resources->gpmi_regs + HW_GPMI_CTRL1_SET); + + /* Select BCH ECC. */ + __raw_writel(BM_GPMI_CTRL1_BCH_MODE, + resources->gpmi_regs + HW_GPMI_CTRL1_SET); + + /* Disable the clock. */ + clk_disable(resources->clock); + return 0; +} + +/* Configures the NFC geometry for BCH. */ +static int set_geometry(struct gpmi_nfc_data *this) +{ + struct resources *resources = &this->resources; + struct nfc_geometry *nfc = &this->nfc_geometry; + unsigned int block_count; + unsigned int block_size; + unsigned int metadata_size; + unsigned int ecc_strength; + unsigned int page_size; + + if (common_nfc_set_geometry(this)) + return !0; + + block_count = nfc->ecc_chunk_count - 1; + block_size = nfc->ecc_chunk_size_in_bytes; + metadata_size = nfc->metadata_size_in_bytes; + ecc_strength = nfc->ecc_strength >> 1; + page_size = nfc->page_size_in_bytes; + + clk_enable(resources->clock); + + /* + * Reset the BCH block. Notice that we pass in true for the just_enable + * flag. This is because the soft reset for the version 0 BCH block + * doesn't work. If you try to soft reset the BCH block, it becomes + * unusable until the next hard reset. + */ + mxs_reset_block(resources->bch_regs); + + /* Configure layout 0. */ + __raw_writel(BF_BCH_FLASH0LAYOUT0_NBLOCKS(block_count) + | BF_BCH_FLASH0LAYOUT0_META_SIZE(metadata_size) + | BF_BCH_FLASH0LAYOUT0_ECC0(ecc_strength) + | BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(block_size), + resources->bch_regs + HW_BCH_FLASH0LAYOUT0); + + __raw_writel(BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(page_size) + | BF_BCH_FLASH0LAYOUT1_ECCN(ecc_strength) + | BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(block_size), + resources->bch_regs + HW_BCH_FLASH0LAYOUT1); + + /* Set *all* chip selects to use layout 0. */ + __raw_writel(0, resources->bch_regs + HW_BCH_LAYOUTSELECT); + + /* Enable interrupts. */ + __raw_writel(BM_BCH_CTRL_COMPLETE_IRQ_EN, + resources->bch_regs + HW_BCH_CTRL_SET); + + clk_disable(resources->clock); + return 0; +} + +static int set_timing(struct gpmi_nfc_data *this, + const struct nand_timing *timing) +{ + struct nfc_hal *nfc = this->nfc; + + nfc->timing = *timing; + return 0; +} + +/** + * get_timing() - Retrieves the NFC hardware timing. + * + * @this: Per-device data. + * @clock_frequency_in_hz: The clock frequency, in Hz, during the current + * I/O transaction. If no I/O transaction is in + * progress, this is the clock frequency during the + * most recent I/O transaction. + * @hardware_timing: The hardware timing configuration in effect during + * the current I/O transaction. If no I/O transaction + * is in progress, this is the hardware timing + * configuration during the most recent I/O + * transaction. + */ +static void get_timing(struct gpmi_nfc_data *this, + unsigned long *clock_frequency_in_hz, + struct gpmi_nfc_hardware_timing *hardware_timing) +{ + struct resources *resources = &this->resources; + struct nfc_hal *nfc = this->nfc; + unsigned char *gpmi_regs = resources->gpmi_regs; + uint32_t register_image; + + /* Return the clock frequency. */ + *clock_frequency_in_hz = nfc->clock_frequency_in_hz; + + /* We'll be reading the hardware, so let's enable the clock. */ + clk_enable(resources->clock); + + /* Retrieve the hardware timing. */ + register_image = __raw_readl(gpmi_regs + HW_GPMI_TIMING0); + + hardware_timing->data_setup_in_cycles = + (register_image & BM_GPMI_TIMING0_DATA_SETUP) >> + BP_GPMI_TIMING0_DATA_SETUP; + + hardware_timing->data_hold_in_cycles = + (register_image & BM_GPMI_TIMING0_DATA_HOLD) >> + BP_GPMI_TIMING0_DATA_HOLD; + + hardware_timing->address_setup_in_cycles = + (register_image & BM_GPMI_TIMING0_ADDRESS_SETUP) >> + BP_GPMI_TIMING0_ADDRESS_SETUP; + + register_image = __raw_readl(gpmi_regs + HW_GPMI_CTRL1); + + hardware_timing->use_half_periods = + (register_image & BM_GPMI_CTRL1_HALF_PERIOD) >> + BP_GPMI_CTRL1_HALF_PERIOD; + + hardware_timing->sample_delay_factor = + (register_image & BM_GPMI_CTRL1_RDN_DELAY) >> + BP_GPMI_CTRL1_RDN_DELAY; + + /* We're done reading the hardware, so disable the clock. */ + clk_disable(resources->clock); +} + +static void exit(struct gpmi_nfc_data *this) +{ +} + +/* Begin the I/O */ +static void begin(struct gpmi_nfc_data *this) +{ + struct resources *resources = &this->resources; + struct nfc_hal *nfc = this->nfc; + unsigned char *gpmi_regs = resources->gpmi_regs; + unsigned int clock_period_in_ns; + uint32_t register_image; + unsigned int dll_wait_time_in_us; + struct gpmi_nfc_hardware_timing hw; + + /* Enable the clock. */ + clk_enable(resources->clock); + + /* set the timing for imx23 */ + if (!GPMI_IS_MX23(this)) + return; + + /* Get the timing information we need. */ + nfc->clock_frequency_in_hz = clk_get_rate(resources->clock); + clock_period_in_ns = 1000000000 / nfc->clock_frequency_in_hz; + + gpmi_nfc_compute_hardware_timing(this, &hw); + + /* Set up all the simple timing parameters. */ + register_image = + BF_GPMI_TIMING0_ADDRESS_SETUP(hw.address_setup_in_cycles) | + BF_GPMI_TIMING0_DATA_HOLD(hw.data_hold_in_cycles) | + BF_GPMI_TIMING0_DATA_SETUP(hw.data_setup_in_cycles) ; + + __raw_writel(register_image, gpmi_regs + HW_GPMI_TIMING0); + + /* + * HEY - PAY ATTENTION! + * + * DLL_ENABLE must be set to zero when setting RDN_DELAY or HALF_PERIOD. + */ + __raw_writel(BM_GPMI_CTRL1_DLL_ENABLE, gpmi_regs + HW_GPMI_CTRL1_CLR); + + /* Clear out the DLL control fields. */ + __raw_writel(BM_GPMI_CTRL1_RDN_DELAY, gpmi_regs + HW_GPMI_CTRL1_CLR); + __raw_writel(BM_GPMI_CTRL1_HALF_PERIOD, gpmi_regs + HW_GPMI_CTRL1_CLR); + + /* If no sample delay is called for, return immediately. */ + if (!hw.sample_delay_factor) + return; + + /* Configure the HALF_PERIOD flag. */ + if (hw.use_half_periods) + __raw_writel(BM_GPMI_CTRL1_HALF_PERIOD, + gpmi_regs + HW_GPMI_CTRL1_SET); + + /* Set the delay factor. */ + __raw_writel(BF_GPMI_CTRL1_RDN_DELAY(hw.sample_delay_factor), + gpmi_regs + HW_GPMI_CTRL1_SET); + + /* Enable the DLL. */ + __raw_writel(BM_GPMI_CTRL1_DLL_ENABLE, gpmi_regs + HW_GPMI_CTRL1_SET); + + /* + * After we enable the GPMI DLL, we have to wait 64 clock cycles before + * we can use the GPMI. + * + * Calculate the amount of time we need to wait, in microseconds. + */ + dll_wait_time_in_us = (clock_period_in_ns * 64) / 1000; + + if (!dll_wait_time_in_us) + dll_wait_time_in_us = 1; + + /* Wait for the DLL to settle. */ + udelay(dll_wait_time_in_us); +} + +static void end(struct gpmi_nfc_data *this) +{ + struct resources *r = &this->resources; + clk_disable(r->clock); +} + +/* Clears a BCH interrupt. */ +static void clear_bch(struct gpmi_nfc_data *this) +{ + struct resources *r = &this->resources; + __raw_writel(BM_BCH_CTRL_COMPLETE_IRQ, r->bch_regs + HW_BCH_CTRL_CLR); +} + +/* Returns the Ready/Busy status of the given chip. */ +static int is_ready(struct gpmi_nfc_data *this, unsigned chip) +{ + struct resources *r = &this->resources; + uint32_t mask; + uint32_t reg; + + if (GPMI_IS_MX23(this)) { + mask = MX23_BM_GPMI_DEBUG_READY0 << chip; + reg = __raw_readl(r->gpmi_regs + HW_GPMI_DEBUG); + } else if (GPMI_IS_MX28(this)) { + mask = MX28_BF_GPMI_STAT_READY_BUSY(1 << chip); + reg = __raw_readl(r->gpmi_regs + HW_GPMI_STAT); + } else + BUG(); + return !!(reg & mask); +} + +static int send_command(struct gpmi_nfc_data *this) +{ + struct dma_chan *channel = get_dma_chan(this); + struct mil *mil = &this->mil; + struct dma_async_tx_descriptor *desc; + struct scatterlist *sgl; + u32 pio[3]; + + /* [1] send out the PIO words */ + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__WRITE) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(mil->current_chip, this) + | BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_CLE) + | BM_GPMI_CTRL0_ADDRESS_INCREMENT + | BF_GPMI_CTRL0_XFER_COUNT(mil->command_length); + pio[1] = pio[2] = 0; + desc = channel->device->device_prep_slave_sg(channel, + (struct scatterlist *)pio, + ARRAY_SIZE(pio), DMA_NONE, 0); + if (!desc) { + log("step 1 error"); + return -1; + } + + /* [2] send out the COMMAND + ADDRESS string stored in @buffer */ + sgl = &mil->cmd_sgl; + + sg_init_one(sgl, mil->cmd_buffer, mil->command_length); + dma_map_sg(this->dev, sgl, 1, DMA_TO_DEVICE); + desc = channel->device->device_prep_slave_sg(channel, + sgl, 1, DMA_TO_DEVICE, 1); + if (!desc) { + log("error"); + return -1; + } + + /* [3] submit the DMA */ + this->dma_type = DMA_FOR_COMMAND; + start_dma_without_bch_irq(this, desc); + return 0; +} + +static int send_data(struct gpmi_nfc_data *this) +{ + struct dma_async_tx_descriptor *desc; + struct dma_chan *channel = get_dma_chan(this); + struct mil *mil = &this->mil; + uint32_t command_mode; + uint32_t address; + u32 pio[2]; + + /* [1] PIO */ + command_mode = BV_GPMI_CTRL0_COMMAND_MODE__WRITE; + address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA; + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(mil->current_chip, this) + | BF_GPMI_CTRL0_ADDRESS(address) + | BF_GPMI_CTRL0_XFER_COUNT(mil->upper_len); + pio[1] = 0; + desc = channel->device->device_prep_slave_sg(channel, + (struct scatterlist *)pio, + ARRAY_SIZE(pio), DMA_NONE, 0); + if (!desc) { + log("step 1 error"); + return -1; + } + + /* [2] send DMA request */ + prepare_data_dma(this, DMA_TO_DEVICE); + desc = channel->device->device_prep_slave_sg(channel, &mil->data_sgl, + 1, DMA_TO_DEVICE, 1); + if (!desc) { + log("step 2 error"); + return -1; + } + /* [3] submit the DMA */ + this->dma_type = DMA_FOR_WRITE_DATA; + start_dma_without_bch_irq(this, desc); + return 0; +} + +static int read_data(struct gpmi_nfc_data *this) +{ + struct dma_async_tx_descriptor *desc; + struct dma_chan *channel = get_dma_chan(this); + struct mil *mil = &this->mil; + u32 pio[2]; + + /* [1] : send PIO */ + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__READ) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(mil->current_chip, this) + | BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_DATA) + | BF_GPMI_CTRL0_XFER_COUNT(mil->upper_len); + pio[1] = 0; + desc = channel->device->device_prep_slave_sg(channel, + (struct scatterlist *)pio, + ARRAY_SIZE(pio), DMA_NONE, 0); + if (!desc) { + log("step 1 error"); + return -1; + } + + /* [2] : send DMA request */ + prepare_data_dma(this, DMA_FROM_DEVICE); + desc = channel->device->device_prep_slave_sg(channel, &mil->data_sgl, + 1, DMA_FROM_DEVICE, 1); + if (!desc) { + log("step 2 error"); + return -1; + } + + /* [3] : submit the DMA */ + this->dma_type = DMA_FOR_READ_DATA; + start_dma_without_bch_irq(this, desc); + return 0; +} + +static int send_page(struct gpmi_nfc_data *this, + dma_addr_t payload, dma_addr_t auxiliary) +{ + struct nfc_geometry *geo = &this->nfc_geometry; + uint32_t command_mode; + uint32_t address; + uint32_t ecc_command; + uint32_t buffer_mask; + struct dma_async_tx_descriptor *desc; + struct dma_chan *channel = get_dma_chan(this); + struct mil *mil = &this->mil; + int chip = mil->current_chip; + u32 pio[6]; + + /* A DMA descriptor that does an ECC page read. */ + command_mode = BV_GPMI_CTRL0_COMMAND_MODE__WRITE; + address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA; + ecc_command = BV_GPMI_ECCCTRL_ECC_CMD__BCH_ENCODE; + buffer_mask = BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE | + BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY; + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(chip, this) + | BF_GPMI_CTRL0_ADDRESS(address) + | BF_GPMI_CTRL0_XFER_COUNT(0); + pio[1] = 0; + pio[2] = BM_GPMI_ECCCTRL_ENABLE_ECC + | BF_GPMI_ECCCTRL_ECC_CMD(ecc_command) + | BF_GPMI_ECCCTRL_BUFFER_MASK(buffer_mask); + pio[3] = geo->page_size_in_bytes; + pio[4] = payload; + pio[5] = auxiliary; + + desc = channel->device->device_prep_slave_sg(channel, + (struct scatterlist *)pio, + ARRAY_SIZE(pio), DMA_NONE, 0); + if (!desc) { + log("step 2 error"); + return -1; + } + this->dma_type = DMA_FOR_WRITE_ECC_PAGE; + return start_dma_with_bch_irq(this, desc); +} + +static int read_page(struct gpmi_nfc_data *this, + dma_addr_t payload, dma_addr_t auxiliary) +{ + struct nfc_geometry *geo = &this->nfc_geometry; + uint32_t command_mode; + uint32_t address; + uint32_t ecc_command; + uint32_t buffer_mask; + struct dma_async_tx_descriptor *desc; + struct dma_chan *channel = get_dma_chan(this); + struct mil *mil = &this->mil; + int chip = mil->current_chip; + u32 pio[6]; + + /* [1] Wait for the chip to report ready. */ + command_mode = BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY; + address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA; + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(chip, this) + | BF_GPMI_CTRL0_ADDRESS(address) + | BF_GPMI_CTRL0_XFER_COUNT(0); + pio[1] = 0; + desc = channel->device->device_prep_slave_sg(channel, + (struct scatterlist *)pio, 2, DMA_NONE, 0); + if (!desc) { + log("step 1 error"); + return -1; + } + + /* [2] Enable the BCH block and read. */ + command_mode = BV_GPMI_CTRL0_COMMAND_MODE__READ; + address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA; + ecc_command = BV_GPMI_ECCCTRL_ECC_CMD__BCH_DECODE; + buffer_mask = BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE + | BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY; + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(chip, this) + | BF_GPMI_CTRL0_ADDRESS(address) + | BF_GPMI_CTRL0_XFER_COUNT(geo->page_size_in_bytes); + + pio[1] = 0; + pio[2] = BM_GPMI_ECCCTRL_ENABLE_ECC + | BF_GPMI_ECCCTRL_ECC_CMD(ecc_command) + | BF_GPMI_ECCCTRL_BUFFER_MASK(buffer_mask); + pio[3] = geo->page_size_in_bytes; + pio[4] = payload; + pio[5] = auxiliary; + desc = channel->device->device_prep_slave_sg(channel, + (struct scatterlist *)pio, + ARRAY_SIZE(pio), DMA_NONE, 1); + if (!desc) { + log("step 2 error"); + return -1; + } + + /* [3] Disable the BCH block */ + command_mode = BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY; + address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA; + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(chip, this) + | BF_GPMI_CTRL0_ADDRESS(address) + | BF_GPMI_CTRL0_XFER_COUNT(geo->page_size_in_bytes); + pio[1] = 0; + desc = channel->device->device_prep_slave_sg(channel, + (struct scatterlist *)pio, 2, DMA_NONE, 1); + if (!desc) { + log("step 3 error"); + return -1; + } + + /* [4] submit the DMA */ + this->dma_type = DMA_FOR_READ_ECC_PAGE; + return start_dma_with_bch_irq(this, desc); +} + +struct nfc_hal gpmi_nfc_hal_imx23_imx28 = { + .description = "GPMI and BCH for IMX23/IMX28", + .max_data_setup_cycles = (BM_GPMI_TIMING0_DATA_SETUP >> + BP_GPMI_TIMING0_DATA_SETUP), + .internal_data_setup_in_ns = 0, + .max_sample_delay_factor = (BM_GPMI_CTRL1_RDN_DELAY >> + BP_GPMI_CTRL1_RDN_DELAY), + .max_dll_clock_period_in_ns = 32, + .max_dll_delay_in_ns = 16, + .init = init_hal, + .set_geometry = set_geometry, + .set_timing = set_timing, + .get_timing = get_timing, + .exit = exit, + .begin = begin, + .end = end, + .clear_bch = clear_bch, + .is_ready = is_ready, + .send_command = send_command, + .read_data = read_data, + .send_data = send_data, + .read_page = read_page, + .send_page = send_page, +}; -- 1.7.0.4 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v6 2/3] MTD : add support for imx23 and imx28 @ 2011-04-20 2:45 ` Huang Shijie 0 siblings, 0 replies; 20+ messages in thread From: Huang Shijie @ 2011-04-20 2:45 UTC (permalink / raw) To: linux-arm-kernel Add the implementation code for imx23 and imx28. The imx23 and imx28 share most of the same code. Signed-off-by: Huang Shijie <b32955@freescale.com> --- drivers/mtd/nand/gpmi-nfc/bch-regs.h | 88 ++++++ drivers/mtd/nand/gpmi-nfc/gpmi-regs.h | 163 ++++++++++ drivers/mtd/nand/gpmi-nfc/hal-mxs.c | 554 +++++++++++++++++++++++++++++++++ 3 files changed, 805 insertions(+), 0 deletions(-) create mode 100644 drivers/mtd/nand/gpmi-nfc/bch-regs.h create mode 100644 drivers/mtd/nand/gpmi-nfc/gpmi-regs.h create mode 100644 drivers/mtd/nand/gpmi-nfc/hal-mxs.c diff --git a/drivers/mtd/nand/gpmi-nfc/bch-regs.h b/drivers/mtd/nand/gpmi-nfc/bch-regs.h new file mode 100644 index 0000000..90af8cf --- /dev/null +++ b/drivers/mtd/nand/gpmi-nfc/bch-regs.h @@ -0,0 +1,88 @@ +/* + * Freescale GPMI NFC NAND Flash Driver + * + * Copyright 2008-2011 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#ifndef __GPMI_NFC_BCH_REGS_H +#define __GPMI_NFC_BCH_REGS_H + +/*============================================================================*/ +#define HW_BCH_CTRL 0x00000000 +#define HW_BCH_CTRL_SET 0x00000004 +#define HW_BCH_CTRL_CLR 0x00000008 +#define HW_BCH_CTRL_TOG 0x0000000c + +#define BM_BCH_CTRL_COMPLETE_IRQ_EN (1 << 8) +#define BM_BCH_CTRL_COMPLETE_IRQ (1 << 0) + +/*============================================================================*/ +#define HW_BCH_STATUS0 0x00000010 +#define HW_BCH_MODE 0x00000020 +#define HW_BCH_ENCODEPTR 0x00000030 +#define HW_BCH_DATAPTR 0x00000040 +#define HW_BCH_METAPTR 0x00000050 +#define HW_BCH_LAYOUTSELECT 0x00000070 + +/*============================================================================*/ +#define HW_BCH_FLASH0LAYOUT0 0x00000080 + +#define BP_BCH_FLASH0LAYOUT0_NBLOCKS 24 +#define BM_BCH_FLASH0LAYOUT0_NBLOCKS (0xff << BP_BCH_FLASH0LAYOUT0_NBLOCKS) +#define BF_BCH_FLASH0LAYOUT0_NBLOCKS(v) \ + (((v) << BP_BCH_FLASH0LAYOUT0_NBLOCKS) & BM_BCH_FLASH0LAYOUT0_NBLOCKS) + +#define BP_BCH_FLASH0LAYOUT0_META_SIZE 16 +#define BM_BCH_FLASH0LAYOUT0_META_SIZE (0xff << BP_BCH_FLASH0LAYOUT0_META_SIZE) +#define BF_BCH_FLASH0LAYOUT0_META_SIZE(v) \ + (((v) << BP_BCH_FLASH0LAYOUT0_META_SIZE)\ + & BM_BCH_FLASH0LAYOUT0_META_SIZE) + +#define BP_BCH_FLASH0LAYOUT0_ECC0 12 +#define BM_BCH_FLASH0LAYOUT0_ECC0 (0xf << BP_BCH_FLASH0LAYOUT0_ECC0) +#define BF_BCH_FLASH0LAYOUT0_ECC0(v) \ + (((v) << BP_BCH_FLASH0LAYOUT0_ECC0) & BM_BCH_FLASH0LAYOUT0_ECC0) + +#define BP_BCH_FLASH0LAYOUT0_DATA0_SIZE 0 +#define BM_BCH_FLASH0LAYOUT0_DATA0_SIZE \ + (0xfff << BP_BCH_FLASH0LAYOUT0_DATA0_SIZE) +#define BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(v) \ + (((v) << BP_BCH_FLASH0LAYOUT0_DATA0_SIZE)\ + & BM_BCH_FLASH0LAYOUT0_DATA0_SIZE) + +/*============================================================================*/ +#define HW_BCH_FLASH0LAYOUT1 0x00000090 + +#define BP_BCH_FLASH0LAYOUT1_PAGE_SIZE 16 +#define BM_BCH_FLASH0LAYOUT1_PAGE_SIZE \ + (0xffff << BP_BCH_FLASH0LAYOUT1_PAGE_SIZE) +#define BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(v) \ + (((v) << BP_BCH_FLASH0LAYOUT1_PAGE_SIZE) \ + & BM_BCH_FLASH0LAYOUT1_PAGE_SIZE) + +#define BP_BCH_FLASH0LAYOUT1_ECCN 12 +#define BM_BCH_FLASH0LAYOUT1_ECCN (0xf << BP_BCH_FLASH0LAYOUT1_ECCN) +#define BF_BCH_FLASH0LAYOUT1_ECCN(v) \ + (((v) << BP_BCH_FLASH0LAYOUT1_ECCN) & BM_BCH_FLASH0LAYOUT1_ECCN) + +#define BP_BCH_FLASH0LAYOUT1_DATAN_SIZE 0 +#define BM_BCH_FLASH0LAYOUT1_DATAN_SIZE \ + (0xfff << BP_BCH_FLASH0LAYOUT1_DATAN_SIZE) +#define BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(v) \ + (((v) << BP_BCH_FLASH0LAYOUT1_DATAN_SIZE) \ + & BM_BCH_FLASH0LAYOUT1_DATAN_SIZE) +#endif diff --git a/drivers/mtd/nand/gpmi-nfc/gpmi-regs.h b/drivers/mtd/nand/gpmi-nfc/gpmi-regs.h new file mode 100644 index 0000000..d012c76 --- /dev/null +++ b/drivers/mtd/nand/gpmi-nfc/gpmi-regs.h @@ -0,0 +1,163 @@ +/* + * Freescale GPMI NFC NAND Flash Driver + * + * Copyright 2008-2011 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#ifndef __GPMI_NFC_GPMI_REGS_H +#define __GPMI_NFC_GPMI_REGS_H + +/*============================================================================*/ +#define HW_GPMI_CTRL0 0x00000000 +#define HW_GPMI_CTRL0_SET 0x00000004 +#define HW_GPMI_CTRL0_CLR 0x00000008 +#define HW_GPMI_CTRL0_TOG 0x0000000c + +#define BP_GPMI_CTRL0_COMMAND_MODE 24 +#define BM_GPMI_CTRL0_COMMAND_MODE (3 << BP_GPMI_CTRL0_COMMAND_MODE) +#define BF_GPMI_CTRL0_COMMAND_MODE(v) \ + (((v) << BP_GPMI_CTRL0_COMMAND_MODE) & BM_GPMI_CTRL0_COMMAND_MODE) +#define BV_GPMI_CTRL0_COMMAND_MODE__WRITE 0x0 +#define BV_GPMI_CTRL0_COMMAND_MODE__READ 0x1 +#define BV_GPMI_CTRL0_COMMAND_MODE__READ_AND_COMPARE 0x2 +#define BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY 0x3 + +#define BM_GPMI_CTRL0_WORD_LENGTH (1 << 23) +#define BV_GPMI_CTRL0_WORD_LENGTH__16_BIT 0x0 +#define BV_GPMI_CTRL0_WORD_LENGTH__8_BIT 0x1 + +/* different in CS between imx23 and imx28 */ +#define BP_GPMI_CTRL0_CS 20 +#define MX23_BM_GPMI_CTRL0_CS (3 << BP_GPMI_CTRL0_CS) +#define MX28_BM_GPMI_CTRL0_CS (7 << BP_GPMI_CTRL0_CS) +#define BF_GPMI_CTRL0_CS(v, x) (((v) << 20) & (GPMI_IS_MX23((x)) \ + ? MX23_BM_GPMI_CTRL0_CS \ + : MX28_BM_GPMI_CTRL0_CS)) + +#define BP_GPMI_CTRL0_ADDRESS 17 +#define BM_GPMI_CTRL0_ADDRESS (3 << BP_GPMI_CTRL0_ADDRESS) +#define BF_GPMI_CTRL0_ADDRESS(v) \ + (((v) << BP_GPMI_CTRL0_ADDRESS) & BM_GPMI_CTRL0_ADDRESS) +#define BV_GPMI_CTRL0_ADDRESS__NAND_DATA 0x0 +#define BV_GPMI_CTRL0_ADDRESS__NAND_CLE 0x1 +#define BV_GPMI_CTRL0_ADDRESS__NAND_ALE 0x2 + +#define BM_GPMI_CTRL0_ADDRESS_INCREMENT (1 << 16) +#define BV_GPMI_CTRL0_ADDRESS_INCREMENT__DISABLED 0x0 +#define BV_GPMI_CTRL0_ADDRESS_INCREMENT__ENABLED 0x1 + +#define BP_GPMI_CTRL0_XFER_COUNT 0 +#define BM_GPMI_CTRL0_XFER_COUNT (0xffff << BP_GPMI_CTRL0_XFER_COUNT) +#define BF_GPMI_CTRL0_XFER_COUNT(v) \ + (((v) << BP_GPMI_CTRL0_XFER_COUNT) & BM_GPMI_CTRL0_XFER_COUNT) + +/*============================================================================*/ +#define HW_GPMI_COMPARE 0x00000010 +/*============================================================================*/ +#define HW_GPMI_ECCCTRL 0x00000020 +#define HW_GPMI_ECCCTRL_SET 0x00000024 +#define HW_GPMI_ECCCTRL_CLR 0x00000028 +#define HW_GPMI_ECCCTRL_TOG 0x0000002c + +#define BP_GPMI_ECCCTRL_ECC_CMD 13 +#define BM_GPMI_ECCCTRL_ECC_CMD (3 << BP_GPMI_ECCCTRL_ECC_CMD) +#define BF_GPMI_ECCCTRL_ECC_CMD(v) \ + (((v) << BP_GPMI_ECCCTRL_ECC_CMD) & BM_GPMI_ECCCTRL_ECC_CMD) +#define BV_GPMI_ECCCTRL_ECC_CMD__BCH_DECODE 0x0 +#define BV_GPMI_ECCCTRL_ECC_CMD__BCH_ENCODE 0x1 + +#define BM_GPMI_ECCCTRL_ENABLE_ECC (1 << 12) +#define BV_GPMI_ECCCTRL_ENABLE_ECC__ENABLE 0x1 +#define BV_GPMI_ECCCTRL_ENABLE_ECC__DISABLE 0x0 + +#define BP_GPMI_ECCCTRL_BUFFER_MASK 0 +#define BM_GPMI_ECCCTRL_BUFFER_MASK (0x1ff << BP_GPMI_ECCCTRL_BUFFER_MASK) +#define BF_GPMI_ECCCTRL_BUFFER_MASK(v) \ + (((v) << BP_GPMI_ECCCTRL_BUFFER_MASK) & BM_GPMI_ECCCTRL_BUFFER_MASK) +#define BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY 0x100 +#define BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE 0x1FF + +/*============================================================================*/ +#define HW_GPMI_ECCCOUNT 0x00000030 +#define HW_GPMI_PAYLOAD 0x00000040 +#define HW_GPMI_AUXILIARY 0x00000050 +/*============================================================================*/ +#define HW_GPMI_CTRL1 0x00000060 +#define HW_GPMI_CTRL1_SET 0x00000064 +#define HW_GPMI_CTRL1_CLR 0x00000068 +#define HW_GPMI_CTRL1_TOG 0x0000006c + +#define BM_GPMI_CTRL1_BCH_MODE (1 << 18) + +#define BP_GPMI_CTRL1_DLL_ENABLE 17 +#define BM_GPMI_CTRL1_DLL_ENABLE (1 << BP_GPMI_CTRL1_DLL_ENABLE) + +#define BP_GPMI_CTRL1_HALF_PERIOD 16 +#define BM_GPMI_CTRL1_HALF_PERIOD (1 << BP_GPMI_CTRL1_HALF_PERIOD) + +#define BP_GPMI_CTRL1_RDN_DELAY 12 +#define BM_GPMI_CTRL1_RDN_DELAY (0xf << BP_GPMI_CTRL1_RDN_DELAY) +#define BF_GPMI_CTRL1_RDN_DELAY(v) \ + (((v) << BP_GPMI_CTRL1_RDN_DELAY) & BM_GPMI_CTRL1_RDN_DELAY) + +#define BM_GPMI_CTRL1_DEV_RESET (1 << 3) +#define BV_GPMI_CTRL1_DEV_RESET__ENABLED 0x0 +#define BV_GPMI_CTRL1_DEV_RESET__DISABLED 0x1 + +#define BM_GPMI_CTRL1_ATA_IRQRDY_POLARITY (1 << 2) +#define BV_GPMI_CTRL1_ATA_IRQRDY_POLARITY__ACTIVELOW 0x0 +#define BV_GPMI_CTRL1_ATA_IRQRDY_POLARITY__ACTIVEHIGH 0x1 + +#define BM_GPMI_CTRL1_CAMERA_MODE (1 << 1) +#define BV_GPMI_CTRL1_GPMI_MODE__NAND 0x0 +#define BV_GPMI_CTRL1_GPMI_MODE__ATA 0x1 + +#define BM_GPMI_CTRL1_GPMI_MODE (1 << 0) + +/*============================================================================*/ +#define HW_GPMI_TIMING0 0x00000070 + +#define BP_GPMI_TIMING0_ADDRESS_SETUP 16 +#define BM_GPMI_TIMING0_ADDRESS_SETUP (0xff << BP_GPMI_TIMING0_ADDRESS_SETUP) +#define BF_GPMI_TIMING0_ADDRESS_SETUP(v) \ + (((v) << BP_GPMI_TIMING0_ADDRESS_SETUP) & BM_GPMI_TIMING0_ADDRESS_SETUP) + +#define BP_GPMI_TIMING0_DATA_HOLD 8 +#define BM_GPMI_TIMING0_DATA_HOLD (0xff << BP_GPMI_TIMING0_DATA_HOLD) +#define BF_GPMI_TIMING0_DATA_HOLD(v) \ + (((v) << BP_GPMI_TIMING0_DATA_HOLD) & BM_GPMI_TIMING0_DATA_HOLD) + +#define BP_GPMI_TIMING0_DATA_SETUP 0 +#define BM_GPMI_TIMING0_DATA_SETUP (0xff << BP_GPMI_TIMING0_DATA_SETUP) +#define BF_GPMI_TIMING0_DATA_SETUP(v) \ + (((v) << BP_GPMI_TIMING0_DATA_SETUP) & BM_GPMI_TIMING0_DATA_SETUP) + +/*============================================================================*/ +#define HW_GPMI_TIMING1 0x00000080 +#define HW_GPMI_TIMING2 0x00000090 +#define HW_GPMI_DATA 0x000000a0 +/*============================ MX28 uses this to detect READY ================*/ +#define HW_GPMI_STAT 0x000000b0 +#define MX28_BP_GPMI_STAT_READY_BUSY 24 +#define MX28_BM_GPMI_STAT_READY_BUSY (0xff << MX28_BP_GPMI_STAT_READY_BUSY) +#define MX28_BF_GPMI_STAT_READY_BUSY(v) \ + (((v) << MX28_BP_GPMI_STAT_READY_BUSY) & MX28_BM_GPMI_STAT_READY_BUSY) +/*============================ MX23 uses this to detect READY ================*/ +#define HW_GPMI_DEBUG 0x000000c0 +#define MX23_BP_GPMI_DEBUG_READY0 28 +#define MX23_BM_GPMI_DEBUG_READY0 (1 << MX23_BP_GPMI_DEBUG_READY0) +#endif diff --git a/drivers/mtd/nand/gpmi-nfc/hal-mxs.c b/drivers/mtd/nand/gpmi-nfc/hal-mxs.c new file mode 100644 index 0000000..8b0181e --- /dev/null +++ b/drivers/mtd/nand/gpmi-nfc/hal-mxs.c @@ -0,0 +1,554 @@ +/* + * Freescale GPMI NFC NAND Flash Driver + * + * Copyright (C) 2008-2011 Freescale Semiconductor, Inc. + * Copyright (C) 2008 Embedded Alley Solutions, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "gpmi-nfc.h" +#include "gpmi-regs.h" +#include "bch-regs.h" + +static int init_hal(struct gpmi_nfc_data *this) +{ + struct resources *resources = &this->resources; + + /* Enable the clock */ + clk_enable(resources->clock); + + /* Reset the GPMI block. */ + mxs_reset_block(resources->gpmi_regs); + + /* Choose NAND mode. */ + __raw_writel(BM_GPMI_CTRL1_GPMI_MODE, + resources->gpmi_regs + HW_GPMI_CTRL1_CLR); + + /* Set the IRQ polarity. */ + __raw_writel(BM_GPMI_CTRL1_ATA_IRQRDY_POLARITY, + resources->gpmi_regs + HW_GPMI_CTRL1_SET); + + /* Disable write protection. */ + __raw_writel(BM_GPMI_CTRL1_DEV_RESET, + resources->gpmi_regs + HW_GPMI_CTRL1_SET); + + /* Select BCH ECC. */ + __raw_writel(BM_GPMI_CTRL1_BCH_MODE, + resources->gpmi_regs + HW_GPMI_CTRL1_SET); + + /* Disable the clock. */ + clk_disable(resources->clock); + return 0; +} + +/* Configures the NFC geometry for BCH. */ +static int set_geometry(struct gpmi_nfc_data *this) +{ + struct resources *resources = &this->resources; + struct nfc_geometry *nfc = &this->nfc_geometry; + unsigned int block_count; + unsigned int block_size; + unsigned int metadata_size; + unsigned int ecc_strength; + unsigned int page_size; + + if (common_nfc_set_geometry(this)) + return !0; + + block_count = nfc->ecc_chunk_count - 1; + block_size = nfc->ecc_chunk_size_in_bytes; + metadata_size = nfc->metadata_size_in_bytes; + ecc_strength = nfc->ecc_strength >> 1; + page_size = nfc->page_size_in_bytes; + + clk_enable(resources->clock); + + /* + * Reset the BCH block. Notice that we pass in true for the just_enable + * flag. This is because the soft reset for the version 0 BCH block + * doesn't work. If you try to soft reset the BCH block, it becomes + * unusable until the next hard reset. + */ + mxs_reset_block(resources->bch_regs); + + /* Configure layout 0. */ + __raw_writel(BF_BCH_FLASH0LAYOUT0_NBLOCKS(block_count) + | BF_BCH_FLASH0LAYOUT0_META_SIZE(metadata_size) + | BF_BCH_FLASH0LAYOUT0_ECC0(ecc_strength) + | BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(block_size), + resources->bch_regs + HW_BCH_FLASH0LAYOUT0); + + __raw_writel(BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(page_size) + | BF_BCH_FLASH0LAYOUT1_ECCN(ecc_strength) + | BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(block_size), + resources->bch_regs + HW_BCH_FLASH0LAYOUT1); + + /* Set *all* chip selects to use layout 0. */ + __raw_writel(0, resources->bch_regs + HW_BCH_LAYOUTSELECT); + + /* Enable interrupts. */ + __raw_writel(BM_BCH_CTRL_COMPLETE_IRQ_EN, + resources->bch_regs + HW_BCH_CTRL_SET); + + clk_disable(resources->clock); + return 0; +} + +static int set_timing(struct gpmi_nfc_data *this, + const struct nand_timing *timing) +{ + struct nfc_hal *nfc = this->nfc; + + nfc->timing = *timing; + return 0; +} + +/** + * get_timing() - Retrieves the NFC hardware timing. + * + * @this: Per-device data. + * @clock_frequency_in_hz: The clock frequency, in Hz, during the current + * I/O transaction. If no I/O transaction is in + * progress, this is the clock frequency during the + * most recent I/O transaction. + * @hardware_timing: The hardware timing configuration in effect during + * the current I/O transaction. If no I/O transaction + * is in progress, this is the hardware timing + * configuration during the most recent I/O + * transaction. + */ +static void get_timing(struct gpmi_nfc_data *this, + unsigned long *clock_frequency_in_hz, + struct gpmi_nfc_hardware_timing *hardware_timing) +{ + struct resources *resources = &this->resources; + struct nfc_hal *nfc = this->nfc; + unsigned char *gpmi_regs = resources->gpmi_regs; + uint32_t register_image; + + /* Return the clock frequency. */ + *clock_frequency_in_hz = nfc->clock_frequency_in_hz; + + /* We'll be reading the hardware, so let's enable the clock. */ + clk_enable(resources->clock); + + /* Retrieve the hardware timing. */ + register_image = __raw_readl(gpmi_regs + HW_GPMI_TIMING0); + + hardware_timing->data_setup_in_cycles = + (register_image & BM_GPMI_TIMING0_DATA_SETUP) >> + BP_GPMI_TIMING0_DATA_SETUP; + + hardware_timing->data_hold_in_cycles = + (register_image & BM_GPMI_TIMING0_DATA_HOLD) >> + BP_GPMI_TIMING0_DATA_HOLD; + + hardware_timing->address_setup_in_cycles = + (register_image & BM_GPMI_TIMING0_ADDRESS_SETUP) >> + BP_GPMI_TIMING0_ADDRESS_SETUP; + + register_image = __raw_readl(gpmi_regs + HW_GPMI_CTRL1); + + hardware_timing->use_half_periods = + (register_image & BM_GPMI_CTRL1_HALF_PERIOD) >> + BP_GPMI_CTRL1_HALF_PERIOD; + + hardware_timing->sample_delay_factor = + (register_image & BM_GPMI_CTRL1_RDN_DELAY) >> + BP_GPMI_CTRL1_RDN_DELAY; + + /* We're done reading the hardware, so disable the clock. */ + clk_disable(resources->clock); +} + +static void exit(struct gpmi_nfc_data *this) +{ +} + +/* Begin the I/O */ +static void begin(struct gpmi_nfc_data *this) +{ + struct resources *resources = &this->resources; + struct nfc_hal *nfc = this->nfc; + unsigned char *gpmi_regs = resources->gpmi_regs; + unsigned int clock_period_in_ns; + uint32_t register_image; + unsigned int dll_wait_time_in_us; + struct gpmi_nfc_hardware_timing hw; + + /* Enable the clock. */ + clk_enable(resources->clock); + + /* set the timing for imx23 */ + if (!GPMI_IS_MX23(this)) + return; + + /* Get the timing information we need. */ + nfc->clock_frequency_in_hz = clk_get_rate(resources->clock); + clock_period_in_ns = 1000000000 / nfc->clock_frequency_in_hz; + + gpmi_nfc_compute_hardware_timing(this, &hw); + + /* Set up all the simple timing parameters. */ + register_image = + BF_GPMI_TIMING0_ADDRESS_SETUP(hw.address_setup_in_cycles) | + BF_GPMI_TIMING0_DATA_HOLD(hw.data_hold_in_cycles) | + BF_GPMI_TIMING0_DATA_SETUP(hw.data_setup_in_cycles) ; + + __raw_writel(register_image, gpmi_regs + HW_GPMI_TIMING0); + + /* + * HEY - PAY ATTENTION! + * + * DLL_ENABLE must be set to zero when setting RDN_DELAY or HALF_PERIOD. + */ + __raw_writel(BM_GPMI_CTRL1_DLL_ENABLE, gpmi_regs + HW_GPMI_CTRL1_CLR); + + /* Clear out the DLL control fields. */ + __raw_writel(BM_GPMI_CTRL1_RDN_DELAY, gpmi_regs + HW_GPMI_CTRL1_CLR); + __raw_writel(BM_GPMI_CTRL1_HALF_PERIOD, gpmi_regs + HW_GPMI_CTRL1_CLR); + + /* If no sample delay is called for, return immediately. */ + if (!hw.sample_delay_factor) + return; + + /* Configure the HALF_PERIOD flag. */ + if (hw.use_half_periods) + __raw_writel(BM_GPMI_CTRL1_HALF_PERIOD, + gpmi_regs + HW_GPMI_CTRL1_SET); + + /* Set the delay factor. */ + __raw_writel(BF_GPMI_CTRL1_RDN_DELAY(hw.sample_delay_factor), + gpmi_regs + HW_GPMI_CTRL1_SET); + + /* Enable the DLL. */ + __raw_writel(BM_GPMI_CTRL1_DLL_ENABLE, gpmi_regs + HW_GPMI_CTRL1_SET); + + /* + * After we enable the GPMI DLL, we have to wait 64 clock cycles before + * we can use the GPMI. + * + * Calculate the amount of time we need to wait, in microseconds. + */ + dll_wait_time_in_us = (clock_period_in_ns * 64) / 1000; + + if (!dll_wait_time_in_us) + dll_wait_time_in_us = 1; + + /* Wait for the DLL to settle. */ + udelay(dll_wait_time_in_us); +} + +static void end(struct gpmi_nfc_data *this) +{ + struct resources *r = &this->resources; + clk_disable(r->clock); +} + +/* Clears a BCH interrupt. */ +static void clear_bch(struct gpmi_nfc_data *this) +{ + struct resources *r = &this->resources; + __raw_writel(BM_BCH_CTRL_COMPLETE_IRQ, r->bch_regs + HW_BCH_CTRL_CLR); +} + +/* Returns the Ready/Busy status of the given chip. */ +static int is_ready(struct gpmi_nfc_data *this, unsigned chip) +{ + struct resources *r = &this->resources; + uint32_t mask; + uint32_t reg; + + if (GPMI_IS_MX23(this)) { + mask = MX23_BM_GPMI_DEBUG_READY0 << chip; + reg = __raw_readl(r->gpmi_regs + HW_GPMI_DEBUG); + } else if (GPMI_IS_MX28(this)) { + mask = MX28_BF_GPMI_STAT_READY_BUSY(1 << chip); + reg = __raw_readl(r->gpmi_regs + HW_GPMI_STAT); + } else + BUG(); + return !!(reg & mask); +} + +static int send_command(struct gpmi_nfc_data *this) +{ + struct dma_chan *channel = get_dma_chan(this); + struct mil *mil = &this->mil; + struct dma_async_tx_descriptor *desc; + struct scatterlist *sgl; + u32 pio[3]; + + /* [1] send out the PIO words */ + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__WRITE) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(mil->current_chip, this) + | BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_CLE) + | BM_GPMI_CTRL0_ADDRESS_INCREMENT + | BF_GPMI_CTRL0_XFER_COUNT(mil->command_length); + pio[1] = pio[2] = 0; + desc = channel->device->device_prep_slave_sg(channel, + (struct scatterlist *)pio, + ARRAY_SIZE(pio), DMA_NONE, 0); + if (!desc) { + log("step 1 error"); + return -1; + } + + /* [2] send out the COMMAND + ADDRESS string stored in @buffer */ + sgl = &mil->cmd_sgl; + + sg_init_one(sgl, mil->cmd_buffer, mil->command_length); + dma_map_sg(this->dev, sgl, 1, DMA_TO_DEVICE); + desc = channel->device->device_prep_slave_sg(channel, + sgl, 1, DMA_TO_DEVICE, 1); + if (!desc) { + log("error"); + return -1; + } + + /* [3] submit the DMA */ + this->dma_type = DMA_FOR_COMMAND; + start_dma_without_bch_irq(this, desc); + return 0; +} + +static int send_data(struct gpmi_nfc_data *this) +{ + struct dma_async_tx_descriptor *desc; + struct dma_chan *channel = get_dma_chan(this); + struct mil *mil = &this->mil; + uint32_t command_mode; + uint32_t address; + u32 pio[2]; + + /* [1] PIO */ + command_mode = BV_GPMI_CTRL0_COMMAND_MODE__WRITE; + address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA; + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(mil->current_chip, this) + | BF_GPMI_CTRL0_ADDRESS(address) + | BF_GPMI_CTRL0_XFER_COUNT(mil->upper_len); + pio[1] = 0; + desc = channel->device->device_prep_slave_sg(channel, + (struct scatterlist *)pio, + ARRAY_SIZE(pio), DMA_NONE, 0); + if (!desc) { + log("step 1 error"); + return -1; + } + + /* [2] send DMA request */ + prepare_data_dma(this, DMA_TO_DEVICE); + desc = channel->device->device_prep_slave_sg(channel, &mil->data_sgl, + 1, DMA_TO_DEVICE, 1); + if (!desc) { + log("step 2 error"); + return -1; + } + /* [3] submit the DMA */ + this->dma_type = DMA_FOR_WRITE_DATA; + start_dma_without_bch_irq(this, desc); + return 0; +} + +static int read_data(struct gpmi_nfc_data *this) +{ + struct dma_async_tx_descriptor *desc; + struct dma_chan *channel = get_dma_chan(this); + struct mil *mil = &this->mil; + u32 pio[2]; + + /* [1] : send PIO */ + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__READ) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(mil->current_chip, this) + | BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_DATA) + | BF_GPMI_CTRL0_XFER_COUNT(mil->upper_len); + pio[1] = 0; + desc = channel->device->device_prep_slave_sg(channel, + (struct scatterlist *)pio, + ARRAY_SIZE(pio), DMA_NONE, 0); + if (!desc) { + log("step 1 error"); + return -1; + } + + /* [2] : send DMA request */ + prepare_data_dma(this, DMA_FROM_DEVICE); + desc = channel->device->device_prep_slave_sg(channel, &mil->data_sgl, + 1, DMA_FROM_DEVICE, 1); + if (!desc) { + log("step 2 error"); + return -1; + } + + /* [3] : submit the DMA */ + this->dma_type = DMA_FOR_READ_DATA; + start_dma_without_bch_irq(this, desc); + return 0; +} + +static int send_page(struct gpmi_nfc_data *this, + dma_addr_t payload, dma_addr_t auxiliary) +{ + struct nfc_geometry *geo = &this->nfc_geometry; + uint32_t command_mode; + uint32_t address; + uint32_t ecc_command; + uint32_t buffer_mask; + struct dma_async_tx_descriptor *desc; + struct dma_chan *channel = get_dma_chan(this); + struct mil *mil = &this->mil; + int chip = mil->current_chip; + u32 pio[6]; + + /* A DMA descriptor that does an ECC page read. */ + command_mode = BV_GPMI_CTRL0_COMMAND_MODE__WRITE; + address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA; + ecc_command = BV_GPMI_ECCCTRL_ECC_CMD__BCH_ENCODE; + buffer_mask = BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE | + BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY; + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(chip, this) + | BF_GPMI_CTRL0_ADDRESS(address) + | BF_GPMI_CTRL0_XFER_COUNT(0); + pio[1] = 0; + pio[2] = BM_GPMI_ECCCTRL_ENABLE_ECC + | BF_GPMI_ECCCTRL_ECC_CMD(ecc_command) + | BF_GPMI_ECCCTRL_BUFFER_MASK(buffer_mask); + pio[3] = geo->page_size_in_bytes; + pio[4] = payload; + pio[5] = auxiliary; + + desc = channel->device->device_prep_slave_sg(channel, + (struct scatterlist *)pio, + ARRAY_SIZE(pio), DMA_NONE, 0); + if (!desc) { + log("step 2 error"); + return -1; + } + this->dma_type = DMA_FOR_WRITE_ECC_PAGE; + return start_dma_with_bch_irq(this, desc); +} + +static int read_page(struct gpmi_nfc_data *this, + dma_addr_t payload, dma_addr_t auxiliary) +{ + struct nfc_geometry *geo = &this->nfc_geometry; + uint32_t command_mode; + uint32_t address; + uint32_t ecc_command; + uint32_t buffer_mask; + struct dma_async_tx_descriptor *desc; + struct dma_chan *channel = get_dma_chan(this); + struct mil *mil = &this->mil; + int chip = mil->current_chip; + u32 pio[6]; + + /* [1] Wait for the chip to report ready. */ + command_mode = BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY; + address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA; + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(chip, this) + | BF_GPMI_CTRL0_ADDRESS(address) + | BF_GPMI_CTRL0_XFER_COUNT(0); + pio[1] = 0; + desc = channel->device->device_prep_slave_sg(channel, + (struct scatterlist *)pio, 2, DMA_NONE, 0); + if (!desc) { + log("step 1 error"); + return -1; + } + + /* [2] Enable the BCH block and read. */ + command_mode = BV_GPMI_CTRL0_COMMAND_MODE__READ; + address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA; + ecc_command = BV_GPMI_ECCCTRL_ECC_CMD__BCH_DECODE; + buffer_mask = BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE + | BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY; + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(chip, this) + | BF_GPMI_CTRL0_ADDRESS(address) + | BF_GPMI_CTRL0_XFER_COUNT(geo->page_size_in_bytes); + + pio[1] = 0; + pio[2] = BM_GPMI_ECCCTRL_ENABLE_ECC + | BF_GPMI_ECCCTRL_ECC_CMD(ecc_command) + | BF_GPMI_ECCCTRL_BUFFER_MASK(buffer_mask); + pio[3] = geo->page_size_in_bytes; + pio[4] = payload; + pio[5] = auxiliary; + desc = channel->device->device_prep_slave_sg(channel, + (struct scatterlist *)pio, + ARRAY_SIZE(pio), DMA_NONE, 1); + if (!desc) { + log("step 2 error"); + return -1; + } + + /* [3] Disable the BCH block */ + command_mode = BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY; + address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA; + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(chip, this) + | BF_GPMI_CTRL0_ADDRESS(address) + | BF_GPMI_CTRL0_XFER_COUNT(geo->page_size_in_bytes); + pio[1] = 0; + desc = channel->device->device_prep_slave_sg(channel, + (struct scatterlist *)pio, 2, DMA_NONE, 1); + if (!desc) { + log("step 3 error"); + return -1; + } + + /* [4] submit the DMA */ + this->dma_type = DMA_FOR_READ_ECC_PAGE; + return start_dma_with_bch_irq(this, desc); +} + +struct nfc_hal gpmi_nfc_hal_imx23_imx28 = { + .description = "GPMI and BCH for IMX23/IMX28", + .max_data_setup_cycles = (BM_GPMI_TIMING0_DATA_SETUP >> + BP_GPMI_TIMING0_DATA_SETUP), + .internal_data_setup_in_ns = 0, + .max_sample_delay_factor = (BM_GPMI_CTRL1_RDN_DELAY >> + BP_GPMI_CTRL1_RDN_DELAY), + .max_dll_clock_period_in_ns = 32, + .max_dll_delay_in_ns = 16, + .init = init_hal, + .set_geometry = set_geometry, + .set_timing = set_timing, + .get_timing = get_timing, + .exit = exit, + .begin = begin, + .end = end, + .clear_bch = clear_bch, + .is_ready = is_ready, + .send_command = send_command, + .read_data = read_data, + .send_data = send_data, + .read_page = read_page, + .send_page = send_page, +}; -- 1.7.0.4 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v6 3/3] MTD : add GPMI driver in the config and Makefile 2011-04-20 2:45 ` Huang Shijie @ 2011-04-20 2:45 ` Huang Shijie -1 siblings, 0 replies; 20+ messages in thread From: Huang Shijie @ 2011-04-20 2:45 UTC (permalink / raw) To: dedekind1; +Cc: Huang Shijie, linux-mtd, linux-arm-kernel add the GPMI driver in the relevant Kconfig and Makefile in the MTD. Signed-off-by: Huang Shijie <b32955@freescale.com> --- drivers/mtd/nand/Kconfig | 11 +++++++++++ drivers/mtd/nand/Makefile | 1 + drivers/mtd/nand/gpmi-nfc/Makefile | 3 +++ 3 files changed, 15 insertions(+), 0 deletions(-) create mode 100644 drivers/mtd/nand/gpmi-nfc/Makefile diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 450afc5..25a1493 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -426,6 +426,17 @@ config MTD_NAND_NANDSIM The simulator may simulate various NAND flash chips for the MTD nand layer. +config MTD_NAND_GPMI_NFC + bool "GPMI NAND Flash Controller driver" + depends on MTD_NAND && (SOC_IMX23 || SOC_IMX28) + select MTD_PARTITIONS + select MTD_CMDLINE_PARTS + help + Enables NAND Flash support for IMX23 or IMX28. + The GPMI controller is very powerful, with the help of BCH + module, it can do the hardware ECC. The GPMI supports several + NAND flashs at the same time. + config MTD_NAND_PLATFORM tristate "Support for generic platform NAND driver" help diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index 8ad6fae..80d1f08 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -48,5 +48,6 @@ obj-$(CONFIG_MTD_NAND_BCM_UMI) += bcm_umi_nand.o nand_bcm_umi.o obj-$(CONFIG_MTD_NAND_MPC5121_NFC) += mpc5121_nfc.o obj-$(CONFIG_MTD_NAND_RICOH) += r852.o obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o +obj-$(CONFIG_MTD_NAND_GPMI_NFC) += gpmi-nfc/ nand-objs := nand_base.o nand_bbt.o diff --git a/drivers/mtd/nand/gpmi-nfc/Makefile b/drivers/mtd/nand/gpmi-nfc/Makefile new file mode 100644 index 0000000..918fb52 --- /dev/null +++ b/drivers/mtd/nand/gpmi-nfc/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_MTD_NAND_GPMI_NFC) += gpmi_nfc.o +gpmi_nfc-objs += gpmi-nfc.o +gpmi_nfc-objs += hal-mxs.o -- 1.7.0.4 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v6 3/3] MTD : add GPMI driver in the config and Makefile @ 2011-04-20 2:45 ` Huang Shijie 0 siblings, 0 replies; 20+ messages in thread From: Huang Shijie @ 2011-04-20 2:45 UTC (permalink / raw) To: linux-arm-kernel add the GPMI driver in the relevant Kconfig and Makefile in the MTD. Signed-off-by: Huang Shijie <b32955@freescale.com> --- drivers/mtd/nand/Kconfig | 11 +++++++++++ drivers/mtd/nand/Makefile | 1 + drivers/mtd/nand/gpmi-nfc/Makefile | 3 +++ 3 files changed, 15 insertions(+), 0 deletions(-) create mode 100644 drivers/mtd/nand/gpmi-nfc/Makefile diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 450afc5..25a1493 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -426,6 +426,17 @@ config MTD_NAND_NANDSIM The simulator may simulate various NAND flash chips for the MTD nand layer. +config MTD_NAND_GPMI_NFC + bool "GPMI NAND Flash Controller driver" + depends on MTD_NAND && (SOC_IMX23 || SOC_IMX28) + select MTD_PARTITIONS + select MTD_CMDLINE_PARTS + help + Enables NAND Flash support for IMX23 or IMX28. + The GPMI controller is very powerful, with the help of BCH + module, it can do the hardware ECC. The GPMI supports several + NAND flashs at the same time. + config MTD_NAND_PLATFORM tristate "Support for generic platform NAND driver" help diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index 8ad6fae..80d1f08 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -48,5 +48,6 @@ obj-$(CONFIG_MTD_NAND_BCM_UMI) += bcm_umi_nand.o nand_bcm_umi.o obj-$(CONFIG_MTD_NAND_MPC5121_NFC) += mpc5121_nfc.o obj-$(CONFIG_MTD_NAND_RICOH) += r852.o obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o +obj-$(CONFIG_MTD_NAND_GPMI_NFC) += gpmi-nfc/ nand-objs := nand_base.o nand_bbt.o diff --git a/drivers/mtd/nand/gpmi-nfc/Makefile b/drivers/mtd/nand/gpmi-nfc/Makefile new file mode 100644 index 0000000..918fb52 --- /dev/null +++ b/drivers/mtd/nand/gpmi-nfc/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_MTD_NAND_GPMI_NFC) += gpmi_nfc.o +gpmi_nfc-objs += gpmi-nfc.o +gpmi_nfc-objs += hal-mxs.o -- 1.7.0.4 ^ permalink raw reply related [flat|nested] 20+ messages in thread
end of thread, other threads:[~2011-04-29 1:42 UTC | newest] Thread overview: 20+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2011-04-20 2:45 [PATCH v6 0/3] add the GPMI controller driver for IMX23/IMX28 Huang Shijie 2011-04-20 2:45 ` Huang Shijie 2011-04-20 2:45 ` [PATCH v6 1/3] MTD : add the common code for GPMI controller driver Huang Shijie 2011-04-20 2:45 ` Huang Shijie 2011-04-21 7:49 ` Lothar Waßmann 2011-04-21 7:49 ` Lothar Waßmann 2011-04-22 9:39 ` Huang Shijie 2011-04-22 9:39 ` Huang Shijie 2011-04-26 2:52 ` Huang Shijie 2011-04-26 2:52 ` Huang Shijie 2011-04-26 10:09 ` Lothar Waßmann 2011-04-26 10:09 ` Lothar Waßmann 2011-04-28 19:20 ` Russell King - ARM Linux 2011-04-28 19:20 ` Russell King - ARM Linux 2011-04-29 1:42 ` Huang Shijie 2011-04-29 1:42 ` Huang Shijie 2011-04-20 2:45 ` [PATCH v6 2/3] MTD : add support for imx23 and imx28 Huang Shijie 2011-04-20 2:45 ` Huang Shijie 2011-04-20 2:45 ` [PATCH v6 3/3] MTD : add GPMI driver in the config and Makefile Huang Shijie 2011-04-20 2:45 ` Huang Shijie
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.