2014-05-28

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 != ibbt_sigs) {

+ 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 != NAND_IBBT_SCHEMA)

+ return 0;

+

+ /* Test IBBT BCH schema */

+ for (i = 0; i < 4; i++)

+ if (ibbt_header->schema != 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;

+ 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;<

Show more