* [PATCH RFC] Alphascale ASM9260 NAND controller driver
@ 2014-12-17 11:45 Oleksij Rempel
2014-12-17 11:45 ` [PATCH RFC] mtd: nand: add asm9260 NFC driver Oleksij Rempel
2014-12-17 13:24 ` [PATCH RFC] Alphascale ASM9260 NAND controller driver Boris Brezillon
0 siblings, 2 replies; 24+ messages in thread
From: Oleksij Rempel @ 2014-12-17 11:45 UTC (permalink / raw)
To: linux-mtd, boris.brezillon, computersforpeace; +Cc: Oleksij Rempel
I collected some questions with this driver. It will be great if you
can help me:
* Do HW_ECC driver should provideo option for SW_ECC?
* My HW do ECC correction for n-bits per 512B, it mean it will
split block in 512B parts. If one of part has uncorrectable error, complete block will be
reported as failed. Are there any way for partial recovery?
* This HW reports count of ECC bitflips for each part. Do i need to return count of errors on
all parts for one block?
* If HW_ECC different stranges, how it should be configured? With DT or automatically
calculated?
Oleksij Rempel (1):
mtd: nand: add asm9260 NFC driver
drivers/mtd/nand/Kconfig | 7 +
drivers/mtd/nand/Makefile | 1 +
drivers/mtd/nand/asm9260_nand.c | 1148 +++++++++++++++++++++++++++++++++++++++
3 files changed, 1156 insertions(+)
create mode 100644 drivers/mtd/nand/asm9260_nand.c
--
1.9.1
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH RFC] mtd: nand: add asm9260 NFC driver
2014-12-17 11:45 [PATCH RFC] Alphascale ASM9260 NAND controller driver Oleksij Rempel
@ 2014-12-17 11:45 ` Oleksij Rempel
2014-12-17 16:15 ` Boris Brezillon
2014-12-17 13:24 ` [PATCH RFC] Alphascale ASM9260 NAND controller driver Boris Brezillon
1 sibling, 1 reply; 24+ messages in thread
From: Oleksij Rempel @ 2014-12-17 11:45 UTC (permalink / raw)
To: linux-mtd, boris.brezillon, computersforpeace; +Cc: Oleksij Rempel
Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
---
drivers/mtd/nand/Kconfig | 7 +
drivers/mtd/nand/Makefile | 1 +
drivers/mtd/nand/asm9260_nand.c | 1148 +++++++++++++++++++++++++++++++++++++++
3 files changed, 1156 insertions(+)
create mode 100644 drivers/mtd/nand/asm9260_nand.c
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index dd10646..580a608 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -41,6 +41,13 @@ config MTD_SM_COMMON
tristate
default n
+config MTD_NAND_ASM9260
+ tristate "NFC support for ASM9260 SoC"
+ depends on OF
+ default n
+ help
+ Enable support for the NAND controller found on Alphascale ASM9260 SoC.
+
config MTD_NAND_DENALI
tristate "Support Denali NAND controller"
depends on HAS_DMA
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 9c847e4..08d660a 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_MTD_NAND_BCH) += nand_bch.o
obj-$(CONFIG_MTD_NAND_IDS) += nand_ids.o
obj-$(CONFIG_MTD_SM_COMMON) += sm_common.o
+obj-$(CONFIG_MTD_NAND_ASM9260) += asm9260_nand.o
obj-$(CONFIG_MTD_NAND_CAFE) += cafe_nand.o
obj-$(CONFIG_MTD_NAND_AMS_DELTA) += ams-delta.o
obj-$(CONFIG_MTD_NAND_DENALI) += denali.o
diff --git a/drivers/mtd/nand/asm9260_nand.c b/drivers/mtd/nand/asm9260_nand.c
new file mode 100644
index 0000000..67dde23
--- /dev/null
+++ b/drivers/mtd/nand/asm9260_nand.c
@@ -0,0 +1,1148 @@
+/*
+ * NAND controller driver for Alphascale ASM9260, which is probably
+ * based on Evatronix NANDFLASH-CTRL IP (version unknown)
+ *
+ * Copyright (C), 2007-2013, Alphascale Tech. Co., Ltd.
+ * 2014 Oleksij Rempel <linux@rempel-privat.de>
+ *
+ * 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/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <linux/clk.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+
+#define mtd_to_priv(m) container_of(m, struct asm9260_nand_priv, mtd)
+
+#define HW_CMD 0x00
+#define BM_CMD_CMD2_S 24
+#define BM_CMD_CMD1_S 16
+#define BM_CMD_CMD0_S 8
+/* 0 - ADDR0, 1 - ADDR1 */
+#define BM_CMD_ADDR1 BIT(7)
+/* 0 - PIO, 1 - DMA */
+#define BM_CMD_DMA BIT(6)
+#define BM_CMD_CMDSEQ_S 0
+/* FIXME: some description for SEQ? */
+#define SEQ1 0x21 /* 6'b100001 */
+#define SEQ2 0x22 /* 6'b100010 */
+#define SEQ4 0x24 /* 6'b100100 */
+#define SEQ5 0x25 /* 6'b100101 */
+#define SEQ6 0x26 /* 6'b100110 */
+#define SEQ7 0x27 /* 6'b100111 */
+#define SEQ9 0x29 /* 6'b101001 */
+#define SEQ10 0x2a /* 6'b101010 */
+#define SEQ11 0x2b /* 6'b101011 */
+#define SEQ15 0x2f /* 6'b101111 */
+#define SEQ0 0x00 /* 6'b000000 */
+#define SEQ3 0x03 /* 6'b000011 */
+#define SEQ8 0x08 /* 6'b001000 */
+#define SEQ12 0x0c /* 6'b001100 */
+#define SEQ13 0x0d /* 6'b001101 */
+#define SEQ14 0x0e /* 6'b001110 */
+#define SEQ16 0x30 /* 6'b110000 */
+#define SEQ17 0x15 /* 6'b010101 */
+#define SEQ18 0x32 /* 6'h110010 */
+
+#define HW_CTRL 0x04
+#define BM_CTRL_DIS_STATUS BIT(23)
+#define BM_CTRL_READ_STAT BIT(22)
+#define BM_CTRL_SMALL_BLOCK_EN BIT(21)
+#define BM_CTRL_ADDR_CYCLE1_S 18
+#define ADDR_CYCLE_0 0x0
+#define ADDR_CYCLE_1 0x1
+#define ADDR_CYCLE_2 0x2
+#define ADDR_CYCLE_3 0x3
+#define ADDR_CYCLE_4 0x4
+#define ADDR_CYCLE_5 0x5
+#define BM_CTRL_ADDR1_AUTO_INCR BIT(17)
+#define BM_CTRL_ADDR0_AUTO_INCR BIT(16)
+#define BM_CTRL_WORK_MODE BIT(15)
+#define BM_CTRL_PORT_EN BIT(14)
+#define BM_CTRL_LOOKU_EN BIT(13)
+#define BM_CTRL_IO_16BIT BIT(12)
+/* Overwrite BM_CTRL_PAGE_SIZE with HW_DATA_SIZE */
+#define BM_CTRL_CUSTOM_PAGE_SIZE BIT(11)
+#define BM_CTRL_PAGE_SIZE_S 8
+#define PAGE_SIZE_256B 0x0
+#define PAGE_SIZE_512B 0x1
+#define PAGE_SIZE_1024B 0x2
+#define PAGE_SIZE_2048B 0x3
+#define PAGE_SIZE_4096B 0x4
+#define PAGE_SIZE_8192B 0x5
+#define PAGE_SIZE_16384B 0x6
+#define PAGE_SIZE_32768B 0x7
+#define BM_CTRL_BLOCK_SIZE_S 6
+#define BLOCK_SIZE_32P 0x0
+#define BLOCK_SIZE_64P 0x1
+#define BLOCK_SIZE_128P 0x2
+#define BLOCK_SIZE_256P 0x3
+#define BM_CTRL_ECC_EN BIT(5)
+#define BM_CTRL_INT_EN BIT(4)
+#define BM_CTRL_SPARE_EN BIT(3)
+/* same values as BM_CTRL_ADDR_CYCLE1_S */
+#define BM_CTRL_ADDR_CYCLE0_S 0
+
+#define HW_STATUS 0x08
+#define BM_CTRL_NFC_BUSY BIT(8)
+/* MEM1_RDY (BIT1) - MEM7_RDY (BIT7) */
+#define BM_CTRL_MEM0_RDY BIT(0)
+
+#define HW_INT_MASK 0x0c
+#define HW_INT_STATUS 0x10
+#define BM_INT_FIFO_ERROR BIT(12)
+/* MEM1_RDY (BIT5) - MEM7_RDY (BIT11) */
+#define BM_INT_MEM0_RDY BIT(4)
+#define BM_INT_ECC_TRSH_ERR BIT(3)
+#define BM_INT_ECC_FATAL_ERR BIT(2)
+#define BM_INT_CMD_END BIT(1)
+
+#define HW_ECC_CTRL 0x14
+/* bits per 512 bytes */
+#define BM_ECC_CAP_S 5
+/* FIXME: reduce all this defines */
+#define ECC_CAP_2 0x0
+#define ECC_CAP_4 0x1
+#define ECC_CAP_6 0x2
+#define ECC_CAP_8 0x3
+#define ECC_CAP_10 0x4
+#define ECC_CAP_12 0x5
+#define ECC_CAP_14 0x6
+#define ECC_CAP_16 0x7
+#define BM_ECC_ERR_THRESHOLD_S 8
+/* FIXME: reduce all this defines */
+#define ECC_THRESHOLD_0 0x0
+#define ECC_THRESHOLD_1 0x1
+#define ECC_THRESHOLD_2 0x2
+#define ECC_THRESHOLD_3 0x3
+#define ECC_THRESHOLD_4 0x4
+#define ECC_THRESHOLD_5 0x5
+#define ECC_THRESHOLD_6 0x6
+#define ECC_THRESHOLD_7 0x7
+#define ECC_THRESHOLD_8 0x8
+#define ECC_THRESHOLD_9 0x9
+#define ECC_THRESHOLD_10 0xa
+#define ECC_THRESHOLD_11 0xb
+#define ECC_THRESHOLD_12 0xc
+#define ECC_THRESHOLD_13 0xd
+#define ECC_THRESHOLD_14 0xe
+#define ECC_THRESHOLD_15 0xf
+#define BM_ECC_ERR_OVER BIT(2)
+/* Uncorrected error. */
+#define BM_ECC_ERR_UNC BIT(1)
+/* Corrected error. */
+#define BM_ECC_ERR_CORRECT BIT(0)
+
+#define HW_ECC_OFFSET 0x18
+#define HW_ADDR0_0 0x1c
+#define HW_ADDR1_0 0x20
+#define HW_ADDR0_1 0x24
+#define HW_ADDR1_1 0x28
+#define HW_SPARE_SIZE 0x30
+#define HW_DMA_ADDR 0x64
+#define HW_DMA_CNT 0x68
+
+#define HW_DMA_CTRL 0x6c
+#define BM_DMA_CTRL_START BIT(7)
+/* 0 - to device; 1 - from device */
+#define BM_DMA_CTRL_FROM_DEVICE BIT(6)
+/* 0 - software maneged; 1 - scatter-gather */
+#define BM_DMA_CTRL_SG BIT(5)
+#define BM_DMA_CTRL_BURST_S 2
+#define DMA_BURST_INCR4 0x0
+#define DMA_BURST_STREAM 0x1
+#define DMA_BURST_SINGLE 0x2
+#define DMA_BURST_INCR 0x3
+#define DMA_BURST_INCR8 0x4
+#define DMA_BURST_INCR16 0x5
+#define BM_DMA_CTRL_ERR BIT(1)
+#define BM_DMA_CTRL_RDY BIT(0)
+
+#define HW_MEM_CTRL 0x80
+#define BM_MEM_CTRL_WP_STATE_MASK 0xff00
+#define BM_MEM_CTRL_UNWPn(x) (1 << ((x) + 8))
+#define BM_MEM_CTRL_CEn(x) (((x) & 7) << 0)
+
+/* BM_CTRL_CUSTOM_PAGE_SIZE should be set */
+#define HW_DATA_SIZE 0x84
+#define HW_READ_STATUS 0x88
+#define HW_TIM_SEQ_0 0x8c
+#define HW_TIMING_ASYN 0x90
+#define HW_TIMING_SYN 0x94
+
+#define HW_FIFO_DATA 0x98
+#define HW_TIME_MODE 0x9c
+#define HW_FIFO_INIT 0xb0
+/*
+ * Counter for ecc related errors.
+ * For each 512 byte block it has 5bit counter.
+ */
+#define HW_ECC_ERR_CNT 0xb8
+
+#define HW_TIM_SEQ_1 0xc8
+
+struct asm9260_nand_priv {
+ struct device *dev;
+ struct mtd_info mtd;
+ struct nand_chip nand;
+
+ struct clk *clk;
+ struct clk *clk_ahb;
+
+ void __iomem *base;
+ int irq_done;
+
+ u32 read_cache;
+ int read_cache_cnt;
+ u32 cmd_cache;
+ u32 ctrl_cache;
+ u32 mem_status_mask;
+
+ unsigned int ecc_cap;
+ unsigned int ecc_threshold;
+ unsigned int spare_size;
+};
+
+/*2KB--4*512B, correction ability: 4bit--7Byte ecc*/
+static struct nand_ecclayout asm9260_nand_oob_64 = {
+ .eccbytes = 4 * 7,
+ .eccpos = {
+ 36, 37, 38, 39, 40, 41, 42,
+ 43, 44, 45, 46, 47, 48, 49,
+ 50, 51, 52, 53, 54, 55, 56,
+ 57, 58, 59, 60, 61, 62, 63},
+ .oobfree = {{2, 34}}
+};
+
+/*4KB--8*512B, correction ability: 6bit--10Byte ecc*/
+static struct nand_ecclayout asm9260_nand_oob_128 = {
+ .eccbytes = 8 * 10,
+ .eccpos = {
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
+ 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,
+ 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
+ 78, 79, 80, 81, 82, 83, 84, 85, 86, 87,
+
+ 88, 89, 90, 91, 92, 93, 94, 95, 96, 97,
+ 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
+ 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
+ 118, 119, 120, 121, 122, 123, 124, 125, 126, 127},
+ .oobfree = {
+ {.offset = 2,
+ .length = 46}}
+};
+
+/*4KB--8*512B, correction ability: 14bit--23Byte ecc*/
+static struct nand_ecclayout asm9260_nand_oob_218 = {
+ .eccbytes = 8 * 23,
+ .eccpos = {
+ 34, 35, 36, 37, 38, 39, 40, 41,
+ 42, 43, 44, 45, 46, 47, 48, 49,
+ 50, 51, 52, 53, 54, 55, 56, 57,
+ 58, 59, 60, 61, 62, 63, 64, 65,
+ 66, 67, 68, 69, 70, 71, 72, 73,
+ 74, 75, 76, 77, 78, 79, 80, 81,
+ 82, 83, 84, 85, 86, 87, 88, 89,
+ 90, 91, 92, 93, 94, 95, 96, 97,
+ 98, 99, 100, 101, 102, 103, 104, 105,
+ 106, 107, 108, 109, 110, 111, 112, 113,
+ 114, 115, 116, 117, 118, 119, 120, 121,
+ 122, 123, 124, 125, 126, 127, 128, 129,
+ 130, 131, 132, 133, 134, 135, 136, 137,
+ 138, 139, 140, 141, 142, 143, 144, 145,
+ 146, 147, 148, 149, 150, 151, 152, 153,
+ 154, 155, 156, 157, 158, 159, 160, 161,
+ 162, 163, 164, 165, 166, 167, 168, 169,
+ 170, 171, 172, 173, 174, 175, 176, 177,
+ 178, 179, 180, 181, 182, 183, 184, 185,
+ 186, 187, 188, 189, 190, 191, 192, 193,
+ 194, 195, 196, 197, 198, 199, 200, 201,
+ 202, 203, 204, 205, 206, 207, 208, 209,
+ 210, 211, 212, 213, 214, 215, 216, 217},
+ .oobfree = {
+ {.offset = 2,
+ .length = 32}}
+};
+
+/*4KB--8*512B, correction ability: 14bit--23Byte ecc*/
+static struct nand_ecclayout asm9260_nand_oob_224 = {
+ .eccbytes = 8 * 23,
+ .eccpos = {
+ 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55,
+ 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71,
+
+ 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87,
+ 88, 89, 90, 91, 92, 93, 94, 95,
+ 96, 97, 98, 99, 100, 101, 102, 103,
+
+ 104, 105, 106, 107, 108, 109, 110, 111,
+ 112, 113, 114, 115, 116, 117, 118, 119,
+ 120, 121, 122, 123, 124, 125, 126, 127,
+ 128, 129, 130, 131, 132, 133, 134, 135,
+
+ 136, 137, 138, 139, 140, 141, 142, 143,
+ 144, 145, 146, 147, 148, 149, 150, 151,
+ 152, 153, 154, 155, 156, 157, 158, 159,
+ 160, 161, 162, 163, 164, 165, 166, 167,
+
+ 168, 169, 170, 171, 172, 173, 174, 175,
+ 176, 177, 178, 179, 180, 181, 182, 183,
+ 184, 185, 186, 187, 188, 189, 190, 191,
+ 192, 193, 194, 195, 196, 197, 198, 199,
+
+ 200, 201, 202, 203, 204, 205, 206, 207,
+ 208, 209, 210, 211, 212, 213, 214, 215,
+ 216, 217, 218, 219, 220, 221, 222, 223},
+ .oobfree = {
+ {.offset = 2,
+ .length = 38}}
+};
+
+
+/*8KB--16*512B, correction ability: 8bit--13Byte ecc*/
+static struct nand_ecclayout asm9260_nand_oob_256 = {
+ .eccbytes = 16*13,
+ .eccpos = {
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
+ 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
+
+ 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
+ 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
+ 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
+ 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
+
+ 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
+ 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
+ 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
+ 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+
+ 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255},
+ .oobfree = {{2, 46}}
+};
+
+/*8KB--16*512B, correction ability: 14bit--23Byte ecc*/
+static struct nand_ecclayout asm9260_nand_oob_436 = {
+ .eccbytes = 16*23,
+ .eccpos = {
+ 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
+ 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
+ 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115,
+ 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131,
+
+ 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147,
+ 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163,
+ 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
+ 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195,
+
+ 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211,
+ 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227,
+ 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243,
+ 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259,
+
+ 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275,
+ 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291,
+ 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307,
+ 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323,
+
+ 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339,
+ 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355,
+ 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371,
+ 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387,
+
+ 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403,
+ 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419,
+ 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435},
+ .oobfree = {{2, 66}}
+};
+
+/*8KB--16*512B, correction ability: 16bit--26Byte ecc*/
+static struct nand_ecclayout asm9260_nand_oob_448 = {
+ .eccbytes = 16*26,
+ .eccpos = {
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
+
+ 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
+ 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
+ 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
+ 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
+
+ 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
+ 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
+ 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
+ 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
+
+ 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+ 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,
+ 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271,
+ 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287,
+
+ 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303,
+ 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319,
+ 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335,
+ 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351,
+
+ 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367,
+ 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383,
+ 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398 ,399,
+ 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415,
+
+ 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431,
+ 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447},
+ .oobfree = {{2, 30}}
+};
+
+/**
+ * struct ecc_info - ASAP1826T ECC INFO Structure
+ * @ecc_cap: The ECC module correction ability.
+ * @ecc_threshold: The acceptable errors level
+ * @ecc_bytes_per_sector: ECC bytes per sector
+ */
+struct ecc_info {
+ int ecc_cap;
+ int ecc_threshold;
+ int ecc_bytes_per_sector;
+};
+
+/*
+* ECC info list
+*
+* ecc_cap, ecc_threshold, ecc bytes per sector
+*/
+struct ecc_info ecc_info_table[8] = {
+ {ECC_CAP_2, ECC_THRESHOLD_2, 4},
+ {ECC_CAP_4, ECC_THRESHOLD_4, 7},
+ {ECC_CAP_6, ECC_THRESHOLD_6, 10},
+ {ECC_CAP_8, ECC_THRESHOLD_8, 13},
+ {ECC_CAP_10, ECC_THRESHOLD_10, 17},
+ {ECC_CAP_12, ECC_THRESHOLD_12, 20},
+ {ECC_CAP_14, ECC_THRESHOLD_14, 23},
+ {ECC_CAP_16, ECC_THRESHOLD_15, 26},
+};
+
+static void asm9260_reg_rmw(struct asm9260_nand_priv *priv,
+ u32 reg_offset, u32 set, u32 clr)
+{
+ u32 val;
+
+ val = ioread32(priv->base + reg_offset);
+ val &= ~clr;
+ val |= set;
+ iowrite32(val, priv->base + reg_offset);
+}
+
+static void asm9260_select_chip(struct mtd_info *mtd, int chip)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+
+ if (chip == -1)
+ iowrite32(BM_MEM_CTRL_WP_STATE_MASK, priv->base + HW_MEM_CTRL);
+ else
+ iowrite32(BM_MEM_CTRL_UNWPn(chip) | BM_MEM_CTRL_CEn(chip),
+ priv->base + HW_MEM_CTRL);
+}
+
+static void asm9260_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
+{
+}
+
+/* TODO: 3 commands are supported by HW. 3-d can be used for TWO PLANE. */
+static void asm9260_nand_cmd_prep(struct asm9260_nand_priv *priv,
+ u8 cmd0, u8 cmd1, u8 cmd2, u8 seq)
+{
+ priv->cmd_cache = (cmd0 << BM_CMD_CMD0_S) | (cmd1 << BM_CMD_CMD1_S);
+ priv->cmd_cache |= seq << BM_CMD_CMDSEQ_S;
+}
+
+static dma_addr_t asm9260_nand_dma_set(struct mtd_info *mtd, void *buf,
+ enum dma_data_direction dir, size_t size)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ dma_addr_t dma_addr;
+
+ dma_addr = dma_map_single(priv->dev, buf, size, dir);
+ if (dma_mapping_error(priv->dev, dma_addr)) {
+ dev_err(priv->dev, "dma_map_single failed!\n");
+ return dma_addr;
+
+ }
+
+ iowrite32(dma_addr, priv->base + HW_DMA_ADDR);
+ iowrite32(size, priv->base + HW_DMA_CNT);
+ iowrite32(BM_DMA_CTRL_START
+ | (dir == DMA_FROM_DEVICE ? BM_DMA_CTRL_FROM_DEVICE : 0)
+ /* TODO: check different DMA_BURST_INCR16 settings */
+ | (DMA_BURST_INCR16 << BM_DMA_CTRL_BURST_S),
+ priv->base + HW_DMA_CTRL);
+ return dma_addr;
+}
+
+/* complete command request */
+static void asm9260_nand_cmd_comp(struct mtd_info *mtd, int dma)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ int timeout;
+ u32 cmd;
+
+ if (!priv->cmd_cache)
+ return;
+
+ if (dma) {
+ priv->cmd_cache |= BM_CMD_DMA;
+ priv->irq_done = 0;
+ /* FIXME: should we allow all MEM* device? */
+ iowrite32(BM_INT_MEM0_RDY, priv->base + HW_INT_MASK);
+ }
+
+ iowrite32(priv->cmd_cache, priv->base + HW_CMD);
+ cmd = priv->cmd_cache;
+ priv->cmd_cache = 0;
+
+ if (dma) {
+ struct nand_chip *nand = &priv->nand;
+
+ /* FIXME: change timeout value */
+ timeout = wait_event_timeout(nand->controller->wq,
+ priv->irq_done, 1 * HZ);
+ if (timeout <= 0) {
+ dev_info(priv->dev,
+ "Request 0x%08x timed out\n", cmd);
+ /* TODO: Do something useful here? */
+ /* FIXME: if we have problems on DMA or PIO, we need to
+ * reset NFC. On asm9260 it is possible only with global
+ * reset register. How can we use it here? */
+ }
+ } else
+ nand_wait_ready(mtd);
+}
+
+static int asm9260_nand_dev_ready(struct mtd_info *mtd)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ u32 tmp;
+
+ tmp = ioread32(priv->base + HW_STATUS);
+
+ /* FIXME: use define instead of 0x1 */
+ return (!(tmp & BM_CTRL_NFC_BUSY) &&
+ (tmp & 0x1));
+}
+
+static void asm9260_nand_ctrl(struct asm9260_nand_priv *priv, u32 set)
+{
+ iowrite32(priv->ctrl_cache | set, priv->base + HW_CTRL);
+}
+
+static void asm9260_nand_set_addr(struct asm9260_nand_priv *priv,
+ u32 row_addr, u32 column)
+{
+ u32 addr[2];
+
+ addr[0] = (column & 0xffff) | (0xffff0000 & (row_addr << 16));
+ addr[1] = (row_addr >> 16) & 0xff;
+
+ iowrite32(addr[0], priv->base + HW_ADDR0_0);
+ iowrite32(addr[1], priv->base + HW_ADDR0_1);
+}
+
+static void asm9260_nand_command_lp(struct mtd_info *mtd,
+ unsigned int command, int column, int page_addr)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+
+ switch (command) {
+ case NAND_CMD_RESET:
+ asm9260_nand_cmd_prep(priv, NAND_CMD_RESET, 0, 0, SEQ0);
+ asm9260_nand_cmd_comp(mtd, 0);
+ break;
+
+ case NAND_CMD_READID:
+ iowrite32(1, priv->base + HW_FIFO_INIT);
+ iowrite32((ADDR_CYCLE_1 << BM_CTRL_ADDR_CYCLE1_S)
+ | BM_CTRL_CUSTOM_PAGE_SIZE
+ | (PAGE_SIZE_4096B << BM_CTRL_PAGE_SIZE_S)
+ | (BLOCK_SIZE_32P << BM_CTRL_BLOCK_SIZE_S)
+ | BM_CTRL_INT_EN
+ | (ADDR_CYCLE_1 << BM_CTRL_ADDR_CYCLE0_S),
+ priv->base + HW_CTRL);
+
+ iowrite32(8, priv->base + HW_DATA_SIZE);
+ iowrite32(column, priv->base + HW_ADDR0_0);
+ asm9260_nand_cmd_prep(priv, NAND_CMD_READID, 0, 0, SEQ1);
+
+ priv->read_cache_cnt = 0;
+ break;
+
+ case NAND_CMD_READOOB:
+ column += mtd->writesize;
+ command = NAND_CMD_READ0;
+ case NAND_CMD_READ0:
+ iowrite32(1, priv->base + HW_FIFO_INIT);
+
+ if (column == 0) {
+ asm9260_nand_ctrl(priv, 0);
+ iowrite32(priv->spare_size, priv->base + HW_SPARE_SIZE);
+ } else if (column == mtd->writesize) {
+ asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
+ iowrite32(mtd->oobsize, priv->base + HW_SPARE_SIZE);
+ iowrite32(mtd->oobsize, priv->base + HW_DATA_SIZE);
+ } else {
+ dev_err(priv->dev, "Couldn't support the column\n");
+ break;
+ }
+
+ asm9260_nand_set_addr(priv, page_addr, column);
+
+ asm9260_nand_cmd_prep(priv, NAND_CMD_READ0,
+ NAND_CMD_READSTART, 0, SEQ10);
+
+ priv->read_cache_cnt = 0;
+ break;
+ case NAND_CMD_SEQIN:
+ iowrite32(1, priv->base + HW_FIFO_INIT);
+
+ if (column == 0) {
+ asm9260_nand_ctrl(priv, 0);
+ iowrite32(priv->spare_size, priv->base + HW_SPARE_SIZE);
+ } else if (column == mtd->writesize) {
+ asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
+ iowrite32(mtd->oobsize, priv->base + HW_DATA_SIZE);
+ }
+
+ asm9260_nand_set_addr(priv, page_addr, column);
+
+ asm9260_nand_cmd_prep(priv, NAND_CMD_SEQIN, NAND_CMD_PAGEPROG,
+ 0, SEQ12);
+
+ break;
+ case NAND_CMD_STATUS:
+ iowrite32(1, priv->base + HW_FIFO_INIT);
+ asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
+
+ /*
+ * Workaround for status bug.
+ * Instead of SEQ4 we need to use SEQ1 here, which will
+ * send cmd with address. For this case we need to make sure
+ * ADDR == 0.
+ */
+ asm9260_nand_set_addr(priv, 0, 0);
+ iowrite32(4, priv->base + HW_DATA_SIZE);
+ asm9260_nand_cmd_prep(priv, NAND_CMD_STATUS, 0, 0, SEQ1);
+
+ priv->read_cache_cnt = 0;
+ break;
+
+ case NAND_CMD_ERASE1:
+ asm9260_nand_set_addr(priv, page_addr, column);
+
+ asm9260_nand_ctrl(priv, 0);
+
+ /*
+ * Prepare and send command now. We don't need to split it in
+ * two stages.
+ */
+ asm9260_nand_cmd_prep(priv, NAND_CMD_ERASE1, NAND_CMD_ERASE2,
+ 0, SEQ14);
+ asm9260_nand_cmd_comp(mtd, 0);
+ break;
+ default:
+ break;
+ }
+}
+
+
+/**
+ * We can't read less then 32 bits on HW_FIFO_DATA. So, to make
+ * read_byte and read_word happy, we use sort of cached 32bit read.
+ * Note: expected values for size should be 1 or 2 bytes.
+ */
+static u32 asm9260_nand_read_cached(struct mtd_info *mtd, int size)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ u8 tmp;
+
+ if ((priv->read_cache_cnt <= 0) || (priv->read_cache_cnt > 4))
+ {
+ asm9260_nand_cmd_comp(mtd, 0);
+ priv->read_cache = ioread32(priv->base + HW_FIFO_DATA);
+ priv->read_cache_cnt = 4;
+ }
+
+ tmp = priv->read_cache >> (8 * (4 - priv->read_cache_cnt));
+ priv->read_cache_cnt -= size;
+
+ return tmp;
+}
+
+static u8 asm9260_nand_read_byte(struct mtd_info *mtd)
+{
+ return 0xff & asm9260_nand_read_cached(mtd, 1);
+}
+
+static u16 asm9260_nand_read_word(struct mtd_info *mtd)
+{
+ return 0xffff & asm9260_nand_read_cached(mtd, 2);
+}
+
+static void asm9260_nand_read_buf(struct mtd_info *mtd, u8 *buf, int len)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ dma_addr_t dma_addr;
+ int dma_ok;
+
+ if (len & 0x3) {
+ dev_err(priv->dev, "Unsupported length (%x)\n", len);
+ return;
+ }
+
+ dma_addr = asm9260_nand_dma_set(mtd, buf, DMA_FROM_DEVICE, len);
+ dma_ok = !(dma_mapping_error(priv->dev, dma_addr));
+ asm9260_nand_cmd_comp(mtd, dma_ok);
+
+ if (dma_ok) {
+ dma_sync_single_for_cpu(priv->dev, dma_addr, len, DMA_FROM_DEVICE);
+ dma_unmap_single(priv->dev, dma_addr, len, DMA_FROM_DEVICE);
+ return;
+ }
+
+ /* fall back to pio mode */
+ len >>= 2;
+ ioread32_rep(priv->base + HW_FIFO_DATA, buf, len);
+}
+
+static void asm9260_nand_write_buf(struct mtd_info *mtd,
+ const u8 *buf, int len)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ dma_addr_t dma_addr;
+ int dma_ok;
+
+ if (len & 0x3) {
+ dev_err(priv->dev, "Unsupported length (%x)\n", len);
+ return;
+ }
+
+ dma_addr = asm9260_nand_dma_set(mtd, buf, DMA_TO_DEVICE, len);
+ dma_ok = !(dma_mapping_error(priv->dev, dma_addr));
+ if (dma_ok)
+ dma_sync_single_for_device(priv->dev, dma_addr, len,
+ DMA_TO_DEVICE);
+ asm9260_nand_cmd_comp(mtd, dma_ok);
+
+ if (dma_ok) {
+ dma_unmap_single(priv->dev, dma_addr, len, DMA_TO_DEVICE);
+ return;
+ }
+
+ /* fall back to pio mode */
+ len >>= 2;
+ iowrite32_rep(priv->base + HW_FIFO_DATA, buf, len);
+}
+
+static int asm9260_nand_write_page_hwecc(struct mtd_info *mtd,
+ struct nand_chip *chip, const u8 *buf,
+ int oob_required)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ u8 *temp_ptr;
+ temp_ptr = (u8 *)buf;
+
+ asm9260_reg_rmw(priv, HW_CTRL, BM_CTRL_ECC_EN, 0);
+ chip->write_buf(mtd, temp_ptr, mtd->writesize);
+
+ if (oob_required)
+ chip->ecc.write_oob(mtd, chip, mtd->writesize);
+ return 0;
+}
+
+static unsigned int asm9260_nand_count_ecc(struct asm9260_nand_priv *priv)
+{
+ u32 tmp, i, count = 0;
+
+ /* FIXME: this layout was tested only on 2048byte NAND.
+ * NANDs with bigger page size should use more registers. */
+ tmp = ioread32(priv->base + HW_ECC_ERR_CNT);
+ for (i = 0; i < 4; i++)
+ count += 0x1f & (tmp >> (5 * i));
+
+ return count;
+}
+
+static int asm9260_nand_read_page_hwecc(struct mtd_info *mtd,
+ struct nand_chip *chip, u8 *buf,
+ int oob_required, int page)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ u8 *temp_ptr;
+ u32 status, max_bitflips = 0;
+
+ temp_ptr = buf;
+
+ /* enable ecc */
+ asm9260_reg_rmw(priv, HW_CTRL, BM_CTRL_ECC_EN, 0);
+ chip->read_buf(mtd, temp_ptr, mtd->writesize);
+
+ status = ioread32(priv->base + HW_ECC_CTRL);
+
+ max_bitflips = asm9260_nand_count_ecc(priv);
+
+ /* FIXME: do we need it for failed bit too? */
+ if (status & BM_ECC_ERR_UNC)
+ mtd->ecc_stats.failed += max_bitflips;
+ else if (status & BM_ECC_ERR_CORRECT)
+ mtd->ecc_stats.corrected += max_bitflips;
+
+ if (oob_required)
+ chip->ecc.read_oob(mtd, chip, page);
+
+ return max_bitflips;
+}
+
+static irqreturn_t asm9260_nand_irq(int irq, void *device_info)
+{
+ struct asm9260_nand_priv *priv = device_info;
+ struct nand_chip *nand = &priv->nand;
+ u32 status;
+
+ status = ioread32(priv->base + HW_INT_STATUS);
+ if (!status)
+ return IRQ_NONE;
+
+ iowrite32(0, priv->base + HW_INT_MASK);
+ iowrite32(0, priv->base + HW_INT_STATUS);
+ priv->irq_done = 1;
+ wake_up(&nand->controller->wq);
+
+ return IRQ_HANDLED;
+}
+
+static void __init asm9260_nand_init_chip(struct nand_chip *nand_chip)
+{
+ nand_chip->select_chip = asm9260_select_chip;
+ nand_chip->cmd_ctrl = asm9260_cmd_ctrl;
+ nand_chip->cmdfunc = asm9260_nand_command_lp;
+ nand_chip->read_byte = asm9260_nand_read_byte;
+ nand_chip->read_word = asm9260_nand_read_word;
+ nand_chip->read_buf = asm9260_nand_read_buf;
+ nand_chip->write_buf = asm9260_nand_write_buf;
+
+ nand_chip->dev_ready = asm9260_nand_dev_ready;
+ nand_chip->chip_delay = 100;
+
+ nand_chip->ecc.mode = NAND_ECC_HW;
+
+ nand_chip->ecc.write_page = asm9260_nand_write_page_hwecc;
+ nand_chip->ecc.read_page = asm9260_nand_read_page_hwecc;
+}
+
+static void __init asm9260_nand_cached_config(struct asm9260_nand_priv *priv)
+{
+ struct nand_chip *nand = &priv->nand;
+ struct mtd_info *mtd = &priv->mtd;
+ u32 addr_cycles, col_cycles, block_shift;
+
+ /* FIXME: remove it or replace it */
+ /* FIXME: these complete part need fixing */
+ block_shift = __ffs(mtd->erasesize) - nand->page_shift;
+ col_cycles = 2;
+ addr_cycles = col_cycles +
+ (((mtd->size >> mtd->writesize) > 65536) ? 3 : 2);
+
+ priv->mem_status_mask = BM_CTRL_MEM0_RDY;
+ priv->ctrl_cache = BM_CTRL_READ_STAT
+ | addr_cycles << BM_CTRL_ADDR_CYCLE1_S
+ | ((nand->page_shift - 8) & 0x7) << BM_CTRL_PAGE_SIZE_S
+ | ((block_shift - 5) & 0x3) << BM_CTRL_BLOCK_SIZE_S
+ | BM_CTRL_INT_EN
+ | addr_cycles << BM_CTRL_ADDR_CYCLE0_S;
+
+ iowrite32(priv->ecc_threshold << BM_ECC_ERR_THRESHOLD_S
+ | priv->ecc_cap << BM_ECC_CAP_S,
+ priv->base + HW_ECC_CTRL);
+ iowrite32(mtd->writesize + priv->spare_size,
+ priv->base + HW_ECC_OFFSET);
+
+}
+
+static void __init asm9260_nand_timing_config(struct asm9260_nand_priv *priv)
+{
+ u32 twhr;
+ u32 trhw;
+ u32 trwh;
+ u32 trwp;
+ u32 tadl = 0;
+ u32 tccs = 0;
+ u32 tsync = 0;
+ u32 trr = 0;
+ u32 twb = 0;
+
+ trwh = 1; //TWH;
+ trwp = 1; //TWP;
+ iowrite32((trwh << 4) | (trwp), priv->base + HW_TIMING_ASYN);
+
+ twhr = 2;
+ trhw = 4;
+ iowrite32((twhr << 24) | (trhw << 16)
+ | (tadl << 8) | (tccs), priv->base + HW_TIM_SEQ_0);
+
+ iowrite32((tsync << 16) | (trr << 9) | (twb),
+ priv->base + HW_TIM_SEQ_1);
+}
+
+static int __init asm9260_ecc_cap_select(struct asm9260_nand_priv *priv,
+ int nand_page_size, int nand_oob_size)
+{
+ int ecc_bytes = 0;
+ int i;
+
+ for (i=(ARRAY_SIZE(ecc_info_table) - 1); i>=0; i--)
+ {
+ if ((nand_oob_size - ecc_info_table[i].ecc_bytes_per_sector
+ * (nand_page_size >> 9)) > (28 + 2))
+ {
+ priv->ecc_cap =
+ ecc_info_table[i].ecc_cap;
+ priv->ecc_threshold =
+ ecc_info_table[i].ecc_threshold;
+ ecc_bytes = ecc_info_table[i].ecc_bytes_per_sector
+ * (nand_page_size >> 9);
+ break;
+ }
+ }
+
+ return ecc_bytes;
+}
+
+static void __init asm9260_nand_ecc_conf(struct asm9260_nand_priv *priv)
+{
+ struct nand_chip *nand = &priv->nand;
+ struct mtd_info *mtd = &priv->mtd;
+
+ if (nand->ecc.mode == NAND_ECC_HW) {
+ /* ECC is calculated for the whole page (1 step) */
+ nand->ecc.size = mtd->writesize;
+
+ /* set ECC page size and oob layout */
+ switch (mtd->writesize) {
+ case 2048:
+ nand->ecc.bytes =
+ asm9260_ecc_cap_select(priv, 2048,
+ mtd->oobsize);
+ nand->ecc.layout = &asm9260_nand_oob_64;
+ nand->ecc.strength = 4;
+ break;
+
+ case 4096:
+ nand->ecc.bytes =
+ asm9260_ecc_cap_select(priv, 4096,
+ mtd->oobsize);
+
+ if (mtd->oobsize == 128) {
+ nand->ecc.layout =
+ &asm9260_nand_oob_128;
+ nand->ecc.strength = 6;
+ } else if (mtd->oobsize == 218) {
+ nand->ecc.layout =
+ &asm9260_nand_oob_218;
+ nand->ecc.strength = 14;
+ } else if (mtd->oobsize == 224) {
+ nand->ecc.layout =
+ &asm9260_nand_oob_224;
+ nand->ecc.strength = 14;
+ } else
+ dev_err(priv->dev, "Unsupported Oob size [%d].\n",
+ mtd->oobsize);
+
+ break;
+
+ case 8192:
+ nand->ecc.bytes =
+ asm9260_ecc_cap_select(priv, 8192,
+ mtd->oobsize);
+
+ if (mtd->oobsize == 256) {
+ nand->ecc.layout =
+ &asm9260_nand_oob_256;
+ nand->ecc.strength = 8;
+ } else if (mtd->oobsize == 436) {
+ nand->ecc.layout =
+ &asm9260_nand_oob_436;
+ nand->ecc.strength = 14;
+ } else if (mtd->oobsize == 448) {
+ nand->ecc.layout =
+ &asm9260_nand_oob_448;
+ nand->ecc.strength = 16;
+ } else
+ dev_err(priv->dev, "Unsupported Oob size [%d].\n",
+ mtd->oobsize);
+ break;
+
+ default:
+ dev_err(priv->dev, "Unsupported Page size [%d].\n",
+ mtd->writesize);
+ break;
+ }
+ }
+
+ priv->spare_size = mtd->oobsize - nand->ecc.bytes;
+}
+
+static int __init asm9260_nand_get_dt_clks(struct asm9260_nand_priv *priv)
+{
+ struct device_node *np = priv->dev->of_node;
+ int clk_idx = 0, err;
+
+ priv->clk = of_clk_get(np, clk_idx);
+ if (IS_ERR(priv->clk))
+ goto out_err;
+
+ /* configure AHB clock */
+ clk_idx = 1;
+ priv->clk_ahb = of_clk_get(np, clk_idx);
+ if (IS_ERR(priv->clk_ahb))
+ goto out_err;
+
+ err = clk_prepare_enable(priv->clk_ahb);
+ if (err)
+ dev_err(priv->dev, "Failed to enable ahb_clk!\n");
+
+ err = clk_set_rate(priv->clk, clk_get_rate(priv->clk_ahb));
+ if (err)
+ dev_err(priv->dev, "Failed to set rate!\n");
+
+ err = clk_prepare_enable(priv->clk);
+ if (err)
+ dev_err(priv->dev, "Failed to enable clk!\n");
+
+ return 0;
+out_err:
+ dev_err(priv->dev, "%s: Failed to get clk (%i)\n", __func__, clk_idx);
+ return 1;
+}
+
+static int __init asm9260_nand_probe(struct platform_device *pdev)
+{
+ struct asm9260_nand_priv *priv;
+ struct nand_chip *nand;
+ struct mtd_info *mtd;
+ struct device_node *np = pdev->dev.of_node;
+ int ret;
+ unsigned int irq;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct asm9260_nand_priv),
+ GFP_KERNEL);
+ if (!priv) {
+ dev_err(&pdev->dev, "Allocation filed!\n");
+ return -ENOMEM;
+ }
+
+ priv->base = of_io_request_and_map(np, 0, np->full_name);
+ if (!priv->base) {
+ dev_err(&pdev->dev, "Unable to map resource!\n");
+ return -EINVAL;
+ }
+
+ priv->dev = &pdev->dev;
+ nand = &priv->nand;
+ nand->priv = priv;
+
+ platform_set_drvdata(pdev, priv);
+ mtd = &priv->mtd;
+ mtd->priv = nand;
+ mtd->owner = THIS_MODULE;
+ mtd->name = dev_name(&pdev->dev);
+
+ priv->read_cache_cnt = 0;
+ priv->irq_done = 0;
+
+ /* FIXME: add more dt options? for example chip number? */
+ if (asm9260_nand_get_dt_clks(priv))
+ return -ENODEV;
+
+ irq = irq_of_parse_and_map(np, 0);
+ if (!irq)
+ return -ENODEV;
+
+ iowrite32(0, priv->base + HW_INT_MASK);
+ ret = devm_request_irq(priv->dev, irq, asm9260_nand_irq,
+ IRQF_ONESHOT | IRQF_SHARED,
+ dev_name(&pdev->dev), priv);
+
+ asm9260_nand_init_chip(nand);
+
+ asm9260_nand_timing_config(priv);
+
+ /* first scan to find the device and get the page size */
+ if (nand_scan_ident(mtd, 1, NULL)) {
+ dev_err(&pdev->dev, "scan_ident filed!\n");
+ return -ENXIO;
+ }
+
+ asm9260_nand_ecc_conf(priv);
+ asm9260_nand_cached_config(priv);
+
+ /* second phase scan */
+ if (nand_scan_tail(mtd)) {
+ dev_err(&pdev->dev, "scan_tail filed!\n");
+ return -ENXIO;
+ }
+
+
+ ret = mtd_device_parse_register(mtd, NULL,
+ &(struct mtd_part_parser_data) {
+ .of_node = pdev->dev.of_node,
+ },
+ NULL, 0);
+
+ return ret;
+}
+
+
+static int asm9260_nand_remove(struct platform_device *pdev)
+{
+ struct asm9260_nand_priv *priv = platform_get_drvdata(pdev);
+
+ nand_release(&priv->mtd);
+
+ return 0;
+}
+
+static const struct of_device_id asm9260_nand_match[] =
+{
+ {
+ .compatible = "alphascale,asm9260-nand",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, asm9260_nand_match);
+
+static struct platform_driver asm9260_nand_driver = {
+ .probe = asm9260_nand_probe,
+ .remove = asm9260_nand_remove,
+ .driver = {
+ .name = "asm9260_nand",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(asm9260_nand_match),
+ },
+};
+
+module_platform_driver(asm9260_nand_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ASM9260 NAND driver");
--
1.9.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH RFC] Alphascale ASM9260 NAND controller driver
2014-12-17 11:45 [PATCH RFC] Alphascale ASM9260 NAND controller driver Oleksij Rempel
2014-12-17 11:45 ` [PATCH RFC] mtd: nand: add asm9260 NFC driver Oleksij Rempel
@ 2014-12-17 13:24 ` Boris Brezillon
2014-12-17 14:36 ` Oleksij Rempel
1 sibling, 1 reply; 24+ messages in thread
From: Boris Brezillon @ 2014-12-17 13:24 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: computersforpeace, linux-mtd
Hi Oleksij,
On Wed, 17 Dec 2014 12:45:17 +0100
Oleksij Rempel <linux@rempel-privat.de> wrote:
> I collected some questions with this driver. It will be great if you
> can help me:
> * Do HW_ECC driver should provideo option for SW_ECC?
This is not mandatory. I did it in the sunxi driver for testing
purpose, but it should work fine without it.
> * My HW do ECC correction for n-bits per 512B, it mean it will
> split block in 512B parts. If one of part has uncorrectable error, complete block will be
> reported as failed. Are there any way for partial recovery?
I think what you're calling block here is actually a page.
Regarding your question, there is no way to recover part of a page, but
each ECC block should be tested and max_bitflip should be returned
(even if some ECC blocks contains too many errors to be corrected).
Take a look at [1] for an example.
> * This HW reports count of ECC bitflips for each part. Do i need to return count of errors on
> all parts for one block?
Again: block == page, and no, you should only return the maximum
corrected bitflips (same example [1]).
> * If HW_ECC different stranges, how it should be configured? With DT or automatically
> calculated?
I don't get that one...
Are you talking about ECC requirements (ECC strength and steps) ?
Best Regards,
Boris
[1]http://lxr.free-electrons.com/source/drivers/mtd/nand/nand_base.c#L1157
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH RFC] Alphascale ASM9260 NAND controller driver
2014-12-17 13:24 ` [PATCH RFC] Alphascale ASM9260 NAND controller driver Boris Brezillon
@ 2014-12-17 14:36 ` Oleksij Rempel
2014-12-17 15:01 ` Oleksij Rempel
2014-12-17 15:01 ` Boris Brezillon
0 siblings, 2 replies; 24+ messages in thread
From: Oleksij Rempel @ 2014-12-17 14:36 UTC (permalink / raw)
To: Boris Brezillon; +Cc: computersforpeace, linux-mtd
[-- Attachment #1: Type: text/plain, Size: 2362 bytes --]
Am 17.12.2014 um 14:24 schrieb Boris Brezillon:
> Hi Oleksij,
>
> On Wed, 17 Dec 2014 12:45:17 +0100
> Oleksij Rempel <linux@rempel-privat.de> wrote:
>
>> I collected some questions with this driver. It will be great if you
>> can help me:
>> * Do HW_ECC driver should provideo option for SW_ECC?
>
> This is not mandatory. I did it in the sunxi driver for testing
> purpose, but it should work fine without it.
>
>> * My HW do ECC correction for n-bits per 512B, it mean it will
>> split block in 512B parts. If one of part has uncorrectable error, complete block will be
>> reported as failed. Are there any way for partial recovery?
>
> I think what you're calling block here is actually a page.
> Regarding your question, there is no way to recover part of a page, but
> each ECC block should be tested and max_bitflip should be returned
> (even if some ECC blocks contains too many errors to be corrected).
> Take a look at [1] for an example.
From Flash point of view block or part which i mean != page. In my case
it is TOSHIBA TC58NVG0S3ETA00, organized as (2048 + 64) bytes × 64 pages
× 1024blocks. The NFC will split each each 2048B page to 512B
blocks/???/parts/sub_page/ecc_step/better_name.
HW_ECC provide bit_flip counter for each sub_page. If all sub_pages are
recovered, there is no problem. Beside the question, how many bit should
be counted. Right now max_bitflips = sum(all_sub_pages).
Second problem which i have is that, there is only one ecc_error flag
for all sub_pages. If one sub_page filed, i don't know which one. Ecc
erroc counter register can't help here. Only way is to reread complete
page with SW_ECC.
>> * This HW reports count of ECC bitflips for each part. Do i need to return count of errors on
>> all parts for one block?
>
> Again: block == page, and no, you should only return the maximum
> corrected bitflips (same example [1]).
>
>
>> * If HW_ECC different stranges, how it should be configured? With DT or automatically
>> calculated?
>
> I don't get that one...
> Are you talking about ECC requirements (ECC strength and steps) ?
Yes. Well, i can't choice ECC step(it is 512B on this HW), only strange.
> Best Regards,
>
> Boris
>
> [1]http://lxr.free-electrons.com/source/drivers/mtd/nand/nand_base.c#L1157
>
--
Regards,
Oleksij
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 213 bytes --]
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH RFC] Alphascale ASM9260 NAND controller driver
2014-12-17 14:36 ` Oleksij Rempel
@ 2014-12-17 15:01 ` Oleksij Rempel
2014-12-17 15:01 ` Boris Brezillon
1 sibling, 0 replies; 24+ messages in thread
From: Oleksij Rempel @ 2014-12-17 15:01 UTC (permalink / raw)
To: Boris Brezillon; +Cc: computersforpeace, linux-mtd
[-- Attachment #1: Type: text/plain, Size: 2791 bytes --]
Am 17.12.2014 um 15:36 schrieb Oleksij Rempel:
> Am 17.12.2014 um 14:24 schrieb Boris Brezillon:
>> Hi Oleksij,
>>
>> On Wed, 17 Dec 2014 12:45:17 +0100
>> Oleksij Rempel <linux@rempel-privat.de> wrote:
>>
>>> I collected some questions with this driver. It will be great if you
>>> can help me:
>>> * Do HW_ECC driver should provideo option for SW_ECC?
>>
>> This is not mandatory. I did it in the sunxi driver for testing
>> purpose, but it should work fine without it.
>>
>>> * My HW do ECC correction for n-bits per 512B, it mean it will
>>> split block in 512B parts. If one of part has uncorrectable error, complete block will be
>>> reported as failed. Are there any way for partial recovery?
>>
>> I think what you're calling block here is actually a page.
>> Regarding your question, there is no way to recover part of a page, but
>> each ECC block should be tested and max_bitflip should be returned
>> (even if some ECC blocks contains too many errors to be corrected).
>> Take a look at [1] for an example.
>
> From Flash point of view block or part which i mean != page. In my case
> it is TOSHIBA TC58NVG0S3ETA00, organized as (2048 + 64) bytes × 64 pages
> × 1024blocks. The NFC will split each each 2048B page to 512B
> blocks/???/parts/sub_page/ecc_step/better_name.
>
> HW_ECC provide bit_flip counter for each sub_page. If all sub_pages are
> recovered, there is no problem. Beside the question, how many bit should
> be counted. Right now max_bitflips = sum(all_sub_pages).
Ok, i understood. max_bitflips = sum(all_sub_pages) makes no sense,
since it is not indicating actual pre fail state of one eccstep/subpage.
> Second problem which i have is that, there is only one ecc_error flag
> for all sub_pages. If one sub_page filed, i don't know which one. Ecc
> erroc counter register can't help here. Only way is to reread complete
> page with SW_ECC.
>
>>> * This HW reports count of ECC bitflips for each part. Do i need to return count of errors on
>>> all parts for one block?
>>
>> Again: block == page, and no, you should only return the maximum
>> corrected bitflips (same example [1]).
>>
>>
>>> * If HW_ECC different stranges, how it should be configured? With DT or automatically
>>> calculated?
>>
>> I don't get that one...
>> Are you talking about ECC requirements (ECC strength and steps) ?
>
> Yes. Well, i can't choice ECC step(it is 512B on this HW), only strange.
>
>> Best Regards,
>>
>> Boris
>>
>> [1]http://lxr.free-electrons.com/source/drivers/mtd/nand/nand_base.c#L1157
>>
>
>
>
>
> ______________________________________________________
> Linux MTD discussion mailing list
> http://lists.infradead.org/mailman/listinfo/linux-mtd/
>
--
Regards,
Oleksij
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 213 bytes --]
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH RFC] Alphascale ASM9260 NAND controller driver
2014-12-17 14:36 ` Oleksij Rempel
2014-12-17 15:01 ` Oleksij Rempel
@ 2014-12-17 15:01 ` Boris Brezillon
1 sibling, 0 replies; 24+ messages in thread
From: Boris Brezillon @ 2014-12-17 15:01 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: computersforpeace, linux-mtd
On Wed, 17 Dec 2014 15:36:49 +0100
Oleksij Rempel <linux@rempel-privat.de> wrote:
> Am 17.12.2014 um 14:24 schrieb Boris Brezillon:
> > Hi Oleksij,
> >
> > On Wed, 17 Dec 2014 12:45:17 +0100
> > Oleksij Rempel <linux@rempel-privat.de> wrote:
> >
> >> I collected some questions with this driver. It will be great if you
> >> can help me:
> >> * Do HW_ECC driver should provideo option for SW_ECC?
> >
> > This is not mandatory. I did it in the sunxi driver for testing
> > purpose, but it should work fine without it.
> >
> >> * My HW do ECC correction for n-bits per 512B, it mean it will
> >> split block in 512B parts. If one of part has uncorrectable error, complete block will be
> >> reported as failed. Are there any way for partial recovery?
> >
> > I think what you're calling block here is actually a page.
> > Regarding your question, there is no way to recover part of a page, but
> > each ECC block should be tested and max_bitflip should be returned
> > (even if some ECC blocks contains too many errors to be corrected).
> > Take a look at [1] for an example.
>
> From Flash point of view block or part which i mean != page. In my case
> it is TOSHIBA TC58NVG0S3ETA00, organized as (2048 + 64) bytes × 64 pages
> × 1024blocks. The NFC will split each each 2048B page to 512B
> blocks/???/parts/sub_page/ecc_step/better_name.
Okay, so let's call it ECC step or ECC block, not just block, cause a
NAND block is containing several NAND pages.
>
> HW_ECC provide bit_flip counter for each sub_page. If all sub_pages are
> recovered, there is no problem. Beside the question, how many bit should
> be counted. Right now max_bitflips = sum(all_sub_pages).
Then you should change it to return
max(nbitflips_on_chunk1, nbitflips_on_chunk2, ...)
>
> Second problem which i have is that, there is only one ecc_error flag
> for all sub_pages. If one sub_page filed, i don't know which one. Ecc
> erroc counter register can't help here. Only way is to reread complete
> page with SW_ECC.
Then I don't have any solution right now (I'll think about it)...
>
> >> * This HW reports count of ECC bitflips for each part. Do i need to return count of errors on
> >> all parts for one block?
> >
> > Again: block == page, and no, you should only return the maximum
> > corrected bitflips (same example [1]).
> >
> >
> >> * If HW_ECC different stranges, how it should be configured? With DT or automatically
> >> calculated?
> >
> > I don't get that one...
> > Are you talking about ECC requirements (ECC strength and steps) ?
>
> Yes. Well, i can't choice ECC step(it is 512B on this HW), only strange.
You mean strength :-), then you should either use the values returned
by ONFI or the one defined in the strength_ds and step_ds fields.
DT porperties should override those definitions (for example to force
ECC strength based on a specific bootloader config).
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH RFC] mtd: nand: add asm9260 NFC driver
2014-12-17 11:45 ` [PATCH RFC] mtd: nand: add asm9260 NFC driver Oleksij Rempel
@ 2014-12-17 16:15 ` Boris Brezillon
2014-12-30 18:09 ` [PATCH RFC v2] " Oleksij Rempel
0 siblings, 1 reply; 24+ messages in thread
From: Boris Brezillon @ 2014-12-17 16:15 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: computersforpeace, linux-mtd
Hi Oleksij,
Here is a quick review (didn't look at driver's internal logic yet).
On Wed, 17 Dec 2014 12:45:18 +0100
Oleksij Rempel <linux@rempel-privat.de> wrote:
I know that I'm the last person that should be saying this (as I often
send patches without any commit message), but you should really add a
commit message.
Moreover, you should really run scripts/checkpatch.pl on your patch
before sending it. Here is the result:
"total: 29 errors, 75 warnings, 1168 lines checked"
I won't detail all these errors/warnings in this review, but please
make sure all of them are gone before sending a new version.
And please add a documentation entry for your DT binding (in a
separate patch).
> Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
> ---
> drivers/mtd/nand/Kconfig | 7 +
> drivers/mtd/nand/Makefile | 1 +
> drivers/mtd/nand/asm9260_nand.c | 1148 +++++++++++++++++++++++++++++++++++++++
> 3 files changed, 1156 insertions(+)
> create mode 100644 drivers/mtd/nand/asm9260_nand.c
>
> diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
> index dd10646..580a608 100644
> --- a/drivers/mtd/nand/Kconfig
> +++ b/drivers/mtd/nand/Kconfig
> @@ -41,6 +41,13 @@ config MTD_SM_COMMON
> tristate
> default n
>
> +config MTD_NAND_ASM9260
> + tristate "NFC support for ASM9260 SoC"
> + depends on OF
> + default n
> + help
> + Enable support for the NAND controller found on Alphascale ASM9260 SoC.
> +
> config MTD_NAND_DENALI
> tristate "Support Denali NAND controller"
> depends on HAS_DMA
> diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
> index 9c847e4..08d660a 100644
> --- a/drivers/mtd/nand/Makefile
> +++ b/drivers/mtd/nand/Makefile
> @@ -8,6 +8,7 @@ obj-$(CONFIG_MTD_NAND_BCH) += nand_bch.o
> obj-$(CONFIG_MTD_NAND_IDS) += nand_ids.o
> obj-$(CONFIG_MTD_SM_COMMON) += sm_common.o
>
> +obj-$(CONFIG_MTD_NAND_ASM9260) += asm9260_nand.o
> obj-$(CONFIG_MTD_NAND_CAFE) += cafe_nand.o
> obj-$(CONFIG_MTD_NAND_AMS_DELTA) += ams-delta.o
> obj-$(CONFIG_MTD_NAND_DENALI) += denali.o
> diff --git a/drivers/mtd/nand/asm9260_nand.c b/drivers/mtd/nand/asm9260_nand.c
> new file mode 100644
> index 0000000..67dde23
> --- /dev/null
> +++ b/drivers/mtd/nand/asm9260_nand.c
> @@ -0,0 +1,1148 @@
> +/*
> + * NAND controller driver for Alphascale ASM9260, which is probably
> + * based on Evatronix NANDFLASH-CTRL IP (version unknown)
> + *
> + * Copyright (C), 2007-2013, Alphascale Tech. Co., Ltd.
> + * 2014 Oleksij Rempel <linux@rempel-privat.de>
> + *
> + * 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/interrupt.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/mtd/nand.h>
> +#include <linux/mtd/partitions.h>
> +#include <linux/clk.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_platform.h>
> +
> +#define mtd_to_priv(m) container_of(m, struct asm9260_nand_priv, mtd)
> +
> +#define HW_CMD 0x00
> +#define BM_CMD_CMD2_S 24
> +#define BM_CMD_CMD1_S 16
> +#define BM_CMD_CMD0_S 8
> +/* 0 - ADDR0, 1 - ADDR1 */
> +#define BM_CMD_ADDR1 BIT(7)
> +/* 0 - PIO, 1 - DMA */
> +#define BM_CMD_DMA BIT(6)
> +#define BM_CMD_CMDSEQ_S 0
> +/* FIXME: some description for SEQ? */
From what I know these sequences can be easily described (they are
only describing operation sequences like READ, WRITE, ...).
Choosing a more talkative name or adding a comment would be fine.
> +#define SEQ1 0x21 /* 6'b100001 */
> +#define SEQ2 0x22 /* 6'b100010 */
> +#define SEQ4 0x24 /* 6'b100100 */
> +#define SEQ5 0x25 /* 6'b100101 */
> +#define SEQ6 0x26 /* 6'b100110 */
> +#define SEQ7 0x27 /* 6'b100111 */
> +#define SEQ9 0x29 /* 6'b101001 */
> +#define SEQ10 0x2a /* 6'b101010 */
> +#define SEQ11 0x2b /* 6'b101011 */
> +#define SEQ15 0x2f /* 6'b101111 */
> +#define SEQ0 0x00 /* 6'b000000 */
> +#define SEQ3 0x03 /* 6'b000011 */
> +#define SEQ8 0x08 /* 6'b001000 */
> +#define SEQ12 0x0c /* 6'b001100 */
> +#define SEQ13 0x0d /* 6'b001101 */
> +#define SEQ14 0x0e /* 6'b001110 */
> +#define SEQ16 0x30 /* 6'b110000 */
> +#define SEQ17 0x15 /* 6'b010101 */
> +#define SEQ18 0x32 /* 6'h110010 */
> +
[...]
> +
> +/*8KB--16*512B, correction ability: 16bit--26Byte ecc*/
> +static struct nand_ecclayout asm9260_nand_oob_448 = {
> + .eccbytes = 16*26,
> + .eccpos = {
> + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
> + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
> + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
> + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
> +
> + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
> + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
> + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
> + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
> +
> + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
> + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
> + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
> + 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
> +
> + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
> + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,
> + 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271,
> + 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287,
> +
> + 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303,
> + 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319,
> + 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335,
> + 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351,
> +
> + 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367,
> + 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383,
> + 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398 ,399,
> + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415,
> +
> + 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431,
> + 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447},
> + .oobfree = {{2, 30}}
> +};
Do you really have to keep these ECC layouts as static definitions ?
From what I see here you're reserving the last OOB bytes for ECC (the
number of reserved ECC bytes depend on ECC strength), and it should
be pretty easy to build it dynamically...
> +
> +/**
> + * struct ecc_info - ASAP1826T ECC INFO Structure
> + * @ecc_cap: The ECC module correction ability.
> + * @ecc_threshold: The acceptable errors level
> + * @ecc_bytes_per_sector: ECC bytes per sector
> + */
> +struct ecc_info {
> + int ecc_cap;
> + int ecc_threshold;
Can you name this field ecc_strength, so that we clearly see the
relationship between the ecc->strength field and this one ?
> + int ecc_bytes_per_sector;
> +};
> +
> +/*
> +* ECC info list
> +*
> +* ecc_cap, ecc_threshold, ecc bytes per sector
> +*/
> +struct ecc_info ecc_info_table[8] = {
> + {ECC_CAP_2, ECC_THRESHOLD_2, 4},
> + {ECC_CAP_4, ECC_THRESHOLD_4, 7},
> + {ECC_CAP_6, ECC_THRESHOLD_6, 10},
> + {ECC_CAP_8, ECC_THRESHOLD_8, 13},
> + {ECC_CAP_10, ECC_THRESHOLD_10, 17},
> + {ECC_CAP_12, ECC_THRESHOLD_12, 20},
> + {ECC_CAP_14, ECC_THRESHOLD_14, 23},
> + {ECC_CAP_16, ECC_THRESHOLD_15, 26},
> +};
> +
> +static void asm9260_reg_rmw(struct asm9260_nand_priv *priv,
> + u32 reg_offset, u32 set, u32 clr)
> +{
> + u32 val;
> +
> + val = ioread32(priv->base + reg_offset);
> + val &= ~clr;
> + val |= set;
> + iowrite32(val, priv->base + reg_offset);
> +}
> +
> +static void asm9260_select_chip(struct mtd_info *mtd, int chip)
> +{
> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> +
> + if (chip == -1)
> + iowrite32(BM_MEM_CTRL_WP_STATE_MASK, priv->base + HW_MEM_CTRL);
> + else
> + iowrite32(BM_MEM_CTRL_UNWPn(chip) | BM_MEM_CTRL_CEn(chip),
> + priv->base + HW_MEM_CTRL);
> +}
> +
> +static void asm9260_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
> +{
> +}
If you don't do anything in this function just drop it, though I'd
prefer to have this one implemented instead of the
asm9260_nand_command_lp and asm9260_nand_command functions (if
possible).
> +
> +/* TODO: 3 commands are supported by HW. 3-d can be used for TWO PLANE. */
> +static void asm9260_nand_cmd_prep(struct asm9260_nand_priv *priv,
> + u8 cmd0, u8 cmd1, u8 cmd2, u8 seq)
> +{
> + priv->cmd_cache = (cmd0 << BM_CMD_CMD0_S) | (cmd1 << BM_CMD_CMD1_S);
> + priv->cmd_cache |= seq << BM_CMD_CMDSEQ_S;
> +}
> +
> +static dma_addr_t asm9260_nand_dma_set(struct mtd_info *mtd, void *buf,
> + enum dma_data_direction dir, size_t size)
> +{
> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> + dma_addr_t dma_addr;
> +
> + dma_addr = dma_map_single(priv->dev, buf, size, dir);
> + if (dma_mapping_error(priv->dev, dma_addr)) {
> + dev_err(priv->dev, "dma_map_single failed!\n");
> + return dma_addr;
> +
> + }
> +
> + iowrite32(dma_addr, priv->base + HW_DMA_ADDR);
> + iowrite32(size, priv->base + HW_DMA_CNT);
> + iowrite32(BM_DMA_CTRL_START
> + | (dir == DMA_FROM_DEVICE ? BM_DMA_CTRL_FROM_DEVICE : 0)
> + /* TODO: check different DMA_BURST_INCR16 settings */
> + | (DMA_BURST_INCR16 << BM_DMA_CTRL_BURST_S),
> + priv->base + HW_DMA_CTRL);
> + return dma_addr;
> +}
> +
> +/* complete command request */
> +static void asm9260_nand_cmd_comp(struct mtd_info *mtd, int dma)
> +{
> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> + int timeout;
> + u32 cmd;
> +
> + if (!priv->cmd_cache)
> + return;
> +
> + if (dma) {
> + priv->cmd_cache |= BM_CMD_DMA;
> + priv->irq_done = 0;
> + /* FIXME: should we allow all MEM* device? */
> + iowrite32(BM_INT_MEM0_RDY, priv->base + HW_INT_MASK);
> + }
> +
> + iowrite32(priv->cmd_cache, priv->base + HW_CMD);
> + cmd = priv->cmd_cache;
> + priv->cmd_cache = 0;
> +
> + if (dma) {
> + struct nand_chip *nand = &priv->nand;
> +
> + /* FIXME: change timeout value */
> + timeout = wait_event_timeout(nand->controller->wq,
> + priv->irq_done, 1 * HZ);
> + if (timeout <= 0) {
> + dev_info(priv->dev,
> + "Request 0x%08x timed out\n", cmd);
> + /* TODO: Do something useful here? */
> + /* FIXME: if we have problems on DMA or PIO, we need to
> + * reset NFC. On asm9260 it is possible only with global
> + * reset register. How can we use it here? */
> + }
> + } else
> + nand_wait_ready(mtd);
> +}
> +
> +static int asm9260_nand_dev_ready(struct mtd_info *mtd)
> +{
> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> + u32 tmp;
> +
> + tmp = ioread32(priv->base + HW_STATUS);
> +
> + /* FIXME: use define instead of 0x1 */
> + return (!(tmp & BM_CTRL_NFC_BUSY) &&
> + (tmp & 0x1));
As stated in your FIXME, add a macro for the 0x1 value.
> +}
> +
> +static void asm9260_nand_ctrl(struct asm9260_nand_priv *priv, u32 set)
> +{
> + iowrite32(priv->ctrl_cache | set, priv->base + HW_CTRL);
> +}
> +
[...]
> +
> +static irqreturn_t asm9260_nand_irq(int irq, void *device_info)
> +{
> + struct asm9260_nand_priv *priv = device_info;
> + struct nand_chip *nand = &priv->nand;
> + u32 status;
> +
> + status = ioread32(priv->base + HW_INT_STATUS);
> + if (!status)
> + return IRQ_NONE;
> +
> + iowrite32(0, priv->base + HW_INT_MASK);
> + iowrite32(0, priv->base + HW_INT_STATUS);
> + priv->irq_done = 1;
> + wake_up(&nand->controller->wq);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void __init asm9260_nand_init_chip(struct nand_chip *nand_chip)
> +{
> + nand_chip->select_chip = asm9260_select_chip;
> + nand_chip->cmd_ctrl = asm9260_cmd_ctrl;
AFAIR, you don't need to specify this one as you're already specifying
select_chip and cmdfunc.
> + nand_chip->cmdfunc = asm9260_nand_command_lp;
> + nand_chip->read_byte = asm9260_nand_read_byte;
> + nand_chip->read_word = asm9260_nand_read_word;
> + nand_chip->read_buf = asm9260_nand_read_buf;
> + nand_chip->write_buf = asm9260_nand_write_buf;
> +
> + nand_chip->dev_ready = asm9260_nand_dev_ready;
> + nand_chip->chip_delay = 100;
> +
> + nand_chip->ecc.mode = NAND_ECC_HW;
> +
> + nand_chip->ecc.write_page = asm9260_nand_write_page_hwecc;
> + nand_chip->ecc.read_page = asm9260_nand_read_page_hwecc;
> +}
> +
> +static void __init asm9260_nand_cached_config(struct asm9260_nand_priv *priv)
> +{
> + struct nand_chip *nand = &priv->nand;
> + struct mtd_info *mtd = &priv->mtd;
> + u32 addr_cycles, col_cycles, block_shift;
> +
> + /* FIXME: remove it or replace it */
> + /* FIXME: these complete part need fixing */
> + block_shift = __ffs(mtd->erasesize) - nand->page_shift;
> + col_cycles = 2;
> + addr_cycles = col_cycles +
> + (((mtd->size >> mtd->writesize) > 65536) ? 3 : 2);
> +
> + priv->mem_status_mask = BM_CTRL_MEM0_RDY;
> + priv->ctrl_cache = BM_CTRL_READ_STAT
> + | addr_cycles << BM_CTRL_ADDR_CYCLE1_S
> + | ((nand->page_shift - 8) & 0x7) << BM_CTRL_PAGE_SIZE_S
> + | ((block_shift - 5) & 0x3) << BM_CTRL_BLOCK_SIZE_S
> + | BM_CTRL_INT_EN
> + | addr_cycles << BM_CTRL_ADDR_CYCLE0_S;
> +
> + iowrite32(priv->ecc_threshold << BM_ECC_ERR_THRESHOLD_S
> + | priv->ecc_cap << BM_ECC_CAP_S,
> + priv->base + HW_ECC_CTRL);
> + iowrite32(mtd->writesize + priv->spare_size,
> + priv->base + HW_ECC_OFFSET);
> +
> +}
> +
> +static void __init asm9260_nand_timing_config(struct asm9260_nand_priv *priv)
> +{
> + u32 twhr;
> + u32 trhw;
> + u32 trwh;
> + u32 trwp;
> + u32 tadl = 0;
> + u32 tccs = 0;
> + u32 tsync = 0;
> + u32 trr = 0;
> + u32 twb = 0;
> +
> + trwh = 1; //TWH;
> + trwp = 1; //TWP;
> + iowrite32((trwh << 4) | (trwp), priv->base + HW_TIMING_ASYN);
> +
> + twhr = 2;
> + trhw = 4;
> + iowrite32((twhr << 24) | (trhw << 16)
> + | (tadl << 8) | (tccs), priv->base + HW_TIM_SEQ_0);
> +
> + iowrite32((tsync << 16) | (trr << 9) | (twb),
> + priv->base + HW_TIM_SEQ_1);
You should take NAND chip specific timings here, not randomly assigning
values.
Take a look at [1].
> +}
> +
> +static int __init asm9260_ecc_cap_select(struct asm9260_nand_priv *priv,
> + int nand_page_size, int nand_oob_size)
> +{
> + int ecc_bytes = 0;
> + int i;
> +
> + for (i=(ARRAY_SIZE(ecc_info_table) - 1); i>=0; i--)
> + {
> + if ((nand_oob_size - ecc_info_table[i].ecc_bytes_per_sector
> + * (nand_page_size >> 9)) > (28 + 2))
> + {
> + priv->ecc_cap =
> + ecc_info_table[i].ecc_cap;
> + priv->ecc_threshold =
> + ecc_info_table[i].ecc_threshold;
> + ecc_bytes = ecc_info_table[i].ecc_bytes_per_sector
> + * (nand_page_size >> 9);
> + break;
> + }
You should really select the ECC config based on ECC strength and ECC
step instead of choosing it from the OOB size...
> + }
> +
> + return ecc_bytes;
> +}
> +
> +static void __init asm9260_nand_ecc_conf(struct asm9260_nand_priv *priv)
> +{
> + struct nand_chip *nand = &priv->nand;
> + struct mtd_info *mtd = &priv->mtd;
> +
> + if (nand->ecc.mode == NAND_ECC_HW) {
> + /* ECC is calculated for the whole page (1 step) */
> + nand->ecc.size = mtd->writesize;
> +
> + /* set ECC page size and oob layout */
> + switch (mtd->writesize) {
> + case 2048:
> + nand->ecc.bytes =
> + asm9260_ecc_cap_select(priv, 2048,
> + mtd->oobsize);
> + nand->ecc.layout = &asm9260_nand_oob_64;
> + nand->ecc.strength = 4;
> + break;
> +
> + case 4096:
> + nand->ecc.bytes =
> + asm9260_ecc_cap_select(priv, 4096,
> + mtd->oobsize);
> +
> + if (mtd->oobsize == 128) {
> + nand->ecc.layout =
> + &asm9260_nand_oob_128;
> + nand->ecc.strength = 6;
> + } else if (mtd->oobsize == 218) {
> + nand->ecc.layout =
> + &asm9260_nand_oob_218;
> + nand->ecc.strength = 14;
> + } else if (mtd->oobsize == 224) {
> + nand->ecc.layout =
> + &asm9260_nand_oob_224;
> + nand->ecc.strength = 14;
> + } else
> + dev_err(priv->dev, "Unsupported Oob size [%d].\n",
> + mtd->oobsize);
> +
> + break;
> +
> + case 8192:
> + nand->ecc.bytes =
> + asm9260_ecc_cap_select(priv, 8192,
> + mtd->oobsize);
> +
> + if (mtd->oobsize == 256) {
> + nand->ecc.layout =
> + &asm9260_nand_oob_256;
> + nand->ecc.strength = 8;
> + } else if (mtd->oobsize == 436) {
> + nand->ecc.layout =
> + &asm9260_nand_oob_436;
> + nand->ecc.strength = 14;
> + } else if (mtd->oobsize == 448) {
> + nand->ecc.layout =
> + &asm9260_nand_oob_448;
> + nand->ecc.strength = 16;
> + } else
> + dev_err(priv->dev, "Unsupported Oob size [%d].\n",
> + mtd->oobsize);
> + break;
> +
> + default:
> + dev_err(priv->dev, "Unsupported Page size [%d].\n",
> + mtd->writesize);
> + break;
> + }
> + }
Again, choose ECC config according to ECC requirements instead of using
as much bytes as possible.
> +
> + priv->spare_size = mtd->oobsize - nand->ecc.bytes;
> +}
> +
> +static int __init asm9260_nand_get_dt_clks(struct asm9260_nand_priv *priv)
> +{
> + struct device_node *np = priv->dev->of_node;
> + int clk_idx = 0, err;
> +
> + priv->clk = of_clk_get(np, clk_idx);
Use devm_clk_get + a name, and specify a "clock-names" property in your
DT, instead of using clk indexes.
> + if (IS_ERR(priv->clk))
> + goto out_err;
> +
> + /* configure AHB clock */
> + clk_idx = 1;
> + priv->clk_ahb = of_clk_get(np, clk_idx);
> + if (IS_ERR(priv->clk_ahb))
> + goto out_err;
> +
> + err = clk_prepare_enable(priv->clk_ahb);
> + if (err)
> + dev_err(priv->dev, "Failed to enable ahb_clk!\n");
> +
> + err = clk_set_rate(priv->clk, clk_get_rate(priv->clk_ahb));
> + if (err)
Disable (I mean disable_unprepare of course) clk_ahb in case of error.
> + dev_err(priv->dev, "Failed to set rate!\n");
> +
> + err = clk_prepare_enable(priv->clk);
> + if (err)
Ditto
> + dev_err(priv->dev, "Failed to enable clk!\n");
> +
> + return 0;
> +out_err:
> + dev_err(priv->dev, "%s: Failed to get clk (%i)\n", __func__, clk_idx);
> + return 1;
> +}
> +
> +static int __init asm9260_nand_probe(struct platform_device *pdev)
> +{
> + struct asm9260_nand_priv *priv;
> + struct nand_chip *nand;
> + struct mtd_info *mtd;
> + struct device_node *np = pdev->dev.of_node;
> + int ret;
> + unsigned int irq;
> +
> + priv = devm_kzalloc(&pdev->dev, sizeof(struct asm9260_nand_priv),
> + GFP_KERNEL);
> + if (!priv) {
> + dev_err(&pdev->dev, "Allocation filed!\n");
> + return -ENOMEM;
> + }
> +
> + priv->base = of_io_request_and_map(np, 0, np->full_name);
Use platform_get_resource + devm_ioremap_resource here (it does the
request and ioremap).
> + if (!priv->base) {
> + dev_err(&pdev->dev, "Unable to map resource!\n");
> + return -EINVAL;
> + }
> +
> + priv->dev = &pdev->dev;
> + nand = &priv->nand;
> + nand->priv = priv;
> +
> + platform_set_drvdata(pdev, priv);
> + mtd = &priv->mtd;
> + mtd->priv = nand;
> + mtd->owner = THIS_MODULE;
> + mtd->name = dev_name(&pdev->dev);
> +
> + priv->read_cache_cnt = 0;
> + priv->irq_done = 0;
> +
> + /* FIXME: add more dt options? for example chip number? */
Yes, if you support multiple chips, then you should specify which chip
is controller by which CS in your DT.
> + if (asm9260_nand_get_dt_clks(priv))
> + return -ENODEV;
> +
> + irq = irq_of_parse_and_map(np, 0);
Use plaform_get_irq.
> + if (!irq)
> + return -ENODEV;
> +
> + iowrite32(0, priv->base + HW_INT_MASK);
> + ret = devm_request_irq(priv->dev, irq, asm9260_nand_irq,
> + IRQF_ONESHOT | IRQF_SHARED,
> + dev_name(&pdev->dev), priv);
> +
> + asm9260_nand_init_chip(nand);
> +
> + asm9260_nand_timing_config(priv);
> +
> + /* first scan to find the device and get the page size */
> + if (nand_scan_ident(mtd, 1, NULL)) {
> + dev_err(&pdev->dev, "scan_ident filed!\n");
> + return -ENXIO;
> + }
> +
> + asm9260_nand_ecc_conf(priv);
> + asm9260_nand_cached_config(priv);
> +
> + /* second phase scan */
> + if (nand_scan_tail(mtd)) {
> + dev_err(&pdev->dev, "scan_tail filed!\n");
> + return -ENXIO;
> + }
> +
> +
> + ret = mtd_device_parse_register(mtd, NULL,
> + &(struct mtd_part_parser_data) {
> + .of_node = pdev->dev.of_node,
> + },
> + NULL, 0);
> +
> + return ret;
> +}
> +
> +
> +static int asm9260_nand_remove(struct platform_device *pdev)
> +{
> + struct asm9260_nand_priv *priv = platform_get_drvdata(pdev);
> +
Disable your clks here.
> + nand_release(&priv->mtd);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id asm9260_nand_match[] =
> +{
> + {
> + .compatible = "alphascale,asm9260-nand",
> + },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, asm9260_nand_match);
> +
> +static struct platform_driver asm9260_nand_driver = {
> + .probe = asm9260_nand_probe,
> + .remove = asm9260_nand_remove,
> + .driver = {
> + .name = "asm9260_nand",
> + .owner = THIS_MODULE,
> + .of_match_table = of_match_ptr(asm9260_nand_match),
> + },
> +};
> +
> +module_platform_driver(asm9260_nand_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("ASM9260 NAND driver");
That's all for now, but that's really a quick review.
I'll try to have a closer look at cmdfunc, read and write functions
later.
Best Regards,
Boris
[1]http://lxr.free-electrons.com/source/include/linux/mtd/nand.h#L1014
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH RFC v2] mtd: nand: add asm9260 NFC driver
2014-12-17 16:15 ` Boris Brezillon
@ 2014-12-30 18:09 ` Oleksij Rempel
2014-12-30 19:09 ` Boris Brezillon
0 siblings, 1 reply; 24+ messages in thread
From: Oleksij Rempel @ 2014-12-30 18:09 UTC (permalink / raw)
To: boris.brezillon, linux-mtd, computersforpeace; +Cc: Oleksij Rempel
Add driver for Nand Flash Controller used on Alphascales ASM9260 chips.
The IP core of this controller has some similarities with
Evatronix NANDFLASH-CTRL IP (unkown revision), so probably it can be reused
by some other SoCs.
Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
---
drivers/mtd/nand/Kconfig | 7 +
drivers/mtd/nand/Makefile | 1 +
drivers/mtd/nand/asm9260_nand.c | 989 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 997 insertions(+)
create mode 100644 drivers/mtd/nand/asm9260_nand.c
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index dd10646..580a608 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -41,6 +41,13 @@ config MTD_SM_COMMON
tristate
default n
+config MTD_NAND_ASM9260
+ tristate "NFC support for ASM9260 SoC"
+ depends on OF
+ default n
+ help
+ Enable support for the NAND controller found on Alphascale ASM9260 SoC.
+
config MTD_NAND_DENALI
tristate "Support Denali NAND controller"
depends on HAS_DMA
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 9c847e4..08d660a 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_MTD_NAND_BCH) += nand_bch.o
obj-$(CONFIG_MTD_NAND_IDS) += nand_ids.o
obj-$(CONFIG_MTD_SM_COMMON) += sm_common.o
+obj-$(CONFIG_MTD_NAND_ASM9260) += asm9260_nand.o
obj-$(CONFIG_MTD_NAND_CAFE) += cafe_nand.o
obj-$(CONFIG_MTD_NAND_AMS_DELTA) += ams-delta.o
obj-$(CONFIG_MTD_NAND_DENALI) += denali.o
diff --git a/drivers/mtd/nand/asm9260_nand.c b/drivers/mtd/nand/asm9260_nand.c
new file mode 100644
index 0000000..ae10907
--- /dev/null
+++ b/drivers/mtd/nand/asm9260_nand.c
@@ -0,0 +1,989 @@
+/*
+ * NAND controller driver for Alphascale ASM9260, which is probably
+ * based on Evatronix NANDFLASH-CTRL IP (version unknown)
+ *
+ * Copyright (C), 2014 Oleksij Rempel <linux@rempel-privat.de>
+ *
+ * Inspired by asm9260_nand.c,
+ * Copyright (C), 2007-2013, Alphascale Tech. Co., Ltd.
+ * 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/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <linux/clk.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/of_mtd.h>
+
+#define ASM9260_ECC_STEP 512
+#define ASM9260_ECC_MAX_BIT 16
+#define ASM9260_MAX_CHIPS 2
+
+#define mtd_to_priv(m) container_of(m, struct asm9260_nand_priv, mtd)
+
+#define HW_CMD 0x00
+#define BM_CMD_CMD2_S 24
+#define BM_CMD_CMD1_S 16
+#define BM_CMD_CMD0_S 8
+/* 0 - ADDR0, 1 - ADDR1 */
+#define BM_CMD_ADDR1 BIT(7)
+/* 0 - PIO, 1 - DMA */
+#define BM_CMD_DMA BIT(6)
+#define BM_CMD_CMDSEQ_S 0
+/* single command, wait for RnB */
+#define SEQ0 0x00
+/* send cmd, addr, wait tWHR, fetch data */
+#define SEQ1 0x21
+/* send cmd, addr, wait RnB, fetch data */
+#define SEQ2 0x22
+/* send cmd, addr, wait tADL, send data, wait RnB */
+#define SEQ3 0x03
+/* send cmd, wait tWHR, fetch data */
+#define SEQ4 0x24
+/* send cmd, 3 x addr, wait tWHR, fetch data */
+#define SEQ5 0x25
+/* wait tRHW, send cmd, 2 x addr, cmd, wait tCCS, fetch data */
+#define SEQ6 0x26
+/* wait tRHW, send cmd, 35 x addr, cmd, wait tCCS, fetch data */
+#define SEQ7 0x27
+/* send cmd, 2 x addr, wait tCCS, fetch data */
+#define SEQ8 0x08
+/* send cmd, 5 x addr, wait RnB */
+#define SEQ9 0x29
+/* send cmd, 5 x addr, cmd, wait RnB, fetch data */
+#define SEQ10 0x2a
+/* send cmd, wait RnB, fetch data */
+#define SEQ11 0x2b
+/* send cmd, 5 x addr, wait tADL, send data, cmd */
+#define SEQ12 0x0c
+/* send cmd, 5 x addr, wait tADL, send data */
+#define SEQ13 0x0d
+/* send cmd, 3 x addr, cmd, wait RnB */
+#define SEQ14 0x0e
+/* send cmd, 5 x addr, cmd, 5 x addr, cmd, wait RnB, fetch data */
+#define SEQ15 0x2f
+/* send cmd, 5 x addr, wait RnB, fetch data */
+#define SEQ17 0x15
+
+#define HW_CTRL 0x04
+#define BM_CTRL_DIS_STATUS BIT(23)
+#define BM_CTRL_READ_STAT BIT(22)
+#define BM_CTRL_SMALL_BLOCK_EN BIT(21)
+#define BM_CTRL_ADDR_CYCLE1_S 18
+#define ADDR_CYCLE_0 0x0
+#define ADDR_CYCLE_1 0x1
+#define ADDR_CYCLE_2 0x2
+#define ADDR_CYCLE_3 0x3
+#define ADDR_CYCLE_4 0x4
+#define ADDR_CYCLE_5 0x5
+#define BM_CTRL_ADDR1_AUTO_INCR BIT(17)
+#define BM_CTRL_ADDR0_AUTO_INCR BIT(16)
+#define BM_CTRL_WORK_MODE BIT(15)
+#define BM_CTRL_PORT_EN BIT(14)
+#define BM_CTRL_LOOKU_EN BIT(13)
+#define BM_CTRL_IO_16BIT BIT(12)
+/* Overwrite BM_CTRL_PAGE_SIZE with HW_DATA_SIZE */
+#define BM_CTRL_CUSTOM_PAGE_SIZE BIT(11)
+#define BM_CTRL_PAGE_SIZE_S 8
+#define BM_CTRL_PAGE_SIZE(x) ((ffs((x) >> 8) - 1) & 0x7)
+#define PAGE_SIZE_256B 0x0
+#define PAGE_SIZE_512B 0x1
+#define PAGE_SIZE_1024B 0x2
+#define PAGE_SIZE_2048B 0x3
+#define PAGE_SIZE_4096B 0x4
+#define PAGE_SIZE_8192B 0x5
+#define PAGE_SIZE_16384B 0x6
+#define PAGE_SIZE_32768B 0x7
+#define BM_CTRL_BLOCK_SIZE_S 6
+#define BM_CTRL_BLOCK_SIZE(x) ((ffs((x) >> 5) - 1) & 0x3)
+#define BLOCK_SIZE_32P 0x0
+#define BLOCK_SIZE_64P 0x1
+#define BLOCK_SIZE_128P 0x2
+#define BLOCK_SIZE_256P 0x3
+#define BM_CTRL_ECC_EN BIT(5)
+#define BM_CTRL_INT_EN BIT(4)
+#define BM_CTRL_SPARE_EN BIT(3)
+/* same values as BM_CTRL_ADDR_CYCLE1_S */
+#define BM_CTRL_ADDR_CYCLE0_S 0
+
+#define HW_STATUS 0x08
+#define BM_CTRL_NFC_BUSY BIT(8)
+/* MEM1_RDY (BIT1) - MEM7_RDY (BIT7) */
+#define BM_CTRL_MEM0_RDY BIT(0)
+
+#define HW_INT_MASK 0x0c
+#define HW_INT_STATUS 0x10
+#define BM_INT_FIFO_ERROR BIT(12)
+#define BM_INT_MEM_RDY_S 4
+/* MEM1_RDY (BIT5) - MEM7_RDY (BIT11) */
+#define BM_INT_MEM0_RDY BIT(4)
+#define BM_INT_ECC_TRSH_ERR BIT(3)
+#define BM_INT_ECC_FATAL_ERR BIT(2)
+#define BM_INT_CMD_END BIT(1)
+
+#define HW_ECC_CTRL 0x14
+/* bits per 512 bytes */
+#define BM_ECC_CAP_S 5
+/* support ecc strange 2, 4, 6, 8, 10, 12, 14, 16. */
+#define BM_ECC_CAPn(x) ((((x) >> 1) - 1) & 0x7)
+/* Warn if some bitflip level (threshold) reached. Max 15 bits. */
+#define BM_ECC_ERR_THRESHOLD_S 8
+#define BM_ECC_ERR_THRESHOLD_M 0xf
+#define BM_ECC_ERR_OVER BIT(2)
+/* Uncorrected error. */
+#define BM_ECC_ERR_UNC BIT(1)
+/* Corrected error. */
+#define BM_ECC_ERR_CORRECT BIT(0)
+
+#define HW_ECC_OFFSET 0x18
+#define HW_ADDR0_0 0x1c
+#define HW_ADDR1_0 0x20
+#define HW_ADDR0_1 0x24
+#define HW_ADDR1_1 0x28
+#define HW_SPARE_SIZE 0x30
+#define HW_DMA_ADDR 0x64
+#define HW_DMA_CNT 0x68
+
+#define HW_DMA_CTRL 0x6c
+#define BM_DMA_CTRL_START BIT(7)
+/* 0 - to device; 1 - from device */
+#define BM_DMA_CTRL_FROM_DEVICE BIT(6)
+/* 0 - software maneged; 1 - scatter-gather */
+#define BM_DMA_CTRL_SG BIT(5)
+#define BM_DMA_CTRL_BURST_S 2
+#define DMA_BURST_INCR4 0x0
+#define DMA_BURST_STREAM 0x1
+#define DMA_BURST_SINGLE 0x2
+#define DMA_BURST_INCR 0x3
+#define DMA_BURST_INCR8 0x4
+#define DMA_BURST_INCR16 0x5
+#define BM_DMA_CTRL_ERR BIT(1)
+#define BM_DMA_CTRL_RDY BIT(0)
+
+#define HW_MEM_CTRL 0x80
+#define BM_MEM_CTRL_WP_STATE_MASK 0xff00
+#define BM_MEM_CTRL_UNWPn(x) (1 << ((x) + 8))
+#define BM_MEM_CTRL_CEn(x) (((x) & 7) << 0)
+
+/* BM_CTRL_CUSTOM_PAGE_SIZE should be set */
+#define HW_DATA_SIZE 0x84
+#define HW_READ_STATUS 0x88
+#define HW_TIM_SEQ_0 0x8c
+#define HW_TIMING_ASYN 0x90
+#define HW_TIMING_SYN 0x94
+
+#define HW_FIFO_DATA 0x98
+#define HW_TIME_MODE 0x9c
+#define HW_FIFO_INIT 0xb0
+/*
+ * Counter for ecc related errors.
+ * For each 512 byte block it has 5bit counter.
+ */
+#define HW_ECC_ERR_CNT 0xb8
+
+#define HW_TIM_SEQ_1 0xc8
+
+struct asm9260_nand_priv {
+ struct device *dev;
+ struct mtd_info mtd;
+ struct nand_chip nand;
+ struct nand_ecclayout ecc_layout;
+
+ struct clk *clk;
+ struct clk *clk_ahb;
+
+ void __iomem *base;
+ int irq_done;
+
+ u32 read_cache;
+ int read_cache_cnt;
+ u32 cmd_cache;
+ u32 ctrl_cache;
+ u32 mem_mask;
+ u32 page_cache;
+ unsigned int wait_time;
+
+ unsigned int spare_size;
+};
+
+static void asm9260_reg_rmw(struct asm9260_nand_priv *priv,
+ u32 reg_offset, u32 set, u32 clr)
+{
+ u32 val;
+
+ val = ioread32(priv->base + reg_offset);
+ val &= ~clr;
+ val |= set;
+ iowrite32(val, priv->base + reg_offset);
+}
+
+static void asm9260_select_chip(struct mtd_info *mtd, int chip)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+
+ if (chip == -1)
+ iowrite32(BM_MEM_CTRL_WP_STATE_MASK, priv->base + HW_MEM_CTRL);
+ else
+ iowrite32(BM_MEM_CTRL_UNWPn(chip) | BM_MEM_CTRL_CEn(chip),
+ priv->base + HW_MEM_CTRL);
+}
+
+/* 3 commands are supported by HW. 3-d can be used for TWO PLANE. */
+static void asm9260_nand_cmd_prep(struct asm9260_nand_priv *priv,
+ u8 cmd0, u8 cmd1, u8 cmd2, u8 seq)
+{
+ priv->cmd_cache = (cmd0 << BM_CMD_CMD0_S) | (cmd1 << BM_CMD_CMD1_S);
+ priv->cmd_cache |= seq << BM_CMD_CMDSEQ_S;
+}
+
+static dma_addr_t asm9260_nand_dma_set(struct mtd_info *mtd, void *buf,
+ enum dma_data_direction dir, size_t size)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ dma_addr_t dma_addr;
+
+ dma_addr = dma_map_single(priv->dev, buf, size, dir);
+ if (dma_mapping_error(priv->dev, dma_addr)) {
+ dev_err(priv->dev, "dma_map_single failed!\n");
+ return dma_addr;
+
+ }
+
+ iowrite32(dma_addr, priv->base + HW_DMA_ADDR);
+ iowrite32(size, priv->base + HW_DMA_CNT);
+ iowrite32(BM_DMA_CTRL_START
+ | (dir == DMA_FROM_DEVICE ? BM_DMA_CTRL_FROM_DEVICE : 0)
+ | (DMA_BURST_INCR16 << BM_DMA_CTRL_BURST_S),
+ priv->base + HW_DMA_CTRL);
+ return dma_addr;
+}
+
+/* complete command request */
+static void asm9260_nand_cmd_comp(struct mtd_info *mtd, int dma)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ int timeout;
+ u32 cmd;
+
+ if (!priv->cmd_cache)
+ return;
+
+ if (dma) {
+ priv->cmd_cache |= BM_CMD_DMA;
+ priv->irq_done = 0;
+ iowrite32(priv->mem_mask << BM_INT_MEM_RDY_S,
+ priv->base + HW_INT_MASK);
+ }
+
+ iowrite32(priv->cmd_cache, priv->base + HW_CMD);
+ cmd = priv->cmd_cache;
+ priv->cmd_cache = 0;
+
+ if (dma) {
+ struct nand_chip *nand = &priv->nand;
+
+ timeout = wait_event_timeout(nand->controller->wq,
+ priv->irq_done,
+ msecs_to_jiffies(priv->wait_time ?
+ priv->wait_time : 20));
+ if (timeout <= 0) {
+ dev_info(priv->dev,
+ "Request 0x%08x timed out\n", cmd);
+ /* TODO: Do something useful here? */
+ /* FIXME: if we have problems on DMA or PIO, we need to
+ * reset NFC. On asm9260 it is possible only with global
+ * reset register. How can we use it here? */
+ }
+ priv->wait_time = 0;
+ } else
+ nand_wait_ready(mtd);
+}
+
+static int asm9260_nand_dev_ready(struct mtd_info *mtd)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ u32 tmp;
+
+ tmp = ioread32(priv->base + HW_STATUS);
+
+ return (!(tmp & BM_CTRL_NFC_BUSY) &&
+ (tmp & priv->mem_mask));
+}
+
+static void asm9260_nand_ctrl(struct asm9260_nand_priv *priv, u32 set)
+{
+ iowrite32(priv->ctrl_cache | set, priv->base + HW_CTRL);
+}
+
+static void asm9260_nand_set_addr(struct asm9260_nand_priv *priv,
+ u32 row_addr, u32 column)
+{
+ u32 addr[2];
+
+ addr[0] = (column & 0xffff) | (0xffff0000 & (row_addr << 16));
+ addr[1] = (row_addr >> 16) & 0xff;
+
+ iowrite32(addr[0], priv->base + HW_ADDR0_0);
+ iowrite32(addr[1], priv->base + HW_ADDR0_1);
+}
+
+static void asm9260_nand_command_lp(struct mtd_info *mtd,
+ unsigned int command, int column, int page_addr)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+
+ switch (command) {
+ case NAND_CMD_RESET:
+ asm9260_nand_cmd_prep(priv, NAND_CMD_RESET, 0, 0, SEQ0);
+ asm9260_nand_cmd_comp(mtd, 0);
+ break;
+
+ case NAND_CMD_READID:
+ iowrite32(1, priv->base + HW_FIFO_INIT);
+ iowrite32((ADDR_CYCLE_1 << BM_CTRL_ADDR_CYCLE1_S)
+ | BM_CTRL_CUSTOM_PAGE_SIZE
+ | (PAGE_SIZE_4096B << BM_CTRL_PAGE_SIZE_S)
+ | (BLOCK_SIZE_32P << BM_CTRL_BLOCK_SIZE_S)
+ | BM_CTRL_INT_EN
+ | (ADDR_CYCLE_1 << BM_CTRL_ADDR_CYCLE0_S),
+ priv->base + HW_CTRL);
+
+ iowrite32(8, priv->base + HW_DATA_SIZE);
+ iowrite32(column, priv->base + HW_ADDR0_0);
+ asm9260_nand_cmd_prep(priv, NAND_CMD_READID, 0, 0, SEQ1);
+
+ priv->read_cache_cnt = 0;
+ break;
+
+ case NAND_CMD_READOOB:
+ column += mtd->writesize;
+ command = NAND_CMD_READ0;
+ case NAND_CMD_READ0:
+ iowrite32(1, priv->base + HW_FIFO_INIT);
+
+ if (column == 0) {
+ asm9260_nand_ctrl(priv, 0);
+ iowrite32(priv->spare_size, priv->base + HW_SPARE_SIZE);
+ } else if (column == mtd->writesize) {
+ asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
+ iowrite32(mtd->oobsize, priv->base + HW_SPARE_SIZE);
+ iowrite32(mtd->oobsize, priv->base + HW_DATA_SIZE);
+ } else {
+ dev_err(priv->dev, "Couldn't support the column\n");
+ break;
+ }
+
+ asm9260_nand_set_addr(priv, page_addr, column);
+
+ asm9260_nand_cmd_prep(priv, NAND_CMD_READ0,
+ NAND_CMD_READSTART, 0, SEQ10);
+
+ priv->read_cache_cnt = 0;
+ break;
+ case NAND_CMD_SEQIN:
+ iowrite32(1, priv->base + HW_FIFO_INIT);
+
+ if (column == 0) {
+ priv->page_cache = page_addr;
+ asm9260_nand_ctrl(priv, 0);
+ iowrite32(priv->spare_size, priv->base + HW_SPARE_SIZE);
+ } else if (column == mtd->writesize) {
+ asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
+ iowrite32(mtd->oobsize, priv->base + HW_DATA_SIZE);
+ }
+
+ asm9260_nand_set_addr(priv, page_addr, column);
+
+ asm9260_nand_cmd_prep(priv, NAND_CMD_SEQIN, NAND_CMD_PAGEPROG,
+ 0, SEQ12);
+
+ break;
+ case NAND_CMD_STATUS:
+ iowrite32(1, priv->base + HW_FIFO_INIT);
+ asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
+
+ /*
+ * Workaround for status bug.
+ * Instead of SEQ4 we need to use SEQ1 here, which will
+ * send cmd with address. For this case we need to make sure
+ * ADDR == 0.
+ */
+ asm9260_nand_set_addr(priv, 0, 0);
+ iowrite32(4, priv->base + HW_DATA_SIZE);
+ asm9260_nand_cmd_prep(priv, NAND_CMD_STATUS, 0, 0, SEQ1);
+
+ priv->read_cache_cnt = 0;
+ break;
+
+ case NAND_CMD_ERASE1:
+ priv->wait_time = 400;
+ asm9260_nand_set_addr(priv, page_addr, column);
+
+ asm9260_nand_ctrl(priv, 0);
+
+ /*
+ * Prepare and send command now. We don't need to split it in
+ * two stages.
+ */
+ asm9260_nand_cmd_prep(priv, NAND_CMD_ERASE1, NAND_CMD_ERASE2,
+ 0, SEQ14);
+ asm9260_nand_cmd_comp(mtd, 0);
+ break;
+ default:
+ break;
+ }
+}
+
+
+/**
+ * We can't read less then 32 bits on HW_FIFO_DATA. So, to make
+ * read_byte and read_word happy, we use sort of cached 32bit read.
+ * Note: expected values for size should be 1 or 2 bytes.
+ */
+static u32 asm9260_nand_read_cached(struct mtd_info *mtd, int size)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ u8 tmp;
+
+ if ((priv->read_cache_cnt <= 0) || (priv->read_cache_cnt > 4)) {
+ asm9260_nand_cmd_comp(mtd, 0);
+ priv->read_cache = ioread32(priv->base + HW_FIFO_DATA);
+ priv->read_cache_cnt = 4;
+ }
+
+ tmp = priv->read_cache >> (8 * (4 - priv->read_cache_cnt));
+ priv->read_cache_cnt -= size;
+
+ return tmp;
+}
+
+static u8 asm9260_nand_read_byte(struct mtd_info *mtd)
+{
+ return 0xff & asm9260_nand_read_cached(mtd, 1);
+}
+
+static u16 asm9260_nand_read_word(struct mtd_info *mtd)
+{
+ return 0xffff & asm9260_nand_read_cached(mtd, 2);
+}
+
+static void asm9260_nand_read_buf(struct mtd_info *mtd, u8 *buf, int len)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ dma_addr_t dma_addr;
+ int dma_ok = 0;
+
+ if (len & 0x3) {
+ dev_err(priv->dev, "Unsupported length (%x)\n", len);
+ return;
+ }
+
+ /*
+ * I hate you UBI for your all vmalloc. Be slow as hell with PIO.
+ * ~ with love from ZeroCopy ~
+ */
+ if (!is_vmalloc_addr(buf)) {
+ dma_addr = asm9260_nand_dma_set(mtd, buf, DMA_FROM_DEVICE, len);
+ dma_ok = !(dma_mapping_error(priv->dev, dma_addr));
+ }
+ asm9260_nand_cmd_comp(mtd, dma_ok);
+
+ if (dma_ok) {
+ dma_sync_single_for_cpu(priv->dev, dma_addr, len,
+ DMA_FROM_DEVICE);
+ dma_unmap_single(priv->dev, dma_addr, len, DMA_FROM_DEVICE);
+ return;
+ }
+
+ /* fall back to pio mode */
+ len >>= 2;
+ ioread32_rep(priv->base + HW_FIFO_DATA, buf, len);
+}
+
+static void asm9260_nand_write_buf(struct mtd_info *mtd,
+ const u8 *buf, int len)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ dma_addr_t dma_addr;
+ int dma_ok = 0;
+
+ if (len & 0x3) {
+ dev_err(priv->dev, "Unsupported length (%x)\n", len);
+ return;
+ }
+
+ if (!is_vmalloc_addr(buf)) {
+ dma_addr = asm9260_nand_dma_set(mtd,
+ (void *)buf, DMA_TO_DEVICE, len);
+ dma_ok = !(dma_mapping_error(priv->dev, dma_addr));
+ }
+
+ if (dma_ok)
+ dma_sync_single_for_device(priv->dev, dma_addr, len,
+ DMA_TO_DEVICE);
+ asm9260_nand_cmd_comp(mtd, dma_ok);
+
+ if (dma_ok) {
+ dma_unmap_single(priv->dev, dma_addr, len, DMA_TO_DEVICE);
+ return;
+ }
+
+ /* fall back to pio mode */
+ len >>= 2;
+ iowrite32_rep(priv->base + HW_FIFO_DATA, buf, len);
+}
+
+static int asm9260_nand_write_page_raw(struct mtd_info *mtd,
+ struct nand_chip *chip, const u8 *buf,
+ int oob_required)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+
+ chip->write_buf(mtd, buf, mtd->writesize);
+ if (oob_required)
+ chip->ecc.write_oob(mtd, chip, priv->page_cache &
+ chip->pagemask);
+ return 0;
+}
+
+static int asm9260_nand_write_page_hwecc(struct mtd_info *mtd,
+ struct nand_chip *chip, const u8 *buf,
+ int oob_required)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+
+ asm9260_reg_rmw(priv, HW_CTRL, BM_CTRL_ECC_EN, 0);
+ chip->ecc.write_page_raw(mtd, chip, buf, oob_required);
+
+ return 0;
+}
+
+static unsigned int asm9260_nand_count_ecc(struct asm9260_nand_priv *priv)
+{
+ u32 tmp, i, count, maxcount = 0;
+
+ /* FIXME: this layout was tested only on 2048byte NAND.
+ * NANDs with bigger page size should use more registers. */
+ tmp = ioread32(priv->base + HW_ECC_ERR_CNT);
+ for (i = 0; i < 4; i++) {
+ count = 0x1f & (tmp >> (5 * i));
+ maxcount = max_t(unsigned int, maxcount, count);
+ }
+
+ return count;
+}
+
+static int asm9260_nand_read_page_hwecc(struct mtd_info *mtd,
+ struct nand_chip *chip, u8 *buf,
+ int oob_required, int page)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ u8 *temp_ptr;
+ u32 status, max_bitflips = 0;
+
+ temp_ptr = buf;
+
+ /* enable ecc */
+ asm9260_reg_rmw(priv, HW_CTRL, BM_CTRL_ECC_EN, 0);
+ chip->read_buf(mtd, temp_ptr, mtd->writesize);
+
+ status = ioread32(priv->base + HW_ECC_CTRL);
+
+ if (status & BM_ECC_ERR_UNC) {
+ u32 ecc_err;
+
+ ecc_err = ioread32(priv->base + HW_ECC_ERR_CNT);
+ /* check if it is erased page (all_DATA_OOB == 0xff) */
+ /* FIXME: should be tested if it is a bullet proof solution.
+ * if not, use is_buf_blank. */
+ if (ecc_err != 0x8421)
+ mtd->ecc_stats.failed++;
+
+ } else if (status & BM_ECC_ERR_CORRECT) {
+ max_bitflips = asm9260_nand_count_ecc(priv);
+ mtd->ecc_stats.corrected += max_bitflips;
+ }
+
+ if (oob_required)
+ chip->ecc.read_oob(mtd, chip, page);
+
+ return max_bitflips;
+}
+
+static irqreturn_t asm9260_nand_irq(int irq, void *device_info)
+{
+ struct asm9260_nand_priv *priv = device_info;
+ struct nand_chip *nand = &priv->nand;
+ u32 status;
+
+ status = ioread32(priv->base + HW_INT_STATUS);
+ if (!status)
+ return IRQ_NONE;
+
+ iowrite32(0, priv->base + HW_INT_MASK);
+ iowrite32(0, priv->base + HW_INT_STATUS);
+ priv->irq_done = 1;
+ wake_up(&nand->controller->wq);
+
+ return IRQ_HANDLED;
+}
+
+static void __init asm9260_nand_init_chip(struct nand_chip *nand_chip)
+{
+ nand_chip->select_chip = asm9260_select_chip;
+ nand_chip->cmdfunc = asm9260_nand_command_lp;
+ nand_chip->read_byte = asm9260_nand_read_byte;
+ nand_chip->read_word = asm9260_nand_read_word;
+ nand_chip->read_buf = asm9260_nand_read_buf;
+ nand_chip->write_buf = asm9260_nand_write_buf;
+
+ nand_chip->dev_ready = asm9260_nand_dev_ready;
+ nand_chip->chip_delay = 100;
+ nand_chip->options |= NAND_NO_SUBPAGE_WRITE;
+
+ nand_chip->ecc.write_page = asm9260_nand_write_page_hwecc;
+ nand_chip->ecc.write_page_raw = asm9260_nand_write_page_raw;
+ nand_chip->ecc.read_page = asm9260_nand_read_page_hwecc;
+}
+
+static int __init asm9260_nand_cached_config(struct asm9260_nand_priv *priv)
+{
+ struct nand_chip *nand = &priv->nand;
+ struct mtd_info *mtd = &priv->mtd;
+ u32 addr_cycles, col_cycles, pages_per_block;
+
+ pages_per_block = mtd->erasesize / mtd->writesize;
+ /* max 256P, min 32P */
+ if (pages_per_block & ~(0x000001e0)) {
+ dev_err(priv->dev, "Unsuported erasesize 0x%x\n",
+ mtd->erasesize);
+ return -EINVAL;
+ }
+
+ /* max 32K, min 256. */
+ if (mtd->writesize & ~(0x0000ff00)) {
+ dev_err(priv->dev, "Unsuported writesize 0x%x\n",
+ mtd->erasesize);
+ return -EINVAL;
+ }
+
+ col_cycles = 2;
+ addr_cycles = col_cycles +
+ (((mtd->size >> mtd->writesize) > 65536) ? 3 : 2);
+
+ priv->ctrl_cache = addr_cycles << BM_CTRL_ADDR_CYCLE1_S
+ | BM_CTRL_PAGE_SIZE(mtd->writesize) << BM_CTRL_PAGE_SIZE_S
+ | BM_CTRL_BLOCK_SIZE(pages_per_block) << BM_CTRL_BLOCK_SIZE_S
+ | BM_CTRL_INT_EN
+ | addr_cycles << BM_CTRL_ADDR_CYCLE0_S;
+
+ iowrite32(BM_ECC_CAPn(nand->ecc.strength) << BM_ECC_CAP_S,
+ priv->base + HW_ECC_CTRL);
+
+ iowrite32(mtd->writesize + priv->spare_size,
+ priv->base + HW_ECC_OFFSET);
+
+ return 0;
+}
+
+static unsigned long __init clk_get_cyc_from_ns(struct clk *clk,
+ unsigned long ns)
+{
+ unsigned int cycle;
+
+ cycle = NSEC_PER_SEC / clk_get_rate(clk);
+ return DIV_ROUND_CLOSEST(ns, cycle);
+}
+
+static void __init asm9260_nand_timing_config(struct asm9260_nand_priv *priv)
+{
+ struct nand_chip *nand = &priv->nand;
+ const struct nand_sdr_timings *time;
+ u32 twhr, trhw, trwh, trwp, tadl, tccs, tsync, trr, twb;
+ int mode;
+
+ mode = nand->onfi_timing_mode_default;
+ dev_info(priv->dev, "ONFI timing mode: %i\n", mode);
+
+ time = onfi_async_timing_mode_to_sdr_timings(mode);
+ if (IS_ERR_OR_NULL(time)) {
+ dev_err(priv->dev, "Can't get onfi_timing_mode: %i\n", mode);
+ return;
+ }
+
+ trwh = clk_get_cyc_from_ns(priv->clk, time->tWH_min / 1000);
+ trwp = clk_get_cyc_from_ns(priv->clk, time->tWP_min / 1000);
+
+ iowrite32((trwh << 4) | (trwp), priv->base + HW_TIMING_ASYN);
+
+ twhr = clk_get_cyc_from_ns(priv->clk, time->tWHR_min / 1000);
+ trhw = clk_get_cyc_from_ns(priv->clk, time->tRHW_min / 1000);
+ tadl = clk_get_cyc_from_ns(priv->clk, time->tADL_min / 1000);
+ /* tCCS - change read/write collumn. Time between last cmd and data. */
+ tccs = clk_get_cyc_from_ns(priv->clk,
+ (time->tCLR_min + time->tCLH_min + time->tRC_min)
+ / 1000);
+
+ iowrite32((twhr << 24) | (trhw << 16)
+ | (tadl << 8) | (tccs), priv->base + HW_TIM_SEQ_0);
+
+ trr = clk_get_cyc_from_ns(priv->clk, time->tRR_min / 1000);
+ tsync = 0; /* ??? */
+ twb = clk_get_cyc_from_ns(priv->clk, time->tWB_max / 1000);
+ iowrite32((tsync << 16) | (trr << 9) | (twb),
+ priv->base + HW_TIM_SEQ_1);
+}
+
+static int __init asm9260_nand_ecc_conf(struct asm9260_nand_priv *priv)
+{
+ struct device_node *np = priv->dev->of_node;
+ struct nand_chip *nand = &priv->nand;
+ struct mtd_info *mtd = &priv->mtd;
+ struct nand_ecclayout *ecc_layout = &priv->ecc_layout;
+ int i, ecc_strength;
+
+ nand->ecc.mode = of_get_nand_ecc_mode(np);
+ switch (nand->ecc.mode) {
+ case NAND_ECC_SOFT:
+ case NAND_ECC_SOFT_BCH:
+ dev_info(priv->dev, "Using soft ECC %i\n", nand->ecc.mode);
+ /* nand_base will find needed settings */
+ return 0;
+ case NAND_ECC_HW:
+ default:
+ dev_info(priv->dev, "Using default NAND_ECC_HW\n");
+ nand->ecc.mode = NAND_ECC_HW;
+ break;
+ }
+
+ ecc_strength = of_get_nand_ecc_strength(np);
+ nand->ecc.size = ASM9260_ECC_STEP;
+ nand->ecc.steps = mtd->writesize / nand->ecc.size;
+
+ if (ecc_strength < nand->ecc_strength_ds) {
+ int ds_corr;
+
+ /* Let's check if ONFI can help us. */
+ if (nand->ecc_strength_ds <= 0) {
+ /* No ONFI and no DT - it is bad. */
+ dev_err(priv->dev,
+ "nand-ecc-strength is not set by DT or ONFI. Please set nand-ecc-strength in DT or add chip quirk in nand_ids.c.\n");
+ return -EINVAL;
+ }
+
+ ds_corr = (mtd->writesize * nand->ecc_strength_ds) /
+ nand->ecc_step_ds;
+ ecc_strength = ds_corr / nand->ecc.steps;
+ dev_info(priv->dev, "ONFI:nand-ecc-strength = %i\n",
+ ecc_strength);
+ } else
+ dev_info(priv->dev, "DT:nand-ecc-strength = %i\n",
+ ecc_strength);
+
+ if (ecc_strength == 0 || ecc_strength > ASM9260_ECC_MAX_BIT) {
+ dev_err(priv->dev,
+ "Not supported ecc_strength value: %i\n",
+ ecc_strength);
+ return -EINVAL;
+ }
+
+ if (ecc_strength & 0x1) {
+ ecc_strength++;
+ dev_info(priv->dev,
+ "Only even ecc_strength value is supported. Recalculating: %i\n",
+ ecc_strength);
+ }
+
+ /* FIXME: do we have max or min page size? */
+
+ /* 13 - the smallest integer for 512 (ASM9260_ECC_STEP). Div to 8bit. */
+ nand->ecc.bytes = DIV_ROUND_CLOSEST(ecc_strength * 13, 8);
+
+ ecc_layout->eccbytes = nand->ecc.bytes * nand->ecc.steps;
+ nand->ecc.layout = ecc_layout;
+ nand->ecc.strength = ecc_strength;
+
+ priv->spare_size = mtd->oobsize - ecc_layout->eccbytes;
+
+ ecc_layout->oobfree[0].offset = 2;
+ ecc_layout->oobfree[0].length = priv->spare_size - 2;
+
+ /* FIXME: can we use same layout as SW_ECC? */
+ for (i = 0; i < ecc_layout->eccbytes; i++)
+ ecc_layout->eccpos[i] = priv->spare_size + i;
+
+ return 0;
+}
+
+static int __init asm9260_nand_get_dt_clks(struct asm9260_nand_priv *priv)
+{
+ int clk_idx = 0, err;
+
+ priv->clk = devm_clk_get(priv->dev, "sys");
+ if (IS_ERR(priv->clk))
+ goto out_err;
+
+ /* configure AHB clock */
+ clk_idx = 1;
+ priv->clk_ahb = devm_clk_get(priv->dev, "ahb");
+ if (IS_ERR(priv->clk_ahb))
+ goto out_err;
+
+ err = clk_prepare_enable(priv->clk_ahb);
+ if (err)
+ dev_err(priv->dev, "Failed to enable ahb_clk!\n");
+
+ err = clk_set_rate(priv->clk, clk_get_rate(priv->clk_ahb));
+ if (err) {
+ clk_disable_unprepare(priv->clk_ahb);
+ dev_err(priv->dev, "Failed to set rate!\n");
+ }
+
+ err = clk_prepare_enable(priv->clk);
+ if (err) {
+ clk_disable_unprepare(priv->clk_ahb);
+ dev_err(priv->dev, "Failed to enable clk!\n");
+ }
+
+ return 0;
+out_err:
+ dev_err(priv->dev, "%s: Failed to get clk (%i)\n", __func__, clk_idx);
+ return 1;
+}
+
+static int __init asm9260_nand_probe(struct platform_device *pdev)
+{
+ struct asm9260_nand_priv *priv;
+ struct nand_chip *nand;
+ struct mtd_info *mtd;
+ struct device_node *np = pdev->dev.of_node;
+ struct resource *r;
+ int ret, i;
+ unsigned int irq;
+ u32 val;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct asm9260_nand_priv),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->base = devm_ioremap_resource(&pdev->dev, r);
+ if (!priv->base) {
+ dev_err(&pdev->dev, "Unable to map resource!\n");
+ return -EINVAL;
+ }
+
+ priv->dev = &pdev->dev;
+ nand = &priv->nand;
+ nand->priv = priv;
+
+ platform_set_drvdata(pdev, priv);
+ mtd = &priv->mtd;
+ mtd->priv = nand;
+ mtd->owner = THIS_MODULE;
+ mtd->name = dev_name(&pdev->dev);
+
+ if (asm9260_nand_get_dt_clks(priv))
+ return -ENODEV;
+
+ irq = platform_get_irq(pdev, 0);
+ if (!irq)
+ return -ENODEV;
+
+ iowrite32(0, priv->base + HW_INT_MASK);
+ ret = devm_request_irq(priv->dev, irq, asm9260_nand_irq,
+ IRQF_ONESHOT | IRQF_SHARED,
+ dev_name(&pdev->dev), priv);
+
+ asm9260_nand_init_chip(nand);
+
+ ret = of_property_read_u32(np, "nand-max-chips", &val);
+ if (ret)
+ val = 1;
+
+ if (val > ASM9260_MAX_CHIPS) {
+ dev_err(&pdev->dev, "Unsupported nand-max-chips value: %i\n",
+ val);
+ return -ENODEV;
+ }
+
+ for (i = 0; i < val; i++)
+ priv->mem_mask |= BM_CTRL_MEM0_RDY << i;
+
+ ret = nand_scan_ident(mtd, val, NULL);
+ if (ret) {
+ dev_err(&pdev->dev, "scan_ident filed!\n");
+ return ret;
+ }
+
+ if (of_get_nand_on_flash_bbt(np)) {
+ dev_info(&pdev->dev, "Use On Flash BBT\n");
+ nand->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB_BBM
+ | NAND_BBT_LASTBLOCK;
+ }
+
+ asm9260_nand_timing_config(priv);
+ asm9260_nand_ecc_conf(priv);
+ ret = asm9260_nand_cached_config(priv);
+ if (ret)
+ return ret;
+
+ /* second phase scan */
+ if (nand_scan_tail(mtd)) {
+ dev_err(&pdev->dev, "scan_tail filed!\n");
+ return -ENXIO;
+ }
+
+
+ ret = mtd_device_parse_register(mtd, NULL,
+ &(struct mtd_part_parser_data) {
+ .of_node = pdev->dev.of_node,
+ },
+ NULL, 0);
+
+ return ret;
+}
+
+
+static int asm9260_nand_remove(struct platform_device *pdev)
+{
+ struct asm9260_nand_priv *priv = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(priv->clk);
+ clk_disable_unprepare(priv->clk_ahb);
+ nand_release(&priv->mtd);
+
+ return 0;
+}
+
+static const struct of_device_id asm9260_nand_match[] = {
+ {
+ .compatible = "alphascale,asm9260-nand",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, asm9260_nand_match);
+
+static struct platform_driver asm9260_nand_driver = {
+ .probe = asm9260_nand_probe,
+ .remove = asm9260_nand_remove,
+ .driver = {
+ .name = "asm9260_nand",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(asm9260_nand_match),
+ },
+};
+
+module_platform_driver(asm9260_nand_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ASM9260 NAND driver");
--
1.9.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH RFC v2] mtd: nand: add asm9260 NFC driver
2014-12-30 18:09 ` [PATCH RFC v2] " Oleksij Rempel
@ 2014-12-30 19:09 ` Boris Brezillon
2014-12-31 12:58 ` [PATCH v3 1/3] " Oleksij Rempel
2014-12-31 12:58 ` Oleksij Rempel
0 siblings, 2 replies; 24+ messages in thread
From: Boris Brezillon @ 2014-12-30 19:09 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: computersforpeace, linux-mtd
Hi Oleksij,
I haven't reviewed the driver code yet, but highlighted warnings
reported by checkpatch.
I'll try to give a full review soon.
On Tue, 30 Dec 2014 19:09:32 +0100
Oleksij Rempel <linux@rempel-privat.de> wrote:
> Add driver for Nand Flash Controller used on Alphascales ASM9260 chips.
> The IP core of this controller has some similarities with
> Evatronix NANDFLASH-CTRL IP (unkown revision), so probably it can be reused
WARNING: 'unkown' may be misspelled - perhaps 'unknown'?
#20:
Evatronix NANDFLASH-CTRL IP (unkown revision), so probably it can be
reused
> by some other SoCs.
>
> Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
> ---
> drivers/mtd/nand/Kconfig | 7 +
> drivers/mtd/nand/Makefile | 1 +
> drivers/mtd/nand/asm9260_nand.c | 989 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 997 insertions(+)
> create mode 100644 drivers/mtd/nand/asm9260_nand.c
>
> diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
> index dd10646..580a608 100644
> --- a/drivers/mtd/nand/Kconfig
> +++ b/drivers/mtd/nand/Kconfig
> @@ -41,6 +41,13 @@ config MTD_SM_COMMON
> tristate
> default n
>
> +config MTD_NAND_ASM9260
> + tristate "NFC support for ASM9260 SoC"
> + depends on OF
> + default n
> + help
> + Enable support for the NAND controller found on Alphascale ASM9260 SoC.
> +
> config MTD_NAND_DENALI
> tristate "Support Denali NAND controller"
> depends on HAS_DMA
> diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
> index 9c847e4..08d660a 100644
> --- a/drivers/mtd/nand/Makefile
> +++ b/drivers/mtd/nand/Makefile
[...]
> +
> +static int __init asm9260_nand_cached_config(struct asm9260_nand_priv *priv)
> +{
> + struct nand_chip *nand = &priv->nand;
> + struct mtd_info *mtd = &priv->mtd;
> + u32 addr_cycles, col_cycles, pages_per_block;
> +
> + pages_per_block = mtd->erasesize / mtd->writesize;
> + /* max 256P, min 32P */
> + if (pages_per_block & ~(0x000001e0)) {
> + dev_err(priv->dev, "Unsuported erasesize 0x%x\n",
WARNING: 'Unsuported' may be misspelled - perhaps 'Unsupported'?
#732: FILE: drivers/mtd/nand/asm9260_nand.c:666:
+ dev_err(priv->dev, "Unsuported erasesize 0x%x\n",
> + mtd->erasesize);
> + return -EINVAL;
> + }
> +
> + /* max 32K, min 256. */
> + if (mtd->writesize & ~(0x0000ff00)) {
> + dev_err(priv->dev, "Unsuported writesize 0x%x\n",
Ditto
> + mtd->erasesize);
> + return -EINVAL;
> + }
> +
[...]
> +
> + return 0;
> +}
> +
> +static const struct of_device_id asm9260_nand_match[] = {
> + {
> + .compatible = "alphascale,asm9260-nand",
WARNING: DT compatible string "alphascale,asm9260-nand" appears
un-documented -- check ./Documentation/devicetree/bindings/ #1036:
FILE: drivers/mtd/nand/asm9260_nand.c:970:
+ .compatible = "alphascale,asm9260-nand",
Add a patch documenting the DT bindings used by your driver.
WARNING: DT compatible string vendor "alphascale" appears un-documented
-- check ./Documentation/devicetree/bindings/vendor-prefixes.txt #1036:
FILE: drivers/mtd/nand/asm9260_nand.c:970:
+ .compatible = "alphascale,asm9260-nand",
Regards,
Boris
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v3 1/3] mtd: nand: add asm9260 NFC driver
2014-12-30 19:09 ` Boris Brezillon
@ 2014-12-31 12:58 ` Oleksij Rempel
2014-12-31 12:58 ` Oleksij Rempel
1 sibling, 0 replies; 24+ messages in thread
From: Oleksij Rempel @ 2014-12-31 12:58 UTC (permalink / raw)
To: boris.brezillon, linux-mtd, computersforpeace; +Cc: Oleksij Rempel
Add driver for Nand Flash Controller used on Alphascales ASM9260 chips.
The IP core of this controller has some similarities with
Evatronix NANDFLASH-CTRL IP (unknown revision), so probably it can be reused
by some other SoCs.
Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
---
drivers/mtd/nand/Kconfig | 7 +
drivers/mtd/nand/Makefile | 1 +
drivers/mtd/nand/asm9260_nand.c | 989 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 997 insertions(+)
create mode 100644 drivers/mtd/nand/asm9260_nand.c
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index dd10646..580a608 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -41,6 +41,13 @@ config MTD_SM_COMMON
tristate
default n
+config MTD_NAND_ASM9260
+ tristate "NFC support for ASM9260 SoC"
+ depends on OF
+ default n
+ help
+ Enable support for the NAND controller found on Alphascale ASM9260 SoC.
+
config MTD_NAND_DENALI
tristate "Support Denali NAND controller"
depends on HAS_DMA
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 9c847e4..08d660a 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_MTD_NAND_BCH) += nand_bch.o
obj-$(CONFIG_MTD_NAND_IDS) += nand_ids.o
obj-$(CONFIG_MTD_SM_COMMON) += sm_common.o
+obj-$(CONFIG_MTD_NAND_ASM9260) += asm9260_nand.o
obj-$(CONFIG_MTD_NAND_CAFE) += cafe_nand.o
obj-$(CONFIG_MTD_NAND_AMS_DELTA) += ams-delta.o
obj-$(CONFIG_MTD_NAND_DENALI) += denali.o
diff --git a/drivers/mtd/nand/asm9260_nand.c b/drivers/mtd/nand/asm9260_nand.c
new file mode 100644
index 0000000..23154be
--- /dev/null
+++ b/drivers/mtd/nand/asm9260_nand.c
@@ -0,0 +1,989 @@
+/*
+ * NAND controller driver for Alphascale ASM9260, which is probably
+ * based on Evatronix NANDFLASH-CTRL IP (version unknown)
+ *
+ * Copyright (C), 2014 Oleksij Rempel <linux@rempel-privat.de>
+ *
+ * Inspired by asm9260_nand.c,
+ * Copyright (C), 2007-2013, Alphascale Tech. Co., Ltd.
+ * 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/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <linux/clk.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/of_mtd.h>
+
+#define ASM9260_ECC_STEP 512
+#define ASM9260_ECC_MAX_BIT 16
+#define ASM9260_MAX_CHIPS 2
+
+#define mtd_to_priv(m) container_of(m, struct asm9260_nand_priv, mtd)
+
+#define HW_CMD 0x00
+#define BM_CMD_CMD2_S 24
+#define BM_CMD_CMD1_S 16
+#define BM_CMD_CMD0_S 8
+/* 0 - ADDR0, 1 - ADDR1 */
+#define BM_CMD_ADDR1 BIT(7)
+/* 0 - PIO, 1 - DMA */
+#define BM_CMD_DMA BIT(6)
+#define BM_CMD_CMDSEQ_S 0
+/* single command, wait for RnB */
+#define SEQ0 0x00
+/* send cmd, addr, wait tWHR, fetch data */
+#define SEQ1 0x21
+/* send cmd, addr, wait RnB, fetch data */
+#define SEQ2 0x22
+/* send cmd, addr, wait tADL, send data, wait RnB */
+#define SEQ3 0x03
+/* send cmd, wait tWHR, fetch data */
+#define SEQ4 0x24
+/* send cmd, 3 x addr, wait tWHR, fetch data */
+#define SEQ5 0x25
+/* wait tRHW, send cmd, 2 x addr, cmd, wait tCCS, fetch data */
+#define SEQ6 0x26
+/* wait tRHW, send cmd, 35 x addr, cmd, wait tCCS, fetch data */
+#define SEQ7 0x27
+/* send cmd, 2 x addr, wait tCCS, fetch data */
+#define SEQ8 0x08
+/* send cmd, 5 x addr, wait RnB */
+#define SEQ9 0x29
+/* send cmd, 5 x addr, cmd, wait RnB, fetch data */
+#define SEQ10 0x2a
+/* send cmd, wait RnB, fetch data */
+#define SEQ11 0x2b
+/* send cmd, 5 x addr, wait tADL, send data, cmd */
+#define SEQ12 0x0c
+/* send cmd, 5 x addr, wait tADL, send data */
+#define SEQ13 0x0d
+/* send cmd, 3 x addr, cmd, wait RnB */
+#define SEQ14 0x0e
+/* send cmd, 5 x addr, cmd, 5 x addr, cmd, wait RnB, fetch data */
+#define SEQ15 0x2f
+/* send cmd, 5 x addr, wait RnB, fetch data */
+#define SEQ17 0x15
+
+#define HW_CTRL 0x04
+#define BM_CTRL_DIS_STATUS BIT(23)
+#define BM_CTRL_READ_STAT BIT(22)
+#define BM_CTRL_SMALL_BLOCK_EN BIT(21)
+#define BM_CTRL_ADDR_CYCLE1_S 18
+#define ADDR_CYCLE_0 0x0
+#define ADDR_CYCLE_1 0x1
+#define ADDR_CYCLE_2 0x2
+#define ADDR_CYCLE_3 0x3
+#define ADDR_CYCLE_4 0x4
+#define ADDR_CYCLE_5 0x5
+#define BM_CTRL_ADDR1_AUTO_INCR BIT(17)
+#define BM_CTRL_ADDR0_AUTO_INCR BIT(16)
+#define BM_CTRL_WORK_MODE BIT(15)
+#define BM_CTRL_PORT_EN BIT(14)
+#define BM_CTRL_LOOKU_EN BIT(13)
+#define BM_CTRL_IO_16BIT BIT(12)
+/* Overwrite BM_CTRL_PAGE_SIZE with HW_DATA_SIZE */
+#define BM_CTRL_CUSTOM_PAGE_SIZE BIT(11)
+#define BM_CTRL_PAGE_SIZE_S 8
+#define BM_CTRL_PAGE_SIZE(x) ((ffs((x) >> 8) - 1) & 0x7)
+#define PAGE_SIZE_256B 0x0
+#define PAGE_SIZE_512B 0x1
+#define PAGE_SIZE_1024B 0x2
+#define PAGE_SIZE_2048B 0x3
+#define PAGE_SIZE_4096B 0x4
+#define PAGE_SIZE_8192B 0x5
+#define PAGE_SIZE_16384B 0x6
+#define PAGE_SIZE_32768B 0x7
+#define BM_CTRL_BLOCK_SIZE_S 6
+#define BM_CTRL_BLOCK_SIZE(x) ((ffs((x) >> 5) - 1) & 0x3)
+#define BLOCK_SIZE_32P 0x0
+#define BLOCK_SIZE_64P 0x1
+#define BLOCK_SIZE_128P 0x2
+#define BLOCK_SIZE_256P 0x3
+#define BM_CTRL_ECC_EN BIT(5)
+#define BM_CTRL_INT_EN BIT(4)
+#define BM_CTRL_SPARE_EN BIT(3)
+/* same values as BM_CTRL_ADDR_CYCLE1_S */
+#define BM_CTRL_ADDR_CYCLE0_S 0
+
+#define HW_STATUS 0x08
+#define BM_CTRL_NFC_BUSY BIT(8)
+/* MEM1_RDY (BIT1) - MEM7_RDY (BIT7) */
+#define BM_CTRL_MEM0_RDY BIT(0)
+
+#define HW_INT_MASK 0x0c
+#define HW_INT_STATUS 0x10
+#define BM_INT_FIFO_ERROR BIT(12)
+#define BM_INT_MEM_RDY_S 4
+/* MEM1_RDY (BIT5) - MEM7_RDY (BIT11) */
+#define BM_INT_MEM0_RDY BIT(4)
+#define BM_INT_ECC_TRSH_ERR BIT(3)
+#define BM_INT_ECC_FATAL_ERR BIT(2)
+#define BM_INT_CMD_END BIT(1)
+
+#define HW_ECC_CTRL 0x14
+/* bits per 512 bytes */
+#define BM_ECC_CAP_S 5
+/* support ecc strange 2, 4, 6, 8, 10, 12, 14, 16. */
+#define BM_ECC_CAPn(x) ((((x) >> 1) - 1) & 0x7)
+/* Warn if some bitflip level (threshold) reached. Max 15 bits. */
+#define BM_ECC_ERR_THRESHOLD_S 8
+#define BM_ECC_ERR_THRESHOLD_M 0xf
+#define BM_ECC_ERR_OVER BIT(2)
+/* Uncorrected error. */
+#define BM_ECC_ERR_UNC BIT(1)
+/* Corrected error. */
+#define BM_ECC_ERR_CORRECT BIT(0)
+
+#define HW_ECC_OFFSET 0x18
+#define HW_ADDR0_0 0x1c
+#define HW_ADDR1_0 0x20
+#define HW_ADDR0_1 0x24
+#define HW_ADDR1_1 0x28
+#define HW_SPARE_SIZE 0x30
+#define HW_DMA_ADDR 0x64
+#define HW_DMA_CNT 0x68
+
+#define HW_DMA_CTRL 0x6c
+#define BM_DMA_CTRL_START BIT(7)
+/* 0 - to device; 1 - from device */
+#define BM_DMA_CTRL_FROM_DEVICE BIT(6)
+/* 0 - software maneged; 1 - scatter-gather */
+#define BM_DMA_CTRL_SG BIT(5)
+#define BM_DMA_CTRL_BURST_S 2
+#define DMA_BURST_INCR4 0x0
+#define DMA_BURST_STREAM 0x1
+#define DMA_BURST_SINGLE 0x2
+#define DMA_BURST_INCR 0x3
+#define DMA_BURST_INCR8 0x4
+#define DMA_BURST_INCR16 0x5
+#define BM_DMA_CTRL_ERR BIT(1)
+#define BM_DMA_CTRL_RDY BIT(0)
+
+#define HW_MEM_CTRL 0x80
+#define BM_MEM_CTRL_WP_STATE_MASK 0xff00
+#define BM_MEM_CTRL_UNWPn(x) (1 << ((x) + 8))
+#define BM_MEM_CTRL_CEn(x) (((x) & 7) << 0)
+
+/* BM_CTRL_CUSTOM_PAGE_SIZE should be set */
+#define HW_DATA_SIZE 0x84
+#define HW_READ_STATUS 0x88
+#define HW_TIM_SEQ_0 0x8c
+#define HW_TIMING_ASYN 0x90
+#define HW_TIMING_SYN 0x94
+
+#define HW_FIFO_DATA 0x98
+#define HW_TIME_MODE 0x9c
+#define HW_FIFO_INIT 0xb0
+/*
+ * Counter for ecc related errors.
+ * For each 512 byte block it has 5bit counter.
+ */
+#define HW_ECC_ERR_CNT 0xb8
+
+#define HW_TIM_SEQ_1 0xc8
+
+struct asm9260_nand_priv {
+ struct device *dev;
+ struct mtd_info mtd;
+ struct nand_chip nand;
+ struct nand_ecclayout ecc_layout;
+
+ struct clk *clk;
+ struct clk *clk_ahb;
+
+ void __iomem *base;
+ int irq_done;
+
+ u32 read_cache;
+ int read_cache_cnt;
+ u32 cmd_cache;
+ u32 ctrl_cache;
+ u32 mem_mask;
+ u32 page_cache;
+ unsigned int wait_time;
+
+ unsigned int spare_size;
+};
+
+static void asm9260_reg_rmw(struct asm9260_nand_priv *priv,
+ u32 reg_offset, u32 set, u32 clr)
+{
+ u32 val;
+
+ val = ioread32(priv->base + reg_offset);
+ val &= ~clr;
+ val |= set;
+ iowrite32(val, priv->base + reg_offset);
+}
+
+static void asm9260_select_chip(struct mtd_info *mtd, int chip)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+
+ if (chip == -1)
+ iowrite32(BM_MEM_CTRL_WP_STATE_MASK, priv->base + HW_MEM_CTRL);
+ else
+ iowrite32(BM_MEM_CTRL_UNWPn(chip) | BM_MEM_CTRL_CEn(chip),
+ priv->base + HW_MEM_CTRL);
+}
+
+/* 3 commands are supported by HW. 3-d can be used for TWO PLANE. */
+static void asm9260_nand_cmd_prep(struct asm9260_nand_priv *priv,
+ u8 cmd0, u8 cmd1, u8 cmd2, u8 seq)
+{
+ priv->cmd_cache = (cmd0 << BM_CMD_CMD0_S) | (cmd1 << BM_CMD_CMD1_S);
+ priv->cmd_cache |= seq << BM_CMD_CMDSEQ_S;
+}
+
+static dma_addr_t asm9260_nand_dma_set(struct mtd_info *mtd, void *buf,
+ enum dma_data_direction dir, size_t size)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ dma_addr_t dma_addr;
+
+ dma_addr = dma_map_single(priv->dev, buf, size, dir);
+ if (dma_mapping_error(priv->dev, dma_addr)) {
+ dev_err(priv->dev, "dma_map_single failed!\n");
+ return dma_addr;
+
+ }
+
+ iowrite32(dma_addr, priv->base + HW_DMA_ADDR);
+ iowrite32(size, priv->base + HW_DMA_CNT);
+ iowrite32(BM_DMA_CTRL_START
+ | (dir == DMA_FROM_DEVICE ? BM_DMA_CTRL_FROM_DEVICE : 0)
+ | (DMA_BURST_INCR16 << BM_DMA_CTRL_BURST_S),
+ priv->base + HW_DMA_CTRL);
+ return dma_addr;
+}
+
+/* complete command request */
+static void asm9260_nand_cmd_comp(struct mtd_info *mtd, int dma)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ int timeout;
+ u32 cmd;
+
+ if (!priv->cmd_cache)
+ return;
+
+ if (dma) {
+ priv->cmd_cache |= BM_CMD_DMA;
+ priv->irq_done = 0;
+ iowrite32(priv->mem_mask << BM_INT_MEM_RDY_S,
+ priv->base + HW_INT_MASK);
+ }
+
+ iowrite32(priv->cmd_cache, priv->base + HW_CMD);
+ cmd = priv->cmd_cache;
+ priv->cmd_cache = 0;
+
+ if (dma) {
+ struct nand_chip *nand = &priv->nand;
+
+ timeout = wait_event_timeout(nand->controller->wq,
+ priv->irq_done,
+ msecs_to_jiffies(priv->wait_time ?
+ priv->wait_time : 20));
+ if (timeout <= 0) {
+ dev_info(priv->dev,
+ "Request 0x%08x timed out\n", cmd);
+ /* TODO: Do something useful here? */
+ /* FIXME: if we have problems on DMA or PIO, we need to
+ * reset NFC. On asm9260 it is possible only with global
+ * reset register. How can we use it here? */
+ }
+ priv->wait_time = 0;
+ } else
+ nand_wait_ready(mtd);
+}
+
+static int asm9260_nand_dev_ready(struct mtd_info *mtd)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ u32 tmp;
+
+ tmp = ioread32(priv->base + HW_STATUS);
+
+ return (!(tmp & BM_CTRL_NFC_BUSY) &&
+ (tmp & priv->mem_mask));
+}
+
+static void asm9260_nand_ctrl(struct asm9260_nand_priv *priv, u32 set)
+{
+ iowrite32(priv->ctrl_cache | set, priv->base + HW_CTRL);
+}
+
+static void asm9260_nand_set_addr(struct asm9260_nand_priv *priv,
+ u32 row_addr, u32 column)
+{
+ u32 addr[2];
+
+ addr[0] = (column & 0xffff) | (0xffff0000 & (row_addr << 16));
+ addr[1] = (row_addr >> 16) & 0xff;
+
+ iowrite32(addr[0], priv->base + HW_ADDR0_0);
+ iowrite32(addr[1], priv->base + HW_ADDR0_1);
+}
+
+static void asm9260_nand_command_lp(struct mtd_info *mtd,
+ unsigned int command, int column, int page_addr)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+
+ switch (command) {
+ case NAND_CMD_RESET:
+ asm9260_nand_cmd_prep(priv, NAND_CMD_RESET, 0, 0, SEQ0);
+ asm9260_nand_cmd_comp(mtd, 0);
+ break;
+
+ case NAND_CMD_READID:
+ iowrite32(1, priv->base + HW_FIFO_INIT);
+ iowrite32((ADDR_CYCLE_1 << BM_CTRL_ADDR_CYCLE1_S)
+ | BM_CTRL_CUSTOM_PAGE_SIZE
+ | (PAGE_SIZE_4096B << BM_CTRL_PAGE_SIZE_S)
+ | (BLOCK_SIZE_32P << BM_CTRL_BLOCK_SIZE_S)
+ | BM_CTRL_INT_EN
+ | (ADDR_CYCLE_1 << BM_CTRL_ADDR_CYCLE0_S),
+ priv->base + HW_CTRL);
+
+ iowrite32(8, priv->base + HW_DATA_SIZE);
+ iowrite32(column, priv->base + HW_ADDR0_0);
+ asm9260_nand_cmd_prep(priv, NAND_CMD_READID, 0, 0, SEQ1);
+
+ priv->read_cache_cnt = 0;
+ break;
+
+ case NAND_CMD_READOOB:
+ column += mtd->writesize;
+ command = NAND_CMD_READ0;
+ case NAND_CMD_READ0:
+ iowrite32(1, priv->base + HW_FIFO_INIT);
+
+ if (column == 0) {
+ asm9260_nand_ctrl(priv, 0);
+ iowrite32(priv->spare_size, priv->base + HW_SPARE_SIZE);
+ } else if (column == mtd->writesize) {
+ asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
+ iowrite32(mtd->oobsize, priv->base + HW_SPARE_SIZE);
+ iowrite32(mtd->oobsize, priv->base + HW_DATA_SIZE);
+ } else {
+ dev_err(priv->dev, "Couldn't support the column\n");
+ break;
+ }
+
+ asm9260_nand_set_addr(priv, page_addr, column);
+
+ asm9260_nand_cmd_prep(priv, NAND_CMD_READ0,
+ NAND_CMD_READSTART, 0, SEQ10);
+
+ priv->read_cache_cnt = 0;
+ break;
+ case NAND_CMD_SEQIN:
+ iowrite32(1, priv->base + HW_FIFO_INIT);
+
+ if (column == 0) {
+ priv->page_cache = page_addr;
+ asm9260_nand_ctrl(priv, 0);
+ iowrite32(priv->spare_size, priv->base + HW_SPARE_SIZE);
+ } else if (column == mtd->writesize) {
+ asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
+ iowrite32(mtd->oobsize, priv->base + HW_DATA_SIZE);
+ }
+
+ asm9260_nand_set_addr(priv, page_addr, column);
+
+ asm9260_nand_cmd_prep(priv, NAND_CMD_SEQIN, NAND_CMD_PAGEPROG,
+ 0, SEQ12);
+
+ break;
+ case NAND_CMD_STATUS:
+ iowrite32(1, priv->base + HW_FIFO_INIT);
+ asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
+
+ /*
+ * Workaround for status bug.
+ * Instead of SEQ4 we need to use SEQ1 here, which will
+ * send cmd with address. For this case we need to make sure
+ * ADDR == 0.
+ */
+ asm9260_nand_set_addr(priv, 0, 0);
+ iowrite32(4, priv->base + HW_DATA_SIZE);
+ asm9260_nand_cmd_prep(priv, NAND_CMD_STATUS, 0, 0, SEQ1);
+
+ priv->read_cache_cnt = 0;
+ break;
+
+ case NAND_CMD_ERASE1:
+ priv->wait_time = 400;
+ asm9260_nand_set_addr(priv, page_addr, column);
+
+ asm9260_nand_ctrl(priv, 0);
+
+ /*
+ * Prepare and send command now. We don't need to split it in
+ * two stages.
+ */
+ asm9260_nand_cmd_prep(priv, NAND_CMD_ERASE1, NAND_CMD_ERASE2,
+ 0, SEQ14);
+ asm9260_nand_cmd_comp(mtd, 0);
+ break;
+ default:
+ break;
+ }
+}
+
+
+/**
+ * We can't read less then 32 bits on HW_FIFO_DATA. So, to make
+ * read_byte and read_word happy, we use sort of cached 32bit read.
+ * Note: expected values for size should be 1 or 2 bytes.
+ */
+static u32 asm9260_nand_read_cached(struct mtd_info *mtd, int size)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ u8 tmp;
+
+ if ((priv->read_cache_cnt <= 0) || (priv->read_cache_cnt > 4)) {
+ asm9260_nand_cmd_comp(mtd, 0);
+ priv->read_cache = ioread32(priv->base + HW_FIFO_DATA);
+ priv->read_cache_cnt = 4;
+ }
+
+ tmp = priv->read_cache >> (8 * (4 - priv->read_cache_cnt));
+ priv->read_cache_cnt -= size;
+
+ return tmp;
+}
+
+static u8 asm9260_nand_read_byte(struct mtd_info *mtd)
+{
+ return 0xff & asm9260_nand_read_cached(mtd, 1);
+}
+
+static u16 asm9260_nand_read_word(struct mtd_info *mtd)
+{
+ return 0xffff & asm9260_nand_read_cached(mtd, 2);
+}
+
+static void asm9260_nand_read_buf(struct mtd_info *mtd, u8 *buf, int len)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ dma_addr_t dma_addr;
+ int dma_ok = 0;
+
+ if (len & 0x3) {
+ dev_err(priv->dev, "Unsupported length (%x)\n", len);
+ return;
+ }
+
+ /*
+ * I hate you UBI for your all vmalloc. Be slow as hell with PIO.
+ * ~ with love from ZeroCopy ~
+ */
+ if (!is_vmalloc_addr(buf)) {
+ dma_addr = asm9260_nand_dma_set(mtd, buf, DMA_FROM_DEVICE, len);
+ dma_ok = !(dma_mapping_error(priv->dev, dma_addr));
+ }
+ asm9260_nand_cmd_comp(mtd, dma_ok);
+
+ if (dma_ok) {
+ dma_sync_single_for_cpu(priv->dev, dma_addr, len,
+ DMA_FROM_DEVICE);
+ dma_unmap_single(priv->dev, dma_addr, len, DMA_FROM_DEVICE);
+ return;
+ }
+
+ /* fall back to pio mode */
+ len >>= 2;
+ ioread32_rep(priv->base + HW_FIFO_DATA, buf, len);
+}
+
+static void asm9260_nand_write_buf(struct mtd_info *mtd,
+ const u8 *buf, int len)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ dma_addr_t dma_addr;
+ int dma_ok = 0;
+
+ if (len & 0x3) {
+ dev_err(priv->dev, "Unsupported length (%x)\n", len);
+ return;
+ }
+
+ if (!is_vmalloc_addr(buf)) {
+ dma_addr = asm9260_nand_dma_set(mtd,
+ (void *)buf, DMA_TO_DEVICE, len);
+ dma_ok = !(dma_mapping_error(priv->dev, dma_addr));
+ }
+
+ if (dma_ok)
+ dma_sync_single_for_device(priv->dev, dma_addr, len,
+ DMA_TO_DEVICE);
+ asm9260_nand_cmd_comp(mtd, dma_ok);
+
+ if (dma_ok) {
+ dma_unmap_single(priv->dev, dma_addr, len, DMA_TO_DEVICE);
+ return;
+ }
+
+ /* fall back to pio mode */
+ len >>= 2;
+ iowrite32_rep(priv->base + HW_FIFO_DATA, buf, len);
+}
+
+static int asm9260_nand_write_page_raw(struct mtd_info *mtd,
+ struct nand_chip *chip, const u8 *buf,
+ int oob_required)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+
+ chip->write_buf(mtd, buf, mtd->writesize);
+ if (oob_required)
+ chip->ecc.write_oob(mtd, chip, priv->page_cache &
+ chip->pagemask);
+ return 0;
+}
+
+static int asm9260_nand_write_page_hwecc(struct mtd_info *mtd,
+ struct nand_chip *chip, const u8 *buf,
+ int oob_required)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+
+ asm9260_reg_rmw(priv, HW_CTRL, BM_CTRL_ECC_EN, 0);
+ chip->ecc.write_page_raw(mtd, chip, buf, oob_required);
+
+ return 0;
+}
+
+static unsigned int asm9260_nand_count_ecc(struct asm9260_nand_priv *priv)
+{
+ u32 tmp, i, count, maxcount = 0;
+
+ /* FIXME: this layout was tested only on 2048byte NAND.
+ * NANDs with bigger page size should use more registers. */
+ tmp = ioread32(priv->base + HW_ECC_ERR_CNT);
+ for (i = 0; i < 4; i++) {
+ count = 0x1f & (tmp >> (5 * i));
+ maxcount = max_t(unsigned int, maxcount, count);
+ }
+
+ return count;
+}
+
+static int asm9260_nand_read_page_hwecc(struct mtd_info *mtd,
+ struct nand_chip *chip, u8 *buf,
+ int oob_required, int page)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ u8 *temp_ptr;
+ u32 status, max_bitflips = 0;
+
+ temp_ptr = buf;
+
+ /* enable ecc */
+ asm9260_reg_rmw(priv, HW_CTRL, BM_CTRL_ECC_EN, 0);
+ chip->read_buf(mtd, temp_ptr, mtd->writesize);
+
+ status = ioread32(priv->base + HW_ECC_CTRL);
+
+ if (status & BM_ECC_ERR_UNC) {
+ u32 ecc_err;
+
+ ecc_err = ioread32(priv->base + HW_ECC_ERR_CNT);
+ /* check if it is erased page (all_DATA_OOB == 0xff) */
+ /* FIXME: should be tested if it is a bullet proof solution.
+ * if not, use is_buf_blank. */
+ if (ecc_err != 0x8421)
+ mtd->ecc_stats.failed++;
+
+ } else if (status & BM_ECC_ERR_CORRECT) {
+ max_bitflips = asm9260_nand_count_ecc(priv);
+ mtd->ecc_stats.corrected += max_bitflips;
+ }
+
+ if (oob_required)
+ chip->ecc.read_oob(mtd, chip, page);
+
+ return max_bitflips;
+}
+
+static irqreturn_t asm9260_nand_irq(int irq, void *device_info)
+{
+ struct asm9260_nand_priv *priv = device_info;
+ struct nand_chip *nand = &priv->nand;
+ u32 status;
+
+ status = ioread32(priv->base + HW_INT_STATUS);
+ if (!status)
+ return IRQ_NONE;
+
+ iowrite32(0, priv->base + HW_INT_MASK);
+ iowrite32(0, priv->base + HW_INT_STATUS);
+ priv->irq_done = 1;
+ wake_up(&nand->controller->wq);
+
+ return IRQ_HANDLED;
+}
+
+static void __init asm9260_nand_init_chip(struct nand_chip *nand_chip)
+{
+ nand_chip->select_chip = asm9260_select_chip;
+ nand_chip->cmdfunc = asm9260_nand_command_lp;
+ nand_chip->read_byte = asm9260_nand_read_byte;
+ nand_chip->read_word = asm9260_nand_read_word;
+ nand_chip->read_buf = asm9260_nand_read_buf;
+ nand_chip->write_buf = asm9260_nand_write_buf;
+
+ nand_chip->dev_ready = asm9260_nand_dev_ready;
+ nand_chip->chip_delay = 100;
+ nand_chip->options |= NAND_NO_SUBPAGE_WRITE;
+
+ nand_chip->ecc.write_page = asm9260_nand_write_page_hwecc;
+ nand_chip->ecc.write_page_raw = asm9260_nand_write_page_raw;
+ nand_chip->ecc.read_page = asm9260_nand_read_page_hwecc;
+}
+
+static int __init asm9260_nand_cached_config(struct asm9260_nand_priv *priv)
+{
+ struct nand_chip *nand = &priv->nand;
+ struct mtd_info *mtd = &priv->mtd;
+ u32 addr_cycles, col_cycles, pages_per_block;
+
+ pages_per_block = mtd->erasesize / mtd->writesize;
+ /* max 256P, min 32P */
+ if (pages_per_block & ~(0x000001e0)) {
+ dev_err(priv->dev, "Unsupported erasesize 0x%x\n",
+ mtd->erasesize);
+ return -EINVAL;
+ }
+
+ /* max 32K, min 256. */
+ if (mtd->writesize & ~(0x0000ff00)) {
+ dev_err(priv->dev, "Unsupported writesize 0x%x\n",
+ mtd->erasesize);
+ return -EINVAL;
+ }
+
+ col_cycles = 2;
+ addr_cycles = col_cycles +
+ (((mtd->size >> mtd->writesize) > 65536) ? 3 : 2);
+
+ priv->ctrl_cache = addr_cycles << BM_CTRL_ADDR_CYCLE1_S
+ | BM_CTRL_PAGE_SIZE(mtd->writesize) << BM_CTRL_PAGE_SIZE_S
+ | BM_CTRL_BLOCK_SIZE(pages_per_block) << BM_CTRL_BLOCK_SIZE_S
+ | BM_CTRL_INT_EN
+ | addr_cycles << BM_CTRL_ADDR_CYCLE0_S;
+
+ iowrite32(BM_ECC_CAPn(nand->ecc.strength) << BM_ECC_CAP_S,
+ priv->base + HW_ECC_CTRL);
+
+ iowrite32(mtd->writesize + priv->spare_size,
+ priv->base + HW_ECC_OFFSET);
+
+ return 0;
+}
+
+static unsigned long __init clk_get_cyc_from_ns(struct clk *clk,
+ unsigned long ns)
+{
+ unsigned int cycle;
+
+ cycle = NSEC_PER_SEC / clk_get_rate(clk);
+ return DIV_ROUND_CLOSEST(ns, cycle);
+}
+
+static void __init asm9260_nand_timing_config(struct asm9260_nand_priv *priv)
+{
+ struct nand_chip *nand = &priv->nand;
+ const struct nand_sdr_timings *time;
+ u32 twhr, trhw, trwh, trwp, tadl, tccs, tsync, trr, twb;
+ int mode;
+
+ mode = nand->onfi_timing_mode_default;
+ dev_info(priv->dev, "ONFI timing mode: %i\n", mode);
+
+ time = onfi_async_timing_mode_to_sdr_timings(mode);
+ if (IS_ERR_OR_NULL(time)) {
+ dev_err(priv->dev, "Can't get onfi_timing_mode: %i\n", mode);
+ return;
+ }
+
+ trwh = clk_get_cyc_from_ns(priv->clk, time->tWH_min / 1000);
+ trwp = clk_get_cyc_from_ns(priv->clk, time->tWP_min / 1000);
+
+ iowrite32((trwh << 4) | (trwp), priv->base + HW_TIMING_ASYN);
+
+ twhr = clk_get_cyc_from_ns(priv->clk, time->tWHR_min / 1000);
+ trhw = clk_get_cyc_from_ns(priv->clk, time->tRHW_min / 1000);
+ tadl = clk_get_cyc_from_ns(priv->clk, time->tADL_min / 1000);
+ /* tCCS - change read/write collumn. Time between last cmd and data. */
+ tccs = clk_get_cyc_from_ns(priv->clk,
+ (time->tCLR_min + time->tCLH_min + time->tRC_min)
+ / 1000);
+
+ iowrite32((twhr << 24) | (trhw << 16)
+ | (tadl << 8) | (tccs), priv->base + HW_TIM_SEQ_0);
+
+ trr = clk_get_cyc_from_ns(priv->clk, time->tRR_min / 1000);
+ tsync = 0; /* ??? */
+ twb = clk_get_cyc_from_ns(priv->clk, time->tWB_max / 1000);
+ iowrite32((tsync << 16) | (trr << 9) | (twb),
+ priv->base + HW_TIM_SEQ_1);
+}
+
+static int __init asm9260_nand_ecc_conf(struct asm9260_nand_priv *priv)
+{
+ struct device_node *np = priv->dev->of_node;
+ struct nand_chip *nand = &priv->nand;
+ struct mtd_info *mtd = &priv->mtd;
+ struct nand_ecclayout *ecc_layout = &priv->ecc_layout;
+ int i, ecc_strength;
+
+ nand->ecc.mode = of_get_nand_ecc_mode(np);
+ switch (nand->ecc.mode) {
+ case NAND_ECC_SOFT:
+ case NAND_ECC_SOFT_BCH:
+ dev_info(priv->dev, "Using soft ECC %i\n", nand->ecc.mode);
+ /* nand_base will find needed settings */
+ return 0;
+ case NAND_ECC_HW:
+ default:
+ dev_info(priv->dev, "Using default NAND_ECC_HW\n");
+ nand->ecc.mode = NAND_ECC_HW;
+ break;
+ }
+
+ ecc_strength = of_get_nand_ecc_strength(np);
+ nand->ecc.size = ASM9260_ECC_STEP;
+ nand->ecc.steps = mtd->writesize / nand->ecc.size;
+
+ if (ecc_strength < nand->ecc_strength_ds) {
+ int ds_corr;
+
+ /* Let's check if ONFI can help us. */
+ if (nand->ecc_strength_ds <= 0) {
+ /* No ONFI and no DT - it is bad. */
+ dev_err(priv->dev,
+ "nand-ecc-strength is not set by DT or ONFI. Please set nand-ecc-strength in DT or add chip quirk in nand_ids.c.\n");
+ return -EINVAL;
+ }
+
+ ds_corr = (mtd->writesize * nand->ecc_strength_ds) /
+ nand->ecc_step_ds;
+ ecc_strength = ds_corr / nand->ecc.steps;
+ dev_info(priv->dev, "ONFI:nand-ecc-strength = %i\n",
+ ecc_strength);
+ } else
+ dev_info(priv->dev, "DT:nand-ecc-strength = %i\n",
+ ecc_strength);
+
+ if (ecc_strength == 0 || ecc_strength > ASM9260_ECC_MAX_BIT) {
+ dev_err(priv->dev,
+ "Not supported ecc_strength value: %i\n",
+ ecc_strength);
+ return -EINVAL;
+ }
+
+ if (ecc_strength & 0x1) {
+ ecc_strength++;
+ dev_info(priv->dev,
+ "Only even ecc_strength value is supported. Recalculating: %i\n",
+ ecc_strength);
+ }
+
+ /* FIXME: do we have max or min page size? */
+
+ /* 13 - the smallest integer for 512 (ASM9260_ECC_STEP). Div to 8bit. */
+ nand->ecc.bytes = DIV_ROUND_CLOSEST(ecc_strength * 13, 8);
+
+ ecc_layout->eccbytes = nand->ecc.bytes * nand->ecc.steps;
+ nand->ecc.layout = ecc_layout;
+ nand->ecc.strength = ecc_strength;
+
+ priv->spare_size = mtd->oobsize - ecc_layout->eccbytes;
+
+ ecc_layout->oobfree[0].offset = 2;
+ ecc_layout->oobfree[0].length = priv->spare_size - 2;
+
+ /* FIXME: can we use same layout as SW_ECC? */
+ for (i = 0; i < ecc_layout->eccbytes; i++)
+ ecc_layout->eccpos[i] = priv->spare_size + i;
+
+ return 0;
+}
+
+static int __init asm9260_nand_get_dt_clks(struct asm9260_nand_priv *priv)
+{
+ int clk_idx = 0, err;
+
+ priv->clk = devm_clk_get(priv->dev, "sys");
+ if (IS_ERR(priv->clk))
+ goto out_err;
+
+ /* configure AHB clock */
+ clk_idx = 1;
+ priv->clk_ahb = devm_clk_get(priv->dev, "ahb");
+ if (IS_ERR(priv->clk_ahb))
+ goto out_err;
+
+ err = clk_prepare_enable(priv->clk_ahb);
+ if (err)
+ dev_err(priv->dev, "Failed to enable ahb_clk!\n");
+
+ err = clk_set_rate(priv->clk, clk_get_rate(priv->clk_ahb));
+ if (err) {
+ clk_disable_unprepare(priv->clk_ahb);
+ dev_err(priv->dev, "Failed to set rate!\n");
+ }
+
+ err = clk_prepare_enable(priv->clk);
+ if (err) {
+ clk_disable_unprepare(priv->clk_ahb);
+ dev_err(priv->dev, "Failed to enable clk!\n");
+ }
+
+ return 0;
+out_err:
+ dev_err(priv->dev, "%s: Failed to get clk (%i)\n", __func__, clk_idx);
+ return 1;
+}
+
+static int __init asm9260_nand_probe(struct platform_device *pdev)
+{
+ struct asm9260_nand_priv *priv;
+ struct nand_chip *nand;
+ struct mtd_info *mtd;
+ struct device_node *np = pdev->dev.of_node;
+ struct resource *r;
+ int ret, i;
+ unsigned int irq;
+ u32 val;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct asm9260_nand_priv),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->base = devm_ioremap_resource(&pdev->dev, r);
+ if (!priv->base) {
+ dev_err(&pdev->dev, "Unable to map resource!\n");
+ return -EINVAL;
+ }
+
+ priv->dev = &pdev->dev;
+ nand = &priv->nand;
+ nand->priv = priv;
+
+ platform_set_drvdata(pdev, priv);
+ mtd = &priv->mtd;
+ mtd->priv = nand;
+ mtd->owner = THIS_MODULE;
+ mtd->name = dev_name(&pdev->dev);
+
+ if (asm9260_nand_get_dt_clks(priv))
+ return -ENODEV;
+
+ irq = platform_get_irq(pdev, 0);
+ if (!irq)
+ return -ENODEV;
+
+ iowrite32(0, priv->base + HW_INT_MASK);
+ ret = devm_request_irq(priv->dev, irq, asm9260_nand_irq,
+ IRQF_ONESHOT | IRQF_SHARED,
+ dev_name(&pdev->dev), priv);
+
+ asm9260_nand_init_chip(nand);
+
+ ret = of_property_read_u32(np, "nand-max-chips", &val);
+ if (ret)
+ val = 1;
+
+ if (val > ASM9260_MAX_CHIPS) {
+ dev_err(&pdev->dev, "Unsupported nand-max-chips value: %i\n",
+ val);
+ return -ENODEV;
+ }
+
+ for (i = 0; i < val; i++)
+ priv->mem_mask |= BM_CTRL_MEM0_RDY << i;
+
+ ret = nand_scan_ident(mtd, val, NULL);
+ if (ret) {
+ dev_err(&pdev->dev, "scan_ident filed!\n");
+ return ret;
+ }
+
+ if (of_get_nand_on_flash_bbt(np)) {
+ dev_info(&pdev->dev, "Use On Flash BBT\n");
+ nand->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB_BBM
+ | NAND_BBT_LASTBLOCK;
+ }
+
+ asm9260_nand_timing_config(priv);
+ asm9260_nand_ecc_conf(priv);
+ ret = asm9260_nand_cached_config(priv);
+ if (ret)
+ return ret;
+
+ /* second phase scan */
+ if (nand_scan_tail(mtd)) {
+ dev_err(&pdev->dev, "scan_tail filed!\n");
+ return -ENXIO;
+ }
+
+
+ ret = mtd_device_parse_register(mtd, NULL,
+ &(struct mtd_part_parser_data) {
+ .of_node = pdev->dev.of_node,
+ },
+ NULL, 0);
+
+ return ret;
+}
+
+
+static int asm9260_nand_remove(struct platform_device *pdev)
+{
+ struct asm9260_nand_priv *priv = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(priv->clk);
+ clk_disable_unprepare(priv->clk_ahb);
+ nand_release(&priv->mtd);
+
+ return 0;
+}
+
+static const struct of_device_id asm9260_nand_match[] = {
+ {
+ .compatible = "alphascale,asm9260-nand",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, asm9260_nand_match);
+
+static struct platform_driver asm9260_nand_driver = {
+ .probe = asm9260_nand_probe,
+ .remove = asm9260_nand_remove,
+ .driver = {
+ .name = "asm9260_nand",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(asm9260_nand_match),
+ },
+};
+
+module_platform_driver(asm9260_nand_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ASM9260 NAND driver");
--
1.9.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v3 1/3] mtd: nand: add asm9260 NFC driver
2014-12-30 19:09 ` Boris Brezillon
2014-12-31 12:58 ` [PATCH v3 1/3] " Oleksij Rempel
@ 2014-12-31 12:58 ` Oleksij Rempel
2014-12-31 12:58 ` [PATCH v3 2/3] mtd: nand: add asm9260-nand devicetree documentation Oleksij Rempel
` (4 more replies)
1 sibling, 5 replies; 24+ messages in thread
From: Oleksij Rempel @ 2014-12-31 12:58 UTC (permalink / raw)
To: boris.brezillon, linux-mtd, computersforpeace; +Cc: Oleksij Rempel
Add driver for Nand Flash Controller used on Alphascales ASM9260 chips.
The IP core of this controller has some similarities with
Evatronix NANDFLASH-CTRL IP (unknown revision), so probably it can be reused
by some other SoCs.
Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
---
drivers/mtd/nand/Kconfig | 7 +
drivers/mtd/nand/Makefile | 1 +
drivers/mtd/nand/asm9260_nand.c | 989 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 997 insertions(+)
create mode 100644 drivers/mtd/nand/asm9260_nand.c
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index dd10646..580a608 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -41,6 +41,13 @@ config MTD_SM_COMMON
tristate
default n
+config MTD_NAND_ASM9260
+ tristate "NFC support for ASM9260 SoC"
+ depends on OF
+ default n
+ help
+ Enable support for the NAND controller found on Alphascale ASM9260 SoC.
+
config MTD_NAND_DENALI
tristate "Support Denali NAND controller"
depends on HAS_DMA
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 9c847e4..08d660a 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_MTD_NAND_BCH) += nand_bch.o
obj-$(CONFIG_MTD_NAND_IDS) += nand_ids.o
obj-$(CONFIG_MTD_SM_COMMON) += sm_common.o
+obj-$(CONFIG_MTD_NAND_ASM9260) += asm9260_nand.o
obj-$(CONFIG_MTD_NAND_CAFE) += cafe_nand.o
obj-$(CONFIG_MTD_NAND_AMS_DELTA) += ams-delta.o
obj-$(CONFIG_MTD_NAND_DENALI) += denali.o
diff --git a/drivers/mtd/nand/asm9260_nand.c b/drivers/mtd/nand/asm9260_nand.c
new file mode 100644
index 0000000..23154be
--- /dev/null
+++ b/drivers/mtd/nand/asm9260_nand.c
@@ -0,0 +1,989 @@
+/*
+ * NAND controller driver for Alphascale ASM9260, which is probably
+ * based on Evatronix NANDFLASH-CTRL IP (version unknown)
+ *
+ * Copyright (C), 2014 Oleksij Rempel <linux@rempel-privat.de>
+ *
+ * Inspired by asm9260_nand.c,
+ * Copyright (C), 2007-2013, Alphascale Tech. Co., Ltd.
+ * 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/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <linux/clk.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/of_mtd.h>
+
+#define ASM9260_ECC_STEP 512
+#define ASM9260_ECC_MAX_BIT 16
+#define ASM9260_MAX_CHIPS 2
+
+#define mtd_to_priv(m) container_of(m, struct asm9260_nand_priv, mtd)
+
+#define HW_CMD 0x00
+#define BM_CMD_CMD2_S 24
+#define BM_CMD_CMD1_S 16
+#define BM_CMD_CMD0_S 8
+/* 0 - ADDR0, 1 - ADDR1 */
+#define BM_CMD_ADDR1 BIT(7)
+/* 0 - PIO, 1 - DMA */
+#define BM_CMD_DMA BIT(6)
+#define BM_CMD_CMDSEQ_S 0
+/* single command, wait for RnB */
+#define SEQ0 0x00
+/* send cmd, addr, wait tWHR, fetch data */
+#define SEQ1 0x21
+/* send cmd, addr, wait RnB, fetch data */
+#define SEQ2 0x22
+/* send cmd, addr, wait tADL, send data, wait RnB */
+#define SEQ3 0x03
+/* send cmd, wait tWHR, fetch data */
+#define SEQ4 0x24
+/* send cmd, 3 x addr, wait tWHR, fetch data */
+#define SEQ5 0x25
+/* wait tRHW, send cmd, 2 x addr, cmd, wait tCCS, fetch data */
+#define SEQ6 0x26
+/* wait tRHW, send cmd, 35 x addr, cmd, wait tCCS, fetch data */
+#define SEQ7 0x27
+/* send cmd, 2 x addr, wait tCCS, fetch data */
+#define SEQ8 0x08
+/* send cmd, 5 x addr, wait RnB */
+#define SEQ9 0x29
+/* send cmd, 5 x addr, cmd, wait RnB, fetch data */
+#define SEQ10 0x2a
+/* send cmd, wait RnB, fetch data */
+#define SEQ11 0x2b
+/* send cmd, 5 x addr, wait tADL, send data, cmd */
+#define SEQ12 0x0c
+/* send cmd, 5 x addr, wait tADL, send data */
+#define SEQ13 0x0d
+/* send cmd, 3 x addr, cmd, wait RnB */
+#define SEQ14 0x0e
+/* send cmd, 5 x addr, cmd, 5 x addr, cmd, wait RnB, fetch data */
+#define SEQ15 0x2f
+/* send cmd, 5 x addr, wait RnB, fetch data */
+#define SEQ17 0x15
+
+#define HW_CTRL 0x04
+#define BM_CTRL_DIS_STATUS BIT(23)
+#define BM_CTRL_READ_STAT BIT(22)
+#define BM_CTRL_SMALL_BLOCK_EN BIT(21)
+#define BM_CTRL_ADDR_CYCLE1_S 18
+#define ADDR_CYCLE_0 0x0
+#define ADDR_CYCLE_1 0x1
+#define ADDR_CYCLE_2 0x2
+#define ADDR_CYCLE_3 0x3
+#define ADDR_CYCLE_4 0x4
+#define ADDR_CYCLE_5 0x5
+#define BM_CTRL_ADDR1_AUTO_INCR BIT(17)
+#define BM_CTRL_ADDR0_AUTO_INCR BIT(16)
+#define BM_CTRL_WORK_MODE BIT(15)
+#define BM_CTRL_PORT_EN BIT(14)
+#define BM_CTRL_LOOKU_EN BIT(13)
+#define BM_CTRL_IO_16BIT BIT(12)
+/* Overwrite BM_CTRL_PAGE_SIZE with HW_DATA_SIZE */
+#define BM_CTRL_CUSTOM_PAGE_SIZE BIT(11)
+#define BM_CTRL_PAGE_SIZE_S 8
+#define BM_CTRL_PAGE_SIZE(x) ((ffs((x) >> 8) - 1) & 0x7)
+#define PAGE_SIZE_256B 0x0
+#define PAGE_SIZE_512B 0x1
+#define PAGE_SIZE_1024B 0x2
+#define PAGE_SIZE_2048B 0x3
+#define PAGE_SIZE_4096B 0x4
+#define PAGE_SIZE_8192B 0x5
+#define PAGE_SIZE_16384B 0x6
+#define PAGE_SIZE_32768B 0x7
+#define BM_CTRL_BLOCK_SIZE_S 6
+#define BM_CTRL_BLOCK_SIZE(x) ((ffs((x) >> 5) - 1) & 0x3)
+#define BLOCK_SIZE_32P 0x0
+#define BLOCK_SIZE_64P 0x1
+#define BLOCK_SIZE_128P 0x2
+#define BLOCK_SIZE_256P 0x3
+#define BM_CTRL_ECC_EN BIT(5)
+#define BM_CTRL_INT_EN BIT(4)
+#define BM_CTRL_SPARE_EN BIT(3)
+/* same values as BM_CTRL_ADDR_CYCLE1_S */
+#define BM_CTRL_ADDR_CYCLE0_S 0
+
+#define HW_STATUS 0x08
+#define BM_CTRL_NFC_BUSY BIT(8)
+/* MEM1_RDY (BIT1) - MEM7_RDY (BIT7) */
+#define BM_CTRL_MEM0_RDY BIT(0)
+
+#define HW_INT_MASK 0x0c
+#define HW_INT_STATUS 0x10
+#define BM_INT_FIFO_ERROR BIT(12)
+#define BM_INT_MEM_RDY_S 4
+/* MEM1_RDY (BIT5) - MEM7_RDY (BIT11) */
+#define BM_INT_MEM0_RDY BIT(4)
+#define BM_INT_ECC_TRSH_ERR BIT(3)
+#define BM_INT_ECC_FATAL_ERR BIT(2)
+#define BM_INT_CMD_END BIT(1)
+
+#define HW_ECC_CTRL 0x14
+/* bits per 512 bytes */
+#define BM_ECC_CAP_S 5
+/* support ecc strange 2, 4, 6, 8, 10, 12, 14, 16. */
+#define BM_ECC_CAPn(x) ((((x) >> 1) - 1) & 0x7)
+/* Warn if some bitflip level (threshold) reached. Max 15 bits. */
+#define BM_ECC_ERR_THRESHOLD_S 8
+#define BM_ECC_ERR_THRESHOLD_M 0xf
+#define BM_ECC_ERR_OVER BIT(2)
+/* Uncorrected error. */
+#define BM_ECC_ERR_UNC BIT(1)
+/* Corrected error. */
+#define BM_ECC_ERR_CORRECT BIT(0)
+
+#define HW_ECC_OFFSET 0x18
+#define HW_ADDR0_0 0x1c
+#define HW_ADDR1_0 0x20
+#define HW_ADDR0_1 0x24
+#define HW_ADDR1_1 0x28
+#define HW_SPARE_SIZE 0x30
+#define HW_DMA_ADDR 0x64
+#define HW_DMA_CNT 0x68
+
+#define HW_DMA_CTRL 0x6c
+#define BM_DMA_CTRL_START BIT(7)
+/* 0 - to device; 1 - from device */
+#define BM_DMA_CTRL_FROM_DEVICE BIT(6)
+/* 0 - software maneged; 1 - scatter-gather */
+#define BM_DMA_CTRL_SG BIT(5)
+#define BM_DMA_CTRL_BURST_S 2
+#define DMA_BURST_INCR4 0x0
+#define DMA_BURST_STREAM 0x1
+#define DMA_BURST_SINGLE 0x2
+#define DMA_BURST_INCR 0x3
+#define DMA_BURST_INCR8 0x4
+#define DMA_BURST_INCR16 0x5
+#define BM_DMA_CTRL_ERR BIT(1)
+#define BM_DMA_CTRL_RDY BIT(0)
+
+#define HW_MEM_CTRL 0x80
+#define BM_MEM_CTRL_WP_STATE_MASK 0xff00
+#define BM_MEM_CTRL_UNWPn(x) (1 << ((x) + 8))
+#define BM_MEM_CTRL_CEn(x) (((x) & 7) << 0)
+
+/* BM_CTRL_CUSTOM_PAGE_SIZE should be set */
+#define HW_DATA_SIZE 0x84
+#define HW_READ_STATUS 0x88
+#define HW_TIM_SEQ_0 0x8c
+#define HW_TIMING_ASYN 0x90
+#define HW_TIMING_SYN 0x94
+
+#define HW_FIFO_DATA 0x98
+#define HW_TIME_MODE 0x9c
+#define HW_FIFO_INIT 0xb0
+/*
+ * Counter for ecc related errors.
+ * For each 512 byte block it has 5bit counter.
+ */
+#define HW_ECC_ERR_CNT 0xb8
+
+#define HW_TIM_SEQ_1 0xc8
+
+struct asm9260_nand_priv {
+ struct device *dev;
+ struct mtd_info mtd;
+ struct nand_chip nand;
+ struct nand_ecclayout ecc_layout;
+
+ struct clk *clk;
+ struct clk *clk_ahb;
+
+ void __iomem *base;
+ int irq_done;
+
+ u32 read_cache;
+ int read_cache_cnt;
+ u32 cmd_cache;
+ u32 ctrl_cache;
+ u32 mem_mask;
+ u32 page_cache;
+ unsigned int wait_time;
+
+ unsigned int spare_size;
+};
+
+static void asm9260_reg_rmw(struct asm9260_nand_priv *priv,
+ u32 reg_offset, u32 set, u32 clr)
+{
+ u32 val;
+
+ val = ioread32(priv->base + reg_offset);
+ val &= ~clr;
+ val |= set;
+ iowrite32(val, priv->base + reg_offset);
+}
+
+static void asm9260_select_chip(struct mtd_info *mtd, int chip)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+
+ if (chip == -1)
+ iowrite32(BM_MEM_CTRL_WP_STATE_MASK, priv->base + HW_MEM_CTRL);
+ else
+ iowrite32(BM_MEM_CTRL_UNWPn(chip) | BM_MEM_CTRL_CEn(chip),
+ priv->base + HW_MEM_CTRL);
+}
+
+/* 3 commands are supported by HW. 3-d can be used for TWO PLANE. */
+static void asm9260_nand_cmd_prep(struct asm9260_nand_priv *priv,
+ u8 cmd0, u8 cmd1, u8 cmd2, u8 seq)
+{
+ priv->cmd_cache = (cmd0 << BM_CMD_CMD0_S) | (cmd1 << BM_CMD_CMD1_S);
+ priv->cmd_cache |= seq << BM_CMD_CMDSEQ_S;
+}
+
+static dma_addr_t asm9260_nand_dma_set(struct mtd_info *mtd, void *buf,
+ enum dma_data_direction dir, size_t size)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ dma_addr_t dma_addr;
+
+ dma_addr = dma_map_single(priv->dev, buf, size, dir);
+ if (dma_mapping_error(priv->dev, dma_addr)) {
+ dev_err(priv->dev, "dma_map_single failed!\n");
+ return dma_addr;
+
+ }
+
+ iowrite32(dma_addr, priv->base + HW_DMA_ADDR);
+ iowrite32(size, priv->base + HW_DMA_CNT);
+ iowrite32(BM_DMA_CTRL_START
+ | (dir == DMA_FROM_DEVICE ? BM_DMA_CTRL_FROM_DEVICE : 0)
+ | (DMA_BURST_INCR16 << BM_DMA_CTRL_BURST_S),
+ priv->base + HW_DMA_CTRL);
+ return dma_addr;
+}
+
+/* complete command request */
+static void asm9260_nand_cmd_comp(struct mtd_info *mtd, int dma)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ int timeout;
+ u32 cmd;
+
+ if (!priv->cmd_cache)
+ return;
+
+ if (dma) {
+ priv->cmd_cache |= BM_CMD_DMA;
+ priv->irq_done = 0;
+ iowrite32(priv->mem_mask << BM_INT_MEM_RDY_S,
+ priv->base + HW_INT_MASK);
+ }
+
+ iowrite32(priv->cmd_cache, priv->base + HW_CMD);
+ cmd = priv->cmd_cache;
+ priv->cmd_cache = 0;
+
+ if (dma) {
+ struct nand_chip *nand = &priv->nand;
+
+ timeout = wait_event_timeout(nand->controller->wq,
+ priv->irq_done,
+ msecs_to_jiffies(priv->wait_time ?
+ priv->wait_time : 20));
+ if (timeout <= 0) {
+ dev_info(priv->dev,
+ "Request 0x%08x timed out\n", cmd);
+ /* TODO: Do something useful here? */
+ /* FIXME: if we have problems on DMA or PIO, we need to
+ * reset NFC. On asm9260 it is possible only with global
+ * reset register. How can we use it here? */
+ }
+ priv->wait_time = 0;
+ } else
+ nand_wait_ready(mtd);
+}
+
+static int asm9260_nand_dev_ready(struct mtd_info *mtd)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ u32 tmp;
+
+ tmp = ioread32(priv->base + HW_STATUS);
+
+ return (!(tmp & BM_CTRL_NFC_BUSY) &&
+ (tmp & priv->mem_mask));
+}
+
+static void asm9260_nand_ctrl(struct asm9260_nand_priv *priv, u32 set)
+{
+ iowrite32(priv->ctrl_cache | set, priv->base + HW_CTRL);
+}
+
+static void asm9260_nand_set_addr(struct asm9260_nand_priv *priv,
+ u32 row_addr, u32 column)
+{
+ u32 addr[2];
+
+ addr[0] = (column & 0xffff) | (0xffff0000 & (row_addr << 16));
+ addr[1] = (row_addr >> 16) & 0xff;
+
+ iowrite32(addr[0], priv->base + HW_ADDR0_0);
+ iowrite32(addr[1], priv->base + HW_ADDR0_1);
+}
+
+static void asm9260_nand_command_lp(struct mtd_info *mtd,
+ unsigned int command, int column, int page_addr)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+
+ switch (command) {
+ case NAND_CMD_RESET:
+ asm9260_nand_cmd_prep(priv, NAND_CMD_RESET, 0, 0, SEQ0);
+ asm9260_nand_cmd_comp(mtd, 0);
+ break;
+
+ case NAND_CMD_READID:
+ iowrite32(1, priv->base + HW_FIFO_INIT);
+ iowrite32((ADDR_CYCLE_1 << BM_CTRL_ADDR_CYCLE1_S)
+ | BM_CTRL_CUSTOM_PAGE_SIZE
+ | (PAGE_SIZE_4096B << BM_CTRL_PAGE_SIZE_S)
+ | (BLOCK_SIZE_32P << BM_CTRL_BLOCK_SIZE_S)
+ | BM_CTRL_INT_EN
+ | (ADDR_CYCLE_1 << BM_CTRL_ADDR_CYCLE0_S),
+ priv->base + HW_CTRL);
+
+ iowrite32(8, priv->base + HW_DATA_SIZE);
+ iowrite32(column, priv->base + HW_ADDR0_0);
+ asm9260_nand_cmd_prep(priv, NAND_CMD_READID, 0, 0, SEQ1);
+
+ priv->read_cache_cnt = 0;
+ break;
+
+ case NAND_CMD_READOOB:
+ column += mtd->writesize;
+ command = NAND_CMD_READ0;
+ case NAND_CMD_READ0:
+ iowrite32(1, priv->base + HW_FIFO_INIT);
+
+ if (column == 0) {
+ asm9260_nand_ctrl(priv, 0);
+ iowrite32(priv->spare_size, priv->base + HW_SPARE_SIZE);
+ } else if (column == mtd->writesize) {
+ asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
+ iowrite32(mtd->oobsize, priv->base + HW_SPARE_SIZE);
+ iowrite32(mtd->oobsize, priv->base + HW_DATA_SIZE);
+ } else {
+ dev_err(priv->dev, "Couldn't support the column\n");
+ break;
+ }
+
+ asm9260_nand_set_addr(priv, page_addr, column);
+
+ asm9260_nand_cmd_prep(priv, NAND_CMD_READ0,
+ NAND_CMD_READSTART, 0, SEQ10);
+
+ priv->read_cache_cnt = 0;
+ break;
+ case NAND_CMD_SEQIN:
+ iowrite32(1, priv->base + HW_FIFO_INIT);
+
+ if (column == 0) {
+ priv->page_cache = page_addr;
+ asm9260_nand_ctrl(priv, 0);
+ iowrite32(priv->spare_size, priv->base + HW_SPARE_SIZE);
+ } else if (column == mtd->writesize) {
+ asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
+ iowrite32(mtd->oobsize, priv->base + HW_DATA_SIZE);
+ }
+
+ asm9260_nand_set_addr(priv, page_addr, column);
+
+ asm9260_nand_cmd_prep(priv, NAND_CMD_SEQIN, NAND_CMD_PAGEPROG,
+ 0, SEQ12);
+
+ break;
+ case NAND_CMD_STATUS:
+ iowrite32(1, priv->base + HW_FIFO_INIT);
+ asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
+
+ /*
+ * Workaround for status bug.
+ * Instead of SEQ4 we need to use SEQ1 here, which will
+ * send cmd with address. For this case we need to make sure
+ * ADDR == 0.
+ */
+ asm9260_nand_set_addr(priv, 0, 0);
+ iowrite32(4, priv->base + HW_DATA_SIZE);
+ asm9260_nand_cmd_prep(priv, NAND_CMD_STATUS, 0, 0, SEQ1);
+
+ priv->read_cache_cnt = 0;
+ break;
+
+ case NAND_CMD_ERASE1:
+ priv->wait_time = 400;
+ asm9260_nand_set_addr(priv, page_addr, column);
+
+ asm9260_nand_ctrl(priv, 0);
+
+ /*
+ * Prepare and send command now. We don't need to split it in
+ * two stages.
+ */
+ asm9260_nand_cmd_prep(priv, NAND_CMD_ERASE1, NAND_CMD_ERASE2,
+ 0, SEQ14);
+ asm9260_nand_cmd_comp(mtd, 0);
+ break;
+ default:
+ break;
+ }
+}
+
+
+/**
+ * We can't read less then 32 bits on HW_FIFO_DATA. So, to make
+ * read_byte and read_word happy, we use sort of cached 32bit read.
+ * Note: expected values for size should be 1 or 2 bytes.
+ */
+static u32 asm9260_nand_read_cached(struct mtd_info *mtd, int size)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ u8 tmp;
+
+ if ((priv->read_cache_cnt <= 0) || (priv->read_cache_cnt > 4)) {
+ asm9260_nand_cmd_comp(mtd, 0);
+ priv->read_cache = ioread32(priv->base + HW_FIFO_DATA);
+ priv->read_cache_cnt = 4;
+ }
+
+ tmp = priv->read_cache >> (8 * (4 - priv->read_cache_cnt));
+ priv->read_cache_cnt -= size;
+
+ return tmp;
+}
+
+static u8 asm9260_nand_read_byte(struct mtd_info *mtd)
+{
+ return 0xff & asm9260_nand_read_cached(mtd, 1);
+}
+
+static u16 asm9260_nand_read_word(struct mtd_info *mtd)
+{
+ return 0xffff & asm9260_nand_read_cached(mtd, 2);
+}
+
+static void asm9260_nand_read_buf(struct mtd_info *mtd, u8 *buf, int len)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ dma_addr_t dma_addr;
+ int dma_ok = 0;
+
+ if (len & 0x3) {
+ dev_err(priv->dev, "Unsupported length (%x)\n", len);
+ return;
+ }
+
+ /*
+ * I hate you UBI for your all vmalloc. Be slow as hell with PIO.
+ * ~ with love from ZeroCopy ~
+ */
+ if (!is_vmalloc_addr(buf)) {
+ dma_addr = asm9260_nand_dma_set(mtd, buf, DMA_FROM_DEVICE, len);
+ dma_ok = !(dma_mapping_error(priv->dev, dma_addr));
+ }
+ asm9260_nand_cmd_comp(mtd, dma_ok);
+
+ if (dma_ok) {
+ dma_sync_single_for_cpu(priv->dev, dma_addr, len,
+ DMA_FROM_DEVICE);
+ dma_unmap_single(priv->dev, dma_addr, len, DMA_FROM_DEVICE);
+ return;
+ }
+
+ /* fall back to pio mode */
+ len >>= 2;
+ ioread32_rep(priv->base + HW_FIFO_DATA, buf, len);
+}
+
+static void asm9260_nand_write_buf(struct mtd_info *mtd,
+ const u8 *buf, int len)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ dma_addr_t dma_addr;
+ int dma_ok = 0;
+
+ if (len & 0x3) {
+ dev_err(priv->dev, "Unsupported length (%x)\n", len);
+ return;
+ }
+
+ if (!is_vmalloc_addr(buf)) {
+ dma_addr = asm9260_nand_dma_set(mtd,
+ (void *)buf, DMA_TO_DEVICE, len);
+ dma_ok = !(dma_mapping_error(priv->dev, dma_addr));
+ }
+
+ if (dma_ok)
+ dma_sync_single_for_device(priv->dev, dma_addr, len,
+ DMA_TO_DEVICE);
+ asm9260_nand_cmd_comp(mtd, dma_ok);
+
+ if (dma_ok) {
+ dma_unmap_single(priv->dev, dma_addr, len, DMA_TO_DEVICE);
+ return;
+ }
+
+ /* fall back to pio mode */
+ len >>= 2;
+ iowrite32_rep(priv->base + HW_FIFO_DATA, buf, len);
+}
+
+static int asm9260_nand_write_page_raw(struct mtd_info *mtd,
+ struct nand_chip *chip, const u8 *buf,
+ int oob_required)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+
+ chip->write_buf(mtd, buf, mtd->writesize);
+ if (oob_required)
+ chip->ecc.write_oob(mtd, chip, priv->page_cache &
+ chip->pagemask);
+ return 0;
+}
+
+static int asm9260_nand_write_page_hwecc(struct mtd_info *mtd,
+ struct nand_chip *chip, const u8 *buf,
+ int oob_required)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+
+ asm9260_reg_rmw(priv, HW_CTRL, BM_CTRL_ECC_EN, 0);
+ chip->ecc.write_page_raw(mtd, chip, buf, oob_required);
+
+ return 0;
+}
+
+static unsigned int asm9260_nand_count_ecc(struct asm9260_nand_priv *priv)
+{
+ u32 tmp, i, count, maxcount = 0;
+
+ /* FIXME: this layout was tested only on 2048byte NAND.
+ * NANDs with bigger page size should use more registers. */
+ tmp = ioread32(priv->base + HW_ECC_ERR_CNT);
+ for (i = 0; i < 4; i++) {
+ count = 0x1f & (tmp >> (5 * i));
+ maxcount = max_t(unsigned int, maxcount, count);
+ }
+
+ return count;
+}
+
+static int asm9260_nand_read_page_hwecc(struct mtd_info *mtd,
+ struct nand_chip *chip, u8 *buf,
+ int oob_required, int page)
+{
+ struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
+ u8 *temp_ptr;
+ u32 status, max_bitflips = 0;
+
+ temp_ptr = buf;
+
+ /* enable ecc */
+ asm9260_reg_rmw(priv, HW_CTRL, BM_CTRL_ECC_EN, 0);
+ chip->read_buf(mtd, temp_ptr, mtd->writesize);
+
+ status = ioread32(priv->base + HW_ECC_CTRL);
+
+ if (status & BM_ECC_ERR_UNC) {
+ u32 ecc_err;
+
+ ecc_err = ioread32(priv->base + HW_ECC_ERR_CNT);
+ /* check if it is erased page (all_DATA_OOB == 0xff) */
+ /* FIXME: should be tested if it is a bullet proof solution.
+ * if not, use is_buf_blank. */
+ if (ecc_err != 0x8421)
+ mtd->ecc_stats.failed++;
+
+ } else if (status & BM_ECC_ERR_CORRECT) {
+ max_bitflips = asm9260_nand_count_ecc(priv);
+ mtd->ecc_stats.corrected += max_bitflips;
+ }
+
+ if (oob_required)
+ chip->ecc.read_oob(mtd, chip, page);
+
+ return max_bitflips;
+}
+
+static irqreturn_t asm9260_nand_irq(int irq, void *device_info)
+{
+ struct asm9260_nand_priv *priv = device_info;
+ struct nand_chip *nand = &priv->nand;
+ u32 status;
+
+ status = ioread32(priv->base + HW_INT_STATUS);
+ if (!status)
+ return IRQ_NONE;
+
+ iowrite32(0, priv->base + HW_INT_MASK);
+ iowrite32(0, priv->base + HW_INT_STATUS);
+ priv->irq_done = 1;
+ wake_up(&nand->controller->wq);
+
+ return IRQ_HANDLED;
+}
+
+static void __init asm9260_nand_init_chip(struct nand_chip *nand_chip)
+{
+ nand_chip->select_chip = asm9260_select_chip;
+ nand_chip->cmdfunc = asm9260_nand_command_lp;
+ nand_chip->read_byte = asm9260_nand_read_byte;
+ nand_chip->read_word = asm9260_nand_read_word;
+ nand_chip->read_buf = asm9260_nand_read_buf;
+ nand_chip->write_buf = asm9260_nand_write_buf;
+
+ nand_chip->dev_ready = asm9260_nand_dev_ready;
+ nand_chip->chip_delay = 100;
+ nand_chip->options |= NAND_NO_SUBPAGE_WRITE;
+
+ nand_chip->ecc.write_page = asm9260_nand_write_page_hwecc;
+ nand_chip->ecc.write_page_raw = asm9260_nand_write_page_raw;
+ nand_chip->ecc.read_page = asm9260_nand_read_page_hwecc;
+}
+
+static int __init asm9260_nand_cached_config(struct asm9260_nand_priv *priv)
+{
+ struct nand_chip *nand = &priv->nand;
+ struct mtd_info *mtd = &priv->mtd;
+ u32 addr_cycles, col_cycles, pages_per_block;
+
+ pages_per_block = mtd->erasesize / mtd->writesize;
+ /* max 256P, min 32P */
+ if (pages_per_block & ~(0x000001e0)) {
+ dev_err(priv->dev, "Unsupported erasesize 0x%x\n",
+ mtd->erasesize);
+ return -EINVAL;
+ }
+
+ /* max 32K, min 256. */
+ if (mtd->writesize & ~(0x0000ff00)) {
+ dev_err(priv->dev, "Unsupported writesize 0x%x\n",
+ mtd->erasesize);
+ return -EINVAL;
+ }
+
+ col_cycles = 2;
+ addr_cycles = col_cycles +
+ (((mtd->size >> mtd->writesize) > 65536) ? 3 : 2);
+
+ priv->ctrl_cache = addr_cycles << BM_CTRL_ADDR_CYCLE1_S
+ | BM_CTRL_PAGE_SIZE(mtd->writesize) << BM_CTRL_PAGE_SIZE_S
+ | BM_CTRL_BLOCK_SIZE(pages_per_block) << BM_CTRL_BLOCK_SIZE_S
+ | BM_CTRL_INT_EN
+ | addr_cycles << BM_CTRL_ADDR_CYCLE0_S;
+
+ iowrite32(BM_ECC_CAPn(nand->ecc.strength) << BM_ECC_CAP_S,
+ priv->base + HW_ECC_CTRL);
+
+ iowrite32(mtd->writesize + priv->spare_size,
+ priv->base + HW_ECC_OFFSET);
+
+ return 0;
+}
+
+static unsigned long __init clk_get_cyc_from_ns(struct clk *clk,
+ unsigned long ns)
+{
+ unsigned int cycle;
+
+ cycle = NSEC_PER_SEC / clk_get_rate(clk);
+ return DIV_ROUND_CLOSEST(ns, cycle);
+}
+
+static void __init asm9260_nand_timing_config(struct asm9260_nand_priv *priv)
+{
+ struct nand_chip *nand = &priv->nand;
+ const struct nand_sdr_timings *time;
+ u32 twhr, trhw, trwh, trwp, tadl, tccs, tsync, trr, twb;
+ int mode;
+
+ mode = nand->onfi_timing_mode_default;
+ dev_info(priv->dev, "ONFI timing mode: %i\n", mode);
+
+ time = onfi_async_timing_mode_to_sdr_timings(mode);
+ if (IS_ERR_OR_NULL(time)) {
+ dev_err(priv->dev, "Can't get onfi_timing_mode: %i\n", mode);
+ return;
+ }
+
+ trwh = clk_get_cyc_from_ns(priv->clk, time->tWH_min / 1000);
+ trwp = clk_get_cyc_from_ns(priv->clk, time->tWP_min / 1000);
+
+ iowrite32((trwh << 4) | (trwp), priv->base + HW_TIMING_ASYN);
+
+ twhr = clk_get_cyc_from_ns(priv->clk, time->tWHR_min / 1000);
+ trhw = clk_get_cyc_from_ns(priv->clk, time->tRHW_min / 1000);
+ tadl = clk_get_cyc_from_ns(priv->clk, time->tADL_min / 1000);
+ /* tCCS - change read/write collumn. Time between last cmd and data. */
+ tccs = clk_get_cyc_from_ns(priv->clk,
+ (time->tCLR_min + time->tCLH_min + time->tRC_min)
+ / 1000);
+
+ iowrite32((twhr << 24) | (trhw << 16)
+ | (tadl << 8) | (tccs), priv->base + HW_TIM_SEQ_0);
+
+ trr = clk_get_cyc_from_ns(priv->clk, time->tRR_min / 1000);
+ tsync = 0; /* ??? */
+ twb = clk_get_cyc_from_ns(priv->clk, time->tWB_max / 1000);
+ iowrite32((tsync << 16) | (trr << 9) | (twb),
+ priv->base + HW_TIM_SEQ_1);
+}
+
+static int __init asm9260_nand_ecc_conf(struct asm9260_nand_priv *priv)
+{
+ struct device_node *np = priv->dev->of_node;
+ struct nand_chip *nand = &priv->nand;
+ struct mtd_info *mtd = &priv->mtd;
+ struct nand_ecclayout *ecc_layout = &priv->ecc_layout;
+ int i, ecc_strength;
+
+ nand->ecc.mode = of_get_nand_ecc_mode(np);
+ switch (nand->ecc.mode) {
+ case NAND_ECC_SOFT:
+ case NAND_ECC_SOFT_BCH:
+ dev_info(priv->dev, "Using soft ECC %i\n", nand->ecc.mode);
+ /* nand_base will find needed settings */
+ return 0;
+ case NAND_ECC_HW:
+ default:
+ dev_info(priv->dev, "Using default NAND_ECC_HW\n");
+ nand->ecc.mode = NAND_ECC_HW;
+ break;
+ }
+
+ ecc_strength = of_get_nand_ecc_strength(np);
+ nand->ecc.size = ASM9260_ECC_STEP;
+ nand->ecc.steps = mtd->writesize / nand->ecc.size;
+
+ if (ecc_strength < nand->ecc_strength_ds) {
+ int ds_corr;
+
+ /* Let's check if ONFI can help us. */
+ if (nand->ecc_strength_ds <= 0) {
+ /* No ONFI and no DT - it is bad. */
+ dev_err(priv->dev,
+ "nand-ecc-strength is not set by DT or ONFI. Please set nand-ecc-strength in DT or add chip quirk in nand_ids.c.\n");
+ return -EINVAL;
+ }
+
+ ds_corr = (mtd->writesize * nand->ecc_strength_ds) /
+ nand->ecc_step_ds;
+ ecc_strength = ds_corr / nand->ecc.steps;
+ dev_info(priv->dev, "ONFI:nand-ecc-strength = %i\n",
+ ecc_strength);
+ } else
+ dev_info(priv->dev, "DT:nand-ecc-strength = %i\n",
+ ecc_strength);
+
+ if (ecc_strength == 0 || ecc_strength > ASM9260_ECC_MAX_BIT) {
+ dev_err(priv->dev,
+ "Not supported ecc_strength value: %i\n",
+ ecc_strength);
+ return -EINVAL;
+ }
+
+ if (ecc_strength & 0x1) {
+ ecc_strength++;
+ dev_info(priv->dev,
+ "Only even ecc_strength value is supported. Recalculating: %i\n",
+ ecc_strength);
+ }
+
+ /* FIXME: do we have max or min page size? */
+
+ /* 13 - the smallest integer for 512 (ASM9260_ECC_STEP). Div to 8bit. */
+ nand->ecc.bytes = DIV_ROUND_CLOSEST(ecc_strength * 13, 8);
+
+ ecc_layout->eccbytes = nand->ecc.bytes * nand->ecc.steps;
+ nand->ecc.layout = ecc_layout;
+ nand->ecc.strength = ecc_strength;
+
+ priv->spare_size = mtd->oobsize - ecc_layout->eccbytes;
+
+ ecc_layout->oobfree[0].offset = 2;
+ ecc_layout->oobfree[0].length = priv->spare_size - 2;
+
+ /* FIXME: can we use same layout as SW_ECC? */
+ for (i = 0; i < ecc_layout->eccbytes; i++)
+ ecc_layout->eccpos[i] = priv->spare_size + i;
+
+ return 0;
+}
+
+static int __init asm9260_nand_get_dt_clks(struct asm9260_nand_priv *priv)
+{
+ int clk_idx = 0, err;
+
+ priv->clk = devm_clk_get(priv->dev, "sys");
+ if (IS_ERR(priv->clk))
+ goto out_err;
+
+ /* configure AHB clock */
+ clk_idx = 1;
+ priv->clk_ahb = devm_clk_get(priv->dev, "ahb");
+ if (IS_ERR(priv->clk_ahb))
+ goto out_err;
+
+ err = clk_prepare_enable(priv->clk_ahb);
+ if (err)
+ dev_err(priv->dev, "Failed to enable ahb_clk!\n");
+
+ err = clk_set_rate(priv->clk, clk_get_rate(priv->clk_ahb));
+ if (err) {
+ clk_disable_unprepare(priv->clk_ahb);
+ dev_err(priv->dev, "Failed to set rate!\n");
+ }
+
+ err = clk_prepare_enable(priv->clk);
+ if (err) {
+ clk_disable_unprepare(priv->clk_ahb);
+ dev_err(priv->dev, "Failed to enable clk!\n");
+ }
+
+ return 0;
+out_err:
+ dev_err(priv->dev, "%s: Failed to get clk (%i)\n", __func__, clk_idx);
+ return 1;
+}
+
+static int __init asm9260_nand_probe(struct platform_device *pdev)
+{
+ struct asm9260_nand_priv *priv;
+ struct nand_chip *nand;
+ struct mtd_info *mtd;
+ struct device_node *np = pdev->dev.of_node;
+ struct resource *r;
+ int ret, i;
+ unsigned int irq;
+ u32 val;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct asm9260_nand_priv),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->base = devm_ioremap_resource(&pdev->dev, r);
+ if (!priv->base) {
+ dev_err(&pdev->dev, "Unable to map resource!\n");
+ return -EINVAL;
+ }
+
+ priv->dev = &pdev->dev;
+ nand = &priv->nand;
+ nand->priv = priv;
+
+ platform_set_drvdata(pdev, priv);
+ mtd = &priv->mtd;
+ mtd->priv = nand;
+ mtd->owner = THIS_MODULE;
+ mtd->name = dev_name(&pdev->dev);
+
+ if (asm9260_nand_get_dt_clks(priv))
+ return -ENODEV;
+
+ irq = platform_get_irq(pdev, 0);
+ if (!irq)
+ return -ENODEV;
+
+ iowrite32(0, priv->base + HW_INT_MASK);
+ ret = devm_request_irq(priv->dev, irq, asm9260_nand_irq,
+ IRQF_ONESHOT | IRQF_SHARED,
+ dev_name(&pdev->dev), priv);
+
+ asm9260_nand_init_chip(nand);
+
+ ret = of_property_read_u32(np, "nand-max-chips", &val);
+ if (ret)
+ val = 1;
+
+ if (val > ASM9260_MAX_CHIPS) {
+ dev_err(&pdev->dev, "Unsupported nand-max-chips value: %i\n",
+ val);
+ return -ENODEV;
+ }
+
+ for (i = 0; i < val; i++)
+ priv->mem_mask |= BM_CTRL_MEM0_RDY << i;
+
+ ret = nand_scan_ident(mtd, val, NULL);
+ if (ret) {
+ dev_err(&pdev->dev, "scan_ident filed!\n");
+ return ret;
+ }
+
+ if (of_get_nand_on_flash_bbt(np)) {
+ dev_info(&pdev->dev, "Use On Flash BBT\n");
+ nand->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB_BBM
+ | NAND_BBT_LASTBLOCK;
+ }
+
+ asm9260_nand_timing_config(priv);
+ asm9260_nand_ecc_conf(priv);
+ ret = asm9260_nand_cached_config(priv);
+ if (ret)
+ return ret;
+
+ /* second phase scan */
+ if (nand_scan_tail(mtd)) {
+ dev_err(&pdev->dev, "scan_tail filed!\n");
+ return -ENXIO;
+ }
+
+
+ ret = mtd_device_parse_register(mtd, NULL,
+ &(struct mtd_part_parser_data) {
+ .of_node = pdev->dev.of_node,
+ },
+ NULL, 0);
+
+ return ret;
+}
+
+
+static int asm9260_nand_remove(struct platform_device *pdev)
+{
+ struct asm9260_nand_priv *priv = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(priv->clk);
+ clk_disable_unprepare(priv->clk_ahb);
+ nand_release(&priv->mtd);
+
+ return 0;
+}
+
+static const struct of_device_id asm9260_nand_match[] = {
+ {
+ .compatible = "alphascale,asm9260-nand",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, asm9260_nand_match);
+
+static struct platform_driver asm9260_nand_driver = {
+ .probe = asm9260_nand_probe,
+ .remove = asm9260_nand_remove,
+ .driver = {
+ .name = "asm9260_nand",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(asm9260_nand_match),
+ },
+};
+
+module_platform_driver(asm9260_nand_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ASM9260 NAND driver");
--
1.9.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v3 2/3] mtd: nand: add asm9260-nand devicetree documentation
2014-12-31 12:58 ` Oleksij Rempel
@ 2014-12-31 12:58 ` Oleksij Rempel
2014-12-31 17:01 ` Boris Brezillon
2014-12-31 12:58 ` [PATCH v3 3/3] mtd: nand: add Toshiba TC58NVG0S3E to nand_ids table Oleksij Rempel
` (3 subsequent siblings)
4 siblings, 1 reply; 24+ messages in thread
From: Oleksij Rempel @ 2014-12-31 12:58 UTC (permalink / raw)
To: boris.brezillon, linux-mtd, computersforpeace; +Cc: Oleksij Rempel
Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
---
.../devicetree/bindings/mtd/asm9260-nand.txt | 25 ++++++++++++++++++++++
1 file changed, 25 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mtd/asm9260-nand.txt
diff --git a/Documentation/devicetree/bindings/mtd/asm9260-nand.txt b/Documentation/devicetree/bindings/mtd/asm9260-nand.txt
new file mode 100644
index 0000000..07bfdac
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/asm9260-nand.txt
@@ -0,0 +1,25 @@
+* Alphascales's asm9260_nand
+
+Required properties:
+- compatible: "alphascale,asm9260-nand"
+- reg: address range of the nfc block
+- interrupts: irq to be used
+- clocks: clock ids.
+- clock-names: only "sys" and "ahb" should be used.
+- nand-ecc-strength: see nand.txt
+- nand-ecc-mode: see nand.txt
+- nand-on-flash-bbt: see nand.txt
+- nand-max-chips: maximal number of attached chips. asm9260 support max 2.
+
+Example:
+
+ nand@d8000000 {
+ compatible = "alphascale,asm9260-nand";
+ reg = <0x80600000 0x100>;
+ interrupts = <29>;
+ nand-ecc-strength = <4>;
+ nand-ecc-mode = "hw";
+ nand-max-chips = <1>;
+ clocks = <&acc CLKID_SYS_NAND>, <&acc CLKID_AHB_NAND>;
+ clock-names = "sys", "ahb";
+ };
--
1.9.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v3 3/3] mtd: nand: add Toshiba TC58NVG0S3E to nand_ids table
2014-12-31 12:58 ` Oleksij Rempel
2014-12-31 12:58 ` [PATCH v3 2/3] mtd: nand: add asm9260-nand devicetree documentation Oleksij Rempel
@ 2014-12-31 12:58 ` Oleksij Rempel
2014-12-31 16:55 ` Boris Brezillon
2015-08-25 19:25 ` Brian Norris
2014-12-31 16:48 ` [PATCH v3 1/3] mtd: nand: add asm9260 NFC driver Boris Brezillon
` (2 subsequent siblings)
4 siblings, 2 replies; 24+ messages in thread
From: Oleksij Rempel @ 2014-12-31 12:58 UTC (permalink / raw)
To: boris.brezillon, linux-mtd, computersforpeace; +Cc: Oleksij Rempel
Add the full description of the Toshiba TC58NVG0S3E NAND chip in the
nand_ids table so that we can later use the NAND ECC infos and ONFI timings
mode in controller drivers.
Tested with asm9260_nand driver.
Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
---
drivers/mtd/nand/nand_ids.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/mtd/nand/nand_ids.c b/drivers/mtd/nand/nand_ids.c
index fbde8910..47b5cdf 100644
--- a/drivers/mtd/nand/nand_ids.c
+++ b/drivers/mtd/nand/nand_ids.c
@@ -31,6 +31,10 @@ struct nand_flash_dev nand_flash_ids[] = {
* listed by full ID. We list them first so that we can easily identify
* the most specific match.
*/
+ {"TC58NVG0S3E 1G 3.3V 8-bit",
+ { .id = {0x98, 0xd1, 0x90, 0x15, 0x76, 0x14, 0x01, 0x00} },
+ SZ_2K, SZ_128, SZ_128K, 0, 8, 64, NAND_ECC_INFO(1, SZ_512),
+ 2 },
{"TC58NVG2S0F 4G 3.3V 8-bit",
{ .id = {0x98, 0xdc, 0x90, 0x26, 0x76, 0x15, 0x01, 0x08} },
SZ_4K, SZ_512, SZ_256K, 0, 8, 224, NAND_ECC_INFO(4, SZ_512) },
--
1.9.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH v3 1/3] mtd: nand: add asm9260 NFC driver
2014-12-31 12:58 ` Oleksij Rempel
2014-12-31 12:58 ` [PATCH v3 2/3] mtd: nand: add asm9260-nand devicetree documentation Oleksij Rempel
2014-12-31 12:58 ` [PATCH v3 3/3] mtd: nand: add Toshiba TC58NVG0S3E to nand_ids table Oleksij Rempel
@ 2014-12-31 16:48 ` Boris Brezillon
2014-12-31 17:26 ` Boris Brezillon
2015-01-01 20:18 ` Oleksij Rempel
2015-01-01 12:58 ` Boris Brezillon
2015-01-01 21:14 ` Boris Brezillon
4 siblings, 2 replies; 24+ messages in thread
From: Boris Brezillon @ 2014-12-31 16:48 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: computersforpeace, linux-mtd
Hi Oleksij,
You should really add a cover letter containing a changelog (updated at
each new version of your cover letter) so that reviewers can easily
identify what has changed.
While you're at it, can you add your mtd test results to the cover
letter ?
On Wed, 31 Dec 2014 13:58:51 +0100
Oleksij Rempel <linux@rempel-privat.de> wrote:
> Add driver for Nand Flash Controller used on Alphascales ASM9260 chips.
> The IP core of this controller has some similarities with
> Evatronix NANDFLASH-CTRL IP (unknown revision), so probably it can be reused
> by some other SoCs.
>
> Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
> ---
> drivers/mtd/nand/Kconfig | 7 +
> drivers/mtd/nand/Makefile | 1 +
> drivers/mtd/nand/asm9260_nand.c | 989 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 997 insertions(+)
> create mode 100644 drivers/mtd/nand/asm9260_nand.c
>
> diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
> index dd10646..580a608 100644
> --- a/drivers/mtd/nand/Kconfig
> +++ b/drivers/mtd/nand/Kconfig
> @@ -41,6 +41,13 @@ config MTD_SM_COMMON
> tristate
> default n
>
> +config MTD_NAND_ASM9260
> + tristate "NFC support for ASM9260 SoC"
> + depends on OF
> + default n
> + help
> + Enable support for the NAND controller found on Alphascale ASM9260 SoC.
> +
> config MTD_NAND_DENALI
> tristate "Support Denali NAND controller"
> depends on HAS_DMA
> diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
> index 9c847e4..08d660a 100644
> --- a/drivers/mtd/nand/Makefile
> +++ b/drivers/mtd/nand/Makefile
> @@ -8,6 +8,7 @@ obj-$(CONFIG_MTD_NAND_BCH) += nand_bch.o
> obj-$(CONFIG_MTD_NAND_IDS) += nand_ids.o
> obj-$(CONFIG_MTD_SM_COMMON) += sm_common.o
>
> +obj-$(CONFIG_MTD_NAND_ASM9260) += asm9260_nand.o
> obj-$(CONFIG_MTD_NAND_CAFE) += cafe_nand.o
> obj-$(CONFIG_MTD_NAND_AMS_DELTA) += ams-delta.o
> obj-$(CONFIG_MTD_NAND_DENALI) += denali.o
> diff --git a/drivers/mtd/nand/asm9260_nand.c b/drivers/mtd/nand/asm9260_nand.c
> new file mode 100644
> index 0000000..23154be
> --- /dev/null
> +++ b/drivers/mtd/nand/asm9260_nand.c
> @@ -0,0 +1,989 @@
> +/*
> + * NAND controller driver for Alphascale ASM9260, which is probably
> + * based on Evatronix NANDFLASH-CTRL IP (version unknown)
> + *
> + * Copyright (C), 2014 Oleksij Rempel <linux@rempel-privat.de>
> + *
> + * Inspired by asm9260_nand.c,
> + * Copyright (C), 2007-2013, Alphascale Tech. Co., Ltd.
> + * 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/interrupt.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/mtd/nand.h>
> +#include <linux/mtd/partitions.h>
> +#include <linux/clk.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_platform.h>
> +#include <linux/of_mtd.h>
> +
> +#define ASM9260_ECC_STEP 512
> +#define ASM9260_ECC_MAX_BIT 16
> +#define ASM9260_MAX_CHIPS 2
> +
> +#define mtd_to_priv(m) container_of(m, struct asm9260_nand_priv, mtd)
> +
> +#define HW_CMD 0x00
> +#define BM_CMD_CMD2_S 24
> +#define BM_CMD_CMD1_S 16
> +#define BM_CMD_CMD0_S 8
> +/* 0 - ADDR0, 1 - ADDR1 */
> +#define BM_CMD_ADDR1 BIT(7)
> +/* 0 - PIO, 1 - DMA */
> +#define BM_CMD_DMA BIT(6)
> +#define BM_CMD_CMDSEQ_S 0
> +/* single command, wait for RnB */
Just nitpicking here, but maybe a single comment describing all the
sequences would be better. Something like this:
/*
* ASM9260 Sequences:
*
* SEQ0: ...
* SEQ1: ...
*/
> +#define SEQ0 0x00
> +/* send cmd, addr, wait tWHR, fetch data */
> +#define SEQ1 0x21
> +/* send cmd, addr, wait RnB, fetch data */
> +#define SEQ2 0x22
> +/* send cmd, addr, wait tADL, send data, wait RnB */
> +#define SEQ3 0x03
> +/* send cmd, wait tWHR, fetch data */
> +#define SEQ4 0x24
> +/* send cmd, 3 x addr, wait tWHR, fetch data */
> +#define SEQ5 0x25
> +/* wait tRHW, send cmd, 2 x addr, cmd, wait tCCS, fetch data */
> +#define SEQ6 0x26
> +/* wait tRHW, send cmd, 35 x addr, cmd, wait tCCS, fetch data */
> +#define SEQ7 0x27
> +/* send cmd, 2 x addr, wait tCCS, fetch data */
> +#define SEQ8 0x08
> +/* send cmd, 5 x addr, wait RnB */
> +#define SEQ9 0x29
> +/* send cmd, 5 x addr, cmd, wait RnB, fetch data */
> +#define SEQ10 0x2a
> +/* send cmd, wait RnB, fetch data */
> +#define SEQ11 0x2b
> +/* send cmd, 5 x addr, wait tADL, send data, cmd */
> +#define SEQ12 0x0c
> +/* send cmd, 5 x addr, wait tADL, send data */
> +#define SEQ13 0x0d
> +/* send cmd, 3 x addr, cmd, wait RnB */
> +#define SEQ14 0x0e
> +/* send cmd, 5 x addr, cmd, 5 x addr, cmd, wait RnB, fetch data */
> +#define SEQ15 0x2f
> +/* send cmd, 5 x addr, wait RnB, fetch data */
> +#define SEQ17 0x15
> +
> +#define HW_CTRL 0x04
> +#define BM_CTRL_DIS_STATUS BIT(23)
> +#define BM_CTRL_READ_STAT BIT(22)
> +#define BM_CTRL_SMALL_BLOCK_EN BIT(21)
> +#define BM_CTRL_ADDR_CYCLE1_S 18
> +#define ADDR_CYCLE_0 0x0
> +#define ADDR_CYCLE_1 0x1
> +#define ADDR_CYCLE_2 0x2
> +#define ADDR_CYCLE_3 0x3
> +#define ADDR_CYCLE_4 0x4
> +#define ADDR_CYCLE_5 0x5
> +#define BM_CTRL_ADDR1_AUTO_INCR BIT(17)
> +#define BM_CTRL_ADDR0_AUTO_INCR BIT(16)
> +#define BM_CTRL_WORK_MODE BIT(15)
> +#define BM_CTRL_PORT_EN BIT(14)
> +#define BM_CTRL_LOOKU_EN BIT(13)
> +#define BM_CTRL_IO_16BIT BIT(12)
> +/* Overwrite BM_CTRL_PAGE_SIZE with HW_DATA_SIZE */
> +#define BM_CTRL_CUSTOM_PAGE_SIZE BIT(11)
> +#define BM_CTRL_PAGE_SIZE_S 8
> +#define BM_CTRL_PAGE_SIZE(x) ((ffs((x) >> 8) - 1) & 0x7)
> +#define PAGE_SIZE_256B 0x0
> +#define PAGE_SIZE_512B 0x1
> +#define PAGE_SIZE_1024B 0x2
> +#define PAGE_SIZE_2048B 0x3
> +#define PAGE_SIZE_4096B 0x4
> +#define PAGE_SIZE_8192B 0x5
> +#define PAGE_SIZE_16384B 0x6
> +#define PAGE_SIZE_32768B 0x7
> +#define BM_CTRL_BLOCK_SIZE_S 6
> +#define BM_CTRL_BLOCK_SIZE(x) ((ffs((x) >> 5) - 1) & 0x3)
> +#define BLOCK_SIZE_32P 0x0
> +#define BLOCK_SIZE_64P 0x1
> +#define BLOCK_SIZE_128P 0x2
> +#define BLOCK_SIZE_256P 0x3
> +#define BM_CTRL_ECC_EN BIT(5)
> +#define BM_CTRL_INT_EN BIT(4)
> +#define BM_CTRL_SPARE_EN BIT(3)
> +/* same values as BM_CTRL_ADDR_CYCLE1_S */
> +#define BM_CTRL_ADDR_CYCLE0_S 0
> +
> +#define HW_STATUS 0x08
> +#define BM_CTRL_NFC_BUSY BIT(8)
> +/* MEM1_RDY (BIT1) - MEM7_RDY (BIT7) */
> +#define BM_CTRL_MEM0_RDY BIT(0)
> +
> +#define HW_INT_MASK 0x0c
> +#define HW_INT_STATUS 0x10
> +#define BM_INT_FIFO_ERROR BIT(12)
> +#define BM_INT_MEM_RDY_S 4
> +/* MEM1_RDY (BIT5) - MEM7_RDY (BIT11) */
> +#define BM_INT_MEM0_RDY BIT(4)
> +#define BM_INT_ECC_TRSH_ERR BIT(3)
> +#define BM_INT_ECC_FATAL_ERR BIT(2)
> +#define BM_INT_CMD_END BIT(1)
> +
> +#define HW_ECC_CTRL 0x14
> +/* bits per 512 bytes */
> +#define BM_ECC_CAP_S 5
> +/* support ecc strange 2, 4, 6, 8, 10, 12, 14, 16. */
^ you mean strength, right ?
> +#define BM_ECC_CAPn(x) ((((x) >> 1) - 1) & 0x7)
> +/* Warn if some bitflip level (threshold) reached. Max 15 bits. */
> +#define BM_ECC_ERR_THRESHOLD_S 8
> +#define BM_ECC_ERR_THRESHOLD_M 0xf
> +#define BM_ECC_ERR_OVER BIT(2)
> +/* Uncorrected error. */
> +#define BM_ECC_ERR_UNC BIT(1)
> +/* Corrected error. */
> +#define BM_ECC_ERR_CORRECT BIT(0)
> +
> +#define HW_ECC_OFFSET 0x18
> +#define HW_ADDR0_0 0x1c
> +#define HW_ADDR1_0 0x20
> +#define HW_ADDR0_1 0x24
> +#define HW_ADDR1_1 0x28
> +#define HW_SPARE_SIZE 0x30
> +#define HW_DMA_ADDR 0x64
> +#define HW_DMA_CNT 0x68
> +
> +#define HW_DMA_CTRL 0x6c
> +#define BM_DMA_CTRL_START BIT(7)
> +/* 0 - to device; 1 - from device */
> +#define BM_DMA_CTRL_FROM_DEVICE BIT(6)
> +/* 0 - software maneged; 1 - scatter-gather */
> +#define BM_DMA_CTRL_SG BIT(5)
> +#define BM_DMA_CTRL_BURST_S 2
> +#define DMA_BURST_INCR4 0x0
> +#define DMA_BURST_STREAM 0x1
> +#define DMA_BURST_SINGLE 0x2
> +#define DMA_BURST_INCR 0x3
> +#define DMA_BURST_INCR8 0x4
> +#define DMA_BURST_INCR16 0x5
> +#define BM_DMA_CTRL_ERR BIT(1)
> +#define BM_DMA_CTRL_RDY BIT(0)
> +
> +#define HW_MEM_CTRL 0x80
> +#define BM_MEM_CTRL_WP_STATE_MASK 0xff00
> +#define BM_MEM_CTRL_UNWPn(x) (1 << ((x) + 8))
> +#define BM_MEM_CTRL_CEn(x) (((x) & 7) << 0)
> +
> +/* BM_CTRL_CUSTOM_PAGE_SIZE should be set */
> +#define HW_DATA_SIZE 0x84
> +#define HW_READ_STATUS 0x88
> +#define HW_TIM_SEQ_0 0x8c
> +#define HW_TIMING_ASYN 0x90
> +#define HW_TIMING_SYN 0x94
> +
> +#define HW_FIFO_DATA 0x98
> +#define HW_TIME_MODE 0x9c
> +#define HW_FIFO_INIT 0xb0
> +/*
> + * Counter for ecc related errors.
> + * For each 512 byte block it has 5bit counter.
> + */
> +#define HW_ECC_ERR_CNT 0xb8
> +
> +#define HW_TIM_SEQ_1 0xc8
> +
> +struct asm9260_nand_priv {
> + struct device *dev;
> + struct mtd_info mtd;
> + struct nand_chip nand;
> + struct nand_ecclayout ecc_layout;
> +
> + struct clk *clk;
> + struct clk *clk_ahb;
> +
> + void __iomem *base;
> + int irq_done;
> +
> + u32 read_cache;
> + int read_cache_cnt;
> + u32 cmd_cache;
> + u32 ctrl_cache;
> + u32 mem_mask;
> + u32 page_cache;
> + unsigned int wait_time;
> +
> + unsigned int spare_size;
> +};
> +
> +static void asm9260_reg_rmw(struct asm9260_nand_priv *priv,
> + u32 reg_offset, u32 set, u32 clr)
I guess rmw stands for read-modify-write, maybe you should choose
a clearer name, like asm9260_reg_update_bits (that's the name chosen by
regmap [1]).
> +{
> + u32 val;
> +
> + val = ioread32(priv->base + reg_offset);
> + val &= ~clr;
> + val |= set;
> + iowrite32(val, priv->base + reg_offset);
> +}
> +
> +static void asm9260_select_chip(struct mtd_info *mtd, int chip)
> +{
> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> +
> + if (chip == -1)
> + iowrite32(BM_MEM_CTRL_WP_STATE_MASK, priv->base + HW_MEM_CTRL);
> + else
> + iowrite32(BM_MEM_CTRL_UNWPn(chip) | BM_MEM_CTRL_CEn(chip),
> + priv->base + HW_MEM_CTRL);
> +}
> +
> +/* 3 commands are supported by HW. 3-d can be used for TWO PLANE. */
> +static void asm9260_nand_cmd_prep(struct asm9260_nand_priv *priv,
> + u8 cmd0, u8 cmd1, u8 cmd2, u8 seq)
> +{
> + priv->cmd_cache = (cmd0 << BM_CMD_CMD0_S) | (cmd1 << BM_CMD_CMD1_S);
> + priv->cmd_cache |= seq << BM_CMD_CMDSEQ_S;
> +}
> +
> +static dma_addr_t asm9260_nand_dma_set(struct mtd_info *mtd, void *buf,
> + enum dma_data_direction dir, size_t size)
> +{
> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> + dma_addr_t dma_addr;
> +
> + dma_addr = dma_map_single(priv->dev, buf, size, dir);
> + if (dma_mapping_error(priv->dev, dma_addr)) {
> + dev_err(priv->dev, "dma_map_single failed!\n");
> + return dma_addr;
> +
> + }
> +
> + iowrite32(dma_addr, priv->base + HW_DMA_ADDR);
> + iowrite32(size, priv->base + HW_DMA_CNT);
> + iowrite32(BM_DMA_CTRL_START
> + | (dir == DMA_FROM_DEVICE ? BM_DMA_CTRL_FROM_DEVICE : 0)
> + | (DMA_BURST_INCR16 << BM_DMA_CTRL_BURST_S),
> + priv->base + HW_DMA_CTRL);
> + return dma_addr;
> +}
> +
> +/* complete command request */
> +static void asm9260_nand_cmd_comp(struct mtd_info *mtd, int dma)
> +{
> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> + int timeout;
> + u32 cmd;
> +
> + if (!priv->cmd_cache)
> + return;
> +
> + if (dma) {
> + priv->cmd_cache |= BM_CMD_DMA;
> + priv->irq_done = 0;
> + iowrite32(priv->mem_mask << BM_INT_MEM_RDY_S,
> + priv->base + HW_INT_MASK);
AFAIU (given the code in probe), this mem_mask represent a bitmask of
R/B signals coming from the available NAND chips.
Here you are trying to send a command to a specific NAND chip, and I'm
not sure you should wait for at least one of the available NAND chips
to be ready. Actually you should wait for the NAND chip you're sending
this command to to be ready.
> + }
> +
> + iowrite32(priv->cmd_cache, priv->base + HW_CMD);
> + cmd = priv->cmd_cache;
> + priv->cmd_cache = 0;
> +
> + if (dma) {
> + struct nand_chip *nand = &priv->nand;
> +
> + timeout = wait_event_timeout(nand->controller->wq,
> + priv->irq_done,
> + msecs_to_jiffies(priv->wait_time ?
> + priv->wait_time : 20));
> + if (timeout <= 0) {
> + dev_info(priv->dev,
> + "Request 0x%08x timed out\n", cmd);
> + /* TODO: Do something useful here? */
> + /* FIXME: if we have problems on DMA or PIO, we need to
> + * reset NFC. On asm9260 it is possible only with global
> + * reset register. How can we use it here? */
> + }
> + priv->wait_time = 0;
> + } else
> + nand_wait_ready(mtd);
> +}
> +
> +static int asm9260_nand_dev_ready(struct mtd_info *mtd)
> +{
> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> + u32 tmp;
> +
> + tmp = ioread32(priv->base + HW_STATUS);
> +
> + return (!(tmp & BM_CTRL_NFC_BUSY) &&
> + (tmp & priv->mem_mask));
Shouldn't you test for BM_CTRL_MEMX_RDY (replace X with the appropriate
CS depending on which NAND chip you're currently addressing).
> +}
> +
> +static void asm9260_nand_ctrl(struct asm9260_nand_priv *priv, u32 set)
> +{
> + iowrite32(priv->ctrl_cache | set, priv->base + HW_CTRL);
> +}
> +
> +static void asm9260_nand_set_addr(struct asm9260_nand_priv *priv,
> + u32 row_addr, u32 column)
> +{
> + u32 addr[2];
> +
> + addr[0] = (column & 0xffff) | (0xffff0000 & (row_addr << 16));
> + addr[1] = (row_addr >> 16) & 0xff;
Another nit: will this always work (especially on big endian
kernels) ?
> +
> + iowrite32(addr[0], priv->base + HW_ADDR0_0);
> + iowrite32(addr[1], priv->base + HW_ADDR0_1);
> +}
> +
> +static void asm9260_nand_command_lp(struct mtd_info *mtd,
> + unsigned int command, int column, int page_addr)
> +{
> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> +
> + switch (command) {
> + case NAND_CMD_RESET:
> + asm9260_nand_cmd_prep(priv, NAND_CMD_RESET, 0, 0, SEQ0);
> + asm9260_nand_cmd_comp(mtd, 0);
> + break;
> +
> + case NAND_CMD_READID:
> + iowrite32(1, priv->base + HW_FIFO_INIT);
> + iowrite32((ADDR_CYCLE_1 << BM_CTRL_ADDR_CYCLE1_S)
> + | BM_CTRL_CUSTOM_PAGE_SIZE
> + | (PAGE_SIZE_4096B << BM_CTRL_PAGE_SIZE_S)
> + | (BLOCK_SIZE_32P << BM_CTRL_BLOCK_SIZE_S)
> + | BM_CTRL_INT_EN
> + | (ADDR_CYCLE_1 << BM_CTRL_ADDR_CYCLE0_S),
> + priv->base + HW_CTRL);
> +
> + iowrite32(8, priv->base + HW_DATA_SIZE);
> + iowrite32(column, priv->base + HW_ADDR0_0);
> + asm9260_nand_cmd_prep(priv, NAND_CMD_READID, 0, 0, SEQ1);
> +
> + priv->read_cache_cnt = 0;
> + break;
> +
> + case NAND_CMD_READOOB:
> + column += mtd->writesize;
> + command = NAND_CMD_READ0;
> + case NAND_CMD_READ0:
> + iowrite32(1, priv->base + HW_FIFO_INIT);
> +
> + if (column == 0) {
> + asm9260_nand_ctrl(priv, 0);
> + iowrite32(priv->spare_size, priv->base + HW_SPARE_SIZE);
> + } else if (column == mtd->writesize) {
> + asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
> + iowrite32(mtd->oobsize, priv->base + HW_SPARE_SIZE);
> + iowrite32(mtd->oobsize, priv->base + HW_DATA_SIZE);
> + } else {
> + dev_err(priv->dev, "Couldn't support the column\n");
> + break;
> + }
> +
> + asm9260_nand_set_addr(priv, page_addr, column);
> +
> + asm9260_nand_cmd_prep(priv, NAND_CMD_READ0,
> + NAND_CMD_READSTART, 0, SEQ10);
> +
> + priv->read_cache_cnt = 0;
> + break;
> + case NAND_CMD_SEQIN:
> + iowrite32(1, priv->base + HW_FIFO_INIT);
> +
> + if (column == 0) {
> + priv->page_cache = page_addr;
> + asm9260_nand_ctrl(priv, 0);
> + iowrite32(priv->spare_size, priv->base + HW_SPARE_SIZE);
> + } else if (column == mtd->writesize) {
> + asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
> + iowrite32(mtd->oobsize, priv->base + HW_DATA_SIZE);
> + }
> +
> + asm9260_nand_set_addr(priv, page_addr, column);
> +
> + asm9260_nand_cmd_prep(priv, NAND_CMD_SEQIN, NAND_CMD_PAGEPROG,
> + 0, SEQ12);
> +
> + break;
> + case NAND_CMD_STATUS:
> + iowrite32(1, priv->base + HW_FIFO_INIT);
> + asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
> +
> + /*
> + * Workaround for status bug.
> + * Instead of SEQ4 we need to use SEQ1 here, which will
> + * send cmd with address. For this case we need to make sure
> + * ADDR == 0.
> + */
> + asm9260_nand_set_addr(priv, 0, 0);
> + iowrite32(4, priv->base + HW_DATA_SIZE);
> + asm9260_nand_cmd_prep(priv, NAND_CMD_STATUS, 0, 0, SEQ1);
> +
> + priv->read_cache_cnt = 0;
> + break;
> +
> + case NAND_CMD_ERASE1:
> + priv->wait_time = 400;
> + asm9260_nand_set_addr(priv, page_addr, column);
> +
> + asm9260_nand_ctrl(priv, 0);
> +
> + /*
> + * Prepare and send command now. We don't need to split it in
> + * two stages.
> + */
> + asm9260_nand_cmd_prep(priv, NAND_CMD_ERASE1, NAND_CMD_ERASE2,
> + 0, SEQ14);
> + asm9260_nand_cmd_comp(mtd, 0);
> + break;
> + default:
> + break;
> + }
> +}
> +
> +
> +/**
> + * We can't read less then 32 bits on HW_FIFO_DATA. So, to make
> + * read_byte and read_word happy, we use sort of cached 32bit read.
> + * Note: expected values for size should be 1 or 2 bytes.
> + */
> +static u32 asm9260_nand_read_cached(struct mtd_info *mtd, int size)
> +{
> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> + u8 tmp;
> +
> + if ((priv->read_cache_cnt <= 0) || (priv->read_cache_cnt > 4)) {
^ how can this ever happen ?
> + asm9260_nand_cmd_comp(mtd, 0);
> + priv->read_cache = ioread32(priv->base + HW_FIFO_DATA);
> + priv->read_cache_cnt = 4;
> + }
> +
> + tmp = priv->read_cache >> (8 * (4 - priv->read_cache_cnt));
> + priv->read_cache_cnt -= size;
> +
> + return tmp;
> +}
> +
> +static u8 asm9260_nand_read_byte(struct mtd_info *mtd)
> +{
> + return 0xff & asm9260_nand_read_cached(mtd, 1);
Maybe this mask operation could be done in asm9260_nand_read_cached, so
that you won't have to bother in read_byte/read_word functions.
> +}
> +
> +static u16 asm9260_nand_read_word(struct mtd_info *mtd)
> +{
> + return 0xffff & asm9260_nand_read_cached(mtd, 2);
You'd better always use read_word, cause if you call read_byte once
then read_word twice, you'll end up with a wrong value after the second
read_word (3 bytes consumed, which means there's only 1 remaining byte
and you're asking for 2).
> +}
> +
> +static void asm9260_nand_read_buf(struct mtd_info *mtd, u8 *buf, int len)
> +{
> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> + dma_addr_t dma_addr;
> + int dma_ok = 0;
> +
> + if (len & 0x3) {
> + dev_err(priv->dev, "Unsupported length (%x)\n", len);
> + return;
> + }
> +
> + /*
> + * I hate you UBI for your all vmalloc. Be slow as hell with PIO.
> + * ~ with love from ZeroCopy ~
> + */
> + if (!is_vmalloc_addr(buf)) {
> + dma_addr = asm9260_nand_dma_set(mtd, buf, DMA_FROM_DEVICE, len);
> + dma_ok = !(dma_mapping_error(priv->dev, dma_addr));
> + }
> + asm9260_nand_cmd_comp(mtd, dma_ok);
> +
> + if (dma_ok) {
> + dma_sync_single_for_cpu(priv->dev, dma_addr, len,
> + DMA_FROM_DEVICE);
> + dma_unmap_single(priv->dev, dma_addr, len, DMA_FROM_DEVICE);
> + return;
> + }
> +
> + /* fall back to pio mode */
> + len >>= 2;
> + ioread32_rep(priv->base + HW_FIFO_DATA, buf, len);
Hm, what if the buf is not aligned on 32bit, or len is not a multiple
of 4 ?
> +}
> +
> +static void asm9260_nand_write_buf(struct mtd_info *mtd,
> + const u8 *buf, int len)
> +{
> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> + dma_addr_t dma_addr;
> + int dma_ok = 0;
> +
> + if (len & 0x3) {
> + dev_err(priv->dev, "Unsupported length (%x)\n", len);
> + return;
> + }
> +
> + if (!is_vmalloc_addr(buf)) {
> + dma_addr = asm9260_nand_dma_set(mtd,
> + (void *)buf, DMA_TO_DEVICE, len);
> + dma_ok = !(dma_mapping_error(priv->dev, dma_addr));
> + }
> +
> + if (dma_ok)
> + dma_sync_single_for_device(priv->dev, dma_addr, len,
> + DMA_TO_DEVICE);
> + asm9260_nand_cmd_comp(mtd, dma_ok);
> +
> + if (dma_ok) {
> + dma_unmap_single(priv->dev, dma_addr, len, DMA_TO_DEVICE);
> + return;
> + }
> +
> + /* fall back to pio mode */
> + len >>= 2;
> + iowrite32_rep(priv->base + HW_FIFO_DATA, buf, len);
Ditto
> +}
> +
> +static int asm9260_nand_write_page_raw(struct mtd_info *mtd,
> + struct nand_chip *chip, const u8 *buf,
> + int oob_required)
> +{
> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> +
> + chip->write_buf(mtd, buf, mtd->writesize);
> + if (oob_required)
> + chip->ecc.write_oob(mtd, chip, priv->page_cache &
> + chip->pagemask);
Can't you just call
chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
And if it works, then you can just drop this raw function since the
default one does the exact same thing.
> + return 0;
> +}
> +
> +static int asm9260_nand_write_page_hwecc(struct mtd_info *mtd,
> + struct nand_chip *chip, const u8 *buf,
> + int oob_required)
> +{
> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> +
> + asm9260_reg_rmw(priv, HW_CTRL, BM_CTRL_ECC_EN, 0);
> + chip->ecc.write_page_raw(mtd, chip, buf, oob_required);
> +
> + return 0;
> +}
> +
> +static unsigned int asm9260_nand_count_ecc(struct asm9260_nand_priv *priv)
> +{
> + u32 tmp, i, count, maxcount = 0;
> +
> + /* FIXME: this layout was tested only on 2048byte NAND.
> + * NANDs with bigger page size should use more registers. */
Yep, you should really fix that, or at least reject NAND with a
different page size until you've fixed that.
> + tmp = ioread32(priv->base + HW_ECC_ERR_CNT);
> + for (i = 0; i < 4; i++) {
> + count = 0x1f & (tmp >> (5 * i));
> + maxcount = max_t(unsigned int, maxcount, count);
> + }
> +
> + return count;
> +}
> +
> +static int asm9260_nand_read_page_hwecc(struct mtd_info *mtd,
> + struct nand_chip *chip, u8 *buf,
> + int oob_required, int page)
> +{
> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> + u8 *temp_ptr;
> + u32 status, max_bitflips = 0;
> +
> + temp_ptr = buf;
> +
> + /* enable ecc */
> + asm9260_reg_rmw(priv, HW_CTRL, BM_CTRL_ECC_EN, 0);
> + chip->read_buf(mtd, temp_ptr, mtd->writesize);
> +
> + status = ioread32(priv->base + HW_ECC_CTRL);
> +
> + if (status & BM_ECC_ERR_UNC) {
> + u32 ecc_err;
> +
> + ecc_err = ioread32(priv->base + HW_ECC_ERR_CNT);
> + /* check if it is erased page (all_DATA_OOB == 0xff) */
> + /* FIXME: should be tested if it is a bullet proof solution.
> + * if not, use is_buf_blank. */
you'd rather use is_buf_blank if you're unsure.
> + if (ecc_err != 0x8421)
> + mtd->ecc_stats.failed++;
> +
> + } else if (status & BM_ECC_ERR_CORRECT) {
> + max_bitflips = asm9260_nand_count_ecc(priv);
max_bitflips should contain the maximum number of bitflips in all
ECC blocks of a given page, here you just returning the number of
bitflips in the last ECC block.
The following should do the trick:
int bitflips = asm9260_nand_count_ecc(priv);
if (bitflips > max_bitflips)
max_bitflips = bitflips;
mtd->ecc_stats.corrected += bitflips;
> + mtd->ecc_stats.corrected += max_bitflips;
> + }
> +
> + if (oob_required)
> + chip->ecc.read_oob(mtd, chip, page);
> +
> + return max_bitflips;
> +}
> +
> +static irqreturn_t asm9260_nand_irq(int irq, void *device_info)
> +{
> + struct asm9260_nand_priv *priv = device_info;
> + struct nand_chip *nand = &priv->nand;
> + u32 status;
> +
> + status = ioread32(priv->base + HW_INT_STATUS);
> + if (!status)
> + return IRQ_NONE;
> +
> + iowrite32(0, priv->base + HW_INT_MASK);
> + iowrite32(0, priv->base + HW_INT_STATUS);
> + priv->irq_done = 1;
You should test the flags before deciding the irq is actually done...
> + wake_up(&nand->controller->wq);
and if the condition is not met, don't wake up the waitqueue.
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void __init asm9260_nand_init_chip(struct nand_chip *nand_chip)
> +{
> + nand_chip->select_chip = asm9260_select_chip;
> + nand_chip->cmdfunc = asm9260_nand_command_lp;
You seems to only support large pages NANDs (> 512 bytes), maybe you
should check if the discovered chip has large pages, and reject the NAND
otherwise.
> + nand_chip->read_byte = asm9260_nand_read_byte;
> + nand_chip->read_word = asm9260_nand_read_word;
> + nand_chip->read_buf = asm9260_nand_read_buf;
> + nand_chip->write_buf = asm9260_nand_write_buf;
> +
> + nand_chip->dev_ready = asm9260_nand_dev_ready;
> + nand_chip->chip_delay = 100;
> + nand_chip->options |= NAND_NO_SUBPAGE_WRITE;
> +
> + nand_chip->ecc.write_page = asm9260_nand_write_page_hwecc;
> + nand_chip->ecc.write_page_raw = asm9260_nand_write_page_raw;
> + nand_chip->ecc.read_page = asm9260_nand_read_page_hwecc;
> +}
> +
> +static int __init asm9260_nand_cached_config(struct asm9260_nand_priv *priv)
> +{
> + struct nand_chip *nand = &priv->nand;
> + struct mtd_info *mtd = &priv->mtd;
> + u32 addr_cycles, col_cycles, pages_per_block;
> +
> + pages_per_block = mtd->erasesize / mtd->writesize;
> + /* max 256P, min 32P */
> + if (pages_per_block & ~(0x000001e0)) {
> + dev_err(priv->dev, "Unsupported erasesize 0x%x\n",
> + mtd->erasesize);
> + return -EINVAL;
> + }
> +
> + /* max 32K, min 256. */
> + if (mtd->writesize & ~(0x0000ff00)) {
> + dev_err(priv->dev, "Unsupported writesize 0x%x\n",
> + mtd->erasesize);
> + return -EINVAL;
> + }
> +
> + col_cycles = 2;
> + addr_cycles = col_cycles +
> + (((mtd->size >> mtd->writesize) > 65536) ? 3 : 2);
> +
> + priv->ctrl_cache = addr_cycles << BM_CTRL_ADDR_CYCLE1_S
> + | BM_CTRL_PAGE_SIZE(mtd->writesize) << BM_CTRL_PAGE_SIZE_S
> + | BM_CTRL_BLOCK_SIZE(pages_per_block) << BM_CTRL_BLOCK_SIZE_S
> + | BM_CTRL_INT_EN
> + | addr_cycles << BM_CTRL_ADDR_CYCLE0_S;
> +
> + iowrite32(BM_ECC_CAPn(nand->ecc.strength) << BM_ECC_CAP_S,
> + priv->base + HW_ECC_CTRL);
> +
> + iowrite32(mtd->writesize + priv->spare_size,
> + priv->base + HW_ECC_OFFSET);
> +
> + return 0;
> +}
> +
> +static unsigned long __init clk_get_cyc_from_ns(struct clk *clk,
> + unsigned long ns)
> +{
> + unsigned int cycle;
> +
> + cycle = NSEC_PER_SEC / clk_get_rate(clk);
> + return DIV_ROUND_CLOSEST(ns, cycle);
> +}
> +
> +static void __init asm9260_nand_timing_config(struct asm9260_nand_priv *priv)
> +{
> + struct nand_chip *nand = &priv->nand;
> + const struct nand_sdr_timings *time;
> + u32 twhr, trhw, trwh, trwp, tadl, tccs, tsync, trr, twb;
> + int mode;
> +
Maybe you should add support for ONFI timing mode retrieval with
onfi_get_async_timing_mode.
> + mode = nand->onfi_timing_mode_default;
> + dev_info(priv->dev, "ONFI timing mode: %i\n", mode);
> +
> + time = onfi_async_timing_mode_to_sdr_timings(mode);
> + if (IS_ERR_OR_NULL(time)) {
> + dev_err(priv->dev, "Can't get onfi_timing_mode: %i\n", mode);
> + return;
> + }
> +
> + trwh = clk_get_cyc_from_ns(priv->clk, time->tWH_min / 1000);
> + trwp = clk_get_cyc_from_ns(priv->clk, time->tWP_min / 1000);
> +
> + iowrite32((trwh << 4) | (trwp), priv->base + HW_TIMING_ASYN);
> +
> + twhr = clk_get_cyc_from_ns(priv->clk, time->tWHR_min / 1000);
> + trhw = clk_get_cyc_from_ns(priv->clk, time->tRHW_min / 1000);
> + tadl = clk_get_cyc_from_ns(priv->clk, time->tADL_min / 1000);
> + /* tCCS - change read/write collumn. Time between last cmd and data. */
> + tccs = clk_get_cyc_from_ns(priv->clk,
> + (time->tCLR_min + time->tCLH_min + time->tRC_min)
> + / 1000);
> +
> + iowrite32((twhr << 24) | (trhw << 16)
> + | (tadl << 8) | (tccs), priv->base + HW_TIM_SEQ_0);
> +
> + trr = clk_get_cyc_from_ns(priv->clk, time->tRR_min / 1000);
> + tsync = 0; /* ??? */
> + twb = clk_get_cyc_from_ns(priv->clk, time->tWB_max / 1000);
> + iowrite32((tsync << 16) | (trr << 9) | (twb),
> + priv->base + HW_TIM_SEQ_1);
> +}
> +
> +static int __init asm9260_nand_ecc_conf(struct asm9260_nand_priv *priv)
> +{
> + struct device_node *np = priv->dev->of_node;
> + struct nand_chip *nand = &priv->nand;
> + struct mtd_info *mtd = &priv->mtd;
> + struct nand_ecclayout *ecc_layout = &priv->ecc_layout;
> + int i, ecc_strength;
> +
> + nand->ecc.mode = of_get_nand_ecc_mode(np);
> + switch (nand->ecc.mode) {
> + case NAND_ECC_SOFT:
> + case NAND_ECC_SOFT_BCH:
> + dev_info(priv->dev, "Using soft ECC %i\n", nand->ecc.mode);
> + /* nand_base will find needed settings */
> + return 0;
> + case NAND_ECC_HW:
> + default:
> + dev_info(priv->dev, "Using default NAND_ECC_HW\n");
> + nand->ecc.mode = NAND_ECC_HW;
> + break;
> + }
> +
> + ecc_strength = of_get_nand_ecc_strength(np);
> + nand->ecc.size = ASM9260_ECC_STEP;
> + nand->ecc.steps = mtd->writesize / nand->ecc.size;
> +
> + if (ecc_strength < nand->ecc_strength_ds) {
> + int ds_corr;
> +
> + /* Let's check if ONFI can help us. */
> + if (nand->ecc_strength_ds <= 0) {
Actually this is not necessarily filled by ONFI parameters (it can be
statically defined in the nand_ids table).
> + /* No ONFI and no DT - it is bad. */
> + dev_err(priv->dev,
> + "nand-ecc-strength is not set by DT or ONFI. Please set nand-ecc-strength in DT or add chip quirk in nand_ids.c.\n");
> + return -EINVAL;
> + }
> +
> + ds_corr = (mtd->writesize * nand->ecc_strength_ds) /
> + nand->ecc_step_ds;
> + ecc_strength = ds_corr / nand->ecc.steps;
> + dev_info(priv->dev, "ONFI:nand-ecc-strength = %i\n",
> + ecc_strength);
> + } else
> + dev_info(priv->dev, "DT:nand-ecc-strength = %i\n",
> + ecc_strength);
> +
> + if (ecc_strength == 0 || ecc_strength > ASM9260_ECC_MAX_BIT) {
> + dev_err(priv->dev,
> + "Not supported ecc_strength value: %i\n",
> + ecc_strength);
> + return -EINVAL;
> + }
> +
> + if (ecc_strength & 0x1) {
> + ecc_strength++;
> + dev_info(priv->dev,
> + "Only even ecc_strength value is supported. Recalculating: %i\n",
> + ecc_strength);
> + }
> +
> + /* FIXME: do we have max or min page size? */
> +
> + /* 13 - the smallest integer for 512 (ASM9260_ECC_STEP). Div to 8bit. */
> + nand->ecc.bytes = DIV_ROUND_CLOSEST(ecc_strength * 13, 8);
> +
> + ecc_layout->eccbytes = nand->ecc.bytes * nand->ecc.steps;
> + nand->ecc.layout = ecc_layout;
> + nand->ecc.strength = ecc_strength;
> +
> + priv->spare_size = mtd->oobsize - ecc_layout->eccbytes;
> +
> + ecc_layout->oobfree[0].offset = 2;
> + ecc_layout->oobfree[0].length = priv->spare_size - 2;
> +
> + /* FIXME: can we use same layout as SW_ECC? */
It depends on what your controller is capable of. If you can define the
offset at which you write the ECC bytes, then yes you can reuse the
same kind of layout used in SW_ECC.
> + for (i = 0; i < ecc_layout->eccbytes; i++)
> + ecc_layout->eccpos[i] = priv->spare_size + i;
> +
> + return 0;
> +}
> +
> +static int __init asm9260_nand_get_dt_clks(struct asm9260_nand_priv *priv)
> +{
> + int clk_idx = 0, err;
> +
> + priv->clk = devm_clk_get(priv->dev, "sys");
> + if (IS_ERR(priv->clk))
> + goto out_err;
> +
> + /* configure AHB clock */
> + clk_idx = 1;
> + priv->clk_ahb = devm_clk_get(priv->dev, "ahb");
> + if (IS_ERR(priv->clk_ahb))
> + goto out_err;
> +
> + err = clk_prepare_enable(priv->clk_ahb);
> + if (err)
> + dev_err(priv->dev, "Failed to enable ahb_clk!\n");
> +
> + err = clk_set_rate(priv->clk, clk_get_rate(priv->clk_ahb));
> + if (err) {
> + clk_disable_unprepare(priv->clk_ahb);
> + dev_err(priv->dev, "Failed to set rate!\n");
> + }
> +
> + err = clk_prepare_enable(priv->clk);
> + if (err) {
> + clk_disable_unprepare(priv->clk_ahb);
> + dev_err(priv->dev, "Failed to enable clk!\n");
> + }
> +
> + return 0;
> +out_err:
> + dev_err(priv->dev, "%s: Failed to get clk (%i)\n", __func__, clk_idx);
> + return 1;
> +}
> +
> +static int __init asm9260_nand_probe(struct platform_device *pdev)
> +{
> + struct asm9260_nand_priv *priv;
> + struct nand_chip *nand;
> + struct mtd_info *mtd;
> + struct device_node *np = pdev->dev.of_node;
> + struct resource *r;
> + int ret, i;
> + unsigned int irq;
> + u32 val;
> +
> + priv = devm_kzalloc(&pdev->dev, sizeof(struct asm9260_nand_priv),
> + GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + priv->base = devm_ioremap_resource(&pdev->dev, r);
> + if (!priv->base) {
> + dev_err(&pdev->dev, "Unable to map resource!\n");
> + return -EINVAL;
> + }
> +
> + priv->dev = &pdev->dev;
> + nand = &priv->nand;
> + nand->priv = priv;
> +
> + platform_set_drvdata(pdev, priv);
> + mtd = &priv->mtd;
> + mtd->priv = nand;
> + mtd->owner = THIS_MODULE;
> + mtd->name = dev_name(&pdev->dev);
> +
> + if (asm9260_nand_get_dt_clks(priv))
> + return -ENODEV;
> +
> + irq = platform_get_irq(pdev, 0);
> + if (!irq)
> + return -ENODEV;
> +
> + iowrite32(0, priv->base + HW_INT_MASK);
> + ret = devm_request_irq(priv->dev, irq, asm9260_nand_irq,
> + IRQF_ONESHOT | IRQF_SHARED,
> + dev_name(&pdev->dev), priv);
> +
> + asm9260_nand_init_chip(nand);
> +
> + ret = of_property_read_u32(np, "nand-max-chips", &val);
> + if (ret)
> + val = 1;
> +
> + if (val > ASM9260_MAX_CHIPS) {
> + dev_err(&pdev->dev, "Unsupported nand-max-chips value: %i\n",
> + val);
> + return -ENODEV;
> + }
> +
> + for (i = 0; i < val; i++)
> + priv->mem_mask |= BM_CTRL_MEM0_RDY << i;
If you want to support multiple NAND chips, then I recommend to rework
the DT representation and to define a asm9260_nand_controller struct:
struct asm9260_nand_controller {
struct nand_hw_control base;
/* asm9260 related stuff */
};
Take a look [2] and [3].
> +
> + ret = nand_scan_ident(mtd, val, NULL);
> + if (ret) {
> + dev_err(&pdev->dev, "scan_ident filed!\n");
> + return ret;
> + }
> +
> + if (of_get_nand_on_flash_bbt(np)) {
> + dev_info(&pdev->dev, "Use On Flash BBT\n");
> + nand->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB_BBM
> + | NAND_BBT_LASTBLOCK;
> + }
> +
> + asm9260_nand_timing_config(priv);
> + asm9260_nand_ecc_conf(priv);
> + ret = asm9260_nand_cached_config(priv);
> + if (ret)
> + return ret;
> +
> + /* second phase scan */
> + if (nand_scan_tail(mtd)) {
> + dev_err(&pdev->dev, "scan_tail filed!\n");
> + return -ENXIO;
> + }
> +
> +
> + ret = mtd_device_parse_register(mtd, NULL,
> + &(struct mtd_part_parser_data) {
> + .of_node = pdev->dev.of_node,
> + },
> + NULL, 0);
Ergh, can you please define a local variable to replace this ugly
mtd_part_parser_data definition ?
That's all I got for now.
Regards,
Boris
[1]http://lxr.free-electrons.com/source/include/linux/regmap.h#L418
[2]https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/devicetree/bindings/mtd/sunxi-nand.txt?id=refs/tags/v3.19-rc2
[3]https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/drivers/mtd/nand/sunxi_nand.c?id=refs/tags/v3.19-rc2#n245
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v3 3/3] mtd: nand: add Toshiba TC58NVG0S3E to nand_ids table
2014-12-31 12:58 ` [PATCH v3 3/3] mtd: nand: add Toshiba TC58NVG0S3E to nand_ids table Oleksij Rempel
@ 2014-12-31 16:55 ` Boris Brezillon
2015-08-25 19:25 ` Brian Norris
1 sibling, 0 replies; 24+ messages in thread
From: Boris Brezillon @ 2014-12-31 16:55 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: computersforpeace, linux-mtd
On Wed, 31 Dec 2014 13:58:53 +0100
Oleksij Rempel <linux@rempel-privat.de> wrote:
> Add the full description of the Toshiba TC58NVG0S3E NAND chip in the
> nand_ids table so that we can later use the NAND ECC infos and ONFI timings
> mode in controller drivers.
>
> Tested with asm9260_nand driver.
>
> Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
Reviewed-by: Boris Brezillon <boris.brezillon@free-electrons.com>
> ---
> drivers/mtd/nand/nand_ids.c | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/drivers/mtd/nand/nand_ids.c b/drivers/mtd/nand/nand_ids.c
> index fbde8910..47b5cdf 100644
> --- a/drivers/mtd/nand/nand_ids.c
> +++ b/drivers/mtd/nand/nand_ids.c
> @@ -31,6 +31,10 @@ struct nand_flash_dev nand_flash_ids[] = {
> * listed by full ID. We list them first so that we can easily identify
> * the most specific match.
> */
> + {"TC58NVG0S3E 1G 3.3V 8-bit",
> + { .id = {0x98, 0xd1, 0x90, 0x15, 0x76, 0x14, 0x01, 0x00} },
> + SZ_2K, SZ_128, SZ_128K, 0, 8, 64, NAND_ECC_INFO(1, SZ_512),
> + 2 },
> {"TC58NVG2S0F 4G 3.3V 8-bit",
> { .id = {0x98, 0xdc, 0x90, 0x26, 0x76, 0x15, 0x01, 0x08} },
> SZ_4K, SZ_512, SZ_256K, 0, 8, 224, NAND_ECC_INFO(4, SZ_512) },
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v3 2/3] mtd: nand: add asm9260-nand devicetree documentation
2014-12-31 12:58 ` [PATCH v3 2/3] mtd: nand: add asm9260-nand devicetree documentation Oleksij Rempel
@ 2014-12-31 17:01 ` Boris Brezillon
0 siblings, 0 replies; 24+ messages in thread
From: Boris Brezillon @ 2014-12-31 17:01 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: computersforpeace, linux-mtd
On Wed, 31 Dec 2014 13:58:52 +0100
Oleksij Rempel <linux@rempel-privat.de> wrote:
Please add a commit message.
> Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
> ---
> .../devicetree/bindings/mtd/asm9260-nand.txt | 25 ++++++++++++++++++++++
> 1 file changed, 25 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mtd/asm9260-nand.txt
>
> diff --git a/Documentation/devicetree/bindings/mtd/asm9260-nand.txt b/Documentation/devicetree/bindings/mtd/asm9260-nand.txt
> new file mode 100644
> index 0000000..07bfdac
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mtd/asm9260-nand.txt
> @@ -0,0 +1,25 @@
> +* Alphascales's asm9260_nand
> +
> +Required properties:
> +- compatible: "alphascale,asm9260-nand"
> +- reg: address range of the nfc block
> +- interrupts: irq to be used
> +- clocks: clock ids.
> +- clock-names: only "sys" and "ahb" should be used.
The following are optional properties
> +- nand-ecc-strength: see nand.txt
> +- nand-ecc-mode: see nand.txt
> +- nand-on-flash-bbt: see nand.txt
> +- nand-max-chips: maximal number of attached chips. asm9260 support max 2.
expect for this one ^
But again, since your NAND controller supports several NAND chips I
think the binding should be reworked to represent it.
How about making the controller a parent node and then each NAND chip a
child of this controller node?
Something similar to this binding [1].
Regards,
Boris
[1]https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/devicetree/bindings/mtd/sunxi-nand.txt?id=refs/tags/v3.19-rc2
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v3 1/3] mtd: nand: add asm9260 NFC driver
2014-12-31 16:48 ` [PATCH v3 1/3] mtd: nand: add asm9260 NFC driver Boris Brezillon
@ 2014-12-31 17:26 ` Boris Brezillon
2015-01-01 20:18 ` Oleksij Rempel
1 sibling, 0 replies; 24+ messages in thread
From: Boris Brezillon @ 2014-12-31 17:26 UTC (permalink / raw)
To: Boris Brezillon; +Cc: computersforpeace, linux-mtd, Oleksij Rempel
On Wed, 31 Dec 2014 17:48:59 +0100
Boris Brezillon <boris.brezillon@free-electrons.com> wrote:
> Hi Oleksij,
>
> You should really add a cover letter containing a changelog (updated at
> each new version of your cover letter) so that reviewers can easily
> identify what has changed.
>
> While you're at it, can you add your mtd test results to the cover
> letter ?
>
> On Wed, 31 Dec 2014 13:58:51 +0100
> Oleksij Rempel <linux@rempel-privat.de> wrote:
>
> > Add driver for Nand Flash Controller used on Alphascales ASM9260 chips.
> > The IP core of this controller has some similarities with
> > Evatronix NANDFLASH-CTRL IP (unknown revision), so probably it can be reused
> > by some other SoCs.
> >
> > Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
> > ---
> > drivers/mtd/nand/Kconfig | 7 +
> > drivers/mtd/nand/Makefile | 1 +
[...]
> > +
> > +static void asm9260_nand_set_addr(struct asm9260_nand_priv *priv,
> > + u32 row_addr, u32 column)
> > +{
> > + u32 addr[2];
> > +
> > + addr[0] = (column & 0xffff) | (0xffff0000 & (row_addr << 16));
> > + addr[1] = (row_addr >> 16) & 0xff;
>
> Another nit: will this always work (especially on big endian
> kernels) ?
>
Forget that one, it will work just fine (sorry for the brainfart).
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v3 1/3] mtd: nand: add asm9260 NFC driver
2014-12-31 12:58 ` Oleksij Rempel
` (2 preceding siblings ...)
2014-12-31 16:48 ` [PATCH v3 1/3] mtd: nand: add asm9260 NFC driver Boris Brezillon
@ 2015-01-01 12:58 ` Boris Brezillon
2015-01-01 21:14 ` Boris Brezillon
4 siblings, 0 replies; 24+ messages in thread
From: Boris Brezillon @ 2015-01-01 12:58 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: computersforpeace, linux-mtd
Hi Oleksij,
I added a few more comments to the ECC config part.
On Wed, 31 Dec 2014 13:58:51 +0100
Oleksij Rempel <linux@rempel-privat.de> wrote:
> Add driver for Nand Flash Controller used on Alphascales ASM9260 chips.
> The IP core of this controller has some similarities with
> Evatronix NANDFLASH-CTRL IP (unknown revision), so probably it can be reused
> by some other SoCs.
>
> Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
> ---
> drivers/mtd/nand/Kconfig | 7 +
> drivers/mtd/nand/Makefile | 1 +
> drivers/mtd/nand/asm9260_nand.c | 989 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 997 insertions(+)
> create mode 100644 drivers/mtd/nand/asm9260_nand.c
>
[...]
> +
> +static int __init asm9260_nand_ecc_conf(struct asm9260_nand_priv *priv)
> +{
> + struct device_node *np = priv->dev->of_node;
> + struct nand_chip *nand = &priv->nand;
> + struct mtd_info *mtd = &priv->mtd;
> + struct nand_ecclayout *ecc_layout = &priv->ecc_layout;
> + int i, ecc_strength;
> +
> + nand->ecc.mode = of_get_nand_ecc_mode(np);
> + switch (nand->ecc.mode) {
> + case NAND_ECC_SOFT:
> + case NAND_ECC_SOFT_BCH:
> + dev_info(priv->dev, "Using soft ECC %i\n", nand->ecc.mode);
> + /* nand_base will find needed settings */
> + return 0;
> + case NAND_ECC_HW:
> + default:
> + dev_info(priv->dev, "Using default NAND_ECC_HW\n");
> + nand->ecc.mode = NAND_ECC_HW;
> + break;
> + }
> +
> + ecc_strength = of_get_nand_ecc_strength(np);
> + nand->ecc.size = ASM9260_ECC_STEP;
You might want to check the requested ECC step (either the one defined
in the DT or the one stored in ecc_step_ds) and fail if it is not 512.
This will force users to choose software ECC if their NAND require a
1024 bytes step.
> + nand->ecc.steps = mtd->writesize / nand->ecc.size;
> +
> + if (ecc_strength < nand->ecc_strength_ds) {
> + int ds_corr;
> +
Hmm, actually one of the goal of DT definition is to force weaker ECC
than what's actually required.
Here you're just overriding the ECC strength retrieved from DT with the
one stored in ecc_strength_ds.
What you can do though is print a warning message to inform users that
they should not use weaker ECC than those required by the chip.
> + /* Let's check if ONFI can help us. */
> + if (nand->ecc_strength_ds <= 0) {
> + /* No ONFI and no DT - it is bad. */
> + dev_err(priv->dev,
> + "nand-ecc-strength is not set by DT or ONFI. Please set nand-ecc-strength in DT or add chip quirk in nand_ids.c.\n");
> + return -EINVAL;
> + }
> +
> + ds_corr = (mtd->writesize * nand->ecc_strength_ds) /
> + nand->ecc_step_ds;
> + ecc_strength = ds_corr / nand->ecc.steps;
Actually "N correctable bits per 512 bytes" is not equivalent to "2N
correctable bits per 1024 bytes" because in the latter the 2N
erroneous bits can be in the first 512 byte block (which is not
possible in the former).
I think this is safer to refuse to use HW_ECC with NAND chips that
require 1024 bytes step (or you could keep the same ECC strength but on
a 512 byte step size).
> + dev_info(priv->dev, "ONFI:nand-ecc-strength = %i\n",
> + ecc_strength);
Change the message here, because ecc_strength_ds is not necessarily
retrieved from ONFI parameters.
> + } else
> + dev_info(priv->dev, "DT:nand-ecc-strength = %i\n",
> + ecc_strength);
Add brackets around the else statement (to be consistent with the if
statement which has brackets).
> +
> + if (ecc_strength == 0 || ecc_strength > ASM9260_ECC_MAX_BIT) {
> + dev_err(priv->dev,
> + "Not supported ecc_strength value: %i\n",
> + ecc_strength);
> + return -EINVAL;
> + }
> +
> + if (ecc_strength & 0x1) {
> + ecc_strength++;
> + dev_info(priv->dev,
> + "Only even ecc_strength value is supported. Recalculating: %i\n",
> + ecc_strength);
> + }
> +
> + /* FIXME: do we have max or min page size? */
> +
> + /* 13 - the smallest integer for 512 (ASM9260_ECC_STEP). Div to 8bit. */
> + nand->ecc.bytes = DIV_ROUND_CLOSEST(ecc_strength * 13, 8);
I'm pretty sure you should use DIV_ROUND_UP here.
> +
> + ecc_layout->eccbytes = nand->ecc.bytes * nand->ecc.steps;
> + nand->ecc.layout = ecc_layout;
> + nand->ecc.strength = ecc_strength;
> +
> + priv->spare_size = mtd->oobsize - ecc_layout->eccbytes;
> +
> + ecc_layout->oobfree[0].offset = 2;
> + ecc_layout->oobfree[0].length = priv->spare_size - 2;
> +
> + /* FIXME: can we use same layout as SW_ECC? */
You mean NAND_ECC_SOFT_BCH, right ?
If this is the case, then you're already using the same layout.
Regards,
Boris
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v3 1/3] mtd: nand: add asm9260 NFC driver
2014-12-31 16:48 ` [PATCH v3 1/3] mtd: nand: add asm9260 NFC driver Boris Brezillon
2014-12-31 17:26 ` Boris Brezillon
@ 2015-01-01 20:18 ` Oleksij Rempel
2015-01-01 21:03 ` Boris Brezillon
1 sibling, 1 reply; 24+ messages in thread
From: Oleksij Rempel @ 2015-01-01 20:18 UTC (permalink / raw)
To: Boris Brezillon; +Cc: computersforpeace, linux-mtd
[-- Attachment #1: Type: text/plain, Size: 20017 bytes --]
Am 31.12.2014 um 17:48 schrieb Boris Brezillon:
> Hi Oleksij,
>
> You should really add a cover letter containing a changelog (updated at
> each new version of your cover letter) so that reviewers can easily
> identify what has changed.
>
> While you're at it, can you add your mtd test results to the cover
> letter ?
ok.
[snip]
>> +/**
>> + * We can't read less then 32 bits on HW_FIFO_DATA. So, to make
>> + * read_byte and read_word happy, we use sort of cached 32bit read.
>> + * Note: expected values for size should be 1 or 2 bytes.
>> + */
>> +static u32 asm9260_nand_read_cached(struct mtd_info *mtd, int size)
>> +{
>> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
>> + u8 tmp;
>> +
>> + if ((priv->read_cache_cnt <= 0) || (priv->read_cache_cnt > 4)) {
>
> ^ how can this ever happen ?
>
>> + asm9260_nand_cmd_comp(mtd, 0);
>> + priv->read_cache = ioread32(priv->base + HW_FIFO_DATA);
>> + priv->read_cache_cnt = 4;
>> + }
>> +
>> + tmp = priv->read_cache >> (8 * (4 - priv->read_cache_cnt));
>> + priv->read_cache_cnt -= size;
>> +
>> + return tmp;
>> +}
>> +
>> +static u8 asm9260_nand_read_byte(struct mtd_info *mtd)
>> +{
>> + return 0xff & asm9260_nand_read_cached(mtd, 1);
>
> Maybe this mask operation could be done in asm9260_nand_read_cached, so
> that you won't have to bother in read_byte/read_word functions.
it is same as "return (u8)asm9260_nand_read_cached(mtd, 1);", just makes
sure compiler do not complain.
>> +}
>> +
>> +static u16 asm9260_nand_read_word(struct mtd_info *mtd)
>> +{
>> + return 0xffff & asm9260_nand_read_cached(mtd, 2);
>
> You'd better always use read_word, cause if you call read_byte once
> then read_word twice, you'll end up with a wrong value after the second
> read_word (3 bytes consumed, which means there's only 1 remaining byte
> and you're asking for 2).
nand_base use both functions. how can i use only one? For example:
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
/* Read entire ID string */
for (i = 0; i < 8; i++)
id_data[i] = chip->read_byte(mtd);
this wont work with read_word.
>> +}
>> +
>> +static void asm9260_nand_read_buf(struct mtd_info *mtd, u8 *buf, int len)
>> +{
>> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
>> + dma_addr_t dma_addr;
>> + int dma_ok = 0;
>> +
>> + if (len & 0x3) {
>> + dev_err(priv->dev, "Unsupported length (%x)\n", len);
>> + return;
>> + }
>> +
>> + /*
>> + * I hate you UBI for your all vmalloc. Be slow as hell with PIO.
>> + * ~ with love from ZeroCopy ~
>> + */
>> + if (!is_vmalloc_addr(buf)) {
>> + dma_addr = asm9260_nand_dma_set(mtd, buf, DMA_FROM_DEVICE, len);
>> + dma_ok = !(dma_mapping_error(priv->dev, dma_addr));
>> + }
>> + asm9260_nand_cmd_comp(mtd, dma_ok);
>> +
>> + if (dma_ok) {
>> + dma_sync_single_for_cpu(priv->dev, dma_addr, len,
>> + DMA_FROM_DEVICE);
>> + dma_unmap_single(priv->dev, dma_addr, len, DMA_FROM_DEVICE);
>> + return;
>> + }
>> +
>> + /* fall back to pio mode */
>> + len >>= 2;
>> + ioread32_rep(priv->base + HW_FIFO_DATA, buf, len);
>
> Hm, what if the buf is not aligned on 32bit, or len is not a multiple
> of 4 ?
I can read only buf == page size. All page sizes suported by this
controller are aligned. See "if (len & 0x3) {", and take a look at
asm9260_nand_read_cached - we can read only 32bit.
>> +}
>> +
>> +static void asm9260_nand_write_buf(struct mtd_info *mtd,
>> + const u8 *buf, int len)
>> +{
>> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
>> + dma_addr_t dma_addr;
>> + int dma_ok = 0;
>> +
>> + if (len & 0x3) {
>> + dev_err(priv->dev, "Unsupported length (%x)\n", len);
>> + return;
>> + }
>> +
>> + if (!is_vmalloc_addr(buf)) {
>> + dma_addr = asm9260_nand_dma_set(mtd,
>> + (void *)buf, DMA_TO_DEVICE, len);
>> + dma_ok = !(dma_mapping_error(priv->dev, dma_addr));
>> + }
>> +
>> + if (dma_ok)
>> + dma_sync_single_for_device(priv->dev, dma_addr, len,
>> + DMA_TO_DEVICE);
>> + asm9260_nand_cmd_comp(mtd, dma_ok);
>> +
>> + if (dma_ok) {
>> + dma_unmap_single(priv->dev, dma_addr, len, DMA_TO_DEVICE);
>> + return;
>> + }
>> +
>> + /* fall back to pio mode */
>> + len >>= 2;
>> + iowrite32_rep(priv->base + HW_FIFO_DATA, buf, len);
>
> Ditto
>
>> +}
>> +
>> +static int asm9260_nand_write_page_raw(struct mtd_info *mtd,
>> + struct nand_chip *chip, const u8 *buf,
>> + int oob_required)
>> +{
>> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
>> +
>> + chip->write_buf(mtd, buf, mtd->writesize);
>> + if (oob_required)
>> + chip->ecc.write_oob(mtd, chip, priv->page_cache &
>> + chip->pagemask);
>
> Can't you just call
> chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
>
> And if it works, then you can just drop this raw function since the
> default one does the exact same thing.
There 3 variants:
- allocate dma == page + oob; and copy each buffer
- use pio and read out oob even if it was not requested
- use dma_map_single, avoid buffer copying, and request oob if needed.
i use last variant even if it meane sending a command to request oob.
>
>> + return 0;
>> +}
>> +
>> +static int asm9260_nand_write_page_hwecc(struct mtd_info *mtd,
>> + struct nand_chip *chip, const u8 *buf,
>> + int oob_required)
>> +{
>> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
>> +
>> + asm9260_reg_rmw(priv, HW_CTRL, BM_CTRL_ECC_EN, 0);
>> + chip->ecc.write_page_raw(mtd, chip, buf, oob_required);
>> +
>> + return 0;
>> +}
>> +
>> +static unsigned int asm9260_nand_count_ecc(struct asm9260_nand_priv *priv)
>> +{
>> + u32 tmp, i, count, maxcount = 0;
>> +
>> + /* FIXME: this layout was tested only on 2048byte NAND.
>> + * NANDs with bigger page size should use more registers. */
>
> Yep, you should really fix that, or at least reject NAND with a
> different page size until you've fixed that.
ok.
>> + tmp = ioread32(priv->base + HW_ECC_ERR_CNT);
>> + for (i = 0; i < 4; i++) {
>> + count = 0x1f & (tmp >> (5 * i));
>> + maxcount = max_t(unsigned int, maxcount, count);
>> + }
>> +
>> + return count;
>> +}
>> +
>> +static int asm9260_nand_read_page_hwecc(struct mtd_info *mtd,
>> + struct nand_chip *chip, u8 *buf,
>> + int oob_required, int page)
>> +{
>> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
>> + u8 *temp_ptr;
>> + u32 status, max_bitflips = 0;
>> +
>> + temp_ptr = buf;
>> +
>> + /* enable ecc */
>> + asm9260_reg_rmw(priv, HW_CTRL, BM_CTRL_ECC_EN, 0);
>> + chip->read_buf(mtd, temp_ptr, mtd->writesize);
>> +
>> + status = ioread32(priv->base + HW_ECC_CTRL);
>> +
>> + if (status & BM_ECC_ERR_UNC) {
>> + u32 ecc_err;
>> +
>> + ecc_err = ioread32(priv->base + HW_ECC_ERR_CNT);
>> + /* check if it is erased page (all_DATA_OOB == 0xff) */
>> + /* FIXME: should be tested if it is a bullet proof solution.
>> + * if not, use is_buf_blank. */
>
> you'd rather use is_buf_blank if you're unsure.
ok, can is_buf_blank be moved to nand_base?
>
>> + if (ecc_err != 0x8421)
>> + mtd->ecc_stats.failed++;
>> +
>> + } else if (status & BM_ECC_ERR_CORRECT) {
>> + max_bitflips = asm9260_nand_count_ecc(priv);
>
> max_bitflips should contain the maximum number of bitflips in all
> ECC blocks of a given page, here you just returning the number of
> bitflips in the last ECC block.
no. see asm9260_nand_count_ecc.
> The following should do the trick:
>
> int bitflips = asm9260_nand_count_ecc(priv);
>
> if (bitflips > max_bitflips)
> max_bitflips = bitflips;
>
> mtd->ecc_stats.corrected += bitflips;
>
>> + mtd->ecc_stats.corrected += max_bitflips;
>> + }
>> +
>> + if (oob_required)
>> + chip->ecc.read_oob(mtd, chip, page);
>> +
>> + return max_bitflips;
>> +}
>> +
>> +static irqreturn_t asm9260_nand_irq(int irq, void *device_info)
>> +{
>> + struct asm9260_nand_priv *priv = device_info;
>> + struct nand_chip *nand = &priv->nand;
>> + u32 status;
>> +
>> + status = ioread32(priv->base + HW_INT_STATUS);
>> + if (!status)
>> + return IRQ_NONE;
>> +
>> + iowrite32(0, priv->base + HW_INT_MASK);
>> + iowrite32(0, priv->base + HW_INT_STATUS);
>> + priv->irq_done = 1;
>
> You should test the flags before deciding the irq is actually done...
ok. if fixed it by changing mem_mask logic.
>
>> + wake_up(&nand->controller->wq);
>
> and if the condition is not met, don't wake up the waitqueue.
>
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static void __init asm9260_nand_init_chip(struct nand_chip *nand_chip)
>> +{
>> + nand_chip->select_chip = asm9260_select_chip;
>> + nand_chip->cmdfunc = asm9260_nand_command_lp;
>
> You seems to only support large pages NANDs (> 512 bytes), maybe you
> should check if the discovered chip has large pages, and reject the NAND
> otherwise.
ok.
>> + nand_chip->read_byte = asm9260_nand_read_byte;
>> + nand_chip->read_word = asm9260_nand_read_word;
>> + nand_chip->read_buf = asm9260_nand_read_buf;
>> + nand_chip->write_buf = asm9260_nand_write_buf;
>> +
>> + nand_chip->dev_ready = asm9260_nand_dev_ready;
>> + nand_chip->chip_delay = 100;
>> + nand_chip->options |= NAND_NO_SUBPAGE_WRITE;
>> +
>> + nand_chip->ecc.write_page = asm9260_nand_write_page_hwecc;
>> + nand_chip->ecc.write_page_raw = asm9260_nand_write_page_raw;
>> + nand_chip->ecc.read_page = asm9260_nand_read_page_hwecc;
>> +}
>> +
>> +static int __init asm9260_nand_cached_config(struct asm9260_nand_priv *priv)
>> +{
>> + struct nand_chip *nand = &priv->nand;
>> + struct mtd_info *mtd = &priv->mtd;
>> + u32 addr_cycles, col_cycles, pages_per_block;
>> +
>> + pages_per_block = mtd->erasesize / mtd->writesize;
>> + /* max 256P, min 32P */
>> + if (pages_per_block & ~(0x000001e0)) {
>> + dev_err(priv->dev, "Unsupported erasesize 0x%x\n",
>> + mtd->erasesize);
>> + return -EINVAL;
>> + }
>> +
>> + /* max 32K, min 256. */
>> + if (mtd->writesize & ~(0x0000ff00)) {
>> + dev_err(priv->dev, "Unsupported writesize 0x%x\n",
>> + mtd->erasesize);
>> + return -EINVAL;
>> + }
>> +
>> + col_cycles = 2;
>> + addr_cycles = col_cycles +
>> + (((mtd->size >> mtd->writesize) > 65536) ? 3 : 2);
>> +
>> + priv->ctrl_cache = addr_cycles << BM_CTRL_ADDR_CYCLE1_S
>> + | BM_CTRL_PAGE_SIZE(mtd->writesize) << BM_CTRL_PAGE_SIZE_S
>> + | BM_CTRL_BLOCK_SIZE(pages_per_block) << BM_CTRL_BLOCK_SIZE_S
>> + | BM_CTRL_INT_EN
>> + | addr_cycles << BM_CTRL_ADDR_CYCLE0_S;
>> +
>> + iowrite32(BM_ECC_CAPn(nand->ecc.strength) << BM_ECC_CAP_S,
>> + priv->base + HW_ECC_CTRL);
>> +
>> + iowrite32(mtd->writesize + priv->spare_size,
>> + priv->base + HW_ECC_OFFSET);
>> +
>> + return 0;
>> +}
>> +
>> +static unsigned long __init clk_get_cyc_from_ns(struct clk *clk,
>> + unsigned long ns)
>> +{
>> + unsigned int cycle;
>> +
>> + cycle = NSEC_PER_SEC / clk_get_rate(clk);
>> + return DIV_ROUND_CLOSEST(ns, cycle);
>> +}
>> +
>> +static void __init asm9260_nand_timing_config(struct asm9260_nand_priv *priv)
>> +{
>> + struct nand_chip *nand = &priv->nand;
>> + const struct nand_sdr_timings *time;
>> + u32 twhr, trhw, trwh, trwp, tadl, tccs, tsync, trr, twb;
>> + int mode;
>> +
>
> Maybe you should add support for ONFI timing mode retrieval with
> onfi_get_async_timing_mode.
instead of nand->onfi_timing_mode_default?
>> + mode = nand->onfi_timing_mode_default;
>> + dev_info(priv->dev, "ONFI timing mode: %i\n", mode);
>> +
>> + time = onfi_async_timing_mode_to_sdr_timings(mode);
>> + if (IS_ERR_OR_NULL(time)) {
>> + dev_err(priv->dev, "Can't get onfi_timing_mode: %i\n", mode);
>> + return;
>> + }
>> +
>> + trwh = clk_get_cyc_from_ns(priv->clk, time->tWH_min / 1000);
>> + trwp = clk_get_cyc_from_ns(priv->clk, time->tWP_min / 1000);
>> +
>> + iowrite32((trwh << 4) | (trwp), priv->base + HW_TIMING_ASYN);
>> +
>> + twhr = clk_get_cyc_from_ns(priv->clk, time->tWHR_min / 1000);
>> + trhw = clk_get_cyc_from_ns(priv->clk, time->tRHW_min / 1000);
>> + tadl = clk_get_cyc_from_ns(priv->clk, time->tADL_min / 1000);
>> + /* tCCS - change read/write collumn. Time between last cmd and data. */
>> + tccs = clk_get_cyc_from_ns(priv->clk,
>> + (time->tCLR_min + time->tCLH_min + time->tRC_min)
>> + / 1000);
>> +
>> + iowrite32((twhr << 24) | (trhw << 16)
>> + | (tadl << 8) | (tccs), priv->base + HW_TIM_SEQ_0);
>> +
>> + trr = clk_get_cyc_from_ns(priv->clk, time->tRR_min / 1000);
>> + tsync = 0; /* ??? */
>> + twb = clk_get_cyc_from_ns(priv->clk, time->tWB_max / 1000);
>> + iowrite32((tsync << 16) | (trr << 9) | (twb),
>> + priv->base + HW_TIM_SEQ_1);
>> +}
>> +
>> +static int __init asm9260_nand_ecc_conf(struct asm9260_nand_priv *priv)
>> +{
>> + struct device_node *np = priv->dev->of_node;
>> + struct nand_chip *nand = &priv->nand;
>> + struct mtd_info *mtd = &priv->mtd;
>> + struct nand_ecclayout *ecc_layout = &priv->ecc_layout;
>> + int i, ecc_strength;
>> +
>> + nand->ecc.mode = of_get_nand_ecc_mode(np);
>> + switch (nand->ecc.mode) {
>> + case NAND_ECC_SOFT:
>> + case NAND_ECC_SOFT_BCH:
>> + dev_info(priv->dev, "Using soft ECC %i\n", nand->ecc.mode);
>> + /* nand_base will find needed settings */
>> + return 0;
>> + case NAND_ECC_HW:
>> + default:
>> + dev_info(priv->dev, "Using default NAND_ECC_HW\n");
>> + nand->ecc.mode = NAND_ECC_HW;
>> + break;
>> + }
>> +
>> + ecc_strength = of_get_nand_ecc_strength(np);
>> + nand->ecc.size = ASM9260_ECC_STEP;
>> + nand->ecc.steps = mtd->writesize / nand->ecc.size;
>> +
>> + if (ecc_strength < nand->ecc_strength_ds) {
>> + int ds_corr;
>> +
>> + /* Let's check if ONFI can help us. */
>> + if (nand->ecc_strength_ds <= 0) {
>
> Actually this is not necessarily filled by ONFI parameters (it can be
> statically defined in the nand_ids table).
Should i sepcify it by dev_err or enough to say it in comment?
>> + /* No ONFI and no DT - it is bad. */
>> + dev_err(priv->dev,
>> + "nand-ecc-strength is not set by DT or ONFI. Please set nand-ecc-strength in DT or add chip quirk in nand_ids.c.\n");
>> + return -EINVAL;
>> + }
>> +
>> + ds_corr = (mtd->writesize * nand->ecc_strength_ds) /
>> + nand->ecc_step_ds;
>> + ecc_strength = ds_corr / nand->ecc.steps;
>> + dev_info(priv->dev, "ONFI:nand-ecc-strength = %i\n",
>> + ecc_strength);
>> + } else
>> + dev_info(priv->dev, "DT:nand-ecc-strength = %i\n",
>> + ecc_strength);
>> +
>> + if (ecc_strength == 0 || ecc_strength > ASM9260_ECC_MAX_BIT) {
>> + dev_err(priv->dev,
>> + "Not supported ecc_strength value: %i\n",
>> + ecc_strength);
>> + return -EINVAL;
>> + }
>> +
>> + if (ecc_strength & 0x1) {
>> + ecc_strength++;
>> + dev_info(priv->dev,
>> + "Only even ecc_strength value is supported. Recalculating: %i\n",
>> + ecc_strength);
>> + }
>> +
>> + /* FIXME: do we have max or min page size? */
>> +
>> + /* 13 - the smallest integer for 512 (ASM9260_ECC_STEP). Div to 8bit. */
>> + nand->ecc.bytes = DIV_ROUND_CLOSEST(ecc_strength * 13, 8);
>> +
>> + ecc_layout->eccbytes = nand->ecc.bytes * nand->ecc.steps;
>> + nand->ecc.layout = ecc_layout;
>> + nand->ecc.strength = ecc_strength;
>> +
>> + priv->spare_size = mtd->oobsize - ecc_layout->eccbytes;
>> +
>> + ecc_layout->oobfree[0].offset = 2;
>> + ecc_layout->oobfree[0].length = priv->spare_size - 2;
>> +
>> + /* FIXME: can we use same layout as SW_ECC? */
>
> It depends on what your controller is capable of. If you can define the
> offset at which you write the ECC bytes, then yes you can reuse the
> same kind of layout used in SW_ECC.
>
>> + for (i = 0; i < ecc_layout->eccbytes; i++)
>> + ecc_layout->eccpos[i] = priv->spare_size + i;
>> +
>> + return 0;
>> +}
>> +
>> +static int __init asm9260_nand_get_dt_clks(struct asm9260_nand_priv *priv)
>> +{
>> + int clk_idx = 0, err;
>> +
>> + priv->clk = devm_clk_get(priv->dev, "sys");
>> + if (IS_ERR(priv->clk))
>> + goto out_err;
>> +
>> + /* configure AHB clock */
>> + clk_idx = 1;
>> + priv->clk_ahb = devm_clk_get(priv->dev, "ahb");
>> + if (IS_ERR(priv->clk_ahb))
>> + goto out_err;
>> +
>> + err = clk_prepare_enable(priv->clk_ahb);
>> + if (err)
>> + dev_err(priv->dev, "Failed to enable ahb_clk!\n");
>> +
>> + err = clk_set_rate(priv->clk, clk_get_rate(priv->clk_ahb));
>> + if (err) {
>> + clk_disable_unprepare(priv->clk_ahb);
>> + dev_err(priv->dev, "Failed to set rate!\n");
>> + }
>> +
>> + err = clk_prepare_enable(priv->clk);
>> + if (err) {
>> + clk_disable_unprepare(priv->clk_ahb);
>> + dev_err(priv->dev, "Failed to enable clk!\n");
>> + }
>> +
>> + return 0;
>> +out_err:
>> + dev_err(priv->dev, "%s: Failed to get clk (%i)\n", __func__, clk_idx);
>> + return 1;
>> +}
>> +
>> +static int __init asm9260_nand_probe(struct platform_device *pdev)
>> +{
>> + struct asm9260_nand_priv *priv;
>> + struct nand_chip *nand;
>> + struct mtd_info *mtd;
>> + struct device_node *np = pdev->dev.of_node;
>> + struct resource *r;
>> + int ret, i;
>> + unsigned int irq;
>> + u32 val;
>> +
>> + priv = devm_kzalloc(&pdev->dev, sizeof(struct asm9260_nand_priv),
>> + GFP_KERNEL);
>> + if (!priv)
>> + return -ENOMEM;
>> +
>> + r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + priv->base = devm_ioremap_resource(&pdev->dev, r);
>> + if (!priv->base) {
>> + dev_err(&pdev->dev, "Unable to map resource!\n");
>> + return -EINVAL;
>> + }
>> +
>> + priv->dev = &pdev->dev;
>> + nand = &priv->nand;
>> + nand->priv = priv;
>> +
>> + platform_set_drvdata(pdev, priv);
>> + mtd = &priv->mtd;
>> + mtd->priv = nand;
>> + mtd->owner = THIS_MODULE;
>> + mtd->name = dev_name(&pdev->dev);
>> +
>> + if (asm9260_nand_get_dt_clks(priv))
>> + return -ENODEV;
>> +
>> + irq = platform_get_irq(pdev, 0);
>> + if (!irq)
>> + return -ENODEV;
>> +
>> + iowrite32(0, priv->base + HW_INT_MASK);
>> + ret = devm_request_irq(priv->dev, irq, asm9260_nand_irq,
>> + IRQF_ONESHOT | IRQF_SHARED,
>> + dev_name(&pdev->dev), priv);
>> +
>> + asm9260_nand_init_chip(nand);
>> +
>> + ret = of_property_read_u32(np, "nand-max-chips", &val);
>> + if (ret)
>> + val = 1;
>> +
>> + if (val > ASM9260_MAX_CHIPS) {
>> + dev_err(&pdev->dev, "Unsupported nand-max-chips value: %i\n",
>> + val);
>> + return -ENODEV;
>> + }
>> +
>> + for (i = 0; i < val; i++)
>> + priv->mem_mask |= BM_CTRL_MEM0_RDY << i;
>
> If you want to support multiple NAND chips, then I recommend to rework
> the DT representation and to define a asm9260_nand_controller struct:
>
> struct asm9260_nand_controller {
> struct nand_hw_control base;
>
> /* asm9260 related stuff */
> };
>
> Take a look [2] and [3].
need to tak clother look. Right now i don't understand meaning of struct
nand_hw_control.
>
>> +
>> + ret = nand_scan_ident(mtd, val, NULL);
>> + if (ret) {
>> + dev_err(&pdev->dev, "scan_ident filed!\n");
>> + return ret;
>> + }
>> +
>> + if (of_get_nand_on_flash_bbt(np)) {
>> + dev_info(&pdev->dev, "Use On Flash BBT\n");
>> + nand->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB_BBM
>> + | NAND_BBT_LASTBLOCK;
>> + }
>> +
>> + asm9260_nand_timing_config(priv);
>> + asm9260_nand_ecc_conf(priv);
>> + ret = asm9260_nand_cached_config(priv);
>> + if (ret)
>> + return ret;
>> +
>> + /* second phase scan */
>> + if (nand_scan_tail(mtd)) {
>> + dev_err(&pdev->dev, "scan_tail filed!\n");
>> + return -ENXIO;
>> + }
>> +
>> +
>> + ret = mtd_device_parse_register(mtd, NULL,
>> + &(struct mtd_part_parser_data) {
>> + .of_node = pdev->dev.of_node,
>> + },
>> + NULL, 0);
>
> Ergh, can you please define a local variable to replace this ugly
> mtd_part_parser_data definition ?
why?
--
Regards,
Oleksij
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 213 bytes --]
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v3 1/3] mtd: nand: add asm9260 NFC driver
2015-01-01 20:18 ` Oleksij Rempel
@ 2015-01-01 21:03 ` Boris Brezillon
0 siblings, 0 replies; 24+ messages in thread
From: Boris Brezillon @ 2015-01-01 21:03 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: computersforpeace, linux-mtd
On Thu, 01 Jan 2015 21:18:44 +0100
Oleksij Rempel <linux@rempel-privat.de> wrote:
> Am 31.12.2014 um 17:48 schrieb Boris Brezillon:
> > Hi Oleksij,
> >
> > You should really add a cover letter containing a changelog (updated at
> > each new version of your cover letter) so that reviewers can easily
> > identify what has changed.
> >
> > While you're at it, can you add your mtd test results to the cover
> > letter ?
>
> ok.
>
> [snip]
>
> >> +/**
> >> + * We can't read less then 32 bits on HW_FIFO_DATA. So, to make
> >> + * read_byte and read_word happy, we use sort of cached 32bit read.
> >> + * Note: expected values for size should be 1 or 2 bytes.
> >> + */
> >> +static u32 asm9260_nand_read_cached(struct mtd_info *mtd, int size)
> >> +{
> >> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> >> + u8 tmp;
> >> +
> >> + if ((priv->read_cache_cnt <= 0) || (priv->read_cache_cnt > 4)) {
> >
> > ^ how can this ever happen ?
> >
> >> + asm9260_nand_cmd_comp(mtd, 0);
> >> + priv->read_cache = ioread32(priv->base + HW_FIFO_DATA);
> >> + priv->read_cache_cnt = 4;
> >> + }
> >> +
> >> + tmp = priv->read_cache >> (8 * (4 - priv->read_cache_cnt));
> >> + priv->read_cache_cnt -= size;
> >> +
> >> + return tmp;
> >> +}
> >> +
> >> +static u8 asm9260_nand_read_byte(struct mtd_info *mtd)
> >> +{
> >> + return 0xff & asm9260_nand_read_cached(mtd, 1);
> >
> > Maybe this mask operation could be done in asm9260_nand_read_cached, so
> > that you won't have to bother in read_byte/read_word functions.
>
> it is same as "return (u8)asm9260_nand_read_cached(mtd, 1);", just makes
> sure compiler do not complain.
Absolutely, I didn't notice the return type of this function (thought
it was returning an u32).
Does it complain when you directly return asm9260_nand_read_cached
result ?
>
> >> +}
> >> +
> >> +static u16 asm9260_nand_read_word(struct mtd_info *mtd)
> >> +{
> >> + return 0xffff & asm9260_nand_read_cached(mtd, 2);
> >
> > You'd better always use read_word, cause if you call read_byte once
> > then read_word twice, you'll end up with a wrong value after the second
> > read_word (3 bytes consumed, which means there's only 1 remaining byte
> > and you're asking for 2).
>
> nand_base use both functions. how can i use only one? For example:
> chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
>
> /* Read entire ID string */
> for (i = 0; i < 8; i++)
> id_data[i] = chip->read_byte(mtd);
>
> this wont work with read_word.
What I meant is that if you start using read_byte after launching a
specific command you should not mix read_byte and read_word, because
otherwise you might end up with erroneous values.
I'm not sure this can really happen (is there any code in nand core
mixing read_byte and read_word ?), but my point is that you should
handle that specific case (only one remaining byte in read_cache while
2 are requested) in asm9260_nand_read_cached...
>
>
> >> +}
> >> +
> >> +static void asm9260_nand_read_buf(struct mtd_info *mtd, u8 *buf, int len)
> >> +{
> >> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> >> + dma_addr_t dma_addr;
> >> + int dma_ok = 0;
> >> +
> >> + if (len & 0x3) {
> >> + dev_err(priv->dev, "Unsupported length (%x)\n", len);
> >> + return;
> >> + }
> >> +
> >> + /*
> >> + * I hate you UBI for your all vmalloc. Be slow as hell with PIO.
> >> + * ~ with love from ZeroCopy ~
> >> + */
> >> + if (!is_vmalloc_addr(buf)) {
> >> + dma_addr = asm9260_nand_dma_set(mtd, buf, DMA_FROM_DEVICE, len);
> >> + dma_ok = !(dma_mapping_error(priv->dev, dma_addr));
> >> + }
> >> + asm9260_nand_cmd_comp(mtd, dma_ok);
> >> +
> >> + if (dma_ok) {
> >> + dma_sync_single_for_cpu(priv->dev, dma_addr, len,
> >> + DMA_FROM_DEVICE);
> >> + dma_unmap_single(priv->dev, dma_addr, len, DMA_FROM_DEVICE);
> >> + return;
> >> + }
> >> +
> >> + /* fall back to pio mode */
> >> + len >>= 2;
> >> + ioread32_rep(priv->base + HW_FIFO_DATA, buf, len);
> >
> > Hm, what if the buf is not aligned on 32bit, or len is not a multiple
> > of 4 ?
>
> I can read only buf == page size. All page sizes suported by this
> controller are aligned. See "if (len & 0x3) {", and take a look at
> asm9260_nand_read_cached - we can read only 32bit.
But read_buf is not only used to read full pages (see [1]), and, AFAIR,
there's nothing preventing mtd users from passing a non 32 bit aligned
buf...
>
>
> >> +}
> >> +
> >> +static void asm9260_nand_write_buf(struct mtd_info *mtd,
> >> + const u8 *buf, int len)
> >> +{
> >> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> >> + dma_addr_t dma_addr;
> >> + int dma_ok = 0;
> >> +
> >> + if (len & 0x3) {
> >> + dev_err(priv->dev, "Unsupported length (%x)\n", len);
> >> + return;
> >> + }
> >> +
> >> + if (!is_vmalloc_addr(buf)) {
> >> + dma_addr = asm9260_nand_dma_set(mtd,
> >> + (void *)buf, DMA_TO_DEVICE, len);
> >> + dma_ok = !(dma_mapping_error(priv->dev, dma_addr));
> >> + }
> >> +
> >> + if (dma_ok)
> >> + dma_sync_single_for_device(priv->dev, dma_addr, len,
> >> + DMA_TO_DEVICE);
> >> + asm9260_nand_cmd_comp(mtd, dma_ok);
> >> +
> >> + if (dma_ok) {
> >> + dma_unmap_single(priv->dev, dma_addr, len, DMA_TO_DEVICE);
> >> + return;
> >> + }
> >> +
> >> + /* fall back to pio mode */
> >> + len >>= 2;
> >> + iowrite32_rep(priv->base + HW_FIFO_DATA, buf, len);
> >
> > Ditto
> >
> >> +}
> >> +
> >> +static int asm9260_nand_write_page_raw(struct mtd_info *mtd,
> >> + struct nand_chip *chip, const u8 *buf,
> >> + int oob_required)
> >> +{
> >> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> >> +
> >> + chip->write_buf(mtd, buf, mtd->writesize);
> >> + if (oob_required)
> >> + chip->ecc.write_oob(mtd, chip, priv->page_cache &
> >> + chip->pagemask);
> >
> > Can't you just call
> > chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
> >
> > And if it works, then you can just drop this raw function since the
> > default one does the exact same thing.
>
> There 3 variants:
> - allocate dma == page + oob; and copy each buffer
> - use pio and read out oob even if it was not requested
> - use dma_map_single, avoid buffer copying, and request oob if needed.
>
> i use last variant even if it meane sending a command to request oob.
Because the controller cannot read in-band and out-of-band data in a
single command ?
>
> >
> >> + return 0;
> >> +}
> >> +
> >> +static int asm9260_nand_write_page_hwecc(struct mtd_info *mtd,
> >> + struct nand_chip *chip, const u8 *buf,
> >> + int oob_required)
> >> +{
> >> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> >> +
> >> + asm9260_reg_rmw(priv, HW_CTRL, BM_CTRL_ECC_EN, 0);
> >> + chip->ecc.write_page_raw(mtd, chip, buf, oob_required);
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static unsigned int asm9260_nand_count_ecc(struct asm9260_nand_priv *priv)
> >> +{
> >> + u32 tmp, i, count, maxcount = 0;
> >> +
> >> + /* FIXME: this layout was tested only on 2048byte NAND.
> >> + * NANDs with bigger page size should use more registers. */
> >
> > Yep, you should really fix that, or at least reject NAND with a
> > different page size until you've fixed that.
>
> ok.
>
> >> + tmp = ioread32(priv->base + HW_ECC_ERR_CNT);
> >> + for (i = 0; i < 4; i++) {
> >> + count = 0x1f & (tmp >> (5 * i));
> >> + maxcount = max_t(unsigned int, maxcount, count);
> >> + }
> >> +
> >> + return count;
> >> +}
> >> +
> >> +static int asm9260_nand_read_page_hwecc(struct mtd_info *mtd,
> >> + struct nand_chip *chip, u8 *buf,
> >> + int oob_required, int page)
> >> +{
> >> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> >> + u8 *temp_ptr;
> >> + u32 status, max_bitflips = 0;
> >> +
> >> + temp_ptr = buf;
> >> +
> >> + /* enable ecc */
> >> + asm9260_reg_rmw(priv, HW_CTRL, BM_CTRL_ECC_EN, 0);
> >> + chip->read_buf(mtd, temp_ptr, mtd->writesize);
> >> +
> >> + status = ioread32(priv->base + HW_ECC_CTRL);
> >> +
> >> + if (status & BM_ECC_ERR_UNC) {
> >> + u32 ecc_err;
> >> +
> >> + ecc_err = ioread32(priv->base + HW_ECC_ERR_CNT);
> >> + /* check if it is erased page (all_DATA_OOB == 0xff) */
> >> + /* FIXME: should be tested if it is a bullet proof solution.
> >> + * if not, use is_buf_blank. */
> >
> > you'd rather use is_buf_blank if you're unsure.
>
> ok, can is_buf_blank be moved to nand_base?
I'll let Brian answer that one.
>
> >
> >> + if (ecc_err != 0x8421)
> >> + mtd->ecc_stats.failed++;
> >> +
> >> + } else if (status & BM_ECC_ERR_CORRECT) {
> >> + max_bitflips = asm9260_nand_count_ecc(priv);
> >
> > max_bitflips should contain the maximum number of bitflips in all
> > ECC blocks of a given page, here you just returning the number of
> > bitflips in the last ECC block.
>
> no. see asm9260_nand_count_ecc.
My bad, you're right (I thought the ECC step loop was done here).
>
> > The following should do the trick:
> >
> > int bitflips = asm9260_nand_count_ecc(priv);
> >
> > if (bitflips > max_bitflips)
> > max_bitflips = bitflips;
> >
> > mtd->ecc_stats.corrected += bitflips;
> >
> >> + mtd->ecc_stats.corrected += max_bitflips;
> >> + }
> >> +
> >> + if (oob_required)
> >> + chip->ecc.read_oob(mtd, chip, page);
> >> +
> >> + return max_bitflips;
> >> +}
> >> +
> >> +static irqreturn_t asm9260_nand_irq(int irq, void *device_info)
> >> +{
> >> + struct asm9260_nand_priv *priv = device_info;
> >> + struct nand_chip *nand = &priv->nand;
> >> + u32 status;
> >> +
> >> + status = ioread32(priv->base + HW_INT_STATUS);
> >> + if (!status)
> >> + return IRQ_NONE;
> >> +
> >> + iowrite32(0, priv->base + HW_INT_MASK);
> >> + iowrite32(0, priv->base + HW_INT_STATUS);
> >> + priv->irq_done = 1;
> >
> > You should test the flags before deciding the irq is actually done...
>
> ok. if fixed it by changing mem_mask logic.
Still, checking the status register is a good practice.
>
> >
> >> + wake_up(&nand->controller->wq);
> >
> > and if the condition is not met, don't wake up the waitqueue.
> >
> >> +
> >> + return IRQ_HANDLED;
> >> +}
> >> +
> >> +static void __init asm9260_nand_init_chip(struct nand_chip *nand_chip)
> >> +{
> >> + nand_chip->select_chip = asm9260_select_chip;
> >> + nand_chip->cmdfunc = asm9260_nand_command_lp;
> >
> > You seems to only support large pages NANDs (> 512 bytes), maybe you
> > should check if the discovered chip has large pages, and reject the NAND
> > otherwise.
>
> ok.
>
> >> + nand_chip->read_byte = asm9260_nand_read_byte;
> >> + nand_chip->read_word = asm9260_nand_read_word;
> >> + nand_chip->read_buf = asm9260_nand_read_buf;
> >> + nand_chip->write_buf = asm9260_nand_write_buf;
> >> +
> >> + nand_chip->dev_ready = asm9260_nand_dev_ready;
> >> + nand_chip->chip_delay = 100;
> >> + nand_chip->options |= NAND_NO_SUBPAGE_WRITE;
> >> +
> >> + nand_chip->ecc.write_page = asm9260_nand_write_page_hwecc;
> >> + nand_chip->ecc.write_page_raw = asm9260_nand_write_page_raw;
> >> + nand_chip->ecc.read_page = asm9260_nand_read_page_hwecc;
> >> +}
> >> +
> >> +static int __init asm9260_nand_cached_config(struct asm9260_nand_priv *priv)
> >> +{
> >> + struct nand_chip *nand = &priv->nand;
> >> + struct mtd_info *mtd = &priv->mtd;
> >> + u32 addr_cycles, col_cycles, pages_per_block;
> >> +
> >> + pages_per_block = mtd->erasesize / mtd->writesize;
> >> + /* max 256P, min 32P */
> >> + if (pages_per_block & ~(0x000001e0)) {
> >> + dev_err(priv->dev, "Unsupported erasesize 0x%x\n",
> >> + mtd->erasesize);
> >> + return -EINVAL;
> >> + }
> >> +
> >> + /* max 32K, min 256. */
> >> + if (mtd->writesize & ~(0x0000ff00)) {
> >> + dev_err(priv->dev, "Unsupported writesize 0x%x\n",
> >> + mtd->erasesize);
> >> + return -EINVAL;
> >> + }
> >> +
> >> + col_cycles = 2;
> >> + addr_cycles = col_cycles +
> >> + (((mtd->size >> mtd->writesize) > 65536) ? 3 : 2);
> >> +
> >> + priv->ctrl_cache = addr_cycles << BM_CTRL_ADDR_CYCLE1_S
> >> + | BM_CTRL_PAGE_SIZE(mtd->writesize) << BM_CTRL_PAGE_SIZE_S
> >> + | BM_CTRL_BLOCK_SIZE(pages_per_block) << BM_CTRL_BLOCK_SIZE_S
> >> + | BM_CTRL_INT_EN
> >> + | addr_cycles << BM_CTRL_ADDR_CYCLE0_S;
> >> +
> >> + iowrite32(BM_ECC_CAPn(nand->ecc.strength) << BM_ECC_CAP_S,
> >> + priv->base + HW_ECC_CTRL);
> >> +
> >> + iowrite32(mtd->writesize + priv->spare_size,
> >> + priv->base + HW_ECC_OFFSET);
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static unsigned long __init clk_get_cyc_from_ns(struct clk *clk,
> >> + unsigned long ns)
> >> +{
> >> + unsigned int cycle;
> >> +
> >> + cycle = NSEC_PER_SEC / clk_get_rate(clk);
> >> + return DIV_ROUND_CLOSEST(ns, cycle);
> >> +}
> >> +
> >> +static void __init asm9260_nand_timing_config(struct asm9260_nand_priv *priv)
> >> +{
> >> + struct nand_chip *nand = &priv->nand;
> >> + const struct nand_sdr_timings *time;
> >> + u32 twhr, trhw, trwh, trwp, tadl, tccs, tsync, trr, twb;
> >> + int mode;
> >> +
> >
> > Maybe you should add support for ONFI timing mode retrieval with
> > onfi_get_async_timing_mode.
>
> instead of nand->onfi_timing_mode_default?
No you should check both: first try to retrieve the onfi timing mode
with the onfi_get_async_timing_mode function and it it returns an error
use onfi_timing_mode_default.
>
> >> + mode = nand->onfi_timing_mode_default;
> >> + dev_info(priv->dev, "ONFI timing mode: %i\n", mode);
> >> +
> >> + time = onfi_async_timing_mode_to_sdr_timings(mode);
> >> + if (IS_ERR_OR_NULL(time)) {
> >> + dev_err(priv->dev, "Can't get onfi_timing_mode: %i\n", mode);
> >> + return;
> >> + }
> >> +
> >> + trwh = clk_get_cyc_from_ns(priv->clk, time->tWH_min / 1000);
> >> + trwp = clk_get_cyc_from_ns(priv->clk, time->tWP_min / 1000);
> >> +
> >> + iowrite32((trwh << 4) | (trwp), priv->base + HW_TIMING_ASYN);
> >> +
> >> + twhr = clk_get_cyc_from_ns(priv->clk, time->tWHR_min / 1000);
> >> + trhw = clk_get_cyc_from_ns(priv->clk, time->tRHW_min / 1000);
> >> + tadl = clk_get_cyc_from_ns(priv->clk, time->tADL_min / 1000);
> >> + /* tCCS - change read/write collumn. Time between last cmd and data. */
> >> + tccs = clk_get_cyc_from_ns(priv->clk,
> >> + (time->tCLR_min + time->tCLH_min + time->tRC_min)
> >> + / 1000);
> >> +
> >> + iowrite32((twhr << 24) | (trhw << 16)
> >> + | (tadl << 8) | (tccs), priv->base + HW_TIM_SEQ_0);
> >> +
> >> + trr = clk_get_cyc_from_ns(priv->clk, time->tRR_min / 1000);
> >> + tsync = 0; /* ??? */
> >> + twb = clk_get_cyc_from_ns(priv->clk, time->tWB_max / 1000);
> >> + iowrite32((tsync << 16) | (trr << 9) | (twb),
> >> + priv->base + HW_TIM_SEQ_1);
> >> +}
> >> +
> >> +static int __init asm9260_nand_ecc_conf(struct asm9260_nand_priv *priv)
> >> +{
> >> + struct device_node *np = priv->dev->of_node;
> >> + struct nand_chip *nand = &priv->nand;
> >> + struct mtd_info *mtd = &priv->mtd;
> >> + struct nand_ecclayout *ecc_layout = &priv->ecc_layout;
> >> + int i, ecc_strength;
> >> +
> >> + nand->ecc.mode = of_get_nand_ecc_mode(np);
> >> + switch (nand->ecc.mode) {
> >> + case NAND_ECC_SOFT:
> >> + case NAND_ECC_SOFT_BCH:
> >> + dev_info(priv->dev, "Using soft ECC %i\n", nand->ecc.mode);
> >> + /* nand_base will find needed settings */
> >> + return 0;
> >> + case NAND_ECC_HW:
> >> + default:
> >> + dev_info(priv->dev, "Using default NAND_ECC_HW\n");
> >> + nand->ecc.mode = NAND_ECC_HW;
> >> + break;
> >> + }
> >> +
> >> + ecc_strength = of_get_nand_ecc_strength(np);
> >> + nand->ecc.size = ASM9260_ECC_STEP;
> >> + nand->ecc.steps = mtd->writesize / nand->ecc.size;
> >> +
> >> + if (ecc_strength < nand->ecc_strength_ds) {
> >> + int ds_corr;
> >> +
> >> + /* Let's check if ONFI can help us. */
> >> + if (nand->ecc_strength_ds <= 0) {
> >
> > Actually this is not necessarily filled by ONFI parameters (it can be
> > statically defined in the nand_ids table).
>
> Should i sepcify it by dev_err or enough to say it in comment?
Keep your error message, just change its content.
>
> >> + /* No ONFI and no DT - it is bad. */
> >> + dev_err(priv->dev,
> >> + "nand-ecc-strength is not set by DT or ONFI. Please set nand-ecc-strength in DT or add chip quirk in nand_ids.c.\n");
> >> + return -EINVAL;
> >> + }
> >> +
> >> + ds_corr = (mtd->writesize * nand->ecc_strength_ds) /
> >> + nand->ecc_step_ds;
> >> + ecc_strength = ds_corr / nand->ecc.steps;
> >> + dev_info(priv->dev, "ONFI:nand-ecc-strength = %i\n",
> >> + ecc_strength);
> >> + } else
> >> + dev_info(priv->dev, "DT:nand-ecc-strength = %i\n",
> >> + ecc_strength);
> >> +
> >> + if (ecc_strength == 0 || ecc_strength > ASM9260_ECC_MAX_BIT) {
> >> + dev_err(priv->dev,
> >> + "Not supported ecc_strength value: %i\n",
> >> + ecc_strength);
> >> + return -EINVAL;
> >> + }
> >> +
> >> + if (ecc_strength & 0x1) {
> >> + ecc_strength++;
> >> + dev_info(priv->dev,
> >> + "Only even ecc_strength value is supported. Recalculating: %i\n",
> >> + ecc_strength);
> >> + }
> >> +
> >> + /* FIXME: do we have max or min page size? */
> >> +
> >> + /* 13 - the smallest integer for 512 (ASM9260_ECC_STEP). Div to 8bit. */
> >> + nand->ecc.bytes = DIV_ROUND_CLOSEST(ecc_strength * 13, 8);
> >> +
> >> + ecc_layout->eccbytes = nand->ecc.bytes * nand->ecc.steps;
> >> + nand->ecc.layout = ecc_layout;
> >> + nand->ecc.strength = ecc_strength;
> >> +
> >> + priv->spare_size = mtd->oobsize - ecc_layout->eccbytes;
> >> +
> >> + ecc_layout->oobfree[0].offset = 2;
> >> + ecc_layout->oobfree[0].length = priv->spare_size - 2;
> >> +
> >> + /* FIXME: can we use same layout as SW_ECC? */
> >
> > It depends on what your controller is capable of. If you can define the
> > offset at which you write the ECC bytes, then yes you can reuse the
> > same kind of layout used in SW_ECC.
> >
> >> + for (i = 0; i < ecc_layout->eccbytes; i++)
> >> + ecc_layout->eccpos[i] = priv->spare_size + i;
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int __init asm9260_nand_get_dt_clks(struct asm9260_nand_priv *priv)
> >> +{
> >> + int clk_idx = 0, err;
> >> +
> >> + priv->clk = devm_clk_get(priv->dev, "sys");
> >> + if (IS_ERR(priv->clk))
> >> + goto out_err;
> >> +
> >> + /* configure AHB clock */
> >> + clk_idx = 1;
> >> + priv->clk_ahb = devm_clk_get(priv->dev, "ahb");
> >> + if (IS_ERR(priv->clk_ahb))
> >> + goto out_err;
> >> +
> >> + err = clk_prepare_enable(priv->clk_ahb);
> >> + if (err)
> >> + dev_err(priv->dev, "Failed to enable ahb_clk!\n");
> >> +
> >> + err = clk_set_rate(priv->clk, clk_get_rate(priv->clk_ahb));
> >> + if (err) {
> >> + clk_disable_unprepare(priv->clk_ahb);
> >> + dev_err(priv->dev, "Failed to set rate!\n");
> >> + }
> >> +
> >> + err = clk_prepare_enable(priv->clk);
> >> + if (err) {
> >> + clk_disable_unprepare(priv->clk_ahb);
> >> + dev_err(priv->dev, "Failed to enable clk!\n");
> >> + }
> >> +
> >> + return 0;
> >> +out_err:
> >> + dev_err(priv->dev, "%s: Failed to get clk (%i)\n", __func__, clk_idx);
> >> + return 1;
> >> +}
> >> +
> >> +static int __init asm9260_nand_probe(struct platform_device *pdev)
> >> +{
> >> + struct asm9260_nand_priv *priv;
> >> + struct nand_chip *nand;
> >> + struct mtd_info *mtd;
> >> + struct device_node *np = pdev->dev.of_node;
> >> + struct resource *r;
> >> + int ret, i;
> >> + unsigned int irq;
> >> + u32 val;
> >> +
> >> + priv = devm_kzalloc(&pdev->dev, sizeof(struct asm9260_nand_priv),
> >> + GFP_KERNEL);
> >> + if (!priv)
> >> + return -ENOMEM;
> >> +
> >> + r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> >> + priv->base = devm_ioremap_resource(&pdev->dev, r);
> >> + if (!priv->base) {
> >> + dev_err(&pdev->dev, "Unable to map resource!\n");
> >> + return -EINVAL;
> >> + }
> >> +
> >> + priv->dev = &pdev->dev;
> >> + nand = &priv->nand;
> >> + nand->priv = priv;
> >> +
> >> + platform_set_drvdata(pdev, priv);
> >> + mtd = &priv->mtd;
> >> + mtd->priv = nand;
> >> + mtd->owner = THIS_MODULE;
> >> + mtd->name = dev_name(&pdev->dev);
> >> +
> >> + if (asm9260_nand_get_dt_clks(priv))
> >> + return -ENODEV;
> >> +
> >> + irq = platform_get_irq(pdev, 0);
> >> + if (!irq)
> >> + return -ENODEV;
> >> +
> >> + iowrite32(0, priv->base + HW_INT_MASK);
> >> + ret = devm_request_irq(priv->dev, irq, asm9260_nand_irq,
> >> + IRQF_ONESHOT | IRQF_SHARED,
> >> + dev_name(&pdev->dev), priv);
> >> +
> >> + asm9260_nand_init_chip(nand);
> >> +
> >> + ret = of_property_read_u32(np, "nand-max-chips", &val);
> >> + if (ret)
> >> + val = 1;
> >> +
> >> + if (val > ASM9260_MAX_CHIPS) {
> >> + dev_err(&pdev->dev, "Unsupported nand-max-chips value: %i\n",
> >> + val);
> >> + return -ENODEV;
> >> + }
> >> +
> >> + for (i = 0; i < val; i++)
> >> + priv->mem_mask |= BM_CTRL_MEM0_RDY << i;
> >
> > If you want to support multiple NAND chips, then I recommend to rework
> > the DT representation and to define a asm9260_nand_controller struct:
> >
> > struct asm9260_nand_controller {
> > struct nand_hw_control base;
> >
> > /* asm9260 related stuff */
> > };
> >
> > Take a look [2] and [3].
>
>
> need to tak clother look. Right now i don't understand meaning of struct
> nand_hw_control.
It is there to represent an NAND flash controller, and is mainly useful
to lock access to the controller so that only one chip can be accessed
through the controller at a given time.
>
> >
> >> +
> >> + ret = nand_scan_ident(mtd, val, NULL);
> >> + if (ret) {
> >> + dev_err(&pdev->dev, "scan_ident filed!\n");
> >> + return ret;
> >> + }
> >> +
> >> + if (of_get_nand_on_flash_bbt(np)) {
> >> + dev_info(&pdev->dev, "Use On Flash BBT\n");
> >> + nand->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB_BBM
> >> + | NAND_BBT_LASTBLOCK;
> >> + }
> >> +
> >> + asm9260_nand_timing_config(priv);
> >> + asm9260_nand_ecc_conf(priv);
> >> + ret = asm9260_nand_cached_config(priv);
> >> + if (ret)
> >> + return ret;
> >> +
> >> + /* second phase scan */
> >> + if (nand_scan_tail(mtd)) {
> >> + dev_err(&pdev->dev, "scan_tail filed!\n");
> >> + return -ENXIO;
> >> + }
> >> +
> >> +
> >> + ret = mtd_device_parse_register(mtd, NULL,
> >> + &(struct mtd_part_parser_data) {
> >> + .of_node = pdev->dev.of_node,
> >> + },
> >> + NULL, 0);
> >
> > Ergh, can you please define a local variable to replace this ugly
> > mtd_part_parser_data definition ?
>
> why?
>
Because I find it hard to read, but maybe I'm the only one to think
that way.
How about the following syntax:
struct mtd_part_parser_data ppdata = {
.of_node = pdev->dev.of_node,
};
[...]
ret = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0);
Regards,
Boris
[1]http://lxr.free-electrons.com/source/drivers/mtd/nand/nand_base.c#L3052
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v3 1/3] mtd: nand: add asm9260 NFC driver
2014-12-31 12:58 ` Oleksij Rempel
` (3 preceding siblings ...)
2015-01-01 12:58 ` Boris Brezillon
@ 2015-01-01 21:14 ` Boris Brezillon
4 siblings, 0 replies; 24+ messages in thread
From: Boris Brezillon @ 2015-01-01 21:14 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: computersforpeace, linux-mtd
On Wed, 31 Dec 2014 13:58:51 +0100
Oleksij Rempel <linux@rempel-privat.de> wrote:
> Add driver for Nand Flash Controller used on Alphascales ASM9260 chips.
> The IP core of this controller has some similarities with
> Evatronix NANDFLASH-CTRL IP (unknown revision), so probably it can be reused
> by some other SoCs.
>
> Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
> ---
> drivers/mtd/nand/Kconfig | 7 +
> drivers/mtd/nand/Makefile | 1 +
> drivers/mtd/nand/asm9260_nand.c | 989 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 997 insertions(+)
> create mode 100644 drivers/mtd/nand/asm9260_nand.c
>
> diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
> index dd10646..580a608 100644
> --- a/drivers/mtd/nand/Kconfig
> +++ b/drivers/mtd/nand/Kconfig
> @@ -41,6 +41,13 @@ config MTD_SM_COMMON
> tristate
> default n
[...]
> +
> +static void asm9260_nand_command_lp(struct mtd_info *mtd,
> + unsigned int command, int column, int page_addr)
> +{
> + struct asm9260_nand_priv *priv = mtd_to_priv(mtd);
> +
> + switch (command) {
> + case NAND_CMD_RESET:
> + asm9260_nand_cmd_prep(priv, NAND_CMD_RESET, 0, 0, SEQ0);
> + asm9260_nand_cmd_comp(mtd, 0);
> + break;
> +
> + case NAND_CMD_READID:
> + iowrite32(1, priv->base + HW_FIFO_INIT);
> + iowrite32((ADDR_CYCLE_1 << BM_CTRL_ADDR_CYCLE1_S)
> + | BM_CTRL_CUSTOM_PAGE_SIZE
> + | (PAGE_SIZE_4096B << BM_CTRL_PAGE_SIZE_S)
> + | (BLOCK_SIZE_32P << BM_CTRL_BLOCK_SIZE_S)
> + | BM_CTRL_INT_EN
> + | (ADDR_CYCLE_1 << BM_CTRL_ADDR_CYCLE0_S),
> + priv->base + HW_CTRL);
> +
> + iowrite32(8, priv->base + HW_DATA_SIZE);
> + iowrite32(column, priv->base + HW_ADDR0_0);
> + asm9260_nand_cmd_prep(priv, NAND_CMD_READID, 0, 0, SEQ1);
> +
> + priv->read_cache_cnt = 0;
> + break;
> +
> + case NAND_CMD_READOOB:
> + column += mtd->writesize;
> + command = NAND_CMD_READ0;
> + case NAND_CMD_READ0:
> + iowrite32(1, priv->base + HW_FIFO_INIT);
> +
> + if (column == 0) {
> + asm9260_nand_ctrl(priv, 0);
> + iowrite32(priv->spare_size, priv->base + HW_SPARE_SIZE);
> + } else if (column == mtd->writesize) {
> + asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
> + iowrite32(mtd->oobsize, priv->base + HW_SPARE_SIZE);
> + iowrite32(mtd->oobsize, priv->base + HW_DATA_SIZE);
> + } else {
> + dev_err(priv->dev, "Couldn't support the column\n");
> + break;
> + }
> +
> + asm9260_nand_set_addr(priv, page_addr, column);
> +
> + asm9260_nand_cmd_prep(priv, NAND_CMD_READ0,
> + NAND_CMD_READSTART, 0, SEQ10);
> +
> + priv->read_cache_cnt = 0;
> + break;
> + case NAND_CMD_SEQIN:
> + iowrite32(1, priv->base + HW_FIFO_INIT);
> +
> + if (column == 0) {
> + priv->page_cache = page_addr;
> + asm9260_nand_ctrl(priv, 0);
> + iowrite32(priv->spare_size, priv->base + HW_SPARE_SIZE);
> + } else if (column == mtd->writesize) {
> + asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
> + iowrite32(mtd->oobsize, priv->base + HW_DATA_SIZE);
> + }
> +
> + asm9260_nand_set_addr(priv, page_addr, column);
> +
> + asm9260_nand_cmd_prep(priv, NAND_CMD_SEQIN, NAND_CMD_PAGEPROG,
> + 0, SEQ12);
> +
> + break;
> + case NAND_CMD_STATUS:
> + iowrite32(1, priv->base + HW_FIFO_INIT);
> + asm9260_nand_ctrl(priv, BM_CTRL_CUSTOM_PAGE_SIZE);
> +
> + /*
> + * Workaround for status bug.
> + * Instead of SEQ4 we need to use SEQ1 here, which will
> + * send cmd with address. For this case we need to make sure
> + * ADDR == 0.
> + */
> + asm9260_nand_set_addr(priv, 0, 0);
> + iowrite32(4, priv->base + HW_DATA_SIZE);
> + asm9260_nand_cmd_prep(priv, NAND_CMD_STATUS, 0, 0, SEQ1);
> +
> + priv->read_cache_cnt = 0;
> + break;
> +
> + case NAND_CMD_ERASE1:
> + priv->wait_time = 400;
> + asm9260_nand_set_addr(priv, page_addr, column);
> +
> + asm9260_nand_ctrl(priv, 0);
> +
> + /*
> + * Prepare and send command now. We don't need to split it in
> + * two stages.
> + */
> + asm9260_nand_cmd_prep(priv, NAND_CMD_ERASE1, NAND_CMD_ERASE2,
> + 0, SEQ14);
> + asm9260_nand_cmd_comp(mtd, 0);
> + break;
> + default:
> + break;
You should support other NAND commands (like the ONFI related ones)
instead of silently ignoring them.
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v3 3/3] mtd: nand: add Toshiba TC58NVG0S3E to nand_ids table
2014-12-31 12:58 ` [PATCH v3 3/3] mtd: nand: add Toshiba TC58NVG0S3E to nand_ids table Oleksij Rempel
2014-12-31 16:55 ` Boris Brezillon
@ 2015-08-25 19:25 ` Brian Norris
2015-08-26 4:00 ` Oleksij Rempel
1 sibling, 1 reply; 24+ messages in thread
From: Brian Norris @ 2015-08-25 19:25 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: boris.brezillon, linux-mtd
An old one here...
On Wed, Dec 31, 2014 at 01:58:53PM +0100, Oleksij Rempel wrote:
> Add the full description of the Toshiba TC58NVG0S3E NAND chip in the
> nand_ids table so that we can later use the NAND ECC infos and ONFI timings
> mode in controller drivers.
>
> Tested with asm9260_nand driver.
Looks like this driver work stalled?
> Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
This patch looks good anyway, so pushed to l2-mtd.git.
Brian
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v3 3/3] mtd: nand: add Toshiba TC58NVG0S3E to nand_ids table
2015-08-25 19:25 ` Brian Norris
@ 2015-08-26 4:00 ` Oleksij Rempel
2015-09-01 21:29 ` Brian Norris
0 siblings, 1 reply; 24+ messages in thread
From: Oleksij Rempel @ 2015-08-26 4:00 UTC (permalink / raw)
To: Brian Norris; +Cc: boris.brezillon, linux-mtd
[-- Attachment #1: Type: text/plain, Size: 1011 bytes --]
Hi,
Am 25.08.2015 um 21:25 schrieb Brian Norris:
> An old one here...
>
> On Wed, Dec 31, 2014 at 01:58:53PM +0100, Oleksij Rempel wrote:
>> Add the full description of the Toshiba TC58NVG0S3E NAND chip in the
>> nand_ids table so that we can later use the NAND ECC infos and ONFI timings
>> mode in controller drivers.
>>
>> Tested with asm9260_nand driver.
>
> Looks like this driver work stalled?
Yea, i'm working on other project. The company with asm9260_nand chip
asked not to be named any more. I assume, they have some legal issues. I
can continue to work on this in my free time, but i need some suggestion
how to handle this issue. Any ideas?
>
>> Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
>
> This patch looks good anyway, so pushed to l2-mtd.git.
Thx
> Brian
>
> ______________________________________________________
> Linux MTD discussion mailing list
> http://lists.infradead.org/mailman/listinfo/linux-mtd/
>
--
Regards,
Oleksij
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 213 bytes --]
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v3 3/3] mtd: nand: add Toshiba TC58NVG0S3E to nand_ids table
2015-08-26 4:00 ` Oleksij Rempel
@ 2015-09-01 21:29 ` Brian Norris
0 siblings, 0 replies; 24+ messages in thread
From: Brian Norris @ 2015-09-01 21:29 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: boris.brezillon, linux-mtd
Hi Oleksij,
On Wed, Aug 26, 2015 at 06:00:38AM +0200, Oleksij Rempel wrote:
> Am 25.08.2015 um 21:25 schrieb Brian Norris:
> > An old one here...
> >
> > On Wed, Dec 31, 2014 at 01:58:53PM +0100, Oleksij Rempel wrote:
> >> Add the full description of the Toshiba TC58NVG0S3E NAND chip in the
> >> nand_ids table so that we can later use the NAND ECC infos and ONFI timings
> >> mode in controller drivers.
> >>
> >> Tested with asm9260_nand driver.
> >
> > Looks like this driver work stalled?
>
> Yea, i'm working on other project. The company with asm9260_nand chip
> asked not to be named any more. I assume, they have some legal issues. I
> can continue to work on this in my free time, but i need some suggestion
> how to handle this issue. Any ideas?
Well, I'm not a lawyer, and I can't give legal advice. But AIUI, as long
as you can fulfill the Developer's Certificate of Origin (see
Documentation/SubmittingPatches), then you can still contribute code.
It's on you to clear up whether you have "the right to submit it under
the open source license indicated in the file."
(IANAL double: but given that the original submissions are already out
on the mailing lists under GPLv2 (the Signed-off-by), nothing would stop
random developer X from picking up your work and upstreaming it, without
any additional permission.)
Brian
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2015-09-01 21:30 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-12-17 11:45 [PATCH RFC] Alphascale ASM9260 NAND controller driver Oleksij Rempel
2014-12-17 11:45 ` [PATCH RFC] mtd: nand: add asm9260 NFC driver Oleksij Rempel
2014-12-17 16:15 ` Boris Brezillon
2014-12-30 18:09 ` [PATCH RFC v2] " Oleksij Rempel
2014-12-30 19:09 ` Boris Brezillon
2014-12-31 12:58 ` [PATCH v3 1/3] " Oleksij Rempel
2014-12-31 12:58 ` Oleksij Rempel
2014-12-31 12:58 ` [PATCH v3 2/3] mtd: nand: add asm9260-nand devicetree documentation Oleksij Rempel
2014-12-31 17:01 ` Boris Brezillon
2014-12-31 12:58 ` [PATCH v3 3/3] mtd: nand: add Toshiba TC58NVG0S3E to nand_ids table Oleksij Rempel
2014-12-31 16:55 ` Boris Brezillon
2015-08-25 19:25 ` Brian Norris
2015-08-26 4:00 ` Oleksij Rempel
2015-09-01 21:29 ` Brian Norris
2014-12-31 16:48 ` [PATCH v3 1/3] mtd: nand: add asm9260 NFC driver Boris Brezillon
2014-12-31 17:26 ` Boris Brezillon
2015-01-01 20:18 ` Oleksij Rempel
2015-01-01 21:03 ` Boris Brezillon
2015-01-01 12:58 ` Boris Brezillon
2015-01-01 21:14 ` Boris Brezillon
2014-12-17 13:24 ` [PATCH RFC] Alphascale ASM9260 NAND controller driver Boris Brezillon
2014-12-17 14:36 ` Oleksij Rempel
2014-12-17 15:01 ` Oleksij Rempel
2014-12-17 15:01 ` Boris Brezillon
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).