summaryrefslogtreecommitdiff
path: root/drivers/mtd/nand/gpmi-nfc/gpmi-nfc-main.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd/nand/gpmi-nfc/gpmi-nfc-main.c')
-rw-r--r--drivers/mtd/nand/gpmi-nfc/gpmi-nfc-main.c1879
1 files changed, 1879 insertions, 0 deletions
diff --git a/drivers/mtd/nand/gpmi-nfc/gpmi-nfc-main.c b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc-main.c
new file mode 100644
index 000000000000..0143f1c358ff
--- /dev/null
+++ b/drivers/mtd/nand/gpmi-nfc/gpmi-nfc-main.c
@@ -0,0 +1,1879 @@
+/*
+ * Freescale GPMI NFC NAND Flash Driver
+ *
+ * Copyright (C) 2010 Freescale Semiconductor, Inc.
+ * Copyright (C) 2008 Embedded Alley Solutions, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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"
+
+/*
+ * This structure contains the "safe" GPMI timing that should succeed with any
+ * NAND Flash device (although, with less-than-optimal performance).
+ */
+
+static struct gpmi_nfc_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,
+};
+
+/*
+ * This array has a pointer to every NFC HAL structure. The probing process will
+ * find and install the one that matches the version given by the platform.
+ */
+
+static struct nfc_hal *(nfc_hals[]) = {
+ &gpmi_nfc_hal_v0,
+ &gpmi_nfc_hal_v1,
+};
+
+/*
+ * This array has a pointer to every Boot ROM Helper structure. The probing
+ * process will find and install the one that matches the version given by the
+ * platform.
+ */
+
+static struct boot_rom_helper *(boot_rom_helpers[]) = {
+ &gpmi_nfc_boot_rom_helper_v0,
+ &gpmi_nfc_boot_rom_helper_v1,
+};
+
+/**
+ * show_device_report() - Contains a shell script that creates a handy report.
+ *
+ * @d: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer that will receive a representation of the attribute.
+ */
+static ssize_t show_device_report(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+ static const char *script =
+ "GPMISysDirectory=/sys/bus/platform/devices/gpmi-nfc.0\n"
+ "\n"
+ "NodeList='\n"
+ "physical_geometry\n"
+ "nfc_info\n"
+ "nfc_geometry\n"
+ "timing\n"
+ "timing_diagram\n"
+ "rom_geometry\n"
+ "mtd_nand_info\n"
+ "mtd_info\n"
+ "'\n"
+ "\n"
+ "cd ${GPMISysDirectory}\n"
+ "\n"
+ "printf '\\n'\n"
+ "\n"
+ "for NodeName in ${NodeList}\n"
+ "do\n"
+ "\n"
+ " printf '--------------------------------------------\\n'\n"
+ " printf '%s\\n' ${NodeName}\n"
+ " printf '--------------------------------------------\\n'\n"
+ " printf '\\n'\n"
+ "\n"
+ " cat ${NodeName}\n"
+ "\n"
+ " printf '\\n'\n"
+ "\n"
+ "done\n"
+ ;
+
+ return sprintf(buf, "%s", script);
+
+}
+
+/**
+ * show_device_numchips() - Shows the number of physical chips.
+ *
+ * This node is made obsolete by the physical_geometry node, but we keep it for
+ * backward compatibility (especially for kobs).
+ *
+ * @d: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer that will receive a representation of the attribute.
+ */
+static ssize_t show_device_numchips(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gpmi_nfc_data *this = dev_get_drvdata(dev);
+ struct physical_geometry *physical = &this->physical_geometry;
+
+ return sprintf(buf, "%d\n", physical->chip_count);
+
+}
+
+/**
+ * show_device_physical_geometry() - Shows the physical Flash device geometry.
+ *
+ * @dev: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer that will receive a representation of the attribute.
+ */
+static ssize_t show_device_physical_geometry(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gpmi_nfc_data *this = dev_get_drvdata(dev);
+ struct nand_device_info *info = &this->device_info;
+ struct physical_geometry *physical = &this->physical_geometry;
+
+ return sprintf(buf,
+ "Description : %s\n"
+ "Chip Count : %u\n"
+ "Chip Size in Bytes : %llu\n"
+ "Block Size in Bytes : %u\n"
+ "Page Data Size in Bytes: %u\n"
+ "Page OOB Size in Bytes : %u\n"
+ ,
+ info->description,
+ physical->chip_count,
+ physical->chip_size_in_bytes,
+ physical->block_size_in_bytes,
+ physical->page_data_size_in_bytes,
+ physical->page_oob_size_in_bytes
+ );
+
+}
+
+/**
+ * show_device_nfc_info() - Shows the NFC-specific information.
+ *
+ * @dev: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer that will receive a representation of the attribute.
+ */
+static ssize_t show_device_nfc_info(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gpmi_nfc_data *this = dev_get_drvdata(dev);
+ struct nfc_hal *nfc = this->nfc;
+
+ return sprintf(buf,
+ "Version : %u\n"
+ "Description : %s\n"
+ "Max Chip Count : %u\n"
+ "Max Data Setup Cycles : 0x%x\n"
+ "Internal Data Setup in ns : %u\n"
+ "Max Sample Delay Factor : 0x%x\n"
+ "Max DLL Clock Period in ns: %u\n"
+ "Max DLL Delay in ns : %u\n"
+ ,
+ nfc->version,
+ nfc->description,
+ nfc->max_chip_count,
+ nfc->max_data_setup_cycles,
+ nfc->internal_data_setup_in_ns,
+ nfc->max_sample_delay_factor,
+ nfc->max_dll_clock_period_in_ns,
+ nfc->max_dll_delay_in_ns
+ );
+
+}
+
+/**
+ * show_device_nfc_geometry() - Shows the NFC view of the device geometry.
+ *
+ * @dev: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer that will receive a representation of the attribute.
+ */
+static ssize_t show_device_nfc_geometry(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gpmi_nfc_data *this = dev_get_drvdata(dev);
+ struct nfc_geometry *nfc = &this->nfc_geometry;
+
+ return sprintf(buf,
+ "ECC Algorithm : %s\n"
+ "ECC Strength : %u\n"
+ "Page Size in Bytes : %u\n"
+ "Metadata Size in Bytes : %u\n"
+ "ECC Chunk Size in Bytes: %u\n"
+ "ECC Chunk Count : %u\n"
+ "Payload Size in Bytes : %u\n"
+ "Auxiliary Size in Bytes: %u\n"
+ "Auxiliary Status Offset: %u\n"
+ "Block Mark Byte Offset : %u\n"
+ "Block Mark Bit Offset : %u\n"
+ ,
+ nfc->ecc_algorithm,
+ nfc->ecc_strength,
+ nfc->page_size_in_bytes,
+ nfc->metadata_size_in_bytes,
+ nfc->ecc_chunk_size_in_bytes,
+ nfc->ecc_chunk_count,
+ nfc->payload_size_in_bytes,
+ nfc->auxiliary_size_in_bytes,
+ nfc->auxiliary_status_offset,
+ nfc->block_mark_byte_offset,
+ nfc->block_mark_bit_offset
+ );
+
+}
+
+/**
+ * show_device_rom_geometry() - Shows the Boot ROM Helper's geometry.
+ *
+ * @dev: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer that will receive a representation of the attribute.
+ */
+static ssize_t show_device_rom_geometry(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gpmi_nfc_data *this = dev_get_drvdata(dev);
+ struct boot_rom_geometry *rom = &this->rom_geometry;
+
+ return sprintf(buf,
+ "Boot Area Count : %u\n"
+ "Boot Area Size in Bytes : %u\n"
+ "Stride Size in Pages : %u\n"
+ "Seach Area Stride Exponent: %u\n"
+ ,
+ rom->boot_area_count,
+ rom->boot_area_size_in_bytes,
+ rom->stride_size_in_pages,
+ rom->search_area_stride_exponent
+ );
+
+}
+
+/**
+ * show_device_mtd_nand_info() - Shows the device's MTD NAND-specific info.
+ *
+ * @dev: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer that will receive a representation of the attribute.
+ */
+static ssize_t show_device_mtd_nand_info(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int o = 0;
+ unsigned int i;
+ unsigned int j;
+ static const unsigned int columns = 8;
+ struct gpmi_nfc_data *this = dev_get_drvdata(dev);
+ struct mil *mil = &this->mil;
+ struct nand_chip *nand = &mil->nand;
+
+ o += sprintf(buf + o,
+ "Options : 0x%08x\n"
+ "Chip Count : %u\n"
+ "Chip Size in Bytes : %llu\n"
+ "Minimum Writable Size in Bytes: %u\n"
+ "Page Shift : %u\n"
+ "Page Mask : 0x%x\n"
+ "Block Shift : %u\n"
+ "BBT Block Shift : %u\n"
+ "Chip Shift : %u\n"
+ "Block Mark Offset : %u\n"
+ "Cached Page Number : %d\n"
+ ,
+ nand->options,
+ nand->numchips,
+ nand->chipsize,
+ nand->subpagesize,
+ nand->page_shift,
+ nand->pagemask,
+ nand->phys_erase_shift,
+ nand->bbt_erase_shift,
+ nand->chip_shift,
+ nand->badblockpos,
+ nand->pagebuf
+ );
+
+ o += sprintf(buf + o,
+ "ECC Byte Count : %u\n"
+ ,
+ nand->ecc.layout->eccbytes
+ );
+
+ /* Loop over rows. */
+
+ for (i = 0; (i * columns) < nand->ecc.layout->eccbytes; i++) {
+
+ /* Loop over columns within rows. */
+
+ for (j = 0; j < columns; j++) {
+
+ if (((i * columns) + j) >= nand->ecc.layout->eccbytes)
+ break;
+
+ o += sprintf(buf + o, " %3u",
+ nand->ecc.layout->eccpos[(i * columns) + j]);
+
+ }
+
+ o += sprintf(buf + o, "\n");
+
+ }
+
+ o += sprintf(buf + o,
+ "OOB Available Bytes : %u\n"
+ ,
+ nand->ecc.layout->oobavail
+ );
+
+ j = 0;
+
+ for (i = 0; j < nand->ecc.layout->oobavail; i++) {
+
+ j += nand->ecc.layout->oobfree[i].length;
+
+ o += sprintf(buf + o,
+ " [%3u, %2u]\n"
+ ,
+ nand->ecc.layout->oobfree[i].offset,
+ nand->ecc.layout->oobfree[i].length
+ );
+
+ }
+
+ return o;
+
+}
+
+/**
+ * show_device_mtd_info() - Shows the device's MTD-specific information.
+ *
+ * @dev: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer that will receive a representation of the attribute.
+ */
+static ssize_t show_device_mtd_info(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int o = 0;
+ unsigned int i;
+ unsigned int j;
+ static const unsigned int columns = 8;
+ struct gpmi_nfc_data *this = dev_get_drvdata(dev);
+ struct mil *mil = &this->mil;
+ struct mtd_info *mtd = &mil->mtd;
+
+ o += sprintf(buf + o,
+ "Name : %s\n"
+ "Type : %u\n"
+ "Flags : 0x%08x\n"
+ "Size in Bytes : %llu\n"
+ "Erase Region Count : %d\n"
+ "Erase Size in Bytes: %u\n"
+ "Write Size in Bytes: %u\n"
+ "OOB Size in Bytes : %u\n"
+ "Errors Corrected : %u\n"
+ "Failed Reads : %u\n"
+ "Bad Block Count : %u\n"
+ "BBT Block Count : %u\n"
+ ,
+ mtd->name,
+ mtd->type,
+ mtd->flags,
+ mtd->size,
+ mtd->numeraseregions,
+ mtd->erasesize,
+ mtd->writesize,
+ mtd->oobsize,
+ mtd->ecc_stats.corrected,
+ mtd->ecc_stats.failed,
+ mtd->ecc_stats.badblocks,
+ mtd->ecc_stats.bbtblocks
+ );
+
+ o += sprintf(buf + o,
+ "ECC Byte Count : %u\n"
+ ,
+ mtd->ecclayout->eccbytes
+ );
+
+ /* Loop over rows. */
+
+ for (i = 0; (i * columns) < mtd->ecclayout->eccbytes; i++) {
+
+ /* Loop over columns within rows. */
+
+ for (j = 0; j < columns; j++) {
+
+ if (((i * columns) + j) >= mtd->ecclayout->eccbytes)
+ break;
+
+ o += sprintf(buf + o, " %3u",
+ mtd->ecclayout->eccpos[(i * columns) + j]);
+
+ }
+
+ o += sprintf(buf + o, "\n");
+
+ }
+
+ o += sprintf(buf + o,
+ "OOB Available Bytes: %u\n"
+ ,
+ mtd->ecclayout->oobavail
+ );
+
+ j = 0;
+
+ for (i = 0; j < mtd->ecclayout->oobavail; i++) {
+
+ j += mtd->ecclayout->oobfree[i].length;
+
+ o += sprintf(buf + o,
+ " [%3u, %2u]\n"
+ ,
+ mtd->ecclayout->oobfree[i].offset,
+ mtd->ecclayout->oobfree[i].length
+ );
+
+ }
+
+ return o;
+
+}
+
+/**
+ * show_device_timing_diagram() - Shows a timing diagram.
+ *
+ * @dev: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer that will receive a representation of the attribute.
+ */
+static ssize_t show_device_timing_diagram(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gpmi_nfc_data *this = dev_get_drvdata(dev);
+ struct gpmi_nfc_platform_data *pdata = this->pdata;
+ struct nfc_hal *nfc = this->nfc;
+ struct gpmi_nfc_timing timing = nfc->timing;
+ struct gpmi_nfc_hardware_timing hardware_timing;
+ unsigned long clock_frequency_in_hz;
+ unsigned long clock_period_in_ns;
+ unsigned int data_setup_in_ns;
+ unsigned int dll_delay_shift;
+ unsigned int sample_delay_in_ns;
+ unsigned int tDS_in_ns;
+ unsigned int tOPEN_in_ns;
+ unsigned int tCLOSE_in_ns;
+ unsigned int tEYE_in_ns;
+ unsigned int tDELAY_in_ns;
+ unsigned int tDS;
+ unsigned int tOPEN;
+ unsigned int tCLOSE;
+ unsigned int tEYE;
+ unsigned int tDELAY;
+ const unsigned int diagram_width_in_chars = 55;
+ unsigned int diagram_width_in_ns;
+ int o = 0;
+ unsigned int i;
+
+ /*
+ * If there are any timing characteristics we need, but don't know, we
+ * pretend they're zero.
+ */
+
+ if (timing.tREA_in_ns < 0)
+ timing.tREA_in_ns = 0;
+
+ if (timing.tRHOH_in_ns < 0)
+ timing.tRHOH_in_ns = 0;
+
+ /* Get information about the current/last I/O transaction. */
+
+ nfc->get_timing(this, &clock_frequency_in_hz, &hardware_timing);
+
+ clock_period_in_ns = 1000000000 / clock_frequency_in_hz;
+
+ /* Compute basic timing facts. */
+
+ data_setup_in_ns =
+ hardware_timing.data_setup_in_cycles * clock_period_in_ns;
+
+ /* Compute data sample delay facts. */
+
+ dll_delay_shift = 3;
+
+ if (hardware_timing.use_half_periods)
+ dll_delay_shift++;
+
+ sample_delay_in_ns =
+ (hardware_timing.sample_delay_factor * clock_period_in_ns) >>
+ dll_delay_shift;
+
+ /* Compute the basic metrics in the diagram, in nanoseconds. */
+
+ tDS_in_ns = data_setup_in_ns;
+ tOPEN_in_ns = pdata->max_prop_delay_in_ns + timing.tREA_in_ns;
+ tCLOSE_in_ns = pdata->min_prop_delay_in_ns + timing.tRHOH_in_ns;
+ tEYE_in_ns = tDS_in_ns + tCLOSE_in_ns - tOPEN_in_ns;
+ tDELAY_in_ns = sample_delay_in_ns;
+
+ /*
+ * We need to translate nanosecond timings into character widths in the
+ * diagram. The first step is to discover how "wide" the diagram is in
+ * nanoseconds. That depends on which happens latest: the sample point
+ * or the close of the eye.
+ */
+
+ if (tCLOSE_in_ns >= tDELAY_in_ns)
+ diagram_width_in_ns = tDS_in_ns + tCLOSE_in_ns;
+ else
+ diagram_width_in_ns = tDS_in_ns + tDELAY_in_ns;
+
+ /* Convert the metrics that appear in the diagram. */
+
+ tDS = (tDS_in_ns * diagram_width_in_chars) / diagram_width_in_ns;
+ tOPEN = (tOPEN_in_ns * diagram_width_in_chars) / diagram_width_in_ns;
+ tCLOSE = (tCLOSE_in_ns * diagram_width_in_chars) / diagram_width_in_ns;
+ tEYE = (tEYE_in_ns * diagram_width_in_chars) / diagram_width_in_ns;
+ tDELAY = (tDELAY_in_ns * diagram_width_in_chars) / diagram_width_in_ns;
+
+ /*
+ * Show the results.
+ *
+ * This code is really ugly, but it draws a pretty picture :)
+ */
+
+ o += sprintf(buf + o, "\n");
+
+
+ o += sprintf(buf + o, "Sample ______");
+ for (i = 0; i < tDS; i++)
+ o += sprintf(buf + o, "_");
+ if (tDELAY > 0)
+ for (i = 0; i < (tDELAY - 1); i++)
+ o += sprintf(buf + o, "_");
+ o += sprintf(buf + o, "|");
+ for (i = 0; i < (diagram_width_in_chars - (tDS + tDELAY)); i++)
+ o += sprintf(buf + o, "_");
+ o += sprintf(buf + o, "\n");
+
+
+ o += sprintf(buf + o, "Strobe ");
+ for (i = 0; i < tDS; i++)
+ o += sprintf(buf + o, " ");
+ o += sprintf(buf + o, "|");
+ if (tDELAY > 1) {
+ for (i = 2; i < tDELAY; i++)
+ o += sprintf(buf + o, "-");
+ o += sprintf(buf + o, "|");
+ }
+ o += sprintf(buf + o, " tDELAY\n");
+
+
+ o += sprintf(buf + o, "\n");
+
+
+ o += sprintf(buf + o, " tDS ");
+ o += sprintf(buf + o, "|");
+ if (tDS > 1) {
+ for (i = 2; i < tDS; i++)
+ o += sprintf(buf + o, "-");
+ o += sprintf(buf + o, "|");
+ }
+ o += sprintf(buf + o, "\n");
+
+
+ o += sprintf(buf + o, " ______");
+ for (i = 0; i < tDS; i++)
+ o += sprintf(buf + o, " ");
+ for (i = 0; i < (diagram_width_in_chars - tDS); i++)
+ o += sprintf(buf + o, "_");
+ o += sprintf(buf + o, "\n");
+
+
+ o += sprintf(buf + o, "RDN ");
+ if (tDS > 0) {
+ if (tDS == 1)
+ o += sprintf(buf + o, "V");
+ else {
+ o += sprintf(buf + o, "\\");
+ for (i = 2; i < tDS; i++)
+ o += sprintf(buf + o, "_");
+ o += sprintf(buf + o, "/");
+ }
+ }
+ o += sprintf(buf + o, "\n");
+
+
+ o += sprintf(buf + o, "\n");
+
+
+ o += sprintf(buf + o, " tOPEN ");
+ o += sprintf(buf + o, "|");
+ if (tOPEN > 1) {
+ for (i = 2; i < tOPEN; i++)
+ o += sprintf(buf + o, "-");
+ o += sprintf(buf + o, "|");
+ }
+ o += sprintf(buf + o, "\n");
+
+
+ o += sprintf(buf + o, " ");
+ for (i = 0; i < tDS; i++)
+ o += sprintf(buf + o, " ");
+ o += sprintf(buf + o, "|");
+ if (tCLOSE > 1) {
+ for (i = 2; i < tCLOSE; i++)
+ o += sprintf(buf + o, "-");
+ o += sprintf(buf + o, "|");
+ }
+ o += sprintf(buf + o, " tCLOSE\n");
+
+
+ o += sprintf(buf + o, " ");
+ for (i = 0; i < tOPEN; i++)
+ o += sprintf(buf + o, " ");
+ if (tEYE > 2) {
+ o += sprintf(buf + o, " ");
+ for (i = 2; i < tEYE; i++)
+ o += sprintf(buf + o, "_");
+ }
+ o += sprintf(buf + o, "\n");
+
+
+ o += sprintf(buf + o, "Data ______");
+ for (i = 0; i < tOPEN; i++)
+ o += sprintf(buf + o, "_");
+ if (tEYE > 0) {
+ if (tEYE == 1)
+ o += sprintf(buf + o, "|");
+ else {
+ o += sprintf(buf + o, "/");
+ for (i = 2; i < tEYE; i++)
+ o += sprintf(buf + o, " ");
+ o += sprintf(buf + o, "\\");
+ }
+ }
+ for (i = 0; i < (diagram_width_in_chars - (tOPEN + tEYE)); i++)
+ o += sprintf(buf + o, "_");
+ o += sprintf(buf + o, "\n");
+
+
+ o += sprintf(buf + o, " ");
+ for (i = 0; i < tOPEN; i++)
+ o += sprintf(buf + o, " ");
+ if (tEYE > 0) {
+ if (tEYE == 1)
+ o += sprintf(buf + o, "|");
+ else {
+ o += sprintf(buf + o, "\\");
+ for (i = 2; i < tEYE; i++)
+ o += sprintf(buf + o, "_");
+ o += sprintf(buf + o, "/");
+ }
+ }
+ o += sprintf(buf + o, "\n");
+
+
+ o += sprintf(buf + o, " ");
+ for (i = 0; i < tOPEN; i++)
+ o += sprintf(buf + o, " ");
+ o += sprintf(buf + o, "|");
+ if (tEYE > 1) {
+ for (i = 2; i < tEYE; i++)
+ o += sprintf(buf + o, "-");
+ o += sprintf(buf + o, "|");
+ }
+ o += sprintf(buf + o, " tEYE\n");
+
+
+ o += sprintf(buf + o, "\n");
+ o += sprintf(buf + o, "tDS : %u ns\n", tDS_in_ns);
+ o += sprintf(buf + o, "tOPEN : %u ns\n", tOPEN_in_ns);
+ o += sprintf(buf + o, "tCLOSE: %u ns\n", tCLOSE_in_ns);
+ o += sprintf(buf + o, "tEYE : %u ns\n", tEYE_in_ns);
+ o += sprintf(buf + o, "tDELAY: %u ns\n", tDELAY_in_ns);
+ o += sprintf(buf + o, "\n");
+
+
+ return o;
+
+}
+
+/**
+ * store_device_invalidate_page_cache() - Invalidates the device's page cache.
+ *
+ * @dev: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer containing a new attribute value.
+ * @size: The size of the buffer.
+ */
+static ssize_t store_device_invalidate_page_cache(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct gpmi_nfc_data *this = dev_get_drvdata(dev);
+
+ /* Invalidate the page cache. */
+
+ this->mil.nand.pagebuf = -1;
+
+ /* Return success. */
+
+ return size;
+
+}
+
+/**
+ * store_device_mark_block_bad() - Marks a block as bad.
+ *
+ * @dev: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer containing a new attribute value.
+ * @size: The size of the buffer.
+ */
+static ssize_t store_device_mark_block_bad(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;
+ struct mtd_info *mtd = &mil->mtd;
+ struct nand_chip *nand = &mil->nand;
+ unsigned long block_number;
+ loff_t byte_address;
+ int error;
+
+ /* Look for nonsense. */
+
+ if (!size)
+ return -EINVAL;
+
+ /* Try to understand the block number. */
+
+ if (strict_strtoul(buf, 0, &block_number))
+ return -EINVAL;
+
+ /* Compute the byte address of this block. */
+
+ byte_address = block_number << nand->phys_erase_shift;
+
+ /* Attempt to mark the block bad. */
+
+ error = mtd->block_markbad(mtd, byte_address);
+
+ if (error)
+ return error;
+
+ /* Return success. */
+
+ return size;
+
+}
+
+/**
+ * show_device_ignorebad() - Shows the value of the 'ignorebad' flag.
+ *
+ * @d: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer that will receive a representation of the attribute.
+ */
+static ssize_t show_device_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);
+}
+
+/**
+ * store_device_ignorebad() - Sets the value of the 'ignorebad' flag.
+ *
+ * @dev: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer containing a new attribute value.
+ * @size: The size of the buffer.
+ */
+static ssize_t store_device_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;
+
+ /* Try to make sense of what arrived from user space. */
+
+ if (strict_strtoul(p, 0, &v) < 0)
+ return size;
+
+ if (v > 0)
+ v = 1;
+
+ /* Only do something if the value is changing. */
+
+ if (v != mil->ignore_bad_block_marks) {
+
+ if (v) {
+
+ /*
+ * If control arrives here, we want to begin ignoring
+ * bad block marks. Reach into the NAND Flash MTD data
+ * structures and set the in-memory BBT pointer to NULL.
+ * 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 = 0;
+
+ } else {
+
+ /*
+ * If control arrives here, we want to stop ignoring
+ * bad block marks. 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;
+
+}
+
+/**
+ * show_device_inject_ecc_error() - Shows the device's error injection flag.
+ *
+ * @dev: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer that will receive a representation of the attribute.
+ */
+static ssize_t show_device_inject_ecc_error(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->inject_ecc_error);
+
+}
+
+/**
+ * store_device_inject_ecc_error() - Sets the device's error injection flag.
+ *
+ * @dev: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer containing a new attribute value.
+ * @size: The size of the buffer.
+ */
+static ssize_t store_device_inject_ecc_error(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;
+ long new_inject_ecc_error;
+
+ /* Look for nonsense. */
+
+ if (!size)
+ return -EINVAL;
+
+ /* Try to understand the ECC error count. */
+
+ if (strict_strtol(buf, 0, &new_inject_ecc_error))
+ return -EINVAL;
+
+ /* Store the value. */
+
+ mil->inject_ecc_error = new_inject_ecc_error;
+
+ /* Return success. */
+
+ return size;
+
+}
+
+/**
+ * show_device_timing_help() - Show help for setting timing.
+ *
+ * @d: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer that will receive a representation of the attribute.
+ */
+static ssize_t show_device_timing_help(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+ static const char *help =
+ "<Data Setup>,<Data Hold>,<Address Setup>,<Sample Delay>,"
+ "<tREA>,<tRLOH>,<tRHOH>\n";
+
+ return sprintf(buf, "%s", help);
+
+}
+
+/**
+ * show_device_timing() - Shows the current timing.
+ *
+ * @d: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer that will receive a representation of the attribute.
+ */
+static ssize_t show_device_timing(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gpmi_nfc_data *this = dev_get_drvdata(dev);
+ struct gpmi_nfc_platform_data *pdata = this->pdata;
+ struct nfc_hal *nfc = this->nfc;
+ struct gpmi_nfc_timing *recorded = &nfc->timing;
+ unsigned long clock_frequency_in_hz;
+ unsigned long clock_period_in_ns;
+ struct gpmi_nfc_hardware_timing hardware;
+ unsigned int effective_data_setup_in_ns;
+ unsigned int effective_data_hold_in_ns;
+ unsigned int effective_address_setup_in_ns;
+ unsigned int dll_delay_shift;
+ unsigned int effective_sample_delay_in_ns;
+
+ /* Get information about the current/last I/O transaction. */
+
+ nfc->get_timing(this, &clock_frequency_in_hz, &hardware);
+
+ clock_period_in_ns = 1000000000 / clock_frequency_in_hz;
+
+ /* Compute basic timing facts. */
+
+ effective_data_setup_in_ns =
+ hardware.data_setup_in_cycles * clock_period_in_ns;
+ effective_data_hold_in_ns =
+ hardware.data_hold_in_cycles * clock_period_in_ns;
+ effective_address_setup_in_ns =
+ hardware.address_setup_in_cycles * clock_period_in_ns;
+
+ /* Compute data sample delay facts. */
+
+ dll_delay_shift = 3;
+
+ if (hardware.use_half_periods)
+ dll_delay_shift++;
+
+ effective_sample_delay_in_ns =
+ (hardware.sample_delay_factor * clock_period_in_ns) >>
+ dll_delay_shift;
+
+ /* Show the results. */
+
+ return sprintf(buf,
+ "Minimum Propagation Delay in ns : %u\n"
+ "Maximum Propagation Delay in ns : %u\n"
+ "Clock Frequency in Hz : %lu\n"
+ "Clock Period in ns : %lu\n"
+ "Recorded Data Setup in ns : %d\n"
+ "Hardware Data Setup in cycles : %u\n"
+ "Effective Data Setup in ns : %u\n"
+ "Recorded Data Hold in ns : %d\n"
+ "Hardware Data Hold in cycles : %u\n"
+ "Effective Data Hold in ns : %u\n"
+ "Recorded Address Setup in ns : %d\n"
+ "Hardware Address Setup in cycles: %u\n"
+ "Effective Address Setup in ns : %u\n"
+ "Using Half Period : %s\n"
+ "Recorded Sample Delay in ns : %d\n"
+ "Hardware Sample Delay Factor : %u\n"
+ "Effective Sample Delay in ns : %u\n"
+ "Recorded tREA in ns : %d\n"
+ "Recorded tRLOH in ns : %d\n"
+ "Recorded tRHOH in ns : %d\n"
+ ,
+ pdata->min_prop_delay_in_ns,
+ pdata->max_prop_delay_in_ns,
+ clock_frequency_in_hz,
+ clock_period_in_ns,
+ recorded->data_setup_in_ns,
+ hardware .data_setup_in_cycles,
+ effective_data_setup_in_ns,
+ recorded->data_hold_in_ns,
+ hardware .data_hold_in_cycles,
+ effective_data_hold_in_ns,
+ recorded->address_setup_in_ns,
+ hardware .address_setup_in_cycles,
+ effective_address_setup_in_ns,
+ hardware .use_half_periods ? "Yes" : "No",
+ recorded->gpmi_sample_delay_in_ns,
+ hardware .sample_delay_factor,
+ effective_sample_delay_in_ns,
+ recorded->tREA_in_ns,
+ recorded->tRLOH_in_ns,
+ recorded->tRHOH_in_ns);
+
+}
+
+/**
+ * store_device_timing() - Sets the current timing.
+ *
+ * @dev: The device of interest.
+ * @attr: The attribute of interest.
+ * @buf: A buffer containing a new attribute value.
+ * @size: The size of the buffer.
+ */
+static ssize_t store_device_timing(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct gpmi_nfc_data *this = dev_get_drvdata(dev);
+ struct nfc_hal *nfc = this->nfc;
+ const char *p = buf;
+ const char *q;
+ char tmps[20];
+ long t;
+ struct gpmi_nfc_timing new;
+
+ int8_t *field_pointers[] = {
+ &new.data_setup_in_ns,
+ &new.data_hold_in_ns,
+ &new.address_setup_in_ns,
+ &new.gpmi_sample_delay_in_ns,
+ &new.tREA_in_ns,
+ &new.tRLOH_in_ns,
+ &new.tRHOH_in_ns,
+ NULL,
+ };
+
+ int8_t **field_pointer = field_pointers;
+
+ /*
+ * Loop over comma-separated timing values in the incoming buffer,
+ * assigning them to fields in the timing structure as we go along.
+ */
+
+ while (*field_pointer != NULL) {
+
+ /* Clear out the temporary buffer. */
+
+ memset(tmps, 0, sizeof(tmps));
+
+ /* Copy the timing value into the temporary buffer. */
+
+ q = strchr(p, ',');
+ if (q)
+ strncpy(tmps, p, min_t(int, sizeof(tmps) - 1, q - p));
+ else
+ strncpy(tmps, p, sizeof(tmps) - 1);
+
+ /* Attempt to convert the current timing value. */
+
+ if (strict_strtol(tmps, 0, &t) < 0)
+ return -EINVAL;
+
+ if ((t > 127) || (t < -128))
+ return -EINVAL;
+
+ /* Assign this value to the current field. */
+
+ **field_pointer = (int8_t) t;
+ field_pointer++;
+
+ /* Check if we ran out of input too soon. */
+
+ if (!q && *field_pointer)
+ return -EINVAL;
+
+ /* Move past the comma to the next timing value. */
+
+ p = q + 1;
+
+ }
+
+ /* Hand over the timing to the NFC. */
+
+ nfc->set_timing(this, &new);
+
+ /* Return success. */
+
+ return size;
+
+}
+
+/* Device attributes that appear in sysfs. */
+
+static DEVICE_ATTR(report , 0555, show_device_report , 0);
+static DEVICE_ATTR(numchips , 0444, show_device_numchips , 0);
+static DEVICE_ATTR(physical_geometry, 0444, show_device_physical_geometry, 0);
+static DEVICE_ATTR(nfc_info , 0444, show_device_nfc_info , 0);
+static DEVICE_ATTR(nfc_geometry , 0444, show_device_nfc_geometry , 0);
+static DEVICE_ATTR(rom_geometry , 0444, show_device_rom_geometry , 0);
+static DEVICE_ATTR(mtd_nand_info , 0444, show_device_mtd_nand_info , 0);
+static DEVICE_ATTR(mtd_info , 0444, show_device_mtd_info , 0);
+static DEVICE_ATTR(timing_diagram , 0444, show_device_timing_diagram , 0);
+static DEVICE_ATTR(timing_help , 0444, show_device_timing_help , 0);
+
+static DEVICE_ATTR(invalidate_page_cache, 0644,
+ 0, store_device_invalidate_page_cache);
+
+static DEVICE_ATTR(mark_block_bad, 0200,
+ 0, store_device_mark_block_bad);
+
+static DEVICE_ATTR(ignorebad, 0644,
+ show_device_ignorebad, store_device_ignorebad);
+
+static DEVICE_ATTR(inject_ecc_error, 0644,
+ show_device_inject_ecc_error, store_device_inject_ecc_error);
+
+static DEVICE_ATTR(timing, 0644,
+ show_device_timing, store_device_timing);
+
+static struct device_attribute *device_attributes[] = {
+ &dev_attr_report,
+ &dev_attr_numchips,
+ &dev_attr_physical_geometry,
+ &dev_attr_nfc_info,
+ &dev_attr_nfc_geometry,
+ &dev_attr_rom_geometry,
+ &dev_attr_mtd_nand_info,
+ &dev_attr_mtd_info,
+ &dev_attr_invalidate_page_cache,
+ &dev_attr_mark_block_bad,
+ &dev_attr_ignorebad,
+ &dev_attr_inject_ecc_error,
+ &dev_attr_timing,
+ &dev_attr_timing_help,
+ &dev_attr_timing_diagram,
+};
+
+/**
+ * validate_the_platform() - Validates information about the platform.
+ *
+ * @pdev: A pointer to the platform device data structure.
+ */
+static int validate_the_platform(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct gpmi_nfc_platform_data *pdata = pdev->dev.platform_data;
+
+ /* Validate the clock name. */
+
+ if (!pdata->clock_name) {
+ dev_err(dev, "No clock name\n");
+ return -ENXIO;
+ }
+
+ /* Validate the partitions. */
+
+ if ((pdata->partitions && (!pdata->partition_count)) ||
+ (!pdata->partitions && (pdata->partition_count))) {
+ dev_err(dev, "Bad partition data\n");
+ return -ENXIO;
+ }
+
+ /* Return success */
+
+ return 0;
+
+}
+
+/**
+ * acquire_register_block() - Tries to acquire and map a register block.
+ *
+ * @this: Per-device data.
+ * @resource_name: The name of the resource.
+ * @reg_block_base: A pointer to a variable that will receive the address of
+ * the mapped register block.
+ */
+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 device *dev = this->dev;
+ void *p;
+ struct resource *r;
+
+ /* Attempt to get information about the given resource. */
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, resource_name);
+
+ if (!r) {
+ dev_err(dev, "Can't get resource information for '%s'\n",
+ resource_name);
+ return -ENXIO;
+ }
+
+ /* Attempt to remap the register block. */
+
+ p = ioremap(r->start, r->end - r->start + 1);
+
+ if (!p) {
+ dev_err(dev, "Can't remap %s\n", resource_name);
+ return -EIO;
+ }
+
+ /* If control arrives here, everything went fine. */
+
+ *reg_block_base = p;
+
+ return 0;
+
+}
+
+/**
+ * release_register_block() - Releases a register block.
+ *
+ * @this: Per-device data.
+ * @reg_block_base: A pointer to the mapped register block.
+ */
+static void release_register_block(struct gpmi_nfc_data *this,
+ void *reg_block_base)
+{
+ iounmap(reg_block_base);
+}
+
+/**
+ * acquire_interrupt() - Tries to acquire an interrupt.
+ *
+ * @this: Per-device data.
+ * @resource_name: The name of the resource.
+ * @interrupt_handler: A pointer to the function that will handle interrupts
+ * from this interrupt number.
+ * @interrupt_number: A pointer to a variable that will receive the acquired
+ * interrupt number.
+ */
+static int acquire_interrupt(
+ struct gpmi_nfc_data *this, const char *resource_name,
+ irq_handler_t interrupt_handler, int *interrupt_number)
+{
+ struct platform_device *pdev = this->pdev;
+ struct device *dev = this->dev;
+ int error = 0;
+ int i;
+
+ /* Attempt to get information about the given resource. */
+
+ i = platform_get_irq_byname(pdev, resource_name);
+
+ if (i < 0) {
+ dev_err(dev, "Can't get resource information for '%s'\n",
+ resource_name);
+ return -ENXIO;
+ }
+
+ /* Attempt to own the interrupt. */
+
+ error = request_irq(i, interrupt_handler, 0, resource_name, this);
+
+ if (error) {
+ dev_err(dev, "Can't own %s\n", resource_name);
+ return -EIO;
+ }
+
+ /* If control arrives here, everything went fine. */
+
+ *interrupt_number = i;
+
+ return 0;
+
+}
+
+/**
+ * release_interrupt() - Releases an interrupt.
+ *
+ * @this: Per-device data.
+ * @interrupt_number: The interrupt number.
+ */
+static void release_interrupt(struct gpmi_nfc_data *this, int interrupt_number)
+{
+ free_irq(interrupt_number, this);
+}
+
+/**
+ * acquire_dma_channels() - Tries to acquire DMA channels.
+ *
+ * @this: Per-device data.
+ * @resource_name: The name of the resource.
+ * @low_channel: A pointer to a variable that will receive the acquired
+ * low DMA channel number.
+ * @high_channel: A pointer to a variable that will receive the acquired
+ * high DMA channel number.
+ */
+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 device *dev = this->dev;
+ int error = 0;
+ struct resource *r;
+ unsigned int dma_channel;
+
+ /* Attempt to get information about the given resource. */
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_DMA, resource_name);
+
+ if (!r) {
+ dev_err(dev, "Can't get resource information for '%s'\n",
+ resource_name);
+ return -ENXIO;
+ }
+
+ /* Loop over DMA channels, attempting to own them. */
+
+ for (dma_channel = r->start; dma_channel <= r->end; dma_channel++) {
+
+ /* Attempt to own the current channel. */
+
+ error = mxs_dma_request(dma_channel, dev, resource_name);
+
+ /* Check if we successfully acquired the current channel. */
+
+ if (error) {
+
+ dev_err(dev, "Can't acquire DMA channel %u\n",
+ dma_channel);
+
+ /* Free all the channels we've already acquired. */
+
+ while (--dma_channel >= 0)
+ mxs_dma_release(dma_channel, dev);
+
+ return error;
+
+ }
+
+ /*
+ * If control arrives here, we successfully acquired the
+ * current channel. Continue initializing it.
+ */
+
+ mxs_dma_reset(dma_channel);
+ mxs_dma_ack_irq(dma_channel);
+
+ }
+
+ /* If control arrives here, all went well. */
+
+ *low_channel = r->start;
+ *high_channel = r->end;
+
+ return 0;
+
+}
+
+/**
+ * release_dma_channels() - Releases DMA channels.
+ *
+ * @this: Per-device data.
+ * @low_channel: The low DMA channel number.
+ * @high_channel: The high DMA channel number.
+ */
+static void release_dma_channels(struct gpmi_nfc_data *this,
+ unsigned low_channel, unsigned high_channel)
+{
+ struct device *dev = this->dev;
+ unsigned int i;
+
+ for (i = low_channel; i <= high_channel; i++)
+ mxs_dma_release(i, dev);
+}
+
+/**
+ * acquire_clock() - Tries to acquire a clock.
+ *
+ * @this: Per-device data.
+ * @resource_name: The name of the clock.
+ * @high_channel: A pointer to a variable that will receive the acquired
+ * clock address.
+ */
+static int acquire_clock(struct gpmi_nfc_data *this,
+ const char *clock_name, struct clk **clock)
+{
+ struct device *dev = this->dev;
+ int error = 0;
+ struct clk *c;
+
+ /* Try to get the clock. */
+
+ c = clk_get(dev, clock_name);
+
+ if (IS_ERR(c)) {
+ error = PTR_ERR(c);
+ dev_err(dev, "Can't own clock %s\n", clock_name);
+ return error;
+ }
+
+ /* If control arrives here, everything went fine. */
+
+ *clock = c;
+
+ return 0;
+
+}
+
+/**
+ * release_clock() - Releases a clock.
+ *
+ * @this: Per-device data.
+ * @clock: A pointer to the clock structure.
+ */
+static void release_clock(struct gpmi_nfc_data *this, struct clk *clock)
+{
+ clk_disable(clock);
+ clk_put(clock);
+}
+
+/**
+ * acquire_resources() - Tries to acquire resources.
+ *
+ * @this: Per-device data.
+ */
+static int acquire_resources(struct gpmi_nfc_data *this)
+{
+ struct gpmi_nfc_platform_data *pdata = this->pdata;
+ struct resources *resources = &this->resources;
+ int error = 0;
+
+ /* 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,
+ gpmi_nfc_bch_isr, &(resources->bch_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 the DMA interrupt. */
+
+ error = acquire_interrupt(this,
+ GPMI_NFC_DMA_INTERRUPT_RES_NAME,
+ gpmi_nfc_dma_isr, &(resources->dma_interrupt));
+
+ if (error)
+ goto exit_dma_interrupt;
+
+ /* Attempt to acquire our clock. */
+
+ error = acquire_clock(this, pdata->clock_name, &(resources->clock));
+
+ if (error)
+ goto exit_clock;
+
+ /* If control arrives here, all went well. */
+
+ return 0;
+
+ /* Control arrives here if something went wrong. */
+
+exit_clock:
+ release_interrupt(this, resources->dma_interrupt);
+exit_dma_interrupt:
+ release_dma_channels(this,
+ resources->dma_low_channel, resources->dma_high_channel);
+exit_dma_channels:
+ release_interrupt(this, resources->bch_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;
+
+}
+
+/**
+ * release_resources() - Releases resources.
+ *
+ * @this: Per-device data.
+ */
+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_interrupt);
+ release_dma_channels(this,
+ resources->dma_low_channel, resources->dma_high_channel);
+ release_interrupt(this, resources->dma_interrupt);
+}
+
+/**
+ * set_up_nfc_hal() - Sets up the NFC HAL.
+ *
+ * @this: Per-device data.
+ */
+static int set_up_nfc_hal(struct gpmi_nfc_data *this)
+{
+ struct gpmi_nfc_platform_data *pdata = this->pdata;
+ struct device *dev = this->dev;
+ struct nfc_hal *nfc;
+ int error = 0;
+ unsigned int i;
+
+ /* Attempt to find an NFC HAL that matches the given version. */
+
+ for (i = 0; i < ARRAY_SIZE(nfc_hals); i++) {
+
+ nfc = nfc_hals[i];
+
+ if (nfc->version == pdata->nfc_version) {
+ this->nfc = nfc;
+ break;
+ }
+
+ }
+
+ /* Check if we found a HAL. */
+
+ if (i >= ARRAY_SIZE(nfc_hals)) {
+ dev_err(dev, "Unkown NFC version %u\n", pdata->nfc_version);
+ return -ENXIO;
+ }
+
+ pr_info("NFC: Version %u, %s\n", nfc->version, nfc->description);
+
+ /*
+ * Check if we can handle the number of chips called for by the platform
+ * data.
+ */
+
+ if (pdata->max_chip_count > nfc->max_chip_count) {
+ dev_err(dev, "Platform data calls for %u chips "
+ "but NFC supports a max of %u.\n",
+ pdata->max_chip_count, nfc->max_chip_count);
+ return -ENXIO;
+ }
+
+ /* Initialize the NFC HAL. */
+
+ error = nfc->init(this);
+
+ if (error)
+ return error;
+
+ /* Set up safe timing. */
+
+ nfc->set_timing(this, &safe_timing);
+
+ /*
+ * If control arrives here, all is well.
+ */
+
+ return 0;
+
+}
+
+/**
+ * set_up_boot_rom_helper() - Sets up the Boot ROM Helper.
+ *
+ * @this: Per-device data.
+ */
+static int set_up_boot_rom_helper(struct gpmi_nfc_data *this)
+{
+ struct gpmi_nfc_platform_data *pdata = this->pdata;
+ struct device *dev = this->dev;
+ unsigned int i;
+ struct boot_rom_helper *rom;
+
+ /* Attempt to find a Boot ROM Helper that matches the given version. */
+
+ for (i = 0; i < ARRAY_SIZE(boot_rom_helpers); i++) {
+
+ rom = boot_rom_helpers[i];
+
+ if (rom->version == pdata->boot_rom_version) {
+ this->rom = rom;
+ break;
+ }
+
+ }
+
+ /* Check if we found a Boot ROM Helper. */
+
+ if (i >= ARRAY_SIZE(boot_rom_helpers)) {
+ dev_err(dev, "Unkown Boot ROM version %u\n",
+ pdata->boot_rom_version);
+ return -ENXIO;
+ }
+
+ pr_info("Boot ROM: Version %u, %s\n", rom->version, rom->description);
+
+ /*
+ * If control arrives here, all is well.
+ */
+
+ return 0;
+
+}
+
+/**
+ * manage_sysfs_files() - Creates/removes sysfs files for this device.
+ *
+ * @this: Per-device data.
+ */
+static void manage_sysfs_files(struct gpmi_nfc_data *this, int create)
+{
+ struct device *dev = this->dev;
+ int error;
+ unsigned int i;
+ struct device_attribute **attr;
+
+ 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);
+ }
+
+ }
+
+}
+
+/**
+ * gpmi_nfc_probe() - Probes for a device and, if possible, takes ownership.
+ *
+ * @pdev: A pointer to the platform device data structure.
+ */
+static int gpmi_nfc_probe(struct platform_device *pdev)
+{
+ int error = 0;
+ struct device *dev = &pdev->dev;
+ struct gpmi_nfc_platform_data *pdata = pdev->dev.platform_data;
+ struct gpmi_nfc_data *this = 0;
+
+ /* Validate the platform device data. */
+
+ error = validate_the_platform(pdev);
+
+ if (error)
+ goto exit_validate_platform;
+
+ /* Allocate memory for the per-device data. */
+
+ this = kzalloc(sizeof(*this), GFP_KERNEL);
+
+ if (!this) {
+ dev_err(dev, "Failed to allocate per-device memory\n");
+ error = -ENOMEM;
+ goto exit_allocate_this;
+ }
+
+ /* 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;
+
+ /* Set up the platform. */
+
+ if (pdata->platform_init)
+ error = pdata->platform_init(pdata->max_chip_count);
+
+ if (error)
+ goto exit_platform_init;
+
+ /* Set up the Boot ROM Helper. */
+
+ error = set_up_boot_rom_helper(this);
+
+ if (error)
+ goto exit_boot_rom_helper_init;
+
+ /* Initialize the MTD Interface Layer. */
+
+ error = gpmi_nfc_mil_init(this);
+
+ if (error)
+ goto exit_mil_init;
+
+ /* Create sysfs entries for this device. */
+
+ manage_sysfs_files(this, true);
+
+ /* Return success. */
+
+ return 0;
+
+ /* Error return paths begin here. */
+
+exit_mil_init:
+exit_boot_rom_helper_init:
+ if (pdata->platform_exit)
+ pdata->platform_exit(pdata->max_chip_count);
+exit_platform_init:
+ this->nfc->exit(this);
+exit_nfc_init:
+ release_resources(this);
+exit_acquire_resources:
+ platform_set_drvdata(pdev, NULL);
+ kfree(this);
+exit_allocate_this:
+exit_validate_platform:
+ return error;
+
+}
+
+/**
+ * gpmi_nfc_remove() - Dissociates this driver from the given device.
+ *
+ * @pdev: A pointer to the platform device data structure.
+ */
+static int __exit gpmi_nfc_remove(struct platform_device *pdev)
+{
+ struct gpmi_nfc_data *this = platform_get_drvdata(pdev);
+ struct gpmi_nfc_platform_data *pdata = this->pdata;
+
+ manage_sysfs_files(this, false);
+ gpmi_nfc_mil_exit(this);
+ if (pdata->platform_exit)
+ pdata->platform_exit(pdata->max_chip_count);
+ this->nfc->exit(this);
+ release_resources(this);
+ platform_set_drvdata(pdev, NULL);
+ kfree(this);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+/**
+ * gpmi_nfc_suspend() - Puts the NFC into a low power state.
+ *
+ * @pdev: A pointer to the platform device data structure.
+ * @state: The new power state.
+ */
+static int gpmi_nfc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ return 0;
+}
+
+/**
+ * gpmi_nfc_resume() - Brings the NFC back from a low power state.
+ *
+ * @pdev: A pointer to the platform device data structure.
+ */
+static int gpmi_nfc_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+
+#else
+
+#define suspend NULL
+#define resume NULL
+
+#endif /* CONFIG_PM */
+
+/*
+ * This structure represents this driver to the platform management system.
+ */
+static struct platform_driver gpmi_nfc_driver = {
+ .driver = {
+ .name = GPMI_NFC_DRIVER_NAME,
+ },
+ .probe = gpmi_nfc_probe,
+ .remove = __exit_p(gpmi_nfc_remove),
+ .suspend = gpmi_nfc_suspend,
+ .resume = gpmi_nfc_resume,
+};
+
+/**
+ * gpmi_nfc_init() - Initializes this module.
+ */
+static int __init gpmi_nfc_init(void)
+{
+
+ pr_info("i.MX GPMI NFC\n");
+
+ /* Register this driver with the platform management system. */
+
+ if (platform_driver_register(&gpmi_nfc_driver) != 0) {
+ pr_err("i.MX GPMI NFC driver registration failed\n");
+ return -ENODEV;
+ }
+
+ /* Return success. */
+
+ return 0;
+
+}
+
+/**
+ * gpmi_nfc_exit() - Deactivates this module.
+ */
+static void __exit gpmi_nfc_exit(void)
+{
+ platform_driver_unregister(&gpmi_nfc_driver);
+}
+
+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");