[PATCH] mtd: nand: stm_nand_bch: add new driver

Lee Jones
2014-05-28T09:20:47Z

This is a squashed version of the submission to avoid re-sending the
entire set over and over, essentially clogging up the MLs.

Cc: computersforpeace@gmail.com
Cc: Gupta, Pekon" <pekon@ti.com>
Cc: Ezequiel Garcia <ezequiel.garcia@free-electrons.com>
Cc: linux-mtd@lists.infradead.org
Signed-off-by: Lee Jones <lee.jones@linaro.org>
---
Documentation/devicetree/bindings/mtd/stm-nand.txt | 87 +
arch/arm/boot/dts/stih41x-b2020.dtsi | 40 +
drivers/mtd/nand/Kconfig | 14 +
drivers/mtd/nand/Makefile | 2 +
drivers/mtd/nand/stm_nand_bch.c | 2415 ++++++++++++++++++++
drivers/mtd/nand/stm_nand_dt.c | 116 +
drivers/mtd/nand/stm_nand_dt.h | 39 +
drivers/mtd/nand/stm_nand_regs.h | 302 +++
include/linux/mtd/stm_nand.h | 104 +
9 files changed, 3119 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mtd/stm-nand.txt
create mode 100644 drivers/mtd/nand/stm_nand_bch.c
create mode 100644 drivers/mtd/nand/stm_nand_dt.c
create mode 100644 drivers/mtd/nand/stm_nand_dt.h
create mode 100644 drivers/mtd/nand/stm_nand_regs.h
create mode 100644 include/linux/mtd/stm_nand.h

diff --git a/Documentation/devicetree/bindings/mtd/stm-nand.txt b/Documentation/devicetree/bindings/mtd/stm-nand.txt
new file mode 100644
index 0000000..d957f49
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/stm-nand.txt
@@ -0,0 +1,87 @@
+STM BCH NAND Support
+--------------------
+
+Required properties:
+
+- compatible : Should be "st,nand-bch"
+- reg : Should contain register's location and length
+- reg-names : "nand_mem" - NAND Controller register map
+ "nand_dma" - BCH Controller DMA configuration map
+- interrupts : Interrupt number
+- interrupt-names : "nand_irq" - NAND Controller IRQ
+- st,nand-banks : Subnode representing one or more "banks" of NAND
+ Flash, connected to an STM NAND Controller (see
+ description below).
+- nand-ecc-strength : Generic NAND property (See mtd/nand.txt)
+ Options are; 0, 18, 30 or 0xFF (AUTO)
+
+Properties describing Bank of NAND Flash ("st,nand-banks"):
+
+- st,nand-csn : Chip select associated with the Bank.
+
+- st,nand-timing-relax : [Optional] Number of IP clock cycles by which to
+ "relax" timing configuration. Required on some boards
+ to accommodate board-level limitations. Applies to
+ ONFI timing mode configuration.
+
+- nand-on-flash-bbt : Generic NAND property (See mtd/nand.txt)
+
+- partitions : [Optional] Subnode describing MTD partition map
+ (see mtd/partition.txt)
+
+Note, during initialisation, the NAND Controller timing registers are configured
+according to one of the following methods, in order of precedence:
+
+ 1. Configuration based on ONFI timing mode, as advertised by the
+ device during ONFI-probing (ONFI-compliant NAND only).
+
+ 2. Use reset/safe timing values
+
+Example:
+
+ nandbch: nand-bch {
+ compatible = "st,nand-bch";
+ reg = <0xfe901000 0x1000>, <0xfef00800 0x0800>;
+ reg-names = "nand_mem", "nand_dma";
+ interrupts = <0 139 0x0>;
+ interrupt-names = "nand_irq";
+ nand-ecc-strength = <30>;
+ st,nand-banks = <&nand_banks>;
+
+ status = "okay";
+ };
+
+ nand_banks: nand-banks {
+ bank0 {
+ /* NAND_BBT_USE_FLASH */
+ nand-on-flash-bbt;
+ st,nand-csn = <0>;
+ st,nand-timing-data = <&nand_timing0>;
+
+ partitions {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0{
+ label = "NAND Flash 1";
+ reg = <0x00000000 0x00800000>;
+ };
+ partition@800000{
+ label = "NAND Flash 2";
+ reg = <0x00800000 0x0F800000>;
+ };
+ };
+ };
+ };
+
+ nand_timing0: nand-timing {
+ sig-setup = <10>;
+ sig-hold = <10>;
+ CE-deassert = <0>;
+ WE-to-RBn = <100>;
+ wr-on = <10>;
+ wr-off = <30>;
+ rd-on = <10>;
+ rd-off = <30>;
+ chip-delay = <30>; /* delay in us */
+ };
diff --git a/arch/arm/boot/dts/stih41x-b2020.dtsi b/arch/arm/boot/dts/stih41x-b2020.dtsi
index bc5818d..7a6a6e8 100644
--- a/arch/arm/boot/dts/stih41x-b2020.dtsi
+++ b/arch/arm/boot/dts/stih41x-b2020.dtsi
@@ -52,5 +52,45 @@
pinctrl-0 = <&pinctrl_rgmii1>;
};

+ nandbch: nand-bch {
+ compatible = "st,nand-bch";
+ reg = <0xfe901000 0x1000>, <0xfef00800 0x0800>;
+ reg-names = "nand_mem", "nand_dma";
+ interrupts = <0 139 0x0>;
+ interrupt-names = "nand_irq";
+ st,nand-banks = <&nand_banks>;
+ nand-ecc-strength = <0xFF>;
+
+ status = "okay";
+ };
+
+ nand_banks: nand-banks {
+ /*
+ * Micron MT29F8G08ABABAWP:
+ * - Size = 8Gib(1GiB); Page = 4096+224; Block = 512KiB
+ * - ECC = 4-bit/540B min
+ * - ONFI 2.1 (timing parameters retrieved during probe)
+ */
+ bank0 {
+ nand-on-flash-bbt;
+ st,nand-csn = <0>;
+ st,nand-timing-relax = <0>;
+
+ partitions {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ partition@0 {
+ /* 8MB */
+ label = "NAND Flash 1";
+ reg = <0x00000000 0x00800000>;
+ };
+ partition@800000 {
+ /* 1GB - 8MB */
+ label = "NAND Flash 2";
+ reg = <0x00800000 0x1F000000>;
+ };
+ };
+ };
+ };
};
};
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 93ae6a6..119aed5 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -510,4 +510,18 @@ config MTD_NAND_XWAY
Enables support for NAND Flash chips on Lantiq XWAY SoCs. NAND is attached
to the External Bus Unit (EBU).

+config MTD_NAND_STM_BCH
+ tristate "STMicroelectronics: NANDi BCH Controller"
+ depends on ARM
+ depends on OF
+ help
+ Adds support for the STMicroelectronics NANDi BCH Controller.
+
+config MTD_NAND_STM_BCH_DT
+ tristate "STMicroelectronics: NANDi BCH Controller Device Tree support"
+ default MTD_NAND_STM_BCH if OF
+ help
+ Adds support for the STMicroelectronics NANDi BCH Controller's
+ Device Tree component.
+
endif # MTD_NAND
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 542b568..890f47f 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -46,6 +46,8 @@ obj-$(CONFIG_MTD_NAND_NUC900) += nuc900_nand.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_STM_BCH) += stm_nand_bch.o
+obj-$(CONFIG_MTD_NAND_STM_BCH_DT) += stm_nand_dt.o
obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand/
obj-$(CONFIG_MTD_NAND_XWAY) += xway_nand.o
obj-$(CONFIG_MTD_NAND_BCM47XXNFLASH) += bcm47xxnflash/
diff --git a/drivers/mtd/nand/stm_nand_bch.c b/drivers/mtd/nand/stm_nand_bch.c
new file mode 100644
index 0000000..5ad78ce
--- /dev/null
+++ b/drivers/mtd/nand/stm_nand_bch.c
@@ -0,0 +1,2415 @@
+/*
+ * drivers/mtd/nand/stm_nand_bch.c
+ *
+ * Support for STMicroelectronics NANDi BCH Controller
+ *
+ * Copyright (c) 2014 STMicroelectronics Limited
+ * Author: Angus Clark <Angus.Clark@st.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/completion.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/stm_nand.h>
+#include <linux/mtd/partitions.h>
+#include <generated/utsrelease.h>
+
+#include "stm_nand_regs.h"
+#include "stm_nand_dt.h"
+
+/* NANDi BCH Controller properties */
+#define NANDI_BCH_SECTOR_SIZE 1024
+#define NANDI_BCH_DMA_ALIGNMENT 64
+#define NANDI_BCH_MAX_BUF_LIST 8
+#define NANDI_BCH_BUF_LIST_SIZE (4 * NANDI_BCH_MAX_BUF_LIST)
+
+/* BCH ECC sizes */
+static int bch_ecc_sizes[] = {
+ [BCH_18BIT_ECC] = 32,
+ [BCH_30BIT_ECC] = 54,
+ [BCH_NO_ECC] = 0,
+};
+
+static int bch_ecc_strength[] = {
+ [BCH_18BIT_ECC] = 18,
+ [BCH_30BIT_ECC] = 30,
+ [BCH_NO_ECC] = 0,
+};
+
+/*
+ * Inband Bad Block Table (IBBT)
+ */
+#define NAND_IBBT_NBLOCKS 4
+#define NAND_IBBT_SIGLEN 4
+#define NAND_IBBT_PRIMARY 0
+#define NAND_IBBT_MIRROR 1
+#define NAND_IBBT_SCHEMA 0x10
+#define NAND_IBBT_BCH_SCHEMA 0x10
+
+static uint8_t ibbt_sigs[2][NAND_IBBT_SIGLEN] = {
+ {'B', 'b', 't', '0'},
+ {'1', 't', 'b', 'B'},
+};
+
+static char *bbt_strs[] = {
+ "primary",
+ "mirror",
+};
+
+/* IBBT header */
+struct nand_ibbt_header {
+ uint8_t signature[4]; /* "Bbt0" or "1tbB" signature */
+ uint8_t version; /* BBT version ("age") */
+ uint8_t reserved[3]; /* padding */
+ uint8_t schema[4]; /* "base" schema (x4) */
+} __packed;
+
+/* Extend IBBT header with some stm-nand-bch niceties */
+struct nand_ibbt_bch_header {
+ struct nand_ibbt_header base;
+ uint8_t schema[4]; /* "private" schema (x4) */
+ uint8_t ecc_size[4]; /* ECC bytes (0, 32, 54) (x4) */
+ char author[64]; /* Arbitrary string for S/W to use */
+} __packed;
+
+/* Bad Block Table (BBT) */
+struct nandi_bbt_info {
+ uint32_t bbt_size; /* Size of bad-block table */
+ uint32_t bbt_vers[2]; /* Version (Primary/Mirror) */
+ uint32_t bbt_block[2]; /* Block No. (Primary/Mirror) */
+ uint8_t *bbt; /* Table data */
+};
+
+/* Collection of MTD/NAND device information */
+struct nandi_info {
+ struct mtd_info mtd; /* MTD info */
+ struct nand_chip chip; /* NAND chip info */
+
+ struct nand_ecclayout ecclayout; /* MTD ECC layout */
+ struct nandi_bbt_info bbt_info; /* Bad Block Table */
+ int nr_parts; /* Number of MTD partitions */
+ struct mtd_partition *parts; /* MTD partitions */
+};
+
+/* NANDi Controller (Hamming/BCH) */
+struct nandi_controller {
+ void __iomem *base; /* Controller base*/
+ void __iomem *dma; /* DMA control base */
+
+ struct clk *bch_clk;
+ struct clk *emi_clk;
+ /* IRQ-triggered Completions: */
+ struct completion seq_completed; /* SEQ Over */
+ struct completion rbn_completed; /* RBn */
+
+ struct device *dev;
+
+ int bch_ecc_mode; /* ECC mode */
+ bool extra_addr; /* Extra address cycle */
+
+ uint32_t blocks_per_device;
+ uint32_t sectors_per_page;
+
+ uint8_t *buf; /* Some buffers to use */
+ uint8_t *page_buf;
+ uint8_t *oob_buf;
+ uint32_t *buf_list;
+
+ int cached_page; /* page number of page in */
+ /* 'page_buf' */
+
+ struct nandi_info info; /* NAND device info */
+};
+
+/* ONFI define 6 timing modes */
+#define ST_NAND_ONFI_TIMING_MODES 6
+
+/*
+ * ONFI NAND Timing Mode Specifications
+ *
+ * Note, 'tR' field (maximum page read time) is extracted from the ONFI
+ * parameter page during device probe.
+ */
+const struct nand_sdr_timings st_nand_onfi_timing_specs[] = {
+ /*
+ * ONFI Timing Mode '0' (supported on all ONFI compliant devices)
+ */
+ [0] = {
+ .tCLS_min = 50,
+ .tCS_min = 70,
+ .tALS_min = 50,
+ .tDS_min = 40,
+ .tWP_min = 50,
+ .tCLH_min = 20,
+ .tCH_min = 20,
+ .tALH_min = 20,
+ .tDH_min = 20,
+ .tWB_max = 200,
+ .tWH_min = 30,
+ .tWC_min = 100,
+ .tRP_min = 50,
+ .tREH_min = 30,
+ .tRC_min = 100,
+ .tREA_max = 40,
+ .tRHOH_min = 0,
+ .tCEA_max = 100,
+ .tCOH_min = 0,
+ .tCHZ_max = 100,
+ },
+
+ /*
+ * ONFI Timing Mode '1'
+ */
+ [1] = {
+ .tCLS_min = 25,
+ .tCS_min = 35,
+ .tALS_min = 25,
+ .tDS_min = 20,
+ .tWP_min = 25,
+ .tCLH_min = 10,
+ .tCH_min = 10,
+ .tALH_min = 10,
+ .tDH_min = 10,
+ .tWB_max = 100,
+ .tWH_min = 15,
+ .tWC_min = 45,
+ .tRP_min = 25,
+ .tREH_min = 15,
+ .tRC_min = 50,
+ .tREA_max = 30,
+ .tRHOH_min = 15,
+ .tCEA_max = 45,
+ .tCOH_min = 15,
+ .tCHZ_max = 50,
+ },
+
+ /*
+ * ONFI Timing Mode '2'
+ */
+ [2] = {
+ .tCLS_min = 15,
+ .tCS_min = 25,
+ .tALS_min = 15,
+ .tDS_min = 15,
+ .tWP_min = 17,
+ .tCLH_min = 10,
+ .tCH_min = 10,
+ .tALH_min = 10,
+ .tDH_min = 5,
+ .tWB_max = 100,
+ .tWH_min = 15,
+ .tWC_min = 35,
+ .tRP_min = 17,
+ .tREH_min = 16,
+ .tRC_min = 35,
+ .tREA_max = 25,
+ .tRHOH_min = 15,
+ .tCEA_max = 30,
+ .tCOH_min = 15,
+ .tCHZ_max = 50,
+ },
+
+ /*
+ * ONFI Timing Mode '3'
+ */
+ [3] = {
+ .tCLS_min = 10,
+ .tCS_min = 25,
+ .tALS_min = 10,
+ .tDS_min = 10,
+ .tWP_min = 15,
+ .tCLH_min = 5,
+ .tCH_min = 5,
+ .tALH_min = 5,
+ .tDH_min = 5,
+ .tWB_max = 100,
+ .tWH_min = 10,
+ .tWC_min = 30,
+ .tRP_min = 15,
+ .tREH_min = 10,
+ .tRC_min = 30,
+ .tREA_max = 20,
+ .tRHOH_min = 15,
+ .tCEA_max = 25,
+ .tCOH_min = 15,
+ .tCHZ_max = 50,
+ },
+
+ /*
+ * ONFI Timing Mode '4' (EDO only)
+ */
+ [4] = {
+ .tCLS_min = 10,
+ .tCS_min = 20,
+ .tALS_min = 10,
+ .tDS_min = 10,
+ .tWP_min = 12,
+ .tCLH_min = 5,
+ .tCH_min = 5,
+ .tALH_min = 5,
+ .tDH_min = 5,
+ .tWB_max = 100,
+ .tWH_min = 10,
+ .tWC_min = 25,
+ .tRP_min = 12,
+ .tREH_min = 10,
+ .tRC_min = 25,
+ .tREA_max = 20,
+ .tRHOH_min = 15,
+ .tCEA_max = 25,
+ .tCOH_min = 15,
+ .tCHZ_max = 30,
+ },
+
+ /*
+ * ONFI Timing Mode '5' (EDO only)
+ */
+ [5] = {
+ .tCLS_min = 10,
+ .tCS_min = 15,
+ .tALS_min = 10,
+ .tDS_min = 7,
+ .tWP_min = 10,
+ .tCLH_min = 5,
+ .tCH_min = 5,
+ .tALH_min = 5,
+ .tDH_min = 5,
+ .tWB_max = 100,
+ .tWH_min = 7,
+ .tWC_min = 20,
+ .tRP_min = 10,
+ .tREH_min = 7,
+ .tRC_min = 20,
+ .tREA_max = 16,
+ .tRHOH_min = 15,
+ .tCEA_max = 25,
+ .tCOH_min = 15,
+ .tCHZ_max = 30,
+ }
+};
+
+/* BCH 'program' structure */
+struct bch_prog {
+ u32 multi_cs_addr[3];
+ u32 multi_cs_config;
+ u8 seq[16];
+ u32 addr;
+ u32 extra;
+ u8 cmd[4];
+ u32 reserved1;
+ u32 gen_cfg;
+ u32 delay;
+ u32 reserved2;
+ u32 seq_cfg;
+};
+
+/* BCH template programs (modified on-the-fly) */
+static struct bch_prog bch_prog_read_page = {
+ .cmd = {
+ NAND_CMD_READ0,
+ NAND_CMD_READSTART,
+ },
+ .seq = {
+ BCH_ECC_SCORE(0),
+ BCH_CMD_ADDR,
+ BCH_CL_CMD_1,
+ BCH_DATA_2_SECTOR,
+ BCH_STOP,
+ },
+ .gen_cfg = (GEN_CFG_DATA_8_NOT_16 |
+ GEN_CFG_EXTRA_ADD_CYCLE |
+ GEN_CFG_LAST_SEQ_NODE),
+ .seq_cfg = SEQ_CFG_GO_STOP,
+};
+
+static struct bch_prog bch_prog_write_page = {
+ .cmd = {
+ NAND_CMD_SEQIN,
+ NAND_CMD_PAGEPROG,
+ NAND_CMD_STATUS,
+ },
+ .seq = {
+ BCH_CMD_ADDR,
+ BCH_DATA_4_SECTOR,
+ BCH_CL_CMD_1,
+ BCH_CL_CMD_2,
+ BCH_OP_ERR,
+ BCH_STOP,
+ },
+ .gen_cfg = (GEN_CFG_DATA_8_NOT_16 |
+ GEN_CFG_EXTRA_ADD_CYCLE |
+ GEN_CFG_LAST_SEQ_NODE),
+ .seq_cfg = (SEQ_CFG_GO_STOP |
+ SEQ_CFG_DATA_WRITE),
+};
+
+static struct bch_prog bch_prog_erase_block = {
+ .seq = {
+ BCH_CL_CMD_1,
+ BCH_AL_EX_0,
+ BCH_AL_EX_1,
+ BCH_AL_EX_2,
+ BCH_CL_CMD_2,
+ BCH_CL_CMD_3,
+ BCH_OP_ERR,
+ BCH_STOP,
+ },
+ .cmd = {
+ NAND_CMD_ERASE1,
+ NAND_CMD_ERASE1,
+ NAND_CMD_ERASE2,
+ NAND_CMD_STATUS,
+ },
+ .gen_cfg = (GEN_CFG_DATA_8_NOT_16 |
+ GEN_CFG_EXTRA_ADD_CYCLE |
+ GEN_CFG_LAST_SEQ_NODE),
+ .seq_cfg = (SEQ_CFG_GO_STOP |
+ SEQ_CFG_ERASE),
+};
+
+/* Configure BCH read/write/erase programs */
+static void bch_configure_progs(struct nandi_controller *nandi)
+{
+ uint8_t data_opa = ffs(nandi->sectors_per_page) - 1;
+ uint8_t data_instr = BCH_INSTR(BCH_OPC_DATA, data_opa);
+ uint32_t gen_cfg_ecc = nandi->bch_ecc_mode << GEN_CFG_ECC_SHIFT;
+
+ /* Set 'DATA' instruction */
+ bch_prog_read_page.seq[3] = data_instr;
+ bch_prog_write_page.seq[1] = data_instr;
+
+ /* Set ECC mode */
+ bch_prog_read_page.gen_cfg |= gen_cfg_ecc;
+ bch_prog_write_page.gen_cfg |= gen_cfg_ecc;
+ bch_prog_erase_block.gen_cfg |= gen_cfg_ecc;
+
+ /*
+ * Template sequences above are defined for devices that use 5 address
+ * cycles for page Read/Write operations (and 3 for Erase operations).
+ * Update sequences for devices that use 4 address cycles.
+ */
+ if (!nandi->extra_addr) {
+ /* Clear 'GEN_CFG_EXTRA_ADD_CYCLE' flag */
+ bch_prog_read_page.gen_cfg &= ~GEN_CFG_EXTRA_ADD_CYCLE;
+ bch_prog_write_page.gen_cfg &= ~GEN_CFG_EXTRA_ADD_CYCLE;
+ bch_prog_erase_block.gen_cfg &= ~GEN_CFG_EXTRA_ADD_CYCLE;
+
+ /* Configure Erase sequence for 2 address cycles */
+ /* (page address) */
+ bch_prog_erase_block.seq[0] = BCH_CL_CMD_1;
+ bch_prog_erase_block.seq[1] = BCH_AL_EX_0;
+ bch_prog_erase_block.seq[2] = BCH_AL_EX_1;
+ bch_prog_erase_block.seq[3] = BCH_CL_CMD_2;
+ bch_prog_erase_block.seq[4] = BCH_CL_CMD_3;
+ bch_prog_erase_block.seq[5] = BCH_OP_ERR;
+ bch_prog_erase_block.seq[6] = BCH_STOP;
+ }
+}
+
+/*
+ * NANDi Interrupts (shared by Hamming and BCH controllers)
+ */
+static irqreturn_t nandi_irq_handler(int irq, void *dev)
+{
+ struct nandi_controller *nandi = dev;
+ unsigned int status;
+
+ status = readl(nandi->base + NANDBCH_INT_STA);
+
+ if (status & NANDBCH_INT_SEQNODESOVER) {
+ /* BCH */
+ writel(NANDBCH_INT_CLR_SEQNODESOVER,
+ nandi->base + NANDBCH_INT_CLR);
+ complete(&nandi->seq_completed);
+ }
+ if (status & NAND_INT_RBN) {
+ /* Hamming */
+ writel(NAND_INT_CLR_RBN, nandi->base + NANDHAM_INT_CLR);
+ complete(&nandi->rbn_completed);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void nandi_enable_interrupts(struct nandi_controller *nandi,
+ uint32_t irqs)
+{
+ uint32_t val;
+
+ val = readl(nandi->base + NANDBCH_INT_EN);
+ val |= irqs;
+ writel(val, nandi->base + NANDBCH_INT_EN);
+}
+
+static void nandi_disable_interrupts(struct nandi_controller *nandi,
+ uint32_t irqs)
+{
+ uint32_t val;
+
+ val = readl(nandi->base + NANDBCH_INT_EN);
+ val &= ~irqs;
+ writel(val, nandi->base + NANDBCH_INT_EN);
+}
+
+/*
+ * BCH Operations
+ */
+static inline void bch_load_prog_cpu(struct nandi_controller *nandi,
+ struct bch_prog *prog)
+{
+ uint32_t *src = (uint32_t *)prog;
+ uint32_t *dst = (uint32_t *)(nandi->base + NANDBCH_ADDRESS_REG_1);
+ int i;
+
+ for (i = 0; i < 16; i++) {
+ /* Skip registers marked as "reserved" */
+ if (i != 11 && i != 14)
+ writel(*src, dst);
+ dst++;
+ src++;
+ }
+}
+
+static void bch_wait_seq(struct nandi_controller *nandi)
+{
+ int ret;
+
+ ret = wait_for_completion_timeout(&nandi->seq_completed, HZ/2);
+ if (!ret)
+ dev_err(nandi->dev, "BCH Seq timeout\n");
+}
+
+static uint8_t bch_erase_block(struct nandi_controller *nandi,
+ loff_t offs)
+{
+ struct nand_chip *chip = &nandi->info.chip;
+ struct bch_prog *prog = &bch_prog_erase_block;
+ uint8_t status;
+
+ dev_dbg(nandi->dev, "%s: offs = 0x%012llx\n", __func__, offs);
+
+ prog->extra = (uint32_t)(offs >> chip->page_shift);
+
+ emiss_nandi_select(STM_NANDI_BCH);
+
+ nandi_enable_interrupts(nandi, NANDBCH_INT_SEQNODESOVER);
+ reinit_completion(&nandi->seq_completed);
+
+ bch_load_prog_cpu(nandi, prog);
+
+ bch_wait_seq(nandi);
+
+ nandi_disable_interrupts(nandi, NANDBCH_INT_SEQNODESOVER);
+
+ status = (uint8_t)(readl(nandi->base +
+ NANDBCH_CHECK_STATUS_REG_A) & 0xff);
+
+ return status;
+}
+
+static int bch_erase(struct mtd_info *mtd, int page)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct nandi_controller *nandi = chip->priv;
+ uint32_t page_size = mtd->writesize;
+ loff_t offs = page * page_size;
+
+ return bch_erase_block(nandi, offs);
+}
+
+/*
+ * Detect an erased page, tolerating and correcting up to a specified number of
+ * bits at '0'. (For many devices, it is now deemed within spec for an erased
+ * page to include a number of bits at '0', either as a result of read-disturb
+ * behaviour or 'stuck-at-zero' failures.) Returns the number of corrected
+ * bits, or a '-1' if we have exceeded the maximum number of bits at '0' (likely
+ * to be a genuine uncorrectable ECC error). In the latter case, the data must
+ * be returned unmodified, in accordance with the MTD API.
+ */
+static int check_erased_page(uint8_t *data, uint32_t page_size, int max_zeros)
+{
+ uint8_t *b = data;
+ int zeros = 0;
+ int i;
+
+ for (i = 0; i < page_size; i++) {
+ zeros += hweight8(~*b++);
+ if (zeros > max_zeros)
+ return -1;
+ }
+
+ if (zeros)
+ memset(data, 0xff, page_size);
+
+ return zeros;
+}
+
+/* Returns the number of ECC errors, or '-1' for uncorrectable error */
+static int bch_read_page(struct nandi_controller *nandi,
+ loff_t offs,
+ uint8_t *buf)
+{
+ struct nand_chip *chip = &nandi->info.chip;
+ struct bch_prog *prog = &bch_prog_read_page;
+ uint32_t page_size = nandi->info.mtd.writesize;
+ unsigned long list_phys;
+ unsigned long buf_phys;
+ uint32_t ecc_err;
+ int ret = 0;
+
+ dev_dbg(nandi->dev, "%s: offs = 0x%012llx\n", __func__, offs);
+
+ BUG_ON(offs & (NANDI_BCH_DMA_ALIGNMENT - 1));
+
+ emiss_nandi_select(STM_NANDI_BCH);
+
+ nandi_enable_interrupts(nandi, NANDBCH_INT_SEQNODESOVER);
+ reinit_completion(&nandi->seq_completed);
+
+ /* Reset ECC stats */
+ writel(CFG_RESET_ECC_ALL | CFG_ENABLE_AFM,
+ nandi->base + NANDBCH_CONTROLLER_CFG);
+ writel(CFG_ENABLE_AFM, nandi->base + NANDBCH_CONTROLLER_CFG);
+
+ prog->addr = (uint32_t)((offs >> (chip->page_shift - 8)) & 0xffffff00);
+
+ buf_phys = dma_map_single(NULL, buf, page_size, DMA_FROM_DEVICE);
+
+ memset(nandi->buf_list, 0x00, NANDI_BCH_BUF_LIST_SIZE);
+ nandi->buf_list[0] = buf_phys | (nandi->sectors_per_page - 1);
+
+ list_phys = dma_map_single(NULL, nandi->buf_list,
+ NANDI_BCH_BUF_LIST_SIZE, DMA_TO_DEVICE);
+
+ writel(list_phys, nandi->base + NANDBCH_BUFFER_LIST_PTR);
+
+ bch_load_prog_cpu(nandi, prog);
+
+ bch_wait_seq(nandi);
+
+ nandi_disable_interrupts(nandi, NANDBCH_INT_SEQNODESOVER);
+
+ dma_unmap_single(NULL, list_phys, NANDI_BCH_BUF_LIST_SIZE,
+ DMA_TO_DEVICE);
+ dma_unmap_single(NULL, buf_phys, page_size, DMA_FROM_DEVICE);
+
+ /* Use the maximum per-sector ECC count! */
+ ecc_err = readl(nandi->base + NANDBCH_ECC_SCORE_REG_A) & 0xff;
+ if (ecc_err == 0xff) {
+ /*
+ * Downgrade uncorrectable ECC error for an erased page,
+ * tolerating 'bch_ecc_strength' bits at zero.
+ */
+ ret = check_erased_page(buf, page_size,
+ bch_ecc_strength[nandi->bch_ecc_mode]);
+ if (ret >= 0)
+ dev_dbg(nandi->dev,
+ "%s: erased page detected: \n"
+ " downgrading uncorrectable ECC error.\n",
+ __func__);
+ } else {
+ ret = (int)ecc_err;
+ }
+
+ return ret;
+}
+
+static int bch_read(struct mtd_info *mtd, struct nand_chip *chip,
+ uint8_t *buf, int oob_required, int page)
+{
+ struct nandi_controller *nandi = chip->priv;
+ uint32_t page_size = mtd->writesize;
+ loff_t offs = page * page_size;
+ bool bounce = false;
+ uint8_t *p;
+ int ret;
+
+ if (((unsigned int)buf & (NANDI_BCH_DMA_ALIGNMENT - 1)) ||
+ (!virt_addr_valid(buf))) /* vmalloc'd buffer! */
+ bounce = true;
+
+ p = bounce ? nandi->page_buf : buf;
+
+ ret = bch_read_page(nandi, offs, p);
+
+ if (bounce)
+ memcpy(buf, p, page_size);
+
+ return ret;
+}
+
+/* Returns the status of the NAND device following the write operation */
+static uint8_t bch_write_page(struct nandi_controller *nandi,
+ loff_t offs, const uint8_t *buf)
+{
+ struct nand_chip *chip = &nandi->info.chip;
+ struct bch_prog *prog = &bch_prog_write_page;
+ uint32_t page_size = nandi->info.mtd.writesize;
+ uint8_t *p = (uint8_t *)buf;
+ unsigned long list_phys;
+ unsigned long buf_phys;
+ uint8_t status;
+ bool bounce = false;
+
+ dev_dbg(nandi->dev, "%s: offs = 0x%012llx\n", __func__, offs);
+
+ BUG_ON(offs & (page_size - 1));
+
+ if (((unsigned long)buf & (NANDI_BCH_DMA_ALIGNMENT - 1)) ||
+ !virt_addr_valid(buf)) { /* vmalloc'd buffer! */
+ bounce = true;
+ }
+
+ if (bounce) {
+ memcpy(nandi->page_buf, buf, page_size);
+ p = nandi->page_buf;
+ nandi->cached_page = -1;
+ }
+
+ emiss_nandi_select(STM_NANDI_BCH);
+
+ nandi_enable_interrupts(nandi, NANDBCH_INT_SEQNODESOVER);
+ reinit_completion(&nandi->seq_completed);
+
+ prog->addr = (uint32_t)((offs >> (chip->page_shift - 8)) & 0xffffff00);
+
+ buf_phys = dma_map_single(NULL, p, page_size, DMA_TO_DEVICE);
+ memset(nandi->buf_list, 0x00, NANDI_BCH_BUF_LIST_SIZE);
+ nandi->buf_list[0] = buf_phys | (nandi->sectors_per_page - 1);
+
+ list_phys = dma_map_single(NULL, nandi->buf_list,
+ NANDI_BCH_BUF_LIST_SIZE, DMA_TO_DEVICE);
+
+ writel(list_phys, nandi->base + NANDBCH_BUFFER_LIST_PTR);
+
+ bch_load_prog_cpu(nandi, prog);
+
+ bch_wait_seq(nandi);
+
+ nandi_disable_interrupts(nandi, NANDBCH_INT_SEQNODESOVER);
+
+ dma_unmap_single(NULL, list_phys, NANDI_BCH_BUF_LIST_SIZE,
+ DMA_TO_DEVICE);
+ dma_unmap_single(NULL, buf_phys, page_size, DMA_FROM_DEVICE);
+
+ status = (uint8_t)(readl(nandi->base +
+ NANDBCH_CHECK_STATUS_REG_A) & 0xff);
+
+ return status;
+}
+
+static int bch_write(struct mtd_info *mtd, struct nand_chip *chip,
+ uint32_t offset, int data_len, const uint8_t *buf,
+ int oob_required, int page, int cached, int raw)
+{
+ struct nandi_controller *nandi = chip->priv;
+ uint32_t page_size = mtd->writesize;
+ loff_t offs = page * page_size;
+ int ret;
+
+ ret = bch_write_page(nandi, offs, buf);
+ if (ret & NAND_STATUS_FAIL)
+ return -EIO;
+
+ return 0;
+}
+
+/*
+ * Hamming-FLEX operations
+ */
+static int flex_wait_rbn(struct nandi_controller *nandi)
+{
+ int ret;
+
+ ret = wait_for_completion_timeout(&nandi->rbn_completed, HZ/2);
+ if (!ret)
+ dev_err(nandi->dev, "FLEX RBn timeout\n");
+
+ return ret;
+}
+
+static void flex_cmd(struct nandi_controller *nandi, uint8_t cmd)
+{
+ uint32_t val;
+
+ val = (FLEX_CMD_CSN | FLEX_CMD_BEATS_1 | cmd);
+ writel(val, nandi->base + NANDHAM_FLEX_CMD);
+}
+
+static void flex_addr(struct nandi_controller *nandi,
+ uint32_t addr, int cycles)
+{
+ addr &= 0x00ffffff;
+
+ BUG_ON(cycles < 1);
+ BUG_ON(cycles > 3);
+
+ addr |= (FLEX_ADDR_CSN | FLEX_ADDR_ADD8_VALID);
+ addr |= (cycles & 0x3) << 28;
+
+ writel(addr, nandi->base + NANDHAM_FLEX_ADD);
+}
+
+/*
+ * Partial implementation of MTD/NAND Interface, based on Hamming-FLEX
+ * operation.
+ *
+ * Allows us to make use of nand_base.c functions where possible
+ * (e.g. nand_scan_ident() and friends).
+ */
+static void flex_command_lp(struct mtd_info *mtd, unsigned int command,
+ int column, int page)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct nandi_controller *nandi = chip->priv;
+
+ emiss_nandi_select(STM_NANDI_HAMMING);
+
+ switch (command) {
+ case NAND_CMD_READOOB:
+ /* Emulate NAND_CMD_READOOB */
+ column += mtd->writesize;
+ command = NAND_CMD_READ0;
+ break;
+ case NAND_CMD_READ0:
+ case NAND_CMD_ERASE1:
+ case NAND_CMD_SEQIN:
+ case NAND_CMD_RESET:
+ case NAND_CMD_PARAM:
+ /* Prime RBn wait */
+ nandi_enable_interrupts(nandi, NAND_INT_RBN);
+ reinit_completion(&nandi->rbn_completed);
+ break;
+ case NAND_CMD_READID:
+ case NAND_CMD_STATUS:
+ case NAND_CMD_ERASE2:
+ break;
+ default:
+ /* Catch unexpected commands */
+ BUG();
+ }
+
+ /*
+ * Command Cycle
+ */
+ flex_cmd(nandi, command);
+
+ /*
+ * Address Cycles
+ */
+ if (column != -1)
+ flex_addr(nandi, column,
+ (command == NAND_CMD_READID) ? 1 : 2);
+
+ if (page != -1)
+ flex_addr(nandi, page, nandi->extra_addr ? 3 : 2);
+
+ /* Complete 'READ0' command */
+ if (command == NAND_CMD_READ0)
+ flex_cmd(nandi, NAND_CMD_READSTART);
+
+ /* Wait for RBn, if required */
+ /* (Note, other commands may handle wait elsewhere) */
+ if (command == NAND_CMD_RESET ||
+ command == NAND_CMD_READ0 ||
+ command == NAND_CMD_PARAM) {
+ flex_wait_rbn(nandi);
+ nandi_disable_interrupts(nandi, NAND_INT_RBN);
+ }
+}
+
+static uint8_t flex_read_byte(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct nandi_controller *nandi = chip->priv;
+
+ emiss_nandi_select(STM_NANDI_HAMMING);
+
+ return (uint8_t)(readl(nandi->base + NANDHAM_FLEX_DATA) & 0xff);
+}
+
+static int flex_wait_func(struct mtd_info *mtd, struct nand_chip *chip)
+{
+ struct nandi_controller *nandi = chip->priv;
+
+ emiss_nandi_select(STM_NANDI_HAMMING);
+
+ flex_wait_rbn(nandi);
+
+ flex_cmd(nandi, NAND_CMD_STATUS);
+
+ return (int)(readl(nandi->base + NANDHAM_FLEX_DATA) & 0xff);
+}
+
+/* Multi-CS devices not supported */
+static void flex_select_chip(struct mtd_info *mtd, int chipnr)
+{
+
+}
+
+static void flex_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct nandi_controller *nandi = chip->priv;
+ int aligned;
+
+ emiss_nandi_select(STM_NANDI_HAMMING);
+
+ /* Read bytes until buf is 4-byte aligned */
+ while (len && ((unsigned int)buf & 0x3)) {
+ *buf++ = (uint8_t)(readl(nandi->base + NANDHAM_FLEX_DATA)
+ & 0xff);
+ len--;
+ };
+
+ /* Use 'BEATS_4'/readsl */
+ if (len > 8) {
+ aligned = len & ~0x3;
+ writel(FLEX_DATA_CFG_BEATS_4 | FLEX_DATA_CFG_CSN,
+ nandi->base + NANDHAM_FLEX_DATAREAD_CONFIG);
+
+ readsl(nandi->base + NANDHAM_FLEX_DATA, buf, aligned >> 2);
+
+ buf += aligned;
+ len -= aligned;
+
+ writel(FLEX_DATA_CFG_BEATS_1 | FLEX_DATA_CFG_CSN,
+ nandi->base + NANDHAM_FLEX_DATAREAD_CONFIG);
+ }
+
+ /* Mop up remaining bytes */
+ while (len > 0) {
+ *buf++ = (uint8_t)(readl(nandi->base + NANDHAM_FLEX_DATA)
+ & 0xff);
+ len--;
+ }
+}
+
+static void flex_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct nandi_controller *nandi = chip->priv;
+ int aligned;
+
+ /* Write bytes until buf is 4-byte aligned */
+ while (len && ((unsigned int)buf & 0x3)) {
+ writel(*buf++, nandi->base + NANDHAM_FLEX_DATA);
+ len--;
+ };
+
+ /* USE 'BEATS_4/writesl */
+ if (len > 8) {
+ aligned = len & ~0x3;
+ writel(FLEX_DATA_CFG_BEATS_4 | FLEX_DATA_CFG_CSN,
+ nandi->base + NANDHAM_FLEX_DATAWRITE_CONFIG);
+ writesl(nandi->base + NANDHAM_FLEX_DATA, buf, aligned >> 2);
+ buf += aligned;
+ len -= aligned;
+ writel(FLEX_DATA_CFG_BEATS_1 | FLEX_DATA_CFG_CSN,
+ nandi->base + NANDHAM_FLEX_DATAWRITE_CONFIG);
+ }
+
+ /* Mop up remaining bytes */
+ while (len > 0) {
+ writel(*buf++, nandi->base + NANDHAM_FLEX_DATA);
+ len--;
+ }
+}
+
+static int flex_read_raw(struct nandi_controller *nandi,
+ uint32_t page_addr,
+ uint32_t col_addr,
+ uint8_t *buf, uint32_t len)
+{
+ dev_dbg(nandi->dev, "%s %u bytes at [0x%06x,0x%04x]\n",
+ __func__, len, page_addr, col_addr);
+
+ BUG_ON(len & 0x3);
+ BUG_ON((unsigned long)buf & 0x3);
+
+ emiss_nandi_select(STM_NANDI_HAMMING);
+ nandi_enable_interrupts(nandi, NAND_INT_RBN);
+ reinit_completion(&nandi->rbn_completed);
+
+ writel(FLEX_DATA_CFG_BEATS_4 | FLEX_DATA_CFG_CSN,
+ nandi->base + NANDHAM_FLEX_DATAREAD_CONFIG);
+
+ flex_cmd(nandi, NAND_CMD_READ0);
+ flex_addr(nandi, col_addr, 2);
+ flex_addr(nandi, page_addr, nandi->extra_addr ? 3 : 2);
+ flex_cmd(nandi, NAND_CMD_READSTART);
+
+ flex_wait_rbn(nandi);
+
+ readsl(nandi->base + NANDHAM_FLEX_DATA, buf, len / 4);
+
+ nandi_disable_interrupts(nandi, NAND_INT_RBN);
+
+ writel(FLEX_DATA_CFG_BEATS_1 | FLEX_DATA_CFG_CSN,
+ nandi->base + NANDHAM_FLEX_DATAREAD_CONFIG);
+
+ return 0;
+}
+
+/*
+ * Bad Block Tables/Bad Block Markers
+ */
+#define BBT_MARK_BAD_FACTORY 0x0
+#define BBT_MARK_BAD_WEAR 0x1
+#define BBT_MARK_GOOD 0x3
+
+static void bbt_set_block_mark(uint8_t *bbt, uint32_t block, uint8_t mark)
+{
+ unsigned int byte = block >> 2;
+ unsigned int shift = (block & 0x3) << 1;
+
+ bbt[byte] &= ~(0x3 << shift);
+ bbt[byte] |= ((mark & 0x3) << shift);
+}
+
+static uint8_t bbt_get_block_mark(uint8_t *bbt, uint32_t block)
+{
+ unsigned int byte = block >> 2;
+ unsigned int shift = (block & 0x3) << 1;
+
+ return (bbt[byte] >> shift) & 0x3;
+}
+
+static int bbt_is_block_bad(uint8_t *bbt, uint32_t block)
+{
+ return bbt_get_block_mark(bbt, block) == BBT_MARK_GOOD ? 0 : 1;
+}
+
+/* Scan page for BBM(s), according to specified BBT options */
+static int nandi_scan_bad_block_markers_page(struct nandi_controller *nandi,
+ uint32_t page)
+{
+ struct mtd_info *mtd = &nandi->info.mtd;
+ struct nand_chip *chip = mtd->priv;
+ uint8_t *oob_buf = nandi->oob_buf;
+ int i, e;
+
+ /* Read the OOB area */
+ flex_read_raw(nandi, page, mtd->writesize, oob_buf, mtd->oobsize);
+
+ if (oob_buf[chip->badblockpos] == 0xff)
+ return 0;
+
+ /* Tolerate 'alien' Hamming Boot Mode ECC */
+ e = 0;
+ for (i = 0; i < mtd->oobsize; i += 16)
+ e += hweight8(oob_buf[i + 3] ^ 'B');
+ if (e <= 1)
+ return 0;
+
+ /* Tolerate 'alien' Hamming AFM ECC */
+ e = 0;
+ for (i = 0; i < mtd->oobsize; i += 16) {
+ e += hweight8(oob_buf[i + 3] ^ 'A');
+ e += hweight8(oob_buf[i + 4] ^ 'F');
+ e += hweight8(oob_buf[i + 5] ^ 'M');
+ if (e <= 1)
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Scan block for BBM(s), according to specified BBT options */
+static int nandi_scan_bad_block_markers_block(struct nandi_controller *nandi,
+ uint32_t block)
+
+{
+ struct mtd_info *mtd = &nandi->info.mtd;
+ struct nand_chip *chip = mtd->priv;
+ uint32_t pages_per_block = mtd->erasesize >> chip->page_shift;
+ uint32_t page = block << (chip->phys_erase_shift - chip->page_shift);
+
+ if (nandi_scan_bad_block_markers_page(nandi, page))
+ return 1;
+
+ if ((chip->bbt_options & NAND_BBT_SCAN2NDPAGE) &&
+ nandi_scan_bad_block_markers_page(nandi, page + 1))
+ return 1;
+
+ if ((chip->bbt_options & NAND_BBT_SCANLASTPAGE) &&
+ nandi_scan_bad_block_markers_page(nandi,
+ page + pages_per_block - 1))
+ return 1;
+
+ return 0;
+}
+
+/* Scan for BBMs and build memory-resident BBT */
+static int nandi_scan_build_bbt(struct nandi_controller *nandi,
+ struct nandi_bbt_info *bbt_info)
+{
+ struct mtd_info *mtd = &nandi->info.mtd;
+ struct nand_chip *chip = mtd->priv;
+ uint32_t page_size = mtd->writesize;
+ uint8_t *bbt = bbt_info->bbt;
+ uint32_t block;
+
+ dev_dbg(nandi->dev,
+ "scan device for bad-block markers [bbt options = 0x%02x]\n",
+ chip->bbt_options);
+
+ memset(bbt, 0xff, page_size);
+ bbt_info->bbt_vers[0] = 0;
+ bbt_info->bbt_vers[1] = 0;
+ bbt_info->bbt_block[0] = nandi->blocks_per_device - 1;
+ bbt_info->bbt_block[1] = nandi->blocks_per_device - 2;
+
+ for (block = 0; block < nandi->blocks_per_device; block++)
+ if (nandi_scan_bad_block_markers_block(nandi, block))
+ bbt_set_block_mark(bbt, block, BBT_MARK_BAD_FACTORY);
+
+ return 0;
+}
+
+/* Populate IBBT BCH Header */
+static void bch_fill_ibbt_header(struct nandi_controller *nandi,
+ struct nand_ibbt_bch_header *ibbt_header,
+ int bak, uint8_t vers)
+{
+ const char author[] = "STLinux " UTS_RELEASE " (stm-nand-bch)";
+
+ memcpy(ibbt_header->base.signature, ibbt_sigs[bak], NAND_IBBT_SIGLEN);
+ ibbt_header->base.version = vers;
+ memset(ibbt_header->base.schema, NAND_IBBT_SCHEMA, 4);
+
+ memset(ibbt_header->schema, NAND_IBBT_SCHEMA, 4);
+ memset(ibbt_header->ecc_size, bch_ecc_sizes[nandi->bch_ecc_mode], 4);
+ memcpy(ibbt_header->author, author, sizeof(author));
+}
+
+/* Write IBBT to Flash */
+static int bch_write_bbt_data(struct nandi_controller *nandi,
+ struct nandi_bbt_info *bbt_info,
+ uint32_t block, int bak, uint8_t vers)
+{
+ struct nand_chip *chip = &nandi->info.chip;
+ uint32_t page_size = nandi->info.mtd.writesize;
+ uint32_t block_size = nandi->info.mtd.erasesize;
+ struct nand_ibbt_bch_header *ibbt_header =
+ (struct nand_ibbt_bch_header *)nandi->page_buf;
+ loff_t offs;
+
+ nandi->cached_page = -1;
+
+ /* Write BBT contents to first page of block */
+ offs = (loff_t)block << chip->phys_erase_shift;
+ if (bch_write_page(nandi, offs, bbt_info->bbt) & NAND_STATUS_FAIL)
+ return 1;
+
+ /* Update IBBT header and write to last page of block */
+ memset(ibbt_header, 0xff, nandi->info.mtd.writesize);
+ bch_fill_ibbt_header(nandi, ibbt_header, bak, vers);
+ offs += block_size - page_size;
+ if (bch_write_page(nandi, offs, (uint8_t *)ibbt_header) &
+ NAND_STATUS_FAIL)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Update Flash-resident BBT:
+ * erase/search suitable block, and write table data to Flash
+ */
+static int bch_update_bbt(struct nandi_controller *nandi,
+ struct nandi_bbt_info *bbt_info,
+ int bak, uint8_t vers)
+{
+ struct nand_chip *chip = &nandi->info.chip;
+ loff_t offs;
+ uint32_t block;
+ uint32_t block_lower;
+ uint32_t block_other;
+
+ block_other = bbt_info->bbt_block[(bak+1)%2];
+ block_lower = nandi->blocks_per_device - NAND_IBBT_NBLOCKS;
+
+ for (block = bbt_info->bbt_block[bak]; block >= block_lower; block--) {
+ offs = (loff_t)block << chip->phys_erase_shift;
+
+ /* Skip if block used by other table */
+ if (block == block_other)
+ continue;
+
+ /* Skip if block is marked bad */
+ if (bbt_is_block_bad(bbt_info->bbt, block))
+ continue;
+
+ /* Erase block, mark bad and skip on failure */
+ if (bch_erase_block(nandi, offs) & NAND_STATUS_FAIL) {
+ dev_info(nandi->dev,
+ "failed to erase block [%u:0x%012llx] while updating BBT\n",
+ block, offs);
+ vers++;
+ bbt_set_block_mark(bbt_info->bbt, block,
+ BBT_MARK_BAD_WEAR);
+ continue;
+ }
+
+ /* Write BBT, mark bad and skip on failure */
+ if (bch_write_bbt_data(nandi, bbt_info, block, bak, vers)) {
+ dev_info(nandi->dev,
+ "failed to write BBT to block [%u:0x%012llx]\n",
+ block, offs);
+ vers++;
+ bbt_set_block_mark(bbt_info->bbt, block,
+ BBT_MARK_BAD_WEAR);
+ continue;
+ }
+
+ /* Success */
+ bbt_info->bbt_block[bak] = block;
+ bbt_info->bbt_vers[bak] = vers;
+ break;
+ }
+
+ /* No space in BBT area */
+ if (block < block_lower) {
+ dev_err(nandi->dev, "no space left in BBT area\n");
+ dev_err(nandi->dev, "failed to update %s BBT\n", bbt_strs[bak]);
+ return -ENOSPC;
+ }
+
+ dev_info(nandi->dev, "wrote BBT [%s:%u] at 0x%012llx [%u]\n",
+ bbt_strs[bak], vers, offs, block);
+
+ return 0;
+}
+
+#define NAND_IBBT_UPDATE_PRIMARY 0x1
+#define NAND_IBBT_UPDATE_MIRROR 0x2
+#define NAND_IBBT_UPDATE_BOTH (NAND_IBBT_UPDATE_PRIMARY | \
+ NAND_IBBT_UPDATE_MIRROR)
+static char *bbt_update_strs[] = {
+ "",
+ "primary",
+ "mirror",
+ "both",
+};
+
+/*
+ * Update Flash-resident BBT(s):
+ * incrementing 'vers' number if required, and ensuring Primary
+ * and Mirror are kept in sync
+ */
+static int bch_update_bbts(struct nandi_controller *nandi,
+ struct nandi_bbt_info *bbt_info,
+ unsigned int update, uint8_t vers)
+{
+ int err;
+
+ dev_info(nandi->dev, "updating %s BBT(s)\n", bbt_update_strs[update]);
+
+ do {
+ /* Update Primary if specified */
+ if (update & NAND_IBBT_UPDATE_PRIMARY) {
+ err = bch_update_bbt(nandi, bbt_info, NAND_IBBT_PRIMARY,
+ vers);
+ /* Bail out on error (e.g. no space left in BBT area) */
+ if (err)
+ return err;
+
+ /*
+ * If update resulted in a new BBT version
+ * (e.g. Erase/Write fail on BBT block) update version
+ * here, and force update of other table.
+ */
+ if (bbt_info->bbt_vers[NAND_IBBT_PRIMARY] != vers) {
+ vers = bbt_info->bbt_vers[NAND_IBBT_PRIMARY];
+ update = NAND_IBBT_UPDATE_MIRROR;
+ }
+ }
+
+ /* Update Mirror if specified */
+ if (update & NAND_IBBT_UPDATE_MIRROR) {
+ err = bch_update_bbt(nandi, bbt_info, NAND_IBBT_MIRROR,
+ vers);
+ /* Bail out on error (e.g. no space left in BBT area) */
+ if (err)
+ return err;
+
+ /*
+ * If update resulted in a new BBT version
+ * (e.g. Erase/Write fail on BBT block) update version
+ * here, and force update of other table.
+ */
+ if (bbt_info->bbt_vers[NAND_IBBT_MIRROR] != vers) {
+ vers = bbt_info->bbt_vers[NAND_IBBT_MIRROR];
+ update = NAND_IBBT_UPDATE_PRIMARY;
+ }
+ }
+
+ /* Continue, until Primary and Mirror versions are in sync */
+ } while (bbt_info->bbt_vers[NAND_IBBT_PRIMARY] !=
+ bbt_info->bbt_vers[NAND_IBBT_MIRROR]);
+
+ return 0;
+}
+
+/* Scan block for IBBT signature */
+static int bch_find_ibbt_sig(struct nandi_controller *nandi,
+ uint32_t block, int *bak, uint8_t *vers,
+ char *author)
+{
+ struct nand_chip *chip = &nandi->info.chip;
+ struct mtd_info *mtd = &nandi->info.mtd;
+ struct nand_ibbt_bch_header *ibbt_header;
+ loff_t offs;
+ uint8_t *buf = nandi->page_buf;
+ int match_sig;
+ unsigned int b;
+ unsigned int i;
+
+ nandi->cached_page = -1;
+
+ /* Load last page of block */
+ offs = (loff_t)block << chip->phys_erase_shift;
+ offs += mtd->erasesize - mtd->writesize;
+ if (bch_read_page(nandi, offs, buf) < 0) {
+ dev_info(nandi->dev,
+ "Uncorrectable ECC error while scanning BBT signature at block %u [0x%012llx]\n",
+ block, offs);
+ return 0;
+ }
+ ibbt_header = (struct nand_ibbt_bch_header *)buf;
+
+ /* Test IBBT signature */
+ match_sig = 0;
+ for (b = 0; b < 2 && !match_sig; b++) {
+ match_sig = 1;
+ for (i = 0; i < NAND_IBBT_SIGLEN; i++) {
+ if (ibbt_header->base.signature[i] != ibbt_sigs[b][i]) {
+ match_sig = 0;
+ break;
+ }
+ }
+
+ }
+
+ if (!match_sig)
+ return 0; /* Failed to match IBBT signature */
+
+ /* Test IBBT schema */
+ for (i = 0; i < 4; i++)
+ if (ibbt_header->base.schema[i] != NAND_IBBT_SCHEMA)
+ return 0;
+
+ /* Test IBBT BCH schema */
+ for (i = 0; i < 4; i++)
+ if (ibbt_header->schema[i] != NAND_IBBT_BCH_SCHEMA)
+ return 0;
+
+ /* We have a match */
+ *vers = ibbt_header->base.version;
+ *bak = b - 1;
+ strncpy(author, ibbt_header->author, 64);
+
+ return 1;
+}
+
+/* Search for and load Flash-resident BBT, updating Primary/Mirror if req'd */
+static int bch_load_bbt(struct nandi_controller *nandi,
+ struct nandi_bbt_info *bbt_info)
+{
+ struct nand_chip *chip = &nandi->info.chip;
+ unsigned int update = 0;
+ uint32_t block;
+ loff_t offs;
+ uint8_t vers;
+ char author[64];
+ int bak;
+
+ dev_dbg(nandi->dev, "looking for Flash-resident BBTs\n");
+
+ bbt_info->bbt_block[0] = 0;
+ bbt_info->bbt_block[1] = 0;
+ bbt_info->bbt_vers[0] = 0;
+ bbt_info->bbt_vers[1] = 0;
+
+ /* Look for IBBT signatures */
+ for (block = nandi->blocks_per_device - NAND_IBBT_NBLOCKS;
+ block < nandi->blocks_per_device;
+ block++) {
+ offs = (loff_t)block << chip->phys_erase_shift;
+
+ if (bch_find_ibbt_sig(nandi, block, &bak, &vers, author)) {
+ dev_dbg(nandi->dev,
+ "found BBT [%s:%u] at 0x%012llx [%u] (%s)\n",
+ bbt_strs[bak], vers, offs, block,
+ author);
+
+ if (bbt_info->bbt_block[bak] == 0 ||
+ ((int8_t)(bbt_info->bbt_vers[bak] - vers)) < 0) {
+ bbt_info->bbt_block[bak] = block;
+ bbt_info->bbt_vers[bak] = vers;
+ }
+ }
+ }
+
+ /* What have we found? */
+ if (bbt_info->bbt_block[0] == 0 && bbt_info->bbt_block[1] == 0) {
+ /* no primary, no mirror: return error */
+ return 1;
+ } else if (bbt_info->bbt_block[0] == 0) {
+ /* no primary: use mirror, update primary */
+ bak = 1;
+ update = NAND_IBBT_UPDATE_PRIMARY;
+ bbt_info->bbt_block[0] = nandi->blocks_per_device - 1;
+ } else if (bbt_info->bbt_block[1] == 0) {
+ /* no mirror: use primary, update mirror */
+ bak = 0;
+ update = NAND_IBBT_UPDATE_MIRROR;
+ bbt_info->bbt_block[1] = nandi->blocks_per_device - 1;
+ } else if (bbt_info->bbt_vers[0] == bbt_info->bbt_vers[1]) {
+ /* primary == mirror: use primary, no update required */
+ bak = 0;
+ } else if ((int8_t)(bbt_info->bbt_vers[1] -
+ bbt_info->bbt_vers[0]) < 0) {
+ /* primary > mirror: use primary, update mirror */
+ bak = 0;
+ update = NAND_IBBT_UPDATE_MIRROR;
+ } else {
+ /* mirror > primary: use mirror, update primary */
+ bak = 1;
+ update = NAND_IBBT_UPDATE_PRIMARY;
+ }
+
+ vers = bbt_info->bbt_vers[bak];
+ block = bbt_info->bbt_block[bak];
+ offs = block << chip->phys_erase_shift;
+ dev_info(nandi->dev, "using BBT [%s:%u] at 0x%012llx [%u]\n",
+ bbt_strs[bak], vers, offs, block);
+
+ /* Read BBT data */
+ if (bch_read_page(nandi, offs, bbt_info->bbt) < 0) {
+ dev_err(nandi->dev,
+ "error while reading BBT %s:%u] at 0x%012llx [%u]\n",
+ bbt_strs[bak], vers, offs, block);
+ return 1;
+ }
+
+ /* Update other BBT if required */
+ if (update)
+ bch_update_bbts(nandi, bbt_info, update, vers);
+
+ return 0;
+}
+
+static int bch_scan_bbt(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct nandi_controller *nandi = chip->priv;
+ struct nandi_bbt_info *bbt_info = &nandi->info.bbt_info;
+ int err;
+ /* Load Flash-resident BBT */
+ err = bch_load_bbt(nandi, bbt_info);
+ if (err) {
+ dev_warn(nandi->dev,
+ "failed to find BBTs:"
+ " scanning device for bad-block markers\n");
+
+ /* Scan, build, and write BBT */
+ nandi_scan_build_bbt(nandi, bbt_info);
+ err = bch_update_bbts(nandi, bbt_info, NAND_IBBT_UPDATE_BOTH,
+ bbt_info->bbt_vers[0] + 1);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int bch_mtd_read_oob(struct mtd_info *mtd,
+ struct nand_chip *chip, int page)
+{
+ BUG();
+ return 0;
+}
+
+static int bch_mtd_write_oob(struct mtd_info *mtd,
+ struct nand_chip *chip, int page)
+{
+ BUG();
+ return 0;
+}
+
+static int bch_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
+ uint8_t *buf, int oob_required, int page)
+{
+ BUG();
+ return 0;
+}
+
+static int bch_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
+ const uint8_t *buf, int oob_required)
+{
+ BUG();
+ return 0;
+}
+
+static void bch_hwctl(struct mtd_info *mtd, int mode)
+{
+ BUG();
+}
+
+static int bch_calculate(struct mtd_info *mtd, const uint8_t *dat,
+ uint8_t *ecc_code)
+{
+ BUG();
+ return 0;
+}
+
+static int bch_correct(struct mtd_info *mtd, uint8_t *dat, uint8_t *read_ecc,
+ uint8_t *calc_ecc)
+{
+ BUG();
+ return 0;
+}
+
+static int bch_block_isbad(struct mtd_info *mtd, loff_t offs, int getchip)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct nandi_controller *nandi = chip->priv;
+
+ uint32_t block;
+
+ /* Check for invalid offset */
+ if (offs > mtd->size)
+ return -EINVAL;
+
+ block = offs >> chip->phys_erase_shift;
+
+ /* Protect blocks reserved for BBTs */
+ if (block >= (nandi->blocks_per_device - NAND_IBBT_NBLOCKS))
+ return 1;
+
+ return bbt_is_block_bad(nandi->info.bbt_info.bbt, block);
+}
+
+static int bch_block_markbad(struct mtd_info *mtd, loff_t offs)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct nandi_controller *nandi = chip->priv;
+
+ uint32_t block;
+ int ret;
+
+ /* Is block already considered bad? (will also catch invalid offsets) */
+ ret = mtd_block_isbad(mtd, offs);
+ if (ret < 0)
+ return ret;
+ if (ret == 1)
+ return 0;
+
+ /* Mark bad */
+ block = offs >> chip->phys_erase_shift;
+ bbt_set_block_mark(nandi->info.bbt_info.bbt, block, BBT_MARK_BAD_WEAR);
+
+ /* Update BBTs, incrementing bbt_vers */
+ ret = bch_update_bbts(nandi, &nandi->info.bbt_info,
+ NAND_IBBT_UPDATE_BOTH,
+ nandi->info.bbt_info.bbt_vers[0] + 1);
+
+ return ret;
+}
+
+static void nandi_dump_bad_blocks(struct nandi_controller *nandi)
+{
+ struct nand_chip *chip = &nandi->info.chip;
+ int bad_count = 0;
+ uint32_t block;
+ uint8_t *bbt = nandi->info.bbt_info.bbt;
+ uint8_t mark;
+
+ pr_info("BBT:\n");
+ for (block = 0; block < nandi->blocks_per_device; block++) {
+ mark = bbt_get_block_mark(bbt, block);
+ if (mark != BBT_MARK_GOOD) {
+ pr_info("\t\tBlock 0x%08x [%05u] marked bad [%s]\n",
+ block << chip->phys_erase_shift, block,
+ (mark == BBT_MARK_BAD_FACTORY) ?
+ "Factory" : "Wear");
+ bad_count++;
+ }
+ }
+ if (bad_count == 0)
+ pr_info("\t\tNo bad blocks listed in BBT\n");
+}
+
+/*
+ * Initialisation
+ */
+static int bch_check_compatibility(struct nandi_controller *nandi,
+ struct mtd_info *mtd,
+ struct nand_chip *chip)
+{
+ if (chip->bits_per_cell > 1)
+ dev_warn(nandi->dev, "MLC NAND not fully supported\n");
+
+ if (chip->options & NAND_BUSWIDTH_16) {
+ dev_err(nandi->dev, "x16 NAND not supported\n");
+ return false;
+ }
+
+ if (nandi->blocks_per_device / 4 > mtd->writesize) {
+ /* Need to implement multi-page BBT support... */
+ dev_err(nandi->dev, "BBT too big to fit in single page\n");
+ return false;
+ }
+
+ if (bch_ecc_sizes[nandi->bch_ecc_mode] * nandi->sectors_per_page >
+ mtd->oobsize) {
+ dev_err(nandi->dev, "insufficient OOB for selected ECC\n");
+ return false;
+ }
+
+ return true;
+}
+
+/* Select strongest ECC scheme compatible with OOB size */
+static int bch_set_ecc_auto(struct nandi_controller *nandi,
+ struct mtd_info *mtd)
+{
+ int oob_bytes_per_sector = mtd->oobsize / nandi->sectors_per_page;
+ int try_ecc_modes[] = { BCH_30BIT_ECC, BCH_18BIT_ECC, -1 };
+ int m, ecc_mode;
+
+ for (m = 0; try_ecc_modes[m] >= 0; m++) {
+ ecc_mode = try_ecc_modes[m];
+ if (oob_bytes_per_sector >= bch_ecc_sizes[ecc_mode]) {
+ nandi->bch_ecc_mode = ecc_mode;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static void nandi_set_mtd_defaults(struct nandi_controller *nandi,
+ struct mtd_info *mtd, struct nand_chip *chip)
+{
+ struct nandi_info *info = &nandi->info;
+ int i;
+
+ /* ecclayout */
+ info->ecclayout.eccbytes = mtd->oobsize;
+ for (i = 0; i < 64; i++)
+ info->ecclayout.eccpos[i] = i;
+ info->ecclayout.oobfree[0].offset = 0;
+ info->ecclayout.oobfree[0].length = 0;
+ chip->ecc.mode = NAND_ECC_HW;
+
+ /* nand_chip */
+ chip->controller = &chip->hwcontrol;
+ spin_lock_init(&chip->controller->lock);
+ init_waitqueue_head(&chip->controller->wq);
+ chip->state = FL_READY;
+ chip->priv = nandi;
+ chip->ecc.layout = &info->ecclayout;
+ chip->options |= NAND_NO_SUBPAGE_WRITE;
+
+ chip->cmdfunc = flex_command_lp;
+ chip->read_byte = flex_read_byte;
+ chip->select_chip = flex_select_chip;
+ chip->waitfunc = flex_wait_func;
+ chip->read_buf = flex_read_buf;
+ chip->write_buf = flex_write_buf;
+
+ chip->bbt_options |= NAND_BBT_USE_FLASH;
+
+ /* mtd_info */
+ mtd->owner = THIS_MODULE;
+ mtd->type = MTD_NANDFLASH;
+ mtd->flags = MTD_CAP_NANDFLASH;
+ mtd->ecclayout = &info->ecclayout;
+ mtd->subpage_sft = 0;
+
+ chip->ecc.hwctl = bch_hwctl;
+ chip->ecc.calculate = bch_calculate;
+ chip->ecc.correct = bch_correct;
+
+ chip->ecc.read_oob = bch_mtd_read_oob;
+ chip->ecc.write_oob = bch_mtd_write_oob;
+
+ chip->ecc.read_page = bch_read;
+ chip->ecc.read_page_raw = bch_read_page_raw;
+ chip->ecc.write_page_raw = bch_write_page_raw;
+ chip->write_page = bch_write;
+ chip->erase = bch_erase;
+
+ chip->scan_bbt = bch_scan_bbt;
+ chip->block_bad = bch_block_isbad;
+ chip->block_markbad = bch_block_markbad;
+}
+
+/*
+ * Timing and Clocks
+ */
+
+static void nandi_clk_enable(struct nandi_controller *nandi)
+{
+ if (nandi->emi_clk)
+ clk_prepare_enable(nandi->emi_clk);
+ if (nandi->bch_clk)
+ clk_prepare_enable(nandi->bch_clk);
+}
+
+static void nandi_clk_disable(struct nandi_controller *nandi)
+{
+ if (nandi->emi_clk)
+ clk_disable_unprepare(nandi->emi_clk);
+ if (nandi->bch_clk)
+ clk_disable_unprepare(nandi->bch_clk);
+}
+
+static struct clk *nandi_clk_setup(struct nandi_controller *nandi,
+ char *clkname)
+{
+ struct clk *clk;
+ int ret;
+
+ clk = clk_get(nandi->dev, clkname);
+ if (IS_ERR_OR_NULL(clk)) {
+ dev_warn(nandi->dev, "Failed to get %s clock\n", clkname);
+ return NULL;
+ }
+
+ ret = clk_prepare_enable(clk);
+ if (ret) {
+ dev_warn(nandi->dev, "Failed to enable %s clock\n", clkname);
+ clk_put(clk);
+ return NULL;
+ }
+
+ return clk;
+}
+
+/* Derive Hamming-FLEX timing register values from 'nand_sdr_timings' data */
+static void flex_calc_timing_registers(const struct nand_sdr_timings *spec,
+ int tCLK, int relax,
+ uint32_t *ctl_timing,
+ uint32_t *wen_timing,
+ uint32_t *ren_timing)
+{
+ int tMAX_HOLD;
+ int n_ctl_setup;
+ int n_ctl_hold;
+ int n_ctl_wb;
+
+ int tMAX_WEN_OFF;
+ int n_wen_on;
+ int n_wen_off;
+
+ int tMAX_REN_OFF;
+ int n_ren_on;
+ int n_ren_off;
+
+ /*
+ * CTL_TIMING
+ */
+
+ /* - SETUP */
+ n_ctl_setup = (spec->tCLS_min - spec->tWP_min + tCLK - 1)/tCLK;
+ if (n_ctl_setup < 1)
+ n_ctl_setup = 1;
+ n_ctl_setup += relax;
+
+ /* - HOLD */
+ tMAX_HOLD = spec->tCLH_min;
+ if (spec->tCH_min > tMAX_HOLD)
+ tMAX_HOLD = spec->tCH_min;
+ if (spec->tALH_min > tMAX_HOLD)
+ tMAX_HOLD = spec->tALH_min;
+ if (spec->tDH_min > tMAX_HOLD)
+ tMAX_HOLD = spec->tDH_min;
+ n_ctl_hold = (tMAX_HOLD + tCLK - 1)/tCLK + relax;
+
+ /* - CE_deassert_hold = 0 */
+
+ /* - WE_high_to_RBn_low */
+ n_ctl_wb = (spec->tWB_max + tCLK - 1)/tCLK;
+
+ *ctl_timing = ((n_ctl_setup & 0xff) |
+ (n_ctl_hold & 0xff) << 8 |
+ (n_ctl_wb & 0xff) << 24);
+
+ /*
+ * WEN_TIMING
+ */
+
+ /* - ON */
+ n_wen_on = (spec->tWH_min + tCLK - 1)/tCLK + relax;
+
+ /* - OFF */
+ tMAX_WEN_OFF = spec->tWC_min - spec->tWH_min;
+ if (spec->tWP_min > tMAX_WEN_OFF)
+ tMAX_WEN_OFF = spec->tWP_min;
+ n_wen_off = (tMAX_WEN_OFF + tCLK - 1)/tCLK + relax;
+
+ *wen_timing = ((n_wen_on & 0xff) |
+ (n_wen_off & 0xff) << 8);
+
+ /*
+ * REN_TIMING
+ */
+
+ /* - ON */
+ n_ren_on = (spec->tREH_min + tCLK - 1)/tCLK + relax;
+
+ /* - OFF */
+ tMAX_REN_OFF = spec->tRC_min - spec->tREH_min;
+ if (spec->tRP_min > tMAX_REN_OFF)
+ tMAX_REN_OFF = spec->tRP_min;
+ if (spec->tREA_max > tMAX_REN_OFF)
+ tMAX_REN_OFF = spec->tREA_max;
+ n_ren_off = (tMAX_REN_OFF + tCLK - 1)/tCLK + 1 + relax;
+
+ *ren_timing = ((n_ren_on & 0xff) |
+ (n_ren_off & 0xff) << 8);
+}
+
+/* Derive BCH timing register values from 'nand_sdr_timings' data */
+static void bch_calc_timing_registers(const struct nand_sdr_timings *spec,
+ int tCLK, int relax,
+ uint32_t *ctl_timing,
+ uint32_t *wen_timing,
+ uint32_t *ren_timing)
+{
+ int tMAX_HOLD;
+ int n_ctl_setup;
+ int n_ctl_hold;
+ int n_ctl_wb;
+
+ int n_wen_on;
+ int n_wen_off;
+ int wen_half_on;
+ int wen_half_off;
+
+ int tMAX_REN_ON;
+ int tMAX_CS_DEASSERT;
+ int n_d_latch;
+ int n_telqv;
+ int n_ren_on;
+ int n_ren_off;
+ int ren_half_on;
+ int ren_half_off;
+
+ /*
+ * CTL_TIMING
+ */
+
+ /* - SETUP */
+ if (spec->tCLS_min > spec->tWP_min)
+ n_ctl_setup = (spec->tCLS_min - spec->tWP_min + tCLK - 1)/tCLK;
+ else
+ n_ctl_setup = 0;
+ n_ctl_setup += relax;
+
+ /* - HOLD */
+ tMAX_HOLD = spec->tCLH_min;
+ if (spec->tCH_min > tMAX_HOLD)
+ tMAX_HOLD = spec->tCH_min;
+ if (spec->tALH_min > tMAX_HOLD)
+ tMAX_HOLD = spec->tALH_min;
+ if (spec->tDH_min > tMAX_HOLD)
+ tMAX_HOLD = spec->tDH_min;
+ n_ctl_hold = (tMAX_HOLD + tCLK - 1)/tCLK + relax;
+ /* - CE_deassert_hold = 0 */
+
+ /* - WE_high_to_RBn_low */
+ n_ctl_wb = (spec->tWB_max + tCLK - 1)/tCLK;
+
+ *ctl_timing = ((n_ctl_setup & 0xff) |
+ (n_ctl_hold & 0xff) << 8 |
+ (n_ctl_wb & 0xff) << 24);
+
+ /*
+ * WEN_TIMING
+ */
+
+ /* - ON */
+ n_wen_on = (2 * spec->tWH_min + tCLK - 1)/tCLK;
+ wen_half_on = n_wen_on % 2;
+ n_wen_on /= 2;
+ n_wen_on += relax;
+
+ /* - OFF */
+ n_wen_off = (2 * spec->tWP_min + tCLK - 1)/tCLK;
+ wen_half_off = n_wen_off % 2;
+ n_wen_off /= 2;
+ n_wen_off += relax;
+
+ *wen_timing = ((n_wen_on & 0xff) |
+ (n_wen_off & 0xff) << 8 |
+ (wen_half_on << 16) |
+ (wen_half_off << 17));
+
+ /*
+ * REN_TIMING
+ */
+
+ /* - ON */
+ tMAX_REN_ON = spec->tRC_min - spec->tRP_min;
+ if (spec->tREH_min > tMAX_REN_ON)
+ tMAX_REN_ON = spec->tREH_min;
+
+ n_ren_on = (2 * tMAX_REN_ON + tCLK - 1)/tCLK;
+ ren_half_on = n_ren_on % 2;
+ n_ren_on /= 2;
+ n_ren_on += relax;
+
+ /* - OFF */
+ n_ren_off = (2 * spec->tREA_max + tCLK - 1)/tCLK;
+ ren_half_off = n_ren_off % 2;
+ n_ren_off /= 2;
+ n_ren_off += relax;
+
+ /* - DATA_LATCH */
+ if (spec->tREA_max <= (spec->tRP_min - (2 * tCLK)))
+ n_d_latch = 0;
+ else if (spec->tREA_max <= (spec->tRP_min - tCLK))
+ n_d_latch = 1;
+ else if ((spec->tREA_max <= spec->tRP_min) && (spec->tRHOH_min >= 2 * tCLK))
+ n_d_latch = 2;
+ else
+ n_d_latch = 3;
+
+ /* - TELQV */
+ tMAX_CS_DEASSERT = spec->tCOH_min;
+ if (spec->tCHZ_max > tMAX_CS_DEASSERT)
+ tMAX_CS_DEASSERT = spec->tCHZ_max;
+
+ n_telqv = (tMAX_CS_DEASSERT + tCLK - 1)/tCLK;
+
+ *ren_timing = ((n_ren_on & 0xff) |
+ (n_ren_off & 0xff) << 8 |
+ (n_d_latch & 0x3) << 16 |
+ (wen_half_on << 18) |
+ (wen_half_off << 19) |
+ (n_telqv & 0xff) << 24);
+}
+
+static void flex_configure_timing_registers(struct nandi_controller *nandi,
+ const struct nand_sdr_timings *spec,
+ int relax)
+{
+ uint32_t ctl_timing;
+ uint32_t wen_timing;
+ uint32_t ren_timing;
+ int emi_t_ns;
+
+ /* Select Hamming Controller */
+ emiss_nandi_select(STM_NANDI_HAMMING);
+
+ /* Get EMI clock (default 100MHz) */
+ if (nandi->emi_clk)
+ emi_t_ns = 1000000000UL / clk_get_rate(nandi->emi_clk);
+ else {
+ dev_warn(nandi->dev,
+ "No EMI clock available; assuming default 100MHz\n");
+ emi_t_ns = 10;
+ }
+
+ /* Derive timing register values from specification */
+ flex_calc_timing_registers(spec, emi_t_ns, relax,
+ &ctl_timing, &wen_timing, &ren_timing);
+
+ dev_dbg(nandi->dev,
+ "updating FLEX timing configuration [0x%08x, 0x%08x, 0x%08x]\n",
+ ctl_timing, wen_timing, ren_timing);
+
+ /* Program timing registers */
+ writel(ctl_timing, nandi->base + NANDHAM_CTL_TIMING);
+ writel(wen_timing, nandi->base + NANDHAM_WEN_TIMING);
+ writel(ren_timing, nandi->base + NANDHAM_REN_TIMING);
+}
+
+static void bch_configure_timing_registers(struct nandi_controller *nandi,
+ const struct nand_sdr_timings *spec,
+ int relax)
+{
+ uint32_t ctl_timing;
+ uint32_t wen_timing;
+ uint32_t ren_timing;
+ int bch_t_ns;
+
+ /* Select BCH Controller */
+ emiss_nandi_select(STM_NANDI_BCH);
+
+ /* Get BCH clock (default 200MHz) */
+ if (nandi->bch_clk)
+ bch_t_ns = 1000000000UL / clk_get_rate(nandi->bch_clk);
+ else {
+ dev_warn(nandi->dev,
+ "No BCH clock available; assuming default 200MHz\n");
+ bch_t_ns = 5;
+ }
+
+ /* Derive timing register values from specification */
+ bch_calc_timing_registers(spec, bch_t_ns, relax,
+ &ctl_timing, &wen_timing, &ren_timing);
+
+ dev_dbg(nandi->dev,
+ "updating BCH timing configuration [0x%08x, 0x%08x, 0x%08x]\n",
+ ctl_timing, wen_timing, ren_timing);
+
+ /* Program timing registers */
+ writel(ctl_timing, nandi->base + NANDBCH_CTL_TIMING);
+ writel(wen_timing, nandi->base + NANDBCH_WEN_TIMING);
+ writel(ren_timing, nandi->base + NANDBCH_REN_TIMING);
+}
+
+static void nandi_configure_timing_registers(struct nandi_controller *nandi,
+ const struct nand_sdr_timings *spec,
+ int relax)
+{
+ bch_configure_timing_registers(nandi, spec, relax);
+ flex_configure_timing_registers(nandi, spec, relax);
+}
+
+static void nandi_init_hamming(struct nandi_controller *nandi, int emi_bank)
+{
+ dev_dbg(nandi->dev, "%s\n", __func__);
+
+ emiss_nandi_select(STM_NANDI_HAMMING);
+
+ /* Reset and disable boot-mode controller */
+ writel(BOOT_CFG_RESET, nandi->base + NANDHAM_BOOTBANK_CFG);
+ udelay(1);
+ writel(0x00000000, nandi->base + NANDHAM_BOOTBANK_CFG);
+
+ /* Reset controller */
+ writel(CFG_RESET, nandi->base + NANDHAM_FLEXMODE_CFG);
+ udelay(1);
+ writel(0x00000000, nandi->base + NANDHAM_FLEXMODE_CFG);
+
+ /* Set EMI Bank */
+ writel(0x1 << emi_bank, nandi->base + NANDHAM_FLEX_MUXCTRL);
+
+ /* Enable FLEX mode */
+ writel(CFG_ENABLE_FLEX, nandi->base + NANDHAM_FLEXMODE_CFG);
+
+ /* Configure FLEX_DATA_READ/WRITE for 1-byte access */
+ writel(FLEX_DATA_CFG_BEATS_1 | FLEX_DATA_CFG_CSN,
+ nandi->base + NANDHAM_FLEX_DATAREAD_CONFIG);
+ writel(FLEX_DATA_CFG_BEATS_1 | FLEX_DATA_CFG_CSN,
+ nandi->base + NANDHAM_FLEX_DATAREAD_CONFIG);
+
+ /* RBn interrupt on rising edge */
+ writel(NAND_EDGE_CFG_RBN_RISING, nandi->base + NANDHAM_INT_EDGE_CFG);
+
+ /* Enable interrupts */
+ nandi_enable_interrupts(nandi, NAND_INT_ENABLE);
+}
+
+static void nandi_init_bch(struct nandi_controller *nandi, int emi_bank)
+{
+ dev_dbg(nandi->dev, "%s\n", __func__);
+
+ /* Initialise BCH Controller */
+ emiss_nandi_select(STM_NANDI_BCH);
+
+ /* Reset and disable boot-mode controller */
+ writel(BOOT_CFG_RESET, nandi->base + NANDBCH_BOOTBANK_CFG);
+ udelay(1);
+ writel(0x00000000, nandi->base + NANDBCH_BOOTBANK_CFG);
+
+ /* Reset AFM controller */
+ writel(CFG_RESET, nandi->base + NANDBCH_CONTROLLER_CFG);
+ udelay(1);
+ writel(0x00000000, nandi->base + NANDBCH_CONTROLLER_CFG);
+
+ /* Set EMI Bank */
+ writel(0x1 << emi_bank, nandi->base + NANDBCH_FLEX_MUXCTRL);
+
+ /* Reset ECC stats */
+ writel(CFG_RESET_ECC_ALL, nandi->base + NANDBCH_CONTROLLER_CFG);
+ udelay(1);
+
+ /* Enable AFM */
+ writel(CFG_ENABLE_AFM, nandi->base + NANDBCH_CONTROLLER_CFG);
+
+ /* Configure Read DMA Plugs (values supplied by Validation) */
+ writel(0x00000005, nandi->dma + EMISS_NAND_RD_DMA_PAGE_SIZE);
+ writel(0x00000005, nandi->dma + EMISS_NAND_RD_DMA_MAX_OPCODE_SIZE);
+ writel(0x00000002, nandi->dma + EMISS_NAND_RD_DMA_MIN_OPCODE_SIZE);
+ writel(0x00000001, nandi->dma + EMISS_NAND_RD_DMA_MAX_CHUNK_SIZE);
+ writel(0x00000000, nandi->dma + EMISS_NAND_RD_DMA_MAX_MESSAGE_SIZE);
+
+ /* Configure Write DMA Plugs (values supplied by Validation) */
+ writel(0x00000005, nandi->dma + EMISS_NAND_WR_DMA_PAGE_SIZE);
+ writel(0x00000005, nandi->dma + EMISS_NAND_WR_DMA_MAX_OPCODE_SIZE);
+ writel(0x00000002, nandi->dma + EMISS_NAND_WR_DMA_MIN_OPCODE_SIZE);
+ writel(0x00000001, nandi->dma + EMISS_NAND_WR_DMA_MAX_CHUNK_SIZE);
+ writel(0x00000000, nandi->dma + EMISS_NAND_WR_DMA_MAX_MESSAGE_SIZE);
+
+ nandi_enable_interrupts(nandi, NAND_INT_ENABLE);
+}
+
+static void nandi_init_controller(struct nandi_controller *nandi,
+ int emi_bank)
+{
+ nandi_init_bch(nandi, emi_bank);
+ nandi_init_hamming(nandi, emi_bank);
+}
+
+/* Initialise working buffers, accomodating DMA alignment constraints */
+static int nandi_init_working_buffers(struct nandi_controller *nandi,
+ struct nandi_bbt_info *bbt_info,
+ struct mtd_info *mtd)
+{
+ uint32_t bbt_buf_size;
+ uint32_t buf_size;
+
+ /* - Page and OOB */
+ buf_size = mtd->writesize + mtd->oobsize + NANDI_BCH_DMA_ALIGNMENT;
+
+ /* - BBT data (page-size aligned) */
+ bbt_info->bbt_size = nandi->blocks_per_device >> 2; /* 2 bits/block */
+ bbt_buf_size = ALIGN(bbt_info->bbt_size, mtd->writesize);
+ buf_size += bbt_buf_size + NANDI_BCH_DMA_ALIGNMENT;
+
+ /* - BCH BUF list */
+ buf_size += NANDI_BCH_BUF_LIST_SIZE + NANDI_BCH_DMA_ALIGNMENT;
+
+ /* Allocate bufffer */
+ nandi->buf = devm_kzalloc(nandi->dev, buf_size, GFP_KERNEL);
+ if (!nandi->buf) {
+ dev_err(nandi->dev, "failed to allocate working buffers\n");
+ return -ENOMEM;
+ }
+
+ /* Set/Align buffer pointers */
+ nandi->page_buf = PTR_ALIGN(nandi->buf, NANDI_BCH_DMA_ALIGNMENT);
+ nandi->oob_buf = nandi->page_buf + mtd->writesize;
+ bbt_info->bbt = PTR_ALIGN(nandi->oob_buf + mtd->oobsize,
+ NANDI_BCH_DMA_ALIGNMENT);
+ nandi->buf_list = (uint32_t *)PTR_ALIGN(bbt_info->bbt + bbt_buf_size,
+ NANDI_BCH_DMA_ALIGNMENT);
+ nandi->cached_page = -1;
+
+ return 0;
+}
+
+static int remap_named_resource(struct platform_device *pdev,
+ char *name,
+ void __iomem **io_ptr)
+{
+ struct resource *res, *mem;
+ resource_size_t size;
+ void __iomem *p;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
+ if (!res)
+ return -ENXIO;
+
+ size = resource_size(res);
+
+ mem = devm_request_mem_region(&pdev->dev, res->start, size, name);
+ if (!mem)
+ return -EBUSY;
+
+ p = devm_ioremap_nocache(&pdev->dev, res->start, size);
+ if (!p)
+ return -ENOMEM;
+
+ *io_ptr = p;
+
+ return 0;
+}
+
+static struct nandi_controller *
+nandi_init_resources(struct platform_device *pdev)
+{
+ struct nandi_controller *nandi;
+ int irq;
+ int err;
+
+ nandi = devm_kzalloc(&pdev->dev, sizeof(*nandi), GFP_KERNEL);
+ if (!nandi) {
+ dev_err(&pdev->dev,
+ "failed to allocate NANDi controller data\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ nandi->dev = &pdev->dev;
+
+ err = remap_named_resource(pdev, "nand_mem", &nandi->base);
+ if (err)
+ return ERR_PTR(err);
+
+ err = remap_named_resource(pdev, "nand_dma", &nandi->dma);
+ if (err)
+ return ERR_PTR(err);
+
+ irq = platform_get_irq_byname(pdev, "nand_irq");
+ if (irq < 0) {
+ dev_err(&pdev->dev, "failed to find IRQ resource\n");
+ return ERR_PTR(irq);
+ }
+
+ err = devm_request_irq(&pdev->dev, irq, nandi_irq_handler,
+ IRQF_DISABLED, dev_name(&pdev->dev), nandi);
+ if (err) {
+ dev_err(&pdev->dev, "irq request failed\n");
+ return ERR_PTR(err);
+ }
+
+ nandi->emi_clk = nandi_clk_setup(nandi, "emi_clk");
+ nandi->bch_clk = nandi_clk_setup(nandi, "bch_clk");
+
+ platform_set_drvdata(pdev, nandi);
+
+ return nandi;
+}
+
+static void *stm_bch_dt_get_pdata(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct stm_plat_nand_bch_data *pdata;
+ int ecc_strength;
+ int ret;
+
+ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ of_property_read_u32(np, "nand-ecc-strength", &ecc_strength);
+ if (ecc_strength == 0)
+ pdata->bch_ecc_cfg = BCH_NO_ECC;
+ else if (ecc_strength == 18)
+ pdata->bch_ecc_cfg = BCH_18BIT_ECC;
+ else if (ecc_strength == 30)
+ pdata->bch_ecc_cfg = BCH_30BIT_ECC;
+ else
+ pdata->bch_ecc_cfg = BCH_ECC_AUTO;
+
+ ret = stm_of_get_nand_banks(&pdev->dev, np, &pdata->bank);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ return pdata;
+}
+
+static int stm_nand_bch_probe(struct platform_device *pdev)
+{
+ const char *part_probes[] = { "cmdlinepart", "ofpart", NULL, };
+ struct stm_plat_nand_bch_data *pdata = pdev->dev.platform_data;
+ struct device_node *np = pdev->dev.of_node;
+ struct mtd_part_parser_data ppdata;
+ struct stm_nand_bank_data *bank;
+ struct nandi_bbt_info *bbt_info;
+ struct nandi_controller *nandi;
+ struct nandi_info *info;
+ struct nand_chip *chip;
+ struct mtd_info *mtd;
+ int compatible, err;
+
+ if (!np) {
+ dev_err(&pdev->dev, "DT node found\n");
+ return -EINVAL;
+ }
+
+ pdata = stm_bch_dt_get_pdata(pdev);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+
+ ppdata.of_node = stm_of_get_partitions_node(np, 0);
+
+ pdev->dev.platform_data = pdata;
+
+ nandi = nandi_init_resources(pdev);
+ if (IS_ERR(nandi)) {
+ dev_err(&pdev->dev, "failed to initialise NANDi resources\n");
+ return PTR_ERR(nandi);
+ }
+
+ init_completion(&nandi->seq_completed);
+ init_completion(&nandi->rbn_completed);
+
+ bank = pdata->bank;
+ if (bank)
+ nandi_init_controller(nandi, bank->csn);
+
+ info = &nandi->info;
+ chip = &info->chip;
+ bbt_info = &info->bbt_info;
+ mtd = &info->mtd;
+ mtd->priv = chip;
+ mtd->name = dev_name(&pdev->dev);
+ mtd->dev.parent = &pdev->dev;
+
+ nandi_set_mtd_defaults(nandi, mtd, chip);
+
+ err = nand_scan_ident(mtd, 1, NULL);
+ if (err)
+ return err;
+
+ /*
+ * Configure timing registers
+ */
+ if (bank && bank->timing_spec) {
+ dev_info(&pdev->dev, "Using platform timing data\n");
+ nandi_configure_timing_registers(nandi, bank->timing_spec,
+ bank->timing_relax);
+ } else if (chip->onfi_version) {
+ int mode = fls(onfi_get_async_timing_mode(chip) - 1);
+
+ /* Modes 4 and 5 (EDO) are not supported on our H/W */
+ if (mode > 3)
+ mode = 3;
+
+ dev_info(&pdev->dev, "Using ONFI Timing Mode %d\n", mode);
+ nandi_configure_timing_registers(nandi,
+ &st_nand_onfi_timing_specs[mode],
+ bank ? bank->timing_relax : 0);
+ } else {
+ dev_warn(&pdev->dev, "No timing data available\n");
+ }
+
+ if (mtd->writesize < NANDI_BCH_SECTOR_SIZE) {
+ dev_err(nandi->dev,
+ "page size incompatible with BCH ECC sector\n");
+ return -EINVAL;
+ }
+
+ /* Derive some working variables */
+ nandi->sectors_per_page = mtd->writesize / NANDI_BCH_SECTOR_SIZE;
+ nandi->blocks_per_device = mtd->size >> chip->phys_erase_shift;
+ nandi->extra_addr = ((chip->chipsize >> chip->page_shift) >
+ 0x10000) ? true : false;
+ mtd->writebufsize = mtd->writesize;
+
+ /* Set ECC mode */
+ if (pdata->bch_ecc_cfg == BCH_ECC_AUTO) {
+ err = bch_set_ecc_auto(nandi, mtd);
+ if (err) {
+ dev_err(nandi->dev, "insufficient OOB for BCH ECC\n");
+ return err;
+ }
+ } else {
+ nandi->bch_ecc_mode = pdata->bch_ecc_cfg;
+ }
+
+ chip->ecc.size = NANDI_BCH_SECTOR_SIZE;
+ chip->ecc.bytes = mtd->oobsize;
+ chip->ecc.strength = bch_ecc_strength[nandi->bch_ecc_mode];
+
+ info->ecclayout.eccbytes =
+ nandi->sectors_per_page * bch_ecc_sizes[nandi->bch_ecc_mode];
+
+ compatible = bch_check_compatibility(nandi, mtd, chip);
+ if (!compatible) {
+ dev_err(nandi->dev,
+ "NAND device incompatible with NANDi/BCH Controller\n");
+ return -EINVAL;
+ }
+
+ /* Tune BCH programs according to device found and ECC mode */
+ bch_configure_progs(nandi);
+
+ err = nandi_init_working_buffers(nandi, bbt_info, mtd);
+ if (err)
+ return err;
+
+ err = nand_scan_tail(mtd);
+ if (err)
+ return err;
+
+ nandi_dump_bad_blocks(nandi);
+
+ /* Add partitions */
+ return mtd_device_parse_register(mtd, part_probes, &ppdata,
+ bank->partitions, bank->nr_partitions);
+}
+
+static int stm_nand_bch_remove(struct platform_device *pdev)
+{
+ struct nandi_controller *nandi = platform_get_drvdata(pdev);
+
+ nand_release(&nandi->info.mtd);
+
+ nandi_clk_disable(nandi);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int stm_nand_bch_suspend(struct device *dev)
+{
+ struct nandi_controller *nandi = dev_get_drvdata(dev);
+
+ nandi_clk_disable(nandi);
+
+ return 0;
+}
+static int stm_nand_bch_resume(struct device *dev)
+{
+ struct nandi_controller *nandi = dev_get_drvdata(dev);
+
+ nandi_clk_enable(nandi);
+
+ return 0;
+}
+
+static int stm_nand_bch_restore(struct device *dev)
+{
+ struct nandi_controller *nandi = dev_get_drvdata(dev);
+ struct stm_plat_nand_bch_data *pdata = dev->platform_data;
+ struct stm_nand_bank_data *bank = pdata->bank;
+
+ nandi_init_controller(nandi, bank->csn);
+
+ return 0;
+}
+
+static const struct dev_pm_ops stm_nand_bch_pm_ops = {
+ .suspend = stm_nand_bch_suspend,
+ .resume = stm_nand_bch_resume,
+ .restore = stm_nand_bch_restore,
+};
+#else
+static const struct dev_pm_ops stm_nand_bch_pm_ops;
+#endif
+
+static struct of_device_id nand_bch_match[] = {
+ { .compatible = "st,nand-bch", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, nand_bch_match);
+
+static struct platform_driver stm_nand_bch_driver = {
+ .probe = stm_nand_bch_probe ,
+ .remove = stm_nand_bch_remove,
+ .driver = {
+ .name = "stm-nand-bch",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(nand_bch_match),
+ .pm = &stm_nand_bch_pm_ops,
+ },
+};
+module_platform_driver(stm_nand_bch_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Angus Clark");
+MODULE_DESCRIPTION("STM NAND BCH driver");
diff --git a/drivers/mtd/nand/stm_nand_dt.c b/drivers/mtd/nand/stm_nand_dt.c
new file mode 100644
index 0000000..21bd20f
--- /dev/null
+++ b/drivers/mtd/nand/stm_nand_dt.c
@@ -0,0 +1,116 @@
+/*
+ * drivers/mtd/nand/stm_nand_dt.c
+ *
+ * Support for NANDi BCH Controller Device Tree component
+ *
+ * Copyright (c) 2014 STMicroelectronics Limited
+ * Author: Author: Srinivas Kandagatla <srinivas.kandagatla@st.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/byteorder/generic.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/stm_nand.h>
+
+#include "stm_nand_regs.h"
+
+/**
+* stm_of_get_partitions_node - get partitions node from stm-nand type devices.
+*
+* @dev device pointer to use for devm allocations.
+* @np device node of the driver.
+* @bank_nr which bank number to use to get partitions.
+*
+* Returns a node pointer if found, with refcount incremented, use
+* of_node_put() on it when done.
+*
+*/
+struct device_node *stm_of_get_partitions_node(struct device_node *np,
+ int bank_nr)
+{
+ struct device_node *banksnp, *banknp, *partsnp = NULL;
+ char name[10];
+
+ banksnp = of_parse_phandle(np, "st,nand-banks", 0);
+ if (!banksnp)
+ return NULL;
+
+ sprintf(name, "bank%d", bank_nr);
+ banknp = of_get_child_by_name(banksnp, name);
+ if (banknp)
+ return NULL;
+
+ partsnp = of_get_child_by_name(banknp, "partitions");
+ of_node_put(banknp);
+
+ return partsnp;
+}
+EXPORT_SYMBOL(stm_of_get_partitions_node);
+
+/**
+ * stm_of_get_nand_banks - Get nand banks info from a given device node.
+ *
+ * @dev device pointer to use for devm allocations.
+ * @np device node of the driver.
+ * @banksptr double pointer to banks which is allocated
+ * and filled with bank data.
+ *
+ * Returns a count of banks found in the given device node.
+ *
+ */
+int stm_of_get_nand_banks(struct device *dev, struct device_node *np,
+ struct stm_nand_bank_data **banksptr)
+{
+ struct stm_nand_bank_data *banks;
+ struct device_node *banknp, *banksnp;
+ int nr_banks = 0;
+
+ if (!np)
+ return -ENODEV;
+
+ banksnp = of_parse_phandle(np, "st,nand-banks", 0);
+ if (!banksnp) {
+ dev_warn(dev, "No NAND banks specified in DT: %s\n",
+ np->full_name);
+ return -ENODEV;
+ }
+
+ for_each_child_of_node(banksnp, banknp)
+ nr_banks++;
+
+ *banksptr = devm_kzalloc(dev, sizeof(*banks) * nr_banks, GFP_KERNEL);
+ banks = *banksptr;
+ banknp = NULL;
+
+ for_each_child_of_node(banksnp, banknp) {
+ int bank = 0;
+
+ of_property_read_u32(banknp, "st,nand-csn", &banks[bank].csn);
+
+ if (of_get_nand_bus_width(banknp) == 16)
+ banks[bank].options |= NAND_BUSWIDTH_16;
+ if (of_get_nand_on_flash_bbt(banknp))
+ banks[bank].bbt_options |= NAND_BBT_USE_FLASH;
+
+ banks[bank].nr_partitions = 0;
+ banks[bank].partitions = NULL;
+
+ of_property_read_u32(banknp, "st,nand-timing-relax",
+ &banks[bank].timing_relax);
+ bank++;
+ }
+
+ return nr_banks;
+}
+EXPORT_SYMBOL(stm_of_get_nand_banks);
diff --git a/drivers/mtd/nand/stm_nand_dt.h b/drivers/mtd/nand/stm_nand_dt.h
new file mode 100644
index 0000000..de4507c
--- /dev/null
+++ b/drivers/mtd/nand/stm_nand_dt.h
@@ -0,0 +1,39 @@
+/*
+ * drivers/mtd/nand/stm_nand_dt.h
+ *
+ * Support for NANDi BCH Controller Device Tree component
+ *
+ * Copyright (c) 2014 STMicroelectronics Limited
+ * Author: Author: Srinivas Kandagatla <srinivas.kandagatla@st.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef STM_NAND_DT_H
+#define STM_NAND_DT_H
+
+#if defined(CONFIG_MTD_NAND_STM_BCH_DT)
+struct device_node *stm_of_get_partitions_node(struct device_node *np,
+ int bank_nr);
+
+int stm_of_get_nand_banks(struct device *dev, struct device_node *np,
+ struct stm_nand_bank_data **banksp);
+#else
+static inline
+struct device_node *stm_of_get_partitions_node(struct device_node *np,
+ int bank_nr)
+{
+ return NULL;
+}
+
+static inline int stm_of_get_nand_banks(struct device *dev,
+ struct device_node *np,
+ struct stm_nand_bank_data **banksp)
+{
+ return 0;
+}
+#endif /* CONFIG_MTD_NAND_STM_BCH_DT */
+#endif /* STM_NAND_DT_H */
diff --git a/drivers/mtd/nand/stm_nand_regs.h b/drivers/mtd/nand/stm_nand_regs.h
new file mode 100644
index 0000000..2b0e069
--- /dev/null
+++ b/drivers/mtd/nand/stm_nand_regs.h
@@ -0,0 +1,302 @@
+/*
+ * drivers/mtd/nand/stm_nand_regs.h
+ *
+ * STMicroelectronics NAND Controller register definitions
+ *
+ * Copyright (c) 2008-2014 STMicroelectronics Limited
+ * Author: Angus Clark <Angus.Clark@st.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef STM_NANDC_REGS_H
+#define STM_NANDC_REGS_H
+
+/* Hamming Controller Registers (Offsets from EMINAND_BASE) */
+#define NANDHAM_BOOTBANK_CFG 0x000
+#define NANDHAM_RBN_STA 0x004
+#define NANDHAM_INT_EN 0x010
+#define NANDHAM_INT_STA 0x014
+#define NANDHAM_INT_CLR 0x018
+#define NANDHAM_INT_EDGE_CFG 0x01C
+#define NANDHAM_CTL_TIMING 0x040
+#define NANDHAM_WEN_TIMING 0x044
+#define NANDHAM_REN_TIMING 0x048
+#define NANDHAM_BLOCK_ZERO_REMAP_REG 0x04C
+#define NANDHAM_FLEXMODE_CFG 0x100
+#define NANDHAM_FLEX_MUXCTRL 0x104
+#define NANDHAM_FLEX_DATAWRITE_CONFIG 0x10C
+#define NANDHAM_FLEX_DATAREAD_CONFIG 0x110
+#define NANDHAM_FLEX_CMD 0x114
+#define NANDHAM_FLEX_ADD 0x118
+#define NANDHAM_FLEX_DATA 0x120
+#define NANDHAM_VERSION_REG 0x144
+#define NANDHAM_MULTI_CS_CONFIG_REG 0x1EC
+#define NANDHAM_AFM_SEQ_REG_1 0x200
+#define NANDHAM_AFM_SEQ_REG_2 0x204
+#define NANDHAM_AFM_SEQ_REG_3 0x208
+#define NANDHAM_AFM_SEQ_REG_4 0x20C
+#define NANDHAM_AFM_ADD 0x210
+#define NANDHAM_AFM_EXTRA 0x214
+#define NANDHAM_AFM_CMD 0x218
+#define NANDHAM_AFM_SEQ_CFG 0x21C
+#define NANDHAM_AFM_GEN_CFG 0x220
+#define NANDHAM_AFM_SEQ_STA 0x240
+#define NANDHAM_AFM_ECC_REG_0 0x280
+#define NANDHAM_AFM_ECC_REG_1 0x284
+#define NANDHAM_AFM_ECC_REG_2 0x288
+#define NANDHAM_AFM_ECC_REG_3 0x28C
+#define NANDHAM_AFM_DATA_FIFO 0x300
+
+/* BCH Controller Registers (Offsets from EMI_NAND) */
+#define NANDBCH_BOOTBANK_CFG 0x000
+#define NANDBCH_RBN_STA 0x004
+#define NANDBCH_INT_EN 0x010
+#define NANDBCH_INT_STA 0x014
+#define NANDBCH_INT_CLR 0x018
+#define NANDBCH_INT_EDGE_CFG 0x01C
+#define NANDBCH_CTL_TIMING 0x040
+#define NANDBCH_WEN_TIMING 0x044
+#define NANDBCH_REN_TIMING 0x048
+#define NANDBCH_BLOCK_ZERO_REMAP_REG 0x04C
+#define NANDBCH_BOOT_STATUS 0x050
+#define NANDBCH_FALSE_BOOT_REG 0x054
+#define NANDBCH_FALSE_BOOT_STATUS 0x058
+#define NANDBCH_CONTROLLER_CFG 0x100
+#define NANDBCH_FLEX_MUXCTRL 0x104
+#define NANDBCH_FLEX_DATAWRITE_CONFIG 0x10C
+#define NANDBCH_FLEX_DATAREAD_CONFIG 0x110
+#define NANDBCH_VERSION_REG 0x144
+#define NANDBCH_ADDRESS_REG_1 0x1F0
+#define NANDBCH_ADDRESS_REG_2 0x1F4
+#define NANDBCH_ADDRESS_REG_3 0x1F8
+#define NANDBCH_MULTI_CS_CONFIG_REG 0x1FC
+#define NANDBCH_SEQ_REG_1 0x200
+#define NANDBCH_SEQ_REG_2 0x204
+#define NANDBCH_SEQ_REG_3 0x208
+#define NANDBCH_SEQ_REG_4 0x20C
+#define NANDBCH_ADD 0x210
+#define NANDBCH_EXTRA_REG 0x214
+#define NANDBCH_CMD 0x218
+#define NANDBCH_GEN_CFG 0x220
+#define NANDBCH_DELAY_REG 0x224
+#define NANDBCH_SEQ_CFG 0x22C
+#define NANDBCH_SEQ_STA 0x270
+#define NANDBCH_DATA_BUFFER_ENTRY_0 0x280
+#define NANDBCH_DATA_BUFFER_ENTRY_1 0x284
+#define NANDBCH_DATA_BUFFER_ENTRY_2 0x288
+#define NANDBCH_DATA_BUFFER_ENTRY_3 0x28C
+#define NANDBCH_DATA_BUFFER_ENTRY_4 0x290
+#define NANDBCH_DATA_BUFFER_ENTRY_5 0x294
+#define NANDBCH_DATA_BUFFER_ENTRY_6 0x298
+#define NANDBCH_DATA_BUFFER_ENTRY_7 0x29C
+#define NANDBCH_ECC_SCORE_REG_A 0x2A0
+#define NANDBCH_ECC_SCORE_REG_B 0x2A4
+#define NANDBCH_CHECK_STATUS_REG_A 0x2A8
+#define NANDBCH_CHECK_STATUS_REG_B 0x2AC
+#define NANDBCH_BUFFER_LIST_PTR 0x300
+#define NANDBCH_SEQ_PTR_REG 0x304
+#define NANDBCH_ERROR_THRESHOLD_REG 0x308
+
+/* EMISS NAND BCH STPLUG Registers (Offsets from EMISS_NAND_DMA) */
+#define EMISS_NAND_RD_DMA_PAGE_SIZE 0x000
+#define EMISS_NAND_RD_DMA_MAX_OPCODE_SIZE 0x004
+#define EMISS_NAND_RD_DMA_MIN_OPCODE_SIZE 0x008
+#define EMISS_NAND_RD_DMA_MAX_CHUNK_SIZE 0x00C
+#define EMISS_NAND_RD_DMA_MAX_MESSAGE_SIZE 0x010
+
+#define EMISS_NAND_WR_DMA_PAGE_SIZE 0x100
+#define EMISS_NAND_WR_DMA_MAX_OPCODE_SIZE 0x104
+#define EMISS_NAND_WR_DMA_MIN_OPCODE_SIZE 0x108
+#define EMISS_NAND_WR_DMA_MAX_CHUNK_SIZE 0x10C
+#define EMISS_NAND_WR_DMA_MAX_MESSAGE_SIZE 0x110
+
+
+/*
+ * Hamming/BCH controller interrupts
+ */
+
+/* NANDxxx_INT_EN/NANDxxx_INT_STA */
+/* Common */
+#define NAND_INT_ENABLE (0x1 << 0)
+#define NAND_INT_RBN (0x1 << 2)
+#define NAND_INT_SEQCHECK (0x1 << 5)
+/* Hamming only */
+#define NANDHAM_INT_DATA_DREQ (0x1 << 3)
+#define NANDHAM_INT_SEQ_DREQ (0x1 << 4)
+#define NANDHAM_INT_ECC_FIX_REQ (0x1 << 6)
+/* BCH only */
+#define NANDBCH_INT_SEQNODESOVER (0x1 << 7)
+#define NANDBCH_INT_ECCTHRESHOLD (0x1 << 8)
+
+/* NANDxxx_INT_CLR */
+/* Common */
+#define NAND_INT_CLR_RBN (0x1 << 2)
+#define NAND_INT_CLR_SEQCHECK (0x1 << 3)
+/* Hamming only */
+#define NANDHAM_INT_CLR_ECC_FIX_REQ (0x1 << 4)
+#define NANDHAM_INT_CLR_DATA_DREQ (0x1 << 5)
+#define NANDHAM_INT_CLR_SEQ_DREQ (0x1 << 6)
+/* BCH only */
+#define NANDBCH_INT_CLR_SEQNODESOVER (0x1 << 5)
+#define NANDBCH_INT_CLR_ECCTHRESHOLD (0x1 << 6)
+
+/* NANDxxx_INT_EDGE_CFG */
+#define NAND_EDGE_CFG_RBN_RISING 0x1
+#define NAND_EDGE_CFG_RBN_FALLING 0x2
+#define NAND_EDGE_CFG_RBN_ANY 0x3
+
+/* NANDBCH_CONTROLLER_CFG/NANDHAM_FLEXMODE_CFG */
+#define CFG_ENABLE_FLEX 0x1
+#define CFG_ENABLE_AFM 0x2
+#define CFG_RESET (0x1 << 3)
+#define CFG_RESET_ECC(x) (0x1 << (7 + (x)))
+#define CFG_RESET_ECC_ALL (0xff << 7)
+
+
+/*
+ * BCH Controller
+ */
+
+/* NANDBCH_BOOTBANK_CFG */
+#define BOOT_CFG_RESET (0x1 << 3)
+
+/* NANDBCH_CTL_TIMING */
+#define NANDBCH_CTL_SETUP(x) ((x) & 0xff)
+#define NANDBCH_CTL_HOLD(x) (((x) & 0xff) << 8)
+#define NANDBCH_CTL_WERBN(x) (((x) & 0xff) << 24)
+
+/* NANDBCH_WEN_TIMING */
+#define NANDBCH_WEN_ONTIME(x) ((x) & 0xff)
+#define NANDBCH_WEN_OFFTIME(x) (((x) & 0xff) << 8)
+#define NANDBCH_WEN_ONHALFCYCLE (0x1 << 16)
+#define NANDBCH_WEN_OFFHALFCYCLE (0x1 << 17)
+
+/* NANDBCH_REN_TIMING */
+#define NANDBCH_REN_ONTIME(x) ((x) & 0xff)
+#define NANDBCH_REN_OFFTIME(x) (((x) & 0xff) << 8)
+#define NANDBCH_REN_ONHALFCYCLE (0x1 << 16)
+#define NANDBCH_REN_OFFHALFCYCLE (0x1 << 17)
+#define NANDBCH_REN_TELQV(x) (((x) & 0xff) << 24)
+
+/* NANDBCH_BLOCK_ZERO_REMAP_REG */
+#define NANDBCH_BACKUP_COPY_FOUND (0x1 << 0)
+#define NANDBCH_ORIG_CODE_CORRUPTED (0x1 << 1)
+#define NANDBCH_BLK_ZERO_REMAP(x) ((x) >> 14)
+
+/* NANDBCH_BOOT_STATUS */
+#define NANDBCH_BOOT_MAX_ERRORS(x) ((x) & 0x1f)
+
+/* NANDBCH_GEN_CFG */
+#define GEN_CFG_DATA_8_NOT_16 (0x1 << 16)
+#define GEN_CFG_EXTRA_ADD_CYCLE (0x1 << 18)
+#define GEN_CFG_2X8_MODE (0x1 << 19)
+#define GEN_CFG_ECC_SHIFT 20
+#define GEN_CFG_18BIT_ECC (BCH_18BIT_ECC << \
+ GEN_CFG_ECC_SHIFT)
+#define GEN_CFG_30BIT_ECC (BCH_30BIT_ECC << \
+ GEN_CFG_ECC_SHIFT)
+#define GEN_CFG_NO_ECC (BCH_NO_ECC << \
+ GEN_CFG_ECC_SHIFT)
+#define GEN_CFG_LAST_SEQ_NODE (0x1 << 22)
+
+/* NANDBCH_SEQ_CFG */
+#define SEQ_CFG_REPEAT_COUNTER(x) ((x) & 0xffff)
+#define SEQ_CFG_SEQ_IDENT(x) (((x) & 0xff) << 16)
+#define SEQ_CFG_DATA_WRITE (0x1 << 24)
+#define SEQ_CFG_ERASE (0x1 << 25)
+#define SEQ_CFG_GO_STOP (0x1 << 26)
+
+/* NANDBCH_SEQ_STA */
+#define SEQ_STA_RUN (0x1 << 4)
+
+/*
+ * BCH Commands
+ */
+#define BCH_OPC_STOP 0x0
+#define BCH_OPC_CMD 0x1
+#define BCH_OPC_INC 0x2
+#define BCH_OPC_DEC_JUMP 0x3
+#define BCH_OPC_DATA 0x4
+#define BCH_OPC_DELAY 0x5
+#define BCH_OPC_CHECK 0x6
+#define BCH_OPC_ADDR 0x7
+#define BCH_OPC_NEXT_CHIP_ON 0x8
+#define BCH_OPC_DEC_JMP_MCS 0x9
+#define BCH_OPC_ECC_SCORE 0xA
+
+#define BCH_INSTR(opc, opr) ((opc) | ((opr) << 4))
+
+#define BCH_CMD_ADDR BCH_INSTR(BCH_OPC_CMD, 0)
+#define BCH_CL_CMD_1 BCH_INSTR(BCH_OPC_CMD, 1)
+#define BCH_CL_CMD_2 BCH_INSTR(BCH_OPC_CMD, 2)
+#define BCH_CL_CMD_3 BCH_INSTR(BCH_OPC_CMD, 3)
+#define BCH_CL_EX_0 BCH_INSTR(BCH_OPC_CMD, 4)
+#define BCH_CL_EX_1 BCH_INSTR(BCH_OPC_CMD, 5)
+#define BCH_CL_EX_2 BCH_INSTR(BCH_OPC_CMD, 6)
+#define BCH_CL_EX_3 BCH_INSTR(BCH_OPC_CMD, 7)
+#define BCH_INC(x) BCH_INSTR(BCH_OPC_INC, (x))
+#define BCH_DEC_JUMP(x) BCH_INSTR(BCH_OPC_DEC_JUMP, (x))
+#define BCH_STOP BCH_INSTR(BCH_OPC_STOP, 0)
+#define BCH_DATA_1_SECTOR BCH_INSTR(BCH_OPC_DATA, 0)
+#define BCH_DATA_2_SECTOR BCH_INSTR(BCH_OPC_DATA, 1)
+#define BCH_DATA_4_SECTOR BCH_INSTR(BCH_OPC_DATA, 2)
+#define BCH_DATA_8_SECTOR BCH_INSTR(BCH_OPC_DATA, 3)
+#define BCH_DATA_16_SECTOR BCH_INSTR(BCH_OPC_DATA, 4)
+#define BCH_DATA_32_SECTOR BCH_INSTR(BCH_OPC_DATA, 5)
+#define BCH_DELAY_0 BCH_INSTR(BCH_OPC_DELAY, 0)
+#define BCH_DELAY_1 BCH_INSTR(BCH_OPC_DELAY, 1)
+#define BCH_OP_ERR BCH_INSTR(BCH_OPC_CHECK, 0)
+#define BCH_CACHE_ERR BCH_INSTR(BCH_OPC_CHECK, 1)
+#define BCH_ERROR BCH_INSTR(BCH_OPC_CHECK, 2)
+#define BCH_AL_EX_0 BCH_INSTR(BCH_OPC_ADDR, 0)
+#define BCH_AL_EX_1 BCH_INSTR(BCH_OPC_ADDR, 1)
+#define BCH_AL_EX_2 BCH_INSTR(BCH_OPC_ADDR, 2)
+#define BCH_AL_EX_3 BCH_INSTR(BCH_OPC_ADDR, 3)
+#define BCH_AL_AD_0 BCH_INSTR(BCH_OPC_ADDR, 4)
+#define BCH_AL_AD_1 BCH_INSTR(BCH_OPC_ADDR, 5)
+#define BCH_AL_AD_2 BCH_INSTR(BCH_OPC_ADDR, 6)
+#define BCH_AL_AD_3 BCH_INSTR(BCH_OPC_ADDR, 7)
+#define BCH_NEXT_CHIP_ON BCH_INSTR(BCH_OPC_NEXT_CHIP_ON, 0)
+#define BCH_DEC_JMP_MCS(x) BCH_INSTR(BCH_OPC_DEC_JMP_MCS, (x))
+#define BCH_ECC_SCORE(x) BCH_INSTR(BCH_OPC_ECC_SCORE, (x))
+
+
+/*
+ * Hamming-FLEX register fields
+ */
+
+/* NANDHAM_FLEX_DATAREAD/WRITE_CONFIG */
+#define FLEX_DATA_CFG_WAIT_RBN (0x1 << 27)
+#define FLEX_DATA_CFG_BEATS_1 (0x1 << 28)
+#define FLEX_DATA_CFG_BEATS_2 (0x2 << 28)
+#define FLEX_DATA_CFG_BEATS_3 (0x3 << 28)
+#define FLEX_DATA_CFG_BEATS_4 (0x0 << 28)
+#define FLEX_DATA_CFG_BYTES_1 (0x0 << 30)
+#define FLEX_DATA_CFG_BYTES_2 (0x1 << 30)
+#define FLEX_DATA_CFG_CSN (0x1 << 31)
+
+/* NANDHAM_FLEX_CMD */
+#define FLEX_CMD_RBN (0x1 << 27)
+#define FLEX_CMD_BEATS_1 (0x1 << 28)
+#define FLEX_CMD_BEATS_2 (0x2 << 28)
+#define FLEX_CMD_BEATS_3 (0x3 << 28)
+#define FLEX_CMD_BEATS_4 (0x0 << 28)
+#define FLEX_CMD_CSN (0x1 << 31)
+#define FLEX_CMD(x) (((x) & 0xff) | \
+ FLEX_CMD_RBN | \
+ FLEX_CMD_BEATS_1 | \
+ FLEX_CMD_CSN)
+/* NANDHAM_FLEX_ADD */
+#define FLEX_ADDR_RBN (0x1 << 27)
+#define FLEX_ADDR_BEATS_1 (0x1 << 28)
+#define FLEX_ADDR_BEATS_2 (0x2 << 28)
+#define FLEX_ADDR_BEATS_3 (0x3 << 28)
+#define FLEX_ADDR_BEATS_4 (0x0 << 28)
+#define FLEX_ADDR_ADD8_VALID (0x1 << 30)
+#define FLEX_ADDR_CSN (0x1 << 31)
+
+#endif /* STM_NANDC_REGS_H */
diff --git a/include/linux/mtd/stm_nand.h b/include/linux/mtd/stm_nand.h
new file mode 100644
index 0000000..3cd3a14
--- /dev/null
+++ b/include/linux/mtd/stm_nand.h
@@ -0,0 +1,104 @@
+/*
+ * include/linux/mtd/stm_mtd.h
+ *
+ * Support for STMicroelectronics NAND Controllers
+ *
+ * Copyright (c) 2014 STMicroelectronics Limited
+ * Author: Angus Clark <Angus.Clark@st.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef __LINUX_STM_NAND_H
+#define __LINUX_STM_NAND_H
+
+#include <linux/io.h>
+
+/*
+ * Board-level specification relating to a 'bank' of NAND Flash
+ */
+struct stm_nand_bank_data {
+ int csn;
+ int nr_partitions;
+ struct mtd_partition *partitions;
+ unsigned int options;
+ unsigned int bbt_options;
+
+ struct nand_sdr_timings *timing_spec;
+
+ /*
+ * No. of IP clk cycles by which to 'relax' the timing configuration.
+ * Required on some boards to to accommodate board-level limitations.
+ * Used in conjunction with 'nand_sdr_timings' and ONFI configuration.
+ */
+ int timing_relax;
+};
+
+/* ECC Modes */
+enum stm_nand_bch_ecc_config {
+ BCH_18BIT_ECC = 0,
+ BCH_30BIT_ECC,
+ BCH_NO_ECC,
+ BCH_ECC_RSRV,
+ BCH_ECC_AUTO,
+};
+
+struct stm_plat_nand_bch_data {
+ struct stm_nand_bank_data *bank;
+ enum stm_nand_bch_ecc_config bch_ecc_cfg;
+
+ /* The threshold at which the number of corrected bit-flips per sector
+ * is deemed to have reached an excessive level (triggers '-EUCLEAN' to
+ * be returned to the caller). The value should be in the range 1 to
+ * <ecc-strength> where <ecc-strength> is 18 or 30, depending on the BCH
+ * ECC mode in operation. A value of 0 is interpreted by the driver as
+ * <ecc-strength>.
+ */
+ unsigned int bch_bitflip_threshold;
+ bool flashss;
+};
+
+#define EMISS_BASE 0xfef01000
+#define EMISS_CONFIG 0x0000
+#define EMISS_CONFIG_HAMMING_NOT_BCH (0x1 << 6)
+
+enum nandi_controllers {
+ STM_NANDI_UNCONFIGURED,
+ STM_NANDI_HAMMING,
+ STM_NANDI_BCH
+};
+
+static inline void emiss_nandi_select(enum nandi_controllers controller)
+{
+ unsigned v;
+ void __iomem *emiss_config_base;
+
+ emiss_config_base = ioremap(EMISS_BASE, 4);
+ if (!emiss_config_base) {
+ pr_err("%s: failed to ioremap EMISS\n", __func__);
+ return;
+ }
+
+ v = readl(emiss_config_base + EMISS_CONFIG);
+
+ if (controller == STM_NANDI_HAMMING) {
+ if (v & EMISS_CONFIG_HAMMING_NOT_BCH)
+ goto out;
+ v |= EMISS_CONFIG_HAMMING_NOT_BCH;
+ } else {
+ if (!(v & EMISS_CONFIG_HAMMING_NOT_BCH))
+ goto out;
+ v &= ~EMISS_CONFIG_HAMMING_NOT_BCH;
+ }
+
+ writel(v, emiss_config_base + EMISS_CONFIG);
+ readl(emiss_config_base + EMISS_CONFIG);
+
+out:
+ iounmap(emiss_config_base);
+}
+
+#endif /* __LINUX_STM_NAND_H */
--
1.8.3.2