diff options
author | Mingjun Zhang <zhang.mingjun@linaro.org> | 2013-03-18 14:20:47 +0800 |
---|---|---|
committer | Mingjun Zhang <zhang.mingjun@linaro.org> | 2013-03-18 14:20:47 +0800 |
commit | 160c3bf4e56e347621eacf41ea1537b5eda9f15f (patch) | |
tree | 9bc2e4e76ed3d21856e17b6645a00c695214207c /drivers | |
parent | 8ee72010119fadefab0d73c4b99ba61b1b0e4f27 (diff) |
mtd: nand: hs nfc: support hisilicon nand flash controller
Hisilicon nfc is a powerful nand flash controller with dma and
hardware ecc functions. This driver supports some non-standard
nand chips, included in the driver's nand ids table.
Signed-off-by: Mingjun Zhang <zhang.mingjun@linaro.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/mtd/nand/Kconfig | 15 | ||||
-rw-r--r-- | drivers/mtd/nand/Makefile | 1 | ||||
-rw-r--r-- | drivers/mtd/nand/hs_nfc/Makefile | 7 | ||||
-rw-r--r-- | drivers/mtd/nand/hs_nfc/debugfs.c | 274 | ||||
-rw-r--r-- | drivers/mtd/nand/hs_nfc/hinfc504.h | 299 | ||||
-rw-r--r-- | drivers/mtd/nand/hs_nfc/hinfc504_hw.c | 845 | ||||
-rw-r--r-- | drivers/mtd/nand/hs_nfc/hinfc504_os.c | 339 | ||||
-rw-r--r-- | drivers/mtd/nand/hs_nfc/hinfc504_read_retry.c | 353 | ||||
-rw-r--r-- | drivers/mtd/nand/hs_nfc/hinfc_common.h | 128 | ||||
-rw-r--r-- | drivers/mtd/nand/hs_nfc/hinfc_spl_ids.c | 195 |
10 files changed, 2456 insertions, 0 deletions
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 5819eb57521..1ac202fe967 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -575,4 +575,19 @@ config MTD_NAND_XWAY Enables support for NAND Flash chips on Lantiq XWAY SoCs. NAND is attached to the External Bus Unit (EBU). +config MTD_NAND_HINFC504 + tristate "support hisilicon nand flash controller v504" + depends on ARCH_HS + default y + help + Enables support for hisilicon's NAND Flash Controller v504. + The NFC can do hardware ECC, it also has a dma engine. + +config MTD_NAND_HINFC504_DEBUG + bool "Hisilicon NAND Flash Controller driver debug" + depends on MTD_NAND_HINFC504 + help + Enable debugging of the NFC driver, use debugfs to log NFC's + operation. + endif # MTD_NAND diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index d76d9120569..a7f49bd1456 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -53,5 +53,6 @@ obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand/ obj-$(CONFIG_MTD_NAND_XWAY) += xway_nand.o obj-$(CONFIG_MTD_NAND_BCM47XXNFLASH) += bcm47xxnflash/ +obj-$(CONFIG_MTD_NAND_HINFC504) += hs_nfc/ nand-objs := nand_base.o nand_bbt.o diff --git a/drivers/mtd/nand/hs_nfc/Makefile b/drivers/mtd/nand/hs_nfc/Makefile new file mode 100644 index 00000000000..97d791af7f8 --- /dev/null +++ b/drivers/mtd/nand/hs_nfc/Makefile @@ -0,0 +1,7 @@ +# +#linux/drivers/mtd/nand/hs_nfc/Makefile +# +obj-$(CONFIG_MTD_NAND_HINFC504) += hinfc504_os.o hinfc504_hw.o hinfc_spl_ids.o \ + hinfc504_read_retry.o + +obj-$(CONFIG_MTD_NAND_HINFC504_DEBUG) += debugfs.o diff --git a/drivers/mtd/nand/hs_nfc/debugfs.c b/drivers/mtd/nand/hs_nfc/debugfs.c new file mode 100644 index 00000000000..d317f471ba5 --- /dev/null +++ b/drivers/mtd/nand/hs_nfc/debugfs.c @@ -0,0 +1,274 @@ +/* + * Hisilicon NAND Flash controller driver + * + * Copyright © 2012-2013 HiSilicon Technologies Co., Ltd. + * http://www.hisilicon.com + * Copyright © 2012-2013 Linaro Ltd. + * http://www.linaro.org + * + * Author: Mingjun Zhang <zhang.mingjun@linaro.org> + * The initial developer of the original code is Zhiyong Cai + * <caizhiyong@huawei.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/seq_file.h> +#include <linux/debugfs.h> +#include <linux/uaccess.h> +#include <linux/export.h> +#include <linux/slab.h> +#include "hinfc504.h" + +#define get_page_index(host) \ + ((host->addr_value[0] >> 16) | (host->addr_value[1] << 16)) + +struct nand_logs_t { + unsigned short hour; + unsigned short min; + unsigned short sec; + unsigned short msec; + + unsigned short addr_cycle; + + unsigned long page; + unsigned long offset; + unsigned long data_len; + + char error; + char ops; + +#define MAX_LOG_DATA_LEN 40 + unsigned char data[MAX_LOG_DATA_LEN]; +}; + +struct nand_logs_root_t { + struct dentry *entry; + + int page_oob_size;/* page_oob_size = page size + oob size */ + int data_offset;/* user config */ + int data_len;/* user config */ + + int log_index; +#define MAX_LOG_NUMS 100 + struct nand_logs_t logs[MAX_LOG_NUMS]; +}; + +static struct nand_logs_root_t *nand_logs_root; + +static void do_gettime(unsigned short *hour, unsigned short *min, + unsigned short *sec, unsigned short *msec) +{ + long val; + struct timeval tv; + + do_gettimeofday(&tv); + val = tv.tv_sec % 86400; /* 86400sec = 1 day */ + + if (hour) + *hour = val / 3600; + val %= 3600; + if (min) + *min = val / 60; + if (sec) + *sec = val % 60; + if (msec) + *msec = tv.tv_usec / 1000; +} +/* + * statistics for nand flash's operations (read/write/erase) + */ +void nand_log_ops(struct hinfc_host *host, char ops) +{ + struct nand_logs_t *logs; + int page_oob_size = nand_logs_root->page_oob_size; + + logs = &nand_logs_root->logs[nand_logs_root->log_index]; + do_gettime(&logs->hour, &logs->min, &logs->sec, &logs->msec); + + logs->ops = ops; + logs->addr_cycle = host->addr_cycle; + + logs->offset = host->addr_value[0] & 0xFFFF; + if (logs->offset > page_oob_size || !logs->offset) + logs->offset = nand_logs_root->data_offset; + + logs->data_len = nand_logs_root->data_len; + if (logs->offset + logs->data_len > page_oob_size) + logs->data_len = page_oob_size - logs->offset; + + switch (ops) { + case 'R':/* read */ + case 'W':/* write */ + logs->page = get_page_index(host); + memcpy(logs->data, host->buffer + logs->offset, logs->data_len); + break; + case 'E':/* erase */ + logs->page = host->addr_value[0]; + logs->offset = 0; + logs->data_len = 0; + break; + default: + break; + } + + nand_logs_root->log_index++; + if (nand_logs_root->log_index >= MAX_LOG_NUMS) + nand_logs_root->log_index = 0; +} + +static int nand_log_show(struct seq_file *m, void *v) +{ + int i, index; + struct nand_logs_t *logs; + + seq_printf(m, "nand log parameters: offset=%d len=%d\n", + nand_logs_root->data_offset, + nand_logs_root->data_len); + + seq_printf(m, " UTC Clock op cylce page-offset data\n"); + + /* list by time */ + for (i = 0; i < MAX_LOG_NUMS; i++) { + index = (nand_logs_root->log_index + i) % MAX_LOG_NUMS; + + logs = &nand_logs_root->logs[index]; + + seq_printf(m, "%02d:%02d:%02d.%04d %c %-2u 0x%08lX-%04lX", + logs->hour, logs->min, + logs->sec, logs->msec, logs->ops, + logs->addr_cycle, logs->page, logs->offset); + + if (logs->ops == 'E') + seq_printf(m, " ---"); + else { + int ix; + seq_printf(m, " %c ", logs->error); + + for (ix = 0; ix < logs->data_len; ix++) { + if ((ix % 16) == 15) + seq_printf(m, "%02X-", logs->data[ix]); + else + seq_printf(m, "%02X ", logs->data[ix]); + } + } + seq_printf(m, "\n"); + } + + return 0; +} +/* + * echo "offset=2048 length=8" > /sys/kernel/debug/hs_nfc/nand_logs + */ +static ssize_t nand_log_config(struct file *file, const char __user *userbuf, + size_t len, loff_t *ppos) +{ +#define LEN 50 + char tmpbuf[LEN], *s, *r, *tok; + int ret; + unsigned long value = 0; + + if (len == 0) + return 0; + if (len >= LEN) + len = LEN - 1; + + if (copy_from_user(tmpbuf, userbuf, len)) + return -EFAULT; + + tmpbuf[len] = '\0'; + + tok = "offset="; + s = strstr(&tmpbuf[0], tok); + if (s) { + r = strsep(&s, " "); + r += strlen(tok); + ret = kstrtoul(r, 0, &value); + if (ret < 0) + return -EINVAL; + + if (value > nand_logs_root->page_oob_size) + value = nand_logs_root->page_oob_size; + + nand_logs_root->data_offset = value; + + tok = "length="; + s = strstr(s, tok); + if (s) { + s += strlen(tok); + + ret = kstrtoul(s, 0, &value); + if (ret < 0) + return -EINVAL; + + if (value > MAX_LOG_DATA_LEN) + value = MAX_LOG_DATA_LEN; + + nand_logs_root->data_len = value; + } + } + + *ppos += len; + return len; +} + +static int nand_log_open(struct inode *inode, struct file *file) +{ + return single_open(file, nand_log_show, inode->i_private); +} + +static const struct file_operations fops_nand_logs = { + .owner = THIS_MODULE, + .open = nand_log_open, + .read = seq_read, + .llseek = seq_lseek, + .write = nand_log_config, + .release = single_release, +}; + +int nand_log_init(struct hinfc_host *host) +{ + struct dentry *dir, *file = NULL; + + dir = debugfs_create_dir("hs_nfc", NULL); + if (dir) + file = debugfs_create_file("nand_logs", 0644, dir, host, + &fops_nand_logs); + + if (!file) + goto err_state; + + nand_logs_root = kzalloc(sizeof(*nand_logs_root), GFP_KERNEL); + if (!nand_logs_root) { + pr_err("mem alloc failed!\n"); + goto err_state; + } + + nand_logs_root->entry = dir; + nand_logs_root->page_oob_size = host->pagesize + host->oobsize; + nand_logs_root->data_len = 8;/* default data len 8 */ + + pr_info("nand log enable\n"); + return 0; + +err_state: + debugfs_remove_recursive(dir); + nand_logs_root = NULL; + return -ENODEV; +} + +void nand_log_exit(void) +{ + if (!nand_logs_root) + return; + + debugfs_remove_recursive(nand_logs_root->entry); + kfree(nand_logs_root); +} diff --git a/drivers/mtd/nand/hs_nfc/hinfc504.h b/drivers/mtd/nand/hs_nfc/hinfc504.h new file mode 100644 index 00000000000..43037cc25c7 --- /dev/null +++ b/drivers/mtd/nand/hs_nfc/hinfc504.h @@ -0,0 +1,299 @@ +/* + * Hisilicon NAND Flash controller driver + * + * Copyright © 2012-2013 HiSilicon Technologies Co., Ltd. + * http://www.hisilicon.com + * Copyright © 2012-2013 Linaro Ltd. + * http://www.linaro.org + * + * Author: Mingjun Zhang <zhang.mingjun@linaro.org> + * The initial developer of the original code is Zhiyong Cai + * <caizhiyong@huawei.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef HINFCV504H +#define HINFCV504H +/******************************************************************************/ +#include <linux/interrupt.h> +#include "hinfc_common.h" + +/* see comments in this driver's probe func */ +#define CONFIG_HINFC504_MAX_CHIP (2) + +/* TODO: nfc default timing value, move it to dts file if needed */ +#define CONFIG_HINFC504_W_LATCH (5) +#define CONFIG_HINFC504_R_LATCH (7) +#define CONFIG_HINFC504_RW_LATCH (3) + +#define HINFC504_NFC_TIMEOUT (2 * HZ) +#define HINFC504_NFC_DMA_TIMEOUT (5 * HZ) +#define HINFC504_CHIP_DELAY (25) + +/*****************************************************************************/ +#define HINFC504_REG_BASE_ADDRESS_LEN (0x100) +#define HINFC504_BUFFER_BASE_ADDRESS_LEN (2048 + 128) + +#define HINFC504_ADDR_CYCLE_MASK 0x4 +#define HINFC504_DMA_ADDR_OFFSET 4096 +/*****************************************************************************/ +#define HINFC504_CON 0x00 +#define HINFC600_CON 0x00 +#define HINFC504_CON_OP_MODE_NORMAL (1U << 0) +#define HINFC504_CON_PAGEISZE_SHIFT (1) +#define HINFC504_CON_PAGESIZE_MASK (0x07) +#define HINFC504_CON_BUS_WIDTH (1U << 4) +#define HINFC504_CON_READY_BUSY_SEL (1U << 8) +#define HINFC504_CON_ECCTYPE_SHIFT (9) +#define HINFC504_CON_ECCTYPE_MASK (0x07) +#define HINFC600_CON_RANDOMIZER_EN (1<<13) +/*****************************************************************************/ +#define HINFC504_PWIDTH 0x04 +#define SET_HINFC504_PWIDTH(_w_lcnt, _r_lcnt, _rw_hcnt) \ + ((_w_lcnt) | (((_r_lcnt) & 0x0F) << 4) | (((_rw_hcnt) & 0x0F) << 8)) + +#define HINFC504_CMD 0x0C +#define HINFC504_ADDRL 0x10 +#define HINFC504_ADDRH 0x14 +#define HINFC504_DATA_NUM 0x18 + +#define HINFC504_OP 0x1C +#define HINFC504_OP_READ_STATUS_EN (1U << 0) +#define HINFC504_OP_READ_DATA_EN (1U << 1) +#define HINFC504_OP_WAIT_READY_EN (1U << 2) +#define HINFC504_OP_CMD2_EN (1U << 3) +#define HINFC504_OP_WRITE_DATA_EN (1U << 4) +#define HINFC504_OP_ADDR_EN (1U << 5) +#define HINFC504_OP_CMD1_EN (1U << 6) +#define HINFC504_OP_NF_CS_SHIFT (7) +#define HINFC504_OP_NF_CS_MASK (3) +#define HINFC504_OP_ADDR_CYCLE_SHIFT (9) +#define HINFC504_OP_ADDR_CYCLE_MASK (7) + +#define HINFC504_STATUS 0x20 +#define HINFC504_READY (1U << 0) + +#define HINFC504_INTEN 0x24 +#define HINFC504_INTEN_DMA (1U << 9) + +#define HINFC504_INTS 0x28 +#define HINFC504_INTS_UE (1U << 6) +#define HINFC504_INTS_DMA (1U << 9) + +#define HINFC504_INTCLR 0x2C +#define HINFC504_INTCLR_CE (1U << 5) +#define HINFC504_INTCLR_UE (1U << 6) +#define HINFC504_INTCLR_DMA (1U << 9) + +#define HINFC504_DMA_CTRL 0x60 +#define HINFC504_DMA_CTRL_DMA_START (1U << 0) +#define HINFC504_DMA_CTRL_WE (1U << 1) +#define HINFC504_DMA_CTRL_DATA_AREA_EN (1U << 2) +#define HINFC504_DMA_CTRL_OOB_AREA_EN (1U << 3) +#define HINFC504_DMA_CTRL_BURST4_EN (1U << 4) +#define HINFC504_DMA_CTRL_BURST8_EN (1U << 5) +#define HINFC504_DMA_CTRL_BURST16_EN (1U << 6) +#define HINFC504_DMA_CTRL_ADDR_NUM_SHIFT (7) +#define HINFC504_DMA_CTRL_ADDR_NUM_MASK (1) +#define HINFC504_DMA_CTRL_CS_SHIFT (8) +#define HINFC504_DMA_CTRL_CS_MASK (0x03) + +#define HINFC504_DMA_ADDR_DATA 0x64 +#define HINFC504_DMA_ADDR_OOB 0x68 +#define HINFC504_DMA_ADDR_DATA1 0xB4 +#define HINFC504_DMA_ADDR_DATA2 0xB8 +#define HINFC504_DMA_ADDR_DATA3 0xBC + +#define HINFC504_DMA_LEN 0x6C +#define HINFC504_DMA_LEN_OOB_SHIFT (16) +#define HINFC504_DMA_LEN_OOB_MASK (0xFFF) + +#define HINFC504_DMA_PARA 0x70 +#define HINFC504_DMA_PARA_DATA_RW_EN (1U << 0) +#define HINFC504_DMA_PARA_OOB_RW_EN (1U << 1) +#define HINFC504_DMA_PARA_DATA_EDC_EN (1U << 2) +#define HINFC504_DMA_PARA_OOB_EDC_EN (1U << 3) +#define HINFC504_DMA_PARA_DATA_ECC_EN (1U << 4) +#define HINFC504_DMA_PARA_OOB_ECC_EN (1U << 5) +#define HINFC504_DMA_PARA_EXT_LEN_SHIFT (6) +#define HINFC504_DMA_PARA_EXT_LEN_MASK (0x03) + +#define HINFC504_VERSION 0x74 +#define HINFC504_LOG_READ_ADDR 0x7C +#define HINFC504_LOG_READ_LEN 0x80 + +#define HINFC504_ECC_REG0 0xA0 +#define HINFC504_ECC_REG1 0xA4 +#define HINFC504_ECC_REG2 0xA8 +#define HINFC504_ECC_REG3 0xAC + +#define HINFC504_RANDOMIZER 0xC0 +#define HINFC504_RANDOMIZER_PAD 0x02 +#define HINFC504_RANDOMIZER_ENABLE 0x01 +/* read nand id or nand status, return from nand data length */ +#define HINFC504_NANDINFO_LEN 0x10 + +#define HINFC600_BOOT_CFG 0xC4 +#define HINFC600_BOOT_CFG_RANDOMIZER_PAD 0x01 + +/* DMA address align with 32 bytes. */ +#define HINFC504_DMA_ALIGN 64 +/*****************************************************************************/ +#define ENABLE 1 +#define DISABLE 0 +/*****************************************************************************/ + +struct hinfc_host { + struct nand_chip *chip; + struct mtd_info *mtd; + void __iomem *iobase; + struct completion cmd_complete; + + int is_randomizer; + unsigned int offset; + unsigned int command; + + int chipselect; + + int ecctype; + unsigned int n24bit_ext_len; + + unsigned int NFC_CON; + unsigned int NFC_CON_ECC_NONE; + + unsigned int addr_cycle; + unsigned int addr_value[2]; + unsigned int cache_addr_value[2]; + unsigned int block_page_mask; + + char *buffer; + dma_addr_t dma_buffer; + dma_addr_t dma_oob; + + unsigned int pagesize; + unsigned int oobsize; + + int version; /* controller version */ + + int need_rr_data; +#define HINFC_READ_RETRY_DATA_LEN 128 + char rr_data[HINFC_READ_RETRY_DATA_LEN]; + +#define HINFC_BAD_BLOCK_POS 0 + unsigned char *bbm; /* nand bad block mark */ + unsigned short *epm; /* nand empty page mark */ + + unsigned int uc_er; + + int (*send_cmd_pageprog)(struct hinfc_host *host); + int (*send_cmd_status)(struct hinfc_host *host); + int (*send_cmd_readstart)(struct hinfc_host *host); + int (*send_cmd_erase)(struct hinfc_host *host); + int (*send_cmd_readid)(struct hinfc_host *host); + int (*send_cmd_reset)(struct hinfc_host *host, int chipselect); + + int (*enable_ecc_randomizer)(struct hinfc_host *host, int ecc_en, + int randomizer_en); + + void (*diagnose_ecc)(struct hinfc_host *host); + + struct read_retry_t *read_retry; +}; +/*****************************************************************************/ +#define HINFC504_READ_1CMD_0ADD_NODATA \ + (HINFC504_OP_CMD1_EN \ + | ((host->chipselect & HINFC504_OP_NF_CS_MASK) \ + << HINFC504_OP_NF_CS_SHIFT)) + +#define HINFC504_READ_1CMD_1ADD_DATA \ + (HINFC504_OP_CMD1_EN \ + | HINFC504_OP_ADDR_EN \ + | HINFC504_OP_READ_DATA_EN \ + | HINFC504_OP_WAIT_READY_EN \ + | ((host->chipselect & HINFC504_OP_NF_CS_MASK) \ + << HINFC504_OP_NF_CS_SHIFT) \ + | (1 << HINFC504_OP_ADDR_CYCLE_SHIFT)) + +#define HINFC504_READ_2CMD_5ADD \ + (HINFC504_OP_CMD1_EN \ + | HINFC504_OP_CMD2_EN \ + | HINFC504_OP_ADDR_EN \ + | HINFC504_OP_READ_DATA_EN \ + | HINFC504_OP_WAIT_READY_EN \ + | ((host->chipselect & HINFC504_OP_NF_CS_MASK) \ + << HINFC504_OP_NF_CS_SHIFT) \ + | (5 << HINFC504_OP_ADDR_CYCLE_SHIFT)) + +#define HINFC504_WRITE_0CMD_1ADD_DATA \ + (HINFC504_OP_ADDR_EN \ + | HINFC504_OP_WRITE_DATA_EN \ + | ((host->chipselect & HINFC504_OP_NF_CS_MASK) \ + << HINFC504_OP_NF_CS_SHIFT) \ + | (1 << HINFC504_OP_ADDR_CYCLE_SHIFT)) + +#define HINFC504_WRITE_1CMD_1ADD_DATA \ + (HINFC504_OP_CMD1_EN \ + | HINFC504_OP_ADDR_EN \ + | HINFC504_OP_WRITE_DATA_EN \ + | ((host->chipselect & HINFC504_OP_NF_CS_MASK) \ + << HINFC504_OP_NF_CS_SHIFT) \ + | (1 << HINFC504_OP_ADDR_CYCLE_SHIFT)) + +#define HINFC504_WRITE_1CMD_2ADD_DATA \ + (HINFC504_OP_CMD1_EN \ + | HINFC504_OP_ADDR_EN \ + | HINFC504_OP_WRITE_DATA_EN \ + | ((host->chipselect & HINFC504_OP_NF_CS_MASK) \ + << HINFC504_OP_NF_CS_SHIFT) \ + | (2 << HINFC504_OP_ADDR_CYCLE_SHIFT)) + +#define HINFC504_WRITE_2CMD_0ADD_NODATA \ + (HINFC504_OP_CMD1_EN \ + | HINFC504_OP_CMD2_EN \ + | ((host->chipselect & HINFC504_OP_NF_CS_MASK) \ + << HINFC504_OP_NF_CS_SHIFT)) + +/*****************************************************************************/ +#define hinfc_read(_host, _reg) \ + readl(_host->iobase + (_reg)) + +#define hinfc_write(_host, _value, _reg) \ + writel((_value), _host->iobase + (_reg)) + +#define HINFC_CMD_SEQ(_cmd0, _cmd1) \ + (((_cmd0) & 0xFF) | ((_cmd1) & 0xFF) << 8) +/*****************************************************************************/ +void wait_controller_finished(struct hinfc_host *host); +void hinfc504_cmdfunc(struct mtd_info *mtd, unsigned command, int column, + int page_addr); +int hinfc504_dev_ready(struct mtd_info *mtd); +void hinfc504_select_chip(struct mtd_info *mtd, int chipselect); +uint8_t hinfc504_read_byte(struct mtd_info *mtd); +u16 hinfc504_read_word(struct mtd_info *mtd); +void hinfc504_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len); +void hinfc504_read_buf(struct mtd_info *mtd, uint8_t *buf, int len); +void hinfc504_host_init(struct hinfc_host *host); +int hinfc504_ecc_probe(struct mtd_info *mtd, struct nand_chip *chip, + struct nand_flash_dev_ex *flash_dev_ex); +irqreturn_t hinfc_irq_handle(int irq, void *devid); + +#ifdef CONFIG_MTD_NAND_HINFC504_DEBUG +int nand_log_init(struct hinfc_host *host); +void nand_log_exit(void); +void nand_log_ops(struct hinfc_host *host, char ops); +#else +static inline int nand_log_init(struct hinfc_host *host) { return 0; } +static inline void nand_log_ops(struct hinfc_host *host, char ops) {}; +static inline void nand_log_exit(void) {}; +#endif +/******************************************************************************/ +#endif /* HINFCV504H */ diff --git a/drivers/mtd/nand/hs_nfc/hinfc504_hw.c b/drivers/mtd/nand/hs_nfc/hinfc504_hw.c new file mode 100644 index 00000000000..d1238f15050 --- /dev/null +++ b/drivers/mtd/nand/hs_nfc/hinfc504_hw.c @@ -0,0 +1,845 @@ +/* + * Hisilicon NAND Flash controller driver + * + * Copyright © 2012-2013 HiSilicon Technologies Co., Ltd. + * http://www.hisilicon.com + * Copyright © 2012-2013 Linaro Ltd. + * http://www.linaro.org + * + * Author: Mingjun Zhang <zhang.mingjun@linaro.org> + * The initial developer of the original code is Zhiyong Cai + * <caizhiyong@huawei.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/mtd/nand.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> + +#include "hinfc504.h" + +/*****************************************************************************/ +void wait_controller_finished(struct hinfc_host *host) +{ + unsigned long timeout = jiffies + HINFC504_NFC_TIMEOUT; + int val; + + while (time_before(jiffies, timeout)) { + val = hinfc_read(host, HINFC504_STATUS); + if (val & HINFC504_READY) + return;/* nfc is ready */ + } + + /* wait cmd timeout */ + pr_err("Wait NAND controller exec cmd timeout.\n"); +} + +static void hinfc504_dma_transfer(struct hinfc_host *host, int todev) +{ + unsigned int dma_addr = (unsigned int)host->dma_buffer; + unsigned long val; + int ret; + + hinfc_write(host, dma_addr, HINFC504_DMA_ADDR_DATA); + + dma_addr += HINFC504_DMA_ADDR_OFFSET; + hinfc_write(host, dma_addr, HINFC504_DMA_ADDR_DATA1); + + dma_addr += HINFC504_DMA_ADDR_OFFSET; + hinfc_write(host, dma_addr, HINFC504_DMA_ADDR_DATA2); + + dma_addr += HINFC504_DMA_ADDR_OFFSET; + hinfc_write(host, dma_addr, HINFC504_DMA_ADDR_DATA3); + + hinfc_write(host, host->dma_oob, HINFC504_DMA_ADDR_OOB); + + if (host->ecctype == et_ecc_none) { + hinfc_write(host, + ((host->oobsize & HINFC504_DMA_LEN_OOB_MASK) + << HINFC504_DMA_LEN_OOB_SHIFT), + HINFC504_DMA_LEN); + + hinfc_write(host, + HINFC504_DMA_PARA_DATA_RW_EN + | HINFC504_DMA_PARA_OOB_RW_EN + | ((host->n24bit_ext_len + & HINFC504_DMA_PARA_EXT_LEN_MASK) + << HINFC504_DMA_PARA_EXT_LEN_SHIFT), + HINFC504_DMA_PARA); + } else + hinfc_write(host, + HINFC504_DMA_PARA_DATA_RW_EN + | HINFC504_DMA_PARA_OOB_RW_EN + | HINFC504_DMA_PARA_DATA_EDC_EN + | HINFC504_DMA_PARA_OOB_EDC_EN + | HINFC504_DMA_PARA_DATA_ECC_EN + | HINFC504_DMA_PARA_OOB_ECC_EN + | ((host->n24bit_ext_len + & HINFC504_DMA_PARA_EXT_LEN_MASK) + << HINFC504_DMA_PARA_EXT_LEN_SHIFT), + HINFC504_DMA_PARA); + + val = (HINFC504_DMA_CTRL_DMA_START + | HINFC504_DMA_CTRL_BURST4_EN + | HINFC504_DMA_CTRL_BURST8_EN + | HINFC504_DMA_CTRL_BURST16_EN + | HINFC504_DMA_CTRL_DATA_AREA_EN + | HINFC504_DMA_CTRL_OOB_AREA_EN + | ((host->addr_cycle == 4 ? 1 : 0) + << HINFC504_DMA_CTRL_ADDR_NUM_SHIFT) + | ((host->chipselect & HINFC504_DMA_CTRL_CS_MASK) + << HINFC504_DMA_CTRL_CS_SHIFT)); + + if (todev) + val |= HINFC504_DMA_CTRL_WE; + + hinfc_write(host, val, HINFC504_DMA_CTRL); + + init_completion(&host->cmd_complete); + ret = wait_for_completion_timeout(&host->cmd_complete, + HINFC504_NFC_DMA_TIMEOUT); + if (!ret) { + pr_err("DMA operation(irq) timeout!\n"); + /* sanity check */ + val = hinfc_read(host, HINFC504_DMA_CTRL); + if (!(val & HINFC504_DMA_CTRL_DMA_START)) + pr_err("dma is already done but without irq ACK!"); + else + pr_err("dma is really timeout!"); + } +} +/*****************************************************************************/ +static int hinfc504_send_cmd_pageprog(struct hinfc_host *host) +{ + + WARN_ON(host->addr_cycle != 4 && host->addr_cycle != 5); + host->addr_value[0] &= 0xffff0000; + + host->enable_ecc_randomizer(host, ENABLE, ENABLE); + + hinfc_write(host, host->addr_value[0], HINFC504_ADDRL); + hinfc_write(host, host->addr_value[1], HINFC504_ADDRH); + hinfc_write(host, NAND_CMD_PAGEPROG << 8 | NAND_CMD_SEQIN, + HINFC504_CMD); + + if (host->is_randomizer) + *host->epm = 0xFFFF; + + hinfc504_dma_transfer(host, 1); + + return 0; +} +/*****************************************************************************/ +#define NAND_BAD_BLOCK 1 +#define NAND_EMPTY_PAGE 2 +#define NAND_VALID_DATA 3 + +static int hinfc504_get_data_status(struct hinfc_host *host) +{ + /* this is block start address */ + if (!((host->addr_value[0] >> 16) & host->block_page_mask)) { + + /* it is a bad block */ + if (*host->bbm == 0) + return NAND_BAD_BLOCK; + /* + * if there are more than 2 bits flipping, it is + * maybe a bad block + */ + if (host->uc_er && *host->bbm != 0xFFFF + && hweight8(*host->bbm) <= 6) + return NAND_BAD_BLOCK; + } + + /* it is an empty page */ + if (*host->epm != 0xFFFF && host->is_randomizer) + return NAND_EMPTY_PAGE; + + return NAND_VALID_DATA; +} +/*****************************************************************************/ + +static int hinfc504_do_read_retry(struct hinfc_host *host) +{ + int ix; + + for (ix = 1; host->uc_er && ix < host->read_retry->count; ix++) { + + hinfc_write(host, HINFC504_INTCLR_UE | HINFC504_INTCLR_CE, + HINFC504_INTCLR); + + host->enable_ecc_randomizer(host, DISABLE, DISABLE); + + /* check whether it is toshiba chip */ + if (host->read_retry->type == NAND_RR_TOSHIBA_24nm) { + + hinfc_write(host, HINFC_CMD_SEQ(0x5C, 0xC5), + HINFC504_CMD); + hinfc_write(host, + HINFC504_WRITE_2CMD_0ADD_NODATA, + HINFC504_OP); + wait_controller_finished(host); + } + + host->read_retry->set_rr_param(host, ix); + + /* check whether it is toshiba chip */ + if (host->read_retry->type == NAND_RR_TOSHIBA_24nm) { + hinfc_write(host, HINFC_CMD_SEQ(0x26, 0x5D), + HINFC504_CMD); + hinfc_write(host, + HINFC504_WRITE_2CMD_0ADD_NODATA, + HINFC504_OP); + wait_controller_finished(host); + } + + host->enable_ecc_randomizer(host, DISABLE, ENABLE); + + hinfc_write(host, HINFC504_INTCLR_UE | HINFC504_INTCLR_CE, + HINFC504_INTCLR); + hinfc_write(host, host->NFC_CON, HINFC504_CON); + hinfc_write(host, host->addr_value[0], HINFC504_ADDRL); + hinfc_write(host, host->addr_value[1], HINFC504_ADDRH); + hinfc_write(host, + HINFC_CMD_SEQ(NAND_CMD_READ0, NAND_CMD_READSTART), + HINFC504_CMD); + + hinfc_write(host, 0, HINFC504_LOG_READ_ADDR); + hinfc_write(host, (host->pagesize + host->oobsize), + HINFC504_LOG_READ_LEN); + + hinfc504_dma_transfer(host, 0); + host->uc_er = (hinfc_read(host, HINFC504_INTS) + & HINFC504_INTS_UE) ? 1 : 0; + + if (host->uc_er && host->diagnose_ecc) + host->diagnose_ecc(host); + } + + host->enable_ecc_randomizer(host, DISABLE, DISABLE); + + host->read_retry->set_rr_param(host, 0); + + return 0; +} +/*****************************************************************************/ + +static int hinfc504_send_cmd_readstart(struct hinfc_host *host) +{ + if ((host->addr_value[0] == host->cache_addr_value[0]) + && (host->addr_value[1] == host->cache_addr_value[1])) + return 0; + + WARN_ON(host->addr_cycle != 4 && host->addr_cycle != 5); + host->addr_value[0] &= 0xffff0000; + + host->enable_ecc_randomizer(host, ENABLE, ENABLE); + + hinfc_write(host, HINFC504_INTCLR_UE | HINFC504_INTCLR_CE, + HINFC504_INTCLR); + hinfc_write(host, host->NFC_CON, HINFC504_CON); + hinfc_write(host, host->addr_value[0], HINFC504_ADDRL); + hinfc_write(host, host->addr_value[1], HINFC504_ADDRH); + hinfc_write(host, NAND_CMD_READSTART << 8 | NAND_CMD_READ0, + HINFC504_CMD); + + hinfc_write(host, 0, HINFC504_LOG_READ_ADDR); + hinfc_write(host, host->pagesize + host->oobsize, + HINFC504_LOG_READ_LEN); + + hinfc504_dma_transfer(host, 0); + host->uc_er = !!(hinfc_read(host, HINFC504_INTS) & HINFC504_INTS_UE); + + if (host->read_retry || host->is_randomizer) { + int status = hinfc504_get_data_status(host); + + if (status == NAND_EMPTY_PAGE) { + /* + * oob area used by yaffs2 only 32 bytes, + * so we only fill 32 bytes. + */ + if (host->is_randomizer) + memset(host->buffer, 0xFF, + host->pagesize + host->oobsize); + host->uc_er = 2; + + } else if (status == NAND_VALID_DATA) { + + /* if NAND chip support read retry */ + if (host->uc_er && host->read_retry) + hinfc504_do_read_retry(host); + + } /* 'else' NAND have a bad block, do nothing. */ + } + + host->cache_addr_value[0] = host->addr_value[0]; + host->cache_addr_value[1] = host->addr_value[1]; + + return 0; +} +/*****************************************************************************/ + +static int hinfc504_send_cmd_erase(struct hinfc_host *host) +{ + host->enable_ecc_randomizer(host, DISABLE, DISABLE); + + hinfc_write(host, host->addr_value[0], HINFC504_ADDRL); + hinfc_write(host, (NAND_CMD_ERASE2 << 8) | NAND_CMD_ERASE1, + HINFC504_CMD); + + hinfc_write(host, HINFC504_OP_WAIT_READY_EN + | HINFC504_OP_CMD2_EN + | HINFC504_OP_CMD1_EN + | HINFC504_OP_ADDR_EN + | ((host->chipselect & HINFC504_OP_NF_CS_MASK) + << HINFC504_OP_NF_CS_SHIFT) + | ((host->addr_cycle & HINFC504_OP_ADDR_CYCLE_MASK) + << HINFC504_OP_ADDR_CYCLE_SHIFT), + HINFC504_OP); + + wait_controller_finished(host); + + return 0; +} +/*****************************************************************************/ + +static int hinfc504_send_cmd_readid(struct hinfc_host *host) +{ + host->enable_ecc_randomizer(host, DISABLE, DISABLE); + + hinfc_write(host, HINFC504_NANDINFO_LEN, HINFC504_DATA_NUM); + hinfc_write(host, NAND_CMD_READID, HINFC504_CMD); + hinfc_write(host, 0, HINFC504_ADDRL); + + /* does not affect R/B line */ + hinfc_write(host, HINFC504_OP_CMD1_EN + | HINFC504_OP_ADDR_EN + | HINFC504_OP_READ_DATA_EN + | ((host->chipselect & HINFC504_OP_NF_CS_MASK) + << HINFC504_OP_NF_CS_SHIFT) + | 1 << HINFC504_OP_ADDR_CYCLE_SHIFT, + HINFC504_OP); + + wait_controller_finished(host); + + return 0; +} +/*****************************************************************************/ + +static int hinfc504_enable_ecc_randomizer(struct hinfc_host *host, + int ecc_en, int randomizer_en) +{ + unsigned int regval; + + if (host->is_randomizer) { + + regval = hinfc_read(host, HINFC504_RANDOMIZER); + if (randomizer_en) + regval |= HINFC504_RANDOMIZER_ENABLE; + else + regval &= ~HINFC504_RANDOMIZER_ENABLE; + hinfc_write(host, regval, HINFC504_RANDOMIZER); + } + + if (ecc_en) + hinfc_write(host, host->NFC_CON, HINFC504_CON); + else + hinfc_write(host, host->NFC_CON_ECC_NONE, HINFC504_CON); + + return 0; +} +/*****************************************************************************/ + +static int hinfc600_enable_ecc_randomizer(struct hinfc_host *host, + int ecc_en, int randomizer_en) +{ + unsigned int nfc_con = ecc_en ? + host->NFC_CON : host->NFC_CON_ECC_NONE; + + if (host->is_randomizer) { + if (randomizer_en) + nfc_con |= HINFC600_CON_RANDOMIZER_EN; + else + nfc_con &= ~HINFC600_CON_RANDOMIZER_EN; + } + + hinfc_write(host, nfc_con, HINFC600_CON); + + return 0; +} +/*****************************************************************************/ + +static int hinfc504_send_cmd_status(struct hinfc_host *host) +{ + host->enable_ecc_randomizer(host, DISABLE, DISABLE); + + hinfc_write(host, HINFC504_NANDINFO_LEN, HINFC504_DATA_NUM); + hinfc_write(host, NAND_CMD_STATUS, HINFC504_CMD); + /* no need to set HINFC504_OP_WAIT_READY_EN, + * cause READ STATUS CMD do not affect or depend on the R/B line + */ + hinfc_write(host, HINFC504_OP_CMD1_EN + | HINFC504_OP_READ_DATA_EN + | ((host->chipselect & HINFC504_OP_NF_CS_MASK) + << HINFC504_OP_NF_CS_SHIFT), + HINFC504_OP); + + wait_controller_finished(host); + + return 0; +} +/*****************************************************************************/ + +static int hinfc504_send_cmd_reset(struct hinfc_host *host, int chipselect) +{ + hinfc_write(host, NAND_CMD_RESET, HINFC504_CMD); + + hinfc_write(host, HINFC504_OP_CMD1_EN + | ((chipselect & HINFC504_OP_NF_CS_MASK) + << HINFC504_OP_NF_CS_SHIFT) + | HINFC504_OP_WAIT_READY_EN, + HINFC504_OP); + + /* tRST <= 500us */ + wait_controller_finished(host); + + return 0; +} +/*****************************************************************************/ +/* + * nfc will monitor the R/B line + */ +int hinfc504_dev_ready(struct mtd_info *mtd) +{ + return 0x1; +} +/*****************************************************************************/ + +void hinfc504_select_chip(struct mtd_info *mtd, int chipselect) +{ + struct nand_chip *chip = mtd->priv; + struct hinfc_host *host = chip->priv; + + if (chipselect < 0) + return; + + BUG_ON(chipselect > CONFIG_HINFC504_MAX_CHIP); + + host->chipselect = chipselect; +} +/*****************************************************************************/ + +uint8_t hinfc504_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct hinfc_host *host = chip->priv; + + if (host->command == NAND_CMD_STATUS) + return readb(chip->IO_ADDR_R); + + host->offset++; + + if (host->command == NAND_CMD_READID) + return readb(chip->IO_ADDR_R + host->offset - 1); + + return readb(host->buffer + host->offset - 1); +} +/*****************************************************************************/ + +u16 hinfc504_read_word(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct hinfc_host *host = chip->priv; + + host->offset += 2; + return readw(host->buffer + host->offset - 2); +} +/*****************************************************************************/ + +void hinfc504_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct hinfc_host *host = chip->priv; + + memcpy(host->buffer + host->offset, buf, len); + host->offset += len; +} +/*****************************************************************************/ + +void hinfc504_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct hinfc_host *host = chip->priv; + + memcpy(buf, host->buffer + host->offset, len); + host->offset += len; +} + +static void set_addr(struct mtd_info *mtd, int column, int page_addr) +{ + struct nand_chip *chip = mtd->priv; + struct hinfc_host *host = chip->priv; + + host->addr_cycle = 0; + host->addr_value[0] = 0; + host->addr_value[1] = 0; + + /* Serially input address */ + if (column != -1) { + /* Adjust columns for 16 bit buswidth */ + if (chip->options & NAND_BUSWIDTH_16) + column >>= 1; + + host->addr_value[0] = column & 0xffff; + host->addr_cycle = 2;/* FIXME */ + } + if (page_addr != -1) { + host->addr_value[0] |= (page_addr & 0xffff) + << (host->addr_cycle * 8); + host->addr_cycle += 2; + /* One more address cycle for devices > 128MiB */ + if (chip->chipsize > (128 << 20)) { + host->addr_cycle += 1; + host->addr_value[1] |= (page_addr >> 16) & 0xff; + } + } +} + +void hinfc504_cmdfunc(struct mtd_info *mtd, unsigned command, int column, + int page_addr) +{ + struct nand_chip *chip = mtd->priv; + struct hinfc_host *host = chip->priv; + int is_cache_invalid = 1; + + host->command = command; + + switch (command) { + case NAND_CMD_READ0: + case NAND_CMD_READOOB: + if (command == NAND_CMD_READ0) + host->offset = column; + else + host->offset = column + mtd->writesize; + + is_cache_invalid = 0; + set_addr(mtd, column, page_addr); + host->send_cmd_readstart(host); + host->uc_er = 0; + nand_log_ops(host, 'R'); + break; + + case NAND_CMD_SEQIN: + host->offset = column; + + case NAND_CMD_ERASE1: + set_addr(mtd, column, page_addr); + break; + + case NAND_CMD_PAGEPROG: + host->send_cmd_pageprog(host); + nand_log_ops(host, 'W'); + break; + + case NAND_CMD_ERASE2: + host->send_cmd_erase(host); + nand_log_ops(host, 'E'); + break; + + case NAND_CMD_READID: + host->offset = column; + memset(chip->IO_ADDR_R, 0, 0x10); + host->send_cmd_readid(host); + break; + + case NAND_CMD_STATUS: + host->offset = 0; + memset(chip->IO_ADDR_R, 0, 0x10); + host->send_cmd_status(host); + break; + + case NAND_CMD_RESET: + host->send_cmd_reset(host, host->chipselect); + break; + + default: + pr_err("Error: unsupported cmd(cmd=%x, col=%x, page=%x)\n", + command, column, page_addr); + } + + if (is_cache_invalid) { + host->cache_addr_value[0] = ~0; + host->cache_addr_value[1] = ~0; + } +} + +/*****************************************************************************/ +static struct nand_ecclayout nand_ecc_default = { + .oobfree = { {2, 30} } +}; + +static struct nand_ecclayout nand_ecc_2K_1bit = { + .oobfree = { {22, 30} } +}; +/*****************************************************************************/ + +static struct page_page_ecc_info page_page_ecc_info[] = { + {pt_pagesize_16K, et_ecc_40bit1k, 1200/*1152*/, &nand_ecc_default}, + {pt_pagesize_8K, et_ecc_40bit1k, 600/*592*/, &nand_ecc_default}, + {pt_pagesize_8K, et_ecc_24bit1k, 368, &nand_ecc_default}, + {pt_pagesize_8K, et_ecc_none, 32, &nand_ecc_default}, + + {pt_pagesize_4K, et_ecc_24bit1k, 200, &nand_ecc_default}, + {pt_pagesize_4K, et_ecc_4bit, 128/*104*/, &nand_ecc_default}, + {pt_pagesize_4K, et_ecc_1bit, 128, &nand_ecc_default}, + {pt_pagesize_4K, et_ecc_none, 32, &nand_ecc_default}, + + {pt_pagesize_2K, et_ecc_24bit1k, 128/*116*/, &nand_ecc_default}, + {pt_pagesize_2K, et_ecc_4bit, 64, &nand_ecc_default}, + {pt_pagesize_2K, et_ecc_1bit, 64, &nand_ecc_2K_1bit}, + {pt_pagesize_2K, et_ecc_none, 32, &nand_ecc_default}, + + {}, +}; +/*****************************************************************************/ +struct page_page_ecc_info *hinfc504_select_ecc(struct mtd_info *mtd, + enum page_type pagetype, enum ecc_type ecctype, + char *cfgmsg, int allow_pagediv) +{ + struct page_page_ecc_info *info = page_page_ecc_info, *fit = NULL; + int pagesize; + + for (; info->layout; info++) { + if (info->pagetype == pagetype && info->ecctype == ecctype) { + fit = info; + break; + } + } + + if (!fit) { + pr_err("Driver(%s mode) does not support this Nand Flash\n" + "pagesize:%s, ecctype:%s\n", + cfgmsg, + get_pagesize_str(pagetype), + get_ecctype_str(ecctype)); + return NULL; + } + + /* TODO: add comments here */ + pagesize = get_pagesize(pagetype); + if ((pagesize != mtd->writesize) && + (pagesize > mtd->writesize || !allow_pagediv)) { + pr_err("Hardware (%s mode) configure pagesize is %d\n" + "But the Nand Flash pageszie is %d\n", + cfgmsg, pagesize, mtd->writesize); + return NULL; + } + + if (fit->oobsize > mtd->oobsize) { + pr_err("(%s mode)the oob size of the nand flash is %d bytes\n" + "but the controller request %d bytes for ecc %s\n", + cfgmsg, mtd->oobsize, fit->oobsize, + get_ecctype_str(ecctype)); + return NULL; + } + + return fit; +} +/*****************************************************************************/ + +int hinfc504_ecc_probe(struct mtd_info *mtd, struct nand_chip *chip, + struct nand_flash_dev_ex *flash_dev_ex) +{ + struct page_page_ecc_info *best = NULL; + struct hinfc_host *host = chip->priv; + + best = hinfc504_select_ecc(mtd, + ((host->NFC_CON >> HINFC504_CON_PAGEISZE_SHIFT) + & HINFC504_CON_PAGESIZE_MASK), + ((host->NFC_CON >> HINFC504_CON_ECCTYPE_SHIFT) + & HINFC504_CON_ECCTYPE_MASK), + "hardware config", 0); + + BUG_ON(!best); + + if (best->ecctype != et_ecc_none) + mtd->oobsize = best->oobsize; + + chip->ecc.layout = best->layout; + + host->ecctype = best->ecctype; + host->pagesize = get_pagesize(best->pagetype); + host->oobsize = mtd->oobsize; + host->block_page_mask = (mtd->erasesize / mtd->writesize) - 1; + + if (host->ecctype == et_ecc_24bit1k) { + if (host->pagesize == SZ_4K) + host->n24bit_ext_len = 0x03; /* 8bytes; */ + else if (host->pagesize == SZ_8K) + host->n24bit_ext_len = 0x01; /* 4bytes; */ + } + host->dma_oob = host->dma_buffer + host->pagesize; + host->bbm = (unsigned char *)(host->dma_oob + HINFC_BAD_BLOCK_POS); + + host->epm = (unsigned short *)(host->dma_oob + + chip->ecc.layout->oobfree[0].offset + 28); + + host->NFC_CON = HINFC504_CON_OP_MODE_NORMAL + | ((best->pagetype & HINFC504_CON_PAGESIZE_MASK) + << HINFC504_CON_PAGEISZE_SHIFT) + | HINFC504_CON_READY_BUSY_SEL + | ((best->ecctype & HINFC504_CON_ECCTYPE_MASK) + << HINFC504_CON_ECCTYPE_SHIFT); + + host->NFC_CON_ECC_NONE = HINFC504_CON_OP_MODE_NORMAL + | ((best->pagetype & HINFC504_CON_PAGESIZE_MASK) + << HINFC504_CON_PAGEISZE_SHIFT) + | HINFC504_CON_READY_BUSY_SEL; + + if (mtd->writesize > NAND_MAX_PAGESIZE + || mtd->oobsize > NAND_MAX_OOBSIZE) { + pr_err("Driver does not support this Nand Flash.\n" + "Please check NAND_MAX_PAGESIZE and NAND_MAX_OOBSIZE\n"); + } + + if (mtd->writesize != host->pagesize) { + unsigned int shift = 0; + unsigned int writesize = mtd->writesize; + while (writesize > host->pagesize) { + writesize >>= 1; + shift++; + } + chip->chipsize = chip->chipsize >> shift; + mtd->erasesize = mtd->erasesize >> shift; + mtd->writesize = host->pagesize; + pr_info("Nand divide into 1/%u\n", (1 << shift)); + } + + flash_dev_ex->ecctype = host->ecctype; + flash_dev_ex->is_randomizer = host->is_randomizer; + + if (flash_dev_ex->read_retry_type != NAND_RR_NONE) { + struct read_retry_t *rr = read_retry_list; + for ( ; rr->type != NAND_RR_NONE; rr++) { + if (rr->type == flash_dev_ex->read_retry_type) { + host->read_retry = rr; + break; + } + } + + if (!host->read_retry) + pr_err("Unsupported 'read retry' type: %d\n", + flash_dev_ex->read_retry_type); + } + + /* + * If it want to support the 'read retry' feature, + * the 'randomizer' feature must be enabed first. + */ + if (host->read_retry && !host->is_randomizer) { + pr_err("This Nand flash need to enable 'randomizer' feature.\n" + "Please configure hardware randomizer PIN."); + } + + return 0; +} +/*****************************************************************************/ + +static int hinfc504_get_randomizer(struct hinfc_host *host) +{ + int val = hinfc_read(host, HINFC504_RANDOMIZER); + + host->is_randomizer = !!(val & HINFC504_RANDOMIZER_PAD); + + val |= (host->is_randomizer ? HINFC504_RANDOMIZER_ENABLE : 0); + hinfc_write(host, val, HINFC504_RANDOMIZER); + + return host->is_randomizer; +} +/*****************************************************************************/ + +static int hinfc600_get_randomizer(struct hinfc_host *host) +{ + int val = hinfc_read(host, HINFC600_BOOT_CFG); + host->is_randomizer = !!(val & HINFC600_BOOT_CFG_RANDOMIZER_PAD); + + return host->is_randomizer; +} +/*****************************************************************************/ + +void hinfc504_diagnose_ecc(struct hinfc_host *host) +{ + /* add debug code here */ +} +/*****************************************************************************/ +irqreturn_t hinfc_irq_handle(int irq, void *devid) +{ + struct hinfc_host *host = devid; + unsigned int status; + + status = hinfc_read(host, HINFC504_INTS); + + if (status & HINFC504_INTS_DMA) { + /* clear DMA irq */ + hinfc_write(host, HINFC504_INTCLR_DMA, HINFC504_INTCLR); + complete(&host->cmd_complete); + } + + return IRQ_HANDLED; +} + +void hinfc504_host_init(struct hinfc_host *host) +{ + host->version = hinfc_read(host, HINFC504_VERSION); + + host->addr_cycle = 0; + host->addr_value[0] = 0; + host->addr_value[1] = 0; + host->cache_addr_value[0] = ~0; + host->cache_addr_value[1] = ~0; + host->chipselect = 0; + + host->send_cmd_pageprog = hinfc504_send_cmd_pageprog; + host->send_cmd_readstart = hinfc504_send_cmd_readstart; + host->send_cmd_erase = hinfc504_send_cmd_erase; + host->send_cmd_readid = hinfc504_send_cmd_readid; + host->send_cmd_status = hinfc504_send_cmd_status; + host->send_cmd_reset = hinfc504_send_cmd_reset; + host->diagnose_ecc = hinfc504_diagnose_ecc; + + host->NFC_CON = hinfc_read(host, HINFC504_CON) + | HINFC504_CON_OP_MODE_NORMAL | HINFC504_CON_READY_BUSY_SEL; + + host->NFC_CON_ECC_NONE = host->NFC_CON + & (~(HINFC504_CON_ECCTYPE_MASK << HINFC504_CON_ECCTYPE_SHIFT)) + & (~HINFC600_CON_RANDOMIZER_EN); + + memset(host->chip->IO_ADDR_R, 0xff, HINFC504_BUFFER_BASE_ADDRESS_LEN); + memset(host->buffer, 0xff, NAND_MAX_PAGESIZE + NAND_MAX_OOBSIZE); + + hinfc_write(host, + SET_HINFC504_PWIDTH(CONFIG_HINFC504_W_LATCH, + CONFIG_HINFC504_R_LATCH, CONFIG_HINFC504_RW_LATCH), + HINFC504_PWIDTH); + + if (HINFC_VER_600 == host->version) { + hinfc600_get_randomizer(host); + host->enable_ecc_randomizer = hinfc600_enable_ecc_randomizer; + } else { + hinfc504_get_randomizer(host); + host->enable_ecc_randomizer = hinfc504_enable_ecc_randomizer; + } + + /* enable dma irq */ + hinfc_write(host, HINFC504_INTEN_DMA, HINFC504_INTEN); +} diff --git a/drivers/mtd/nand/hs_nfc/hinfc504_os.c b/drivers/mtd/nand/hs_nfc/hinfc504_os.c new file mode 100644 index 00000000000..555e53d7dff --- /dev/null +++ b/drivers/mtd/nand/hs_nfc/hinfc504_os.c @@ -0,0 +1,339 @@ +/* + * Hisilicon NAND Flash controller driver + * + * Copyright © 2012-2013 HiSilicon Technologies Co., Ltd. + * http://www.hisilicon.com + * Copyright © 2012-2013 Linaro Ltd. + * http://www.linaro.org + * + * Author: Mingjun Zhang <zhang.mingjun@linaro.org> + * The initial developer of the original code is Zhiyong Cai + * <caizhiyong@huawei.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/of.h> +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/mtd/nand.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> + +#include "hinfc504.h" + +/*****************************************************************************/ +#define DRIVER_NAME "hinand" + +/*****************************************************************************/ +static int hinfc504_os_version_check(struct hinfc_host *host) +{ + int val = readl(host->iobase + HINFC504_VERSION); + int support; + + if (val == HINFC_VER_600 || val == HINFC_VER_505) + support = true; + else + support = false; + + pr_info("Nand Flash Controller Version: 0x%x. %sSupported\n", + val, support ? "" : "Not "); + + return support; +} +#ifdef CONFIG_OF +static int hinfc504_probe_dt(struct platform_device *pdev, + struct hinfc_host *host) +{ + struct device_node *np = pdev->dev.of_node; + + if (!np) + return -ENODEV; + + of_property_read_u8_array(np, "hisilicon,nand-read-retry", + (u8 *)&host->rr_data, HINFC_READ_RETRY_DATA_LEN); + + return 0; +} +#else +static int hinfc504_probe_dt(struct platform_device *pdev, + struct hinfc_host *host) +{ +} +#endif + +/*****************************************************************************/ +/* + * there are several chipselects in the nfc controller, that means + * each of the cs can be connected with the different chip, but + * the driver does not support this scenario. However the driver + * support each of cs hook with the same kind of nand chip. + */ +static int hinfc504_os_probe(struct platform_device *pdev) +{ + int i, ret, irq, max_chips = CONFIG_HINFC504_MAX_CHIP; + struct device *dev = &pdev->dev; + struct hinfc_host *host; + struct nand_chip *chip; + struct mtd_info *mtd; + struct resource *res; + struct nand_flash_dev_ex flash_dev_ex; + struct nand_flash_dev *def; + + memset(&flash_dev_ex, 0, sizeof(flash_dev_ex)); + + host = kzalloc(sizeof(*host) + sizeof(*chip) + sizeof(*mtd), + GFP_KERNEL); + if (!host) { + dev_err(dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, host); + ret = hinfc504_probe_dt(pdev, host); + if (ret) { + dev_err(&pdev->dev, "no platform data\n"); + return -ENODEV; + } + + chip = (struct nand_chip *)&host[1]; + mtd = (struct mtd_info *)&chip[1]; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no IRQ resource defined\n"); + ret = -ENXIO; + goto err_res; + } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + goto err_res; + } + dev_dbg(dev, "res.start=0x%x, res.end=0x%x\n", res->start, res->end); + + host->iobase = devm_request_and_ioremap(dev, res); + if (!host->iobase) { + ret = -EIO; + dev_err(dev, "devm_request_and_ioremap[0] fail\n"); + goto err_res; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + ret = -ENODEV; + goto err_res; + } + dev_dbg(dev, "res.start=0x%x, res.end=0x%x\n", res->start, res->end); + + chip->IO_ADDR_R = chip->IO_ADDR_W = devm_request_and_ioremap(dev, res); + if (!chip->IO_ADDR_R) { + ret = -EIO; + dev_err(dev, "devm_request_and_ioremap[1] fail\n"); + goto err_res; + } + + ret = hinfc504_os_version_check(host); + if (!ret) + goto err_res; + +#if 0 + host->clk = clk_get(dev, NULL); + if (IS_ERR(host->clk)) { + dev_err(dev, "failed to get nand clock\n"); + ret = PTR_ERR(info->clk); + goto err_res; + } + clk_prepare_enable(host->clk); +#endif + host->chip = chip; + host->mtd = mtd; + + mtd->priv = chip; + mtd->owner = THIS_MODULE; + /* use driver's unique name instead of device's dts name */ + mtd->name = DRIVER_NAME; + + chip->priv = host; + chip->cmdfunc = hinfc504_cmdfunc; + chip->dev_ready = hinfc504_dev_ready; + chip->select_chip = hinfc504_select_chip; + chip->read_byte = hinfc504_read_byte; + chip->read_word = hinfc504_read_word; + chip->write_buf = hinfc504_write_buf; + chip->read_buf = hinfc504_read_buf; + + chip->chip_delay = HINFC504_CHIP_DELAY; + chip->options = NAND_SKIP_BBTSCAN; + chip->ecc.layout = NULL; + chip->ecc.mode = NAND_ECC_NONE; + + host->buffer = dma_alloc_coherent(dev, + NAND_MAX_PAGESIZE + NAND_MAX_OOBSIZE, + &host->dma_buffer, GFP_KERNEL); + if (!host->buffer) { + dev_err(dev, "Can't malloc memory for NAND driver.\n"); + goto err_buf; + } + + hinfc504_host_init(host); + + ret = request_irq(irq, hinfc_irq_handle, IRQF_DISABLED, "nandc", host); + if (ret < 0) { + dev_err(dev, "failed to request IRQ\n"); + goto err_irq; + } + + chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + chip->cmdfunc(mtd, NAND_CMD_READID, 0, -1); + /* Read entire ID string */ + for (i = 0; i < 8; i++) + flash_dev_ex.ids[i] = chip->read_byte(mtd); + /* + * we support some non-standard nand chips in nand_flash_special_dev + * if the id matches, then use the info in the table, otherwise, + * def is NULL, use the normal way to probe the nand info + */ + def = nand_get_flash_type_ex(&flash_dev_ex); + + ret = nand_scan_ident(mtd, max_chips, def); + if (ret) { + ret = -ENODEV; + goto err_ident; + } + + flash_dev_ex.oobsize = mtd->oobsize; + hinfc504_ecc_probe(mtd, chip, &flash_dev_ex); + nand_scan_tail(mtd); + + nand_log_init(host); + + ret = mtd_device_parse_register(mtd, NULL, NULL, NULL, 0); + if (ret) { + dev_err(dev, "Err MTD partition=%d\n", ret); + goto err_mtd; + } + + return 0; + +err_mtd: + nand_release(mtd); +err_ident: +err_irq: +err_buf: + if (host->buffer) + dma_free_coherent(dev, + NAND_MAX_PAGESIZE + NAND_MAX_OOBSIZE, + host->buffer, host->dma_buffer); + kfree(host); +err_res: + platform_set_drvdata(pdev, NULL); + + return ret; +} + +/*****************************************************************************/ +int hinfc504_os_remove(struct platform_device *pdev) +{ + struct hinfc_host *host = platform_get_drvdata(pdev); + int irq; + + //clk_unprepare_disable(host->clk); + + nand_release(host->mtd); + dma_free_coherent(&pdev->dev, + (NAND_MAX_PAGESIZE + NAND_MAX_OOBSIZE), + host->buffer, + host->dma_buffer); + + irq = platform_get_irq(pdev, 0); + if (irq >= 0) + free_irq(irq, host); + + kfree(host); + platform_set_drvdata(pdev, NULL); + + nand_log_exit(); + + return 0; +} +/*****************************************************************************/ +#ifdef CONFIG_PM +static int hinfc504_os_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct hinfc_host *host = platform_get_drvdata(pdev); + + while ((hinfc_read(host, HINFC504_STATUS) & 0x1) == 0x0) + ; + + while ((hinfc_read(host, HINFC504_DMA_CTRL)) + & HINFC504_DMA_CTRL_DMA_START) + _cond_resched(); + + //clk_unprepare_disable(host->clk); + + return 0; +} +/*****************************************************************************/ +static int hinfc504_os_resume(struct platform_device *pdev) +{ + int cs; + struct hinfc_host *host = platform_get_drvdata(pdev); + struct nand_chip *chip = host->chip; + + //clk_prepare_enable(host->clk); + + for (cs = 0; cs < chip->numchips; cs++) + host->send_cmd_reset(host, cs); + hinfc_write(host, + SET_HINFC504_PWIDTH(CONFIG_HINFC504_W_LATCH, + CONFIG_HINFC504_R_LATCH, CONFIG_HINFC504_RW_LATCH), + HINFC504_PWIDTH); + + return 0; +} +#else +#define hinfc504_os_suspend NULL +#define hinfc504_os_resume NULL +#endif /* CONFIG_PM */ + +/*****************************************************************************/ +#ifdef CONFIG_OF +static const struct of_device_id nfc_id_table[] = { + { .compatible = "hisilicon,nfc504" }, + {} +}; +MODULE_DEVICE_TABLE(of, nfc_id_table); +#endif + +static struct platform_driver hinfc504_os_pltdrv = { + .driver = { + .name = DRIVER_NAME, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(nfc_id_table), +#endif + }, + .probe = hinfc504_os_probe, + .remove = hinfc504_os_remove, + .suspend = hinfc504_os_suspend, + .resume = hinfc504_os_resume, +}; + +module_platform_driver(hinfc504_os_pltdrv); +/*****************************************************************************/ +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Zhiyong Cai"); +MODULE_AUTHOR("Mingjun Zhang"); +MODULE_DESCRIPTION("Hisilicon Nand Flash Controller V504 Driver"); +/*****************************************************************************/ diff --git a/drivers/mtd/nand/hs_nfc/hinfc504_read_retry.c b/drivers/mtd/nand/hs_nfc/hinfc504_read_retry.c new file mode 100644 index 00000000000..f357f657970 --- /dev/null +++ b/drivers/mtd/nand/hs_nfc/hinfc504_read_retry.c @@ -0,0 +1,353 @@ +/* + * Hisilicon NAND Flash controller driver + * + * Copyright © 2012-2013 HiSilicon Technologies Co., Ltd. + * http://www.hisilicon.com + * Copyright © 2012-2013 Linaro Ltd. + * http://www.linaro.org + * + * Author: Mingjun Zhang <zhang.mingjun@linaro.org> + * The initial developer of the original code is Zhiyong Cai + * <caizhiyong@huawei.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/io.h> +#include "hinfc504.h" +#include "hinfc_common.h" + +/* hynix */ +static char *hynix_otp_check(const char otp[8 * 128]) +{ + int index = 0; + int ix, jx; + char *ptr = NULL; + int min, cur; + char *otp_origin, *otp_inverse; + + min = 64; + for (ix = 0; ix < 8; ix++) { + + otp_origin = (char *)otp + ix * 128; + otp_inverse = otp_origin + 64; + cur = 0; + + for (jx = 0; jx < 64; jx++, otp_origin++, otp_inverse++) { + if (((*otp_origin) ^ (*otp_inverse)) == 0xFF) + continue; + cur++; + } + if (cur < min) { + min = cur; + index = ix; + ptr = (char *)otp + ix * 128; + if (!cur) + break; + } + } + + pr_info("RR select parameter %d from %d, error %d\n", index, ix, min); + + return ptr; +} + +static int hinfc504_hynix_get_rr_param(struct hinfc_host *host) +{ + host->enable_ecc_randomizer(host, DISABLE, DISABLE); + /* step1: reset the chip */ + host->send_cmd_reset(host, host->chipselect); + + /* step2: cmd: 0x36, address: 0xAE, data: 0x00 */ + hinfc_write(host, 1, HINFC504_DATA_NUM); /* data length 1 */ + writel(0x0, host->chip->IO_ADDR_R); /* data: 0x00 */ + hinfc_write(host, 0xAE, HINFC504_ADDRL); /* address: 0xAE */ + hinfc_write(host, 0x36, HINFC504_CMD); /* cmd: 0x36 */ + hinfc_write(host, HINFC504_WRITE_1CMD_1ADD_DATA, HINFC504_OP); + wait_controller_finished(host); + + /* step3: address: 0xB0, data: 0x4D */ + writel(0x4D, host->chip->IO_ADDR_R); /* data: 0x4d */ + hinfc_write(host, 1, HINFC504_DATA_NUM); /* data length 1 */ + hinfc_write(host, 0xB0, HINFC504_ADDRL); /* address: 0xB0 */ + /* only address and data, without cmd */ + hinfc_write(host, HINFC504_WRITE_0CMD_1ADD_DATA, HINFC504_OP); + wait_controller_finished(host); + + /* step4: cmd: 0x16, 0x17, 0x04, 0x19 */ + hinfc_write(host, 0x17 << 8 | 0x16, HINFC504_CMD); + hinfc_write(host, 0, HINFC504_OP); + hinfc_write(host, HINFC504_WRITE_2CMD_0ADD_NODATA, HINFC504_OP); + wait_controller_finished(host); + + hinfc_write(host, 0x19 << 8 | 0x04, HINFC504_CMD); + hinfc_write(host, 0, HINFC504_OP); + hinfc_write(host, HINFC504_WRITE_2CMD_0ADD_NODATA, HINFC504_OP); + wait_controller_finished(host); + + /* step5: cmd: 0x00 0x30, address: 0x02 00 00 00 */ + hinfc_write(host, 0x2000000, HINFC504_ADDRL); + hinfc_write(host, 0x30 << 8 | 0x00, HINFC504_CMD); + hinfc_write(host, 0, HINFC504_OP); + hinfc_write(host, 0x80, HINFC504_DATA_NUM); + hinfc_write(host, HINFC504_READ_2CMD_5ADD, HINFC504_OP); + wait_controller_finished(host); + + /*step6 save otp read retry table to mem*/ + { + char *otp = hynix_otp_check(host->chip->IO_ADDR_R + 2); + memcpy(host->rr_data, otp, 64); + host->need_rr_data = 1; + } + + /* step7: reset the chip */ + host->send_cmd_reset(host, host->chipselect); + + /* step8: cmd: 0x38 */ + hinfc_write(host, 0x38, HINFC504_CMD); + hinfc_write(host, HINFC504_WRITE_2CMD_0ADD_NODATA, HINFC504_OP); + wait_controller_finished(host); + + host->enable_ecc_randomizer(host, ENABLE, ENABLE); + /* get hynix otp table finish */ + return 0; +} + +#define HINFC504_HYNIX_RR_BASEADDR 0xB0 +static int hinfc504_hynix_set_rr_reg(struct hinfc_host *host, char *val) +{ + int i; + host->enable_ecc_randomizer(host, DISABLE, DISABLE); + hinfc_write(host, 1, HINFC504_DATA_NUM); + + for (i = 0; i < 8; i++) { + switch (i) { + case 0: + writel(val[i], host->chip->IO_ADDR_R); + hinfc_write(host, HINFC504_HYNIX_RR_BASEADDR, + HINFC504_ADDRL); + hinfc_write(host, 0x36, HINFC504_CMD); + hinfc_write(host, HINFC504_WRITE_1CMD_1ADD_DATA, + HINFC504_OP); + break; + case 7: + hinfc_write(host, 0x16, HINFC504_CMD); + hinfc_write(host, HINFC504_WRITE_2CMD_0ADD_NODATA, + HINFC504_OP); + break; + default: + writel(val[i], host->chip->IO_ADDR_R); + hinfc_write(host, HINFC504_HYNIX_RR_BASEADDR + i, + HINFC504_ADDRL); + hinfc_write(host, HINFC504_WRITE_0CMD_1ADD_DATA, + HINFC504_OP); + break; + + } + wait_controller_finished(host); + } + host->enable_ecc_randomizer(host, ENABLE, ENABLE); + return 0; +} + +static int hinfc504_hynix_set_rr_param(struct hinfc_host *host, int param) +{ + unsigned char *rr; + + if (!(host->rr_data[0] | host->rr_data[1] + | host->rr_data[2] | host->rr_data[3]) + || !param) + return -1; + + rr = (unsigned char *)&host->rr_data[((param & 0x07) << 3)]; + + /* set the read retry regs to adjust reading level */ + return hinfc504_hynix_set_rr_reg(host, (char *)rr); +} + +/* samsung */ +static int hinfc504_samsung_set_rr_reg(struct hinfc_host *host, int param) +{ +#define SAMSUNG_RR_CMD 0xA1 + + unsigned char samsung_rr_params[15][4] = { + {0x00, 0x00, 0x00, 0x00}, + {0x05, 0x0A, 0x00, 0x00}, + {0x28, 0x00, 0xEC, 0xD8}, + {0xED, 0xF5, 0xED, 0xE6}, + {0x0A, 0x0F, 0x05, 0x00}, + {0x0F, 0x0A, 0xFB, 0xEC}, + {0xE8, 0xEF, 0xE8, 0xDC}, + {0xF1, 0xFB, 0xFE, 0xF0}, + {0x0A, 0x00, 0xFB, 0xEC}, + {0xD0, 0xE2, 0xD0, 0xC2}, + {0x14, 0x0F, 0xFB, 0xEC}, + {0xE8, 0xFB, 0xE8, 0xDC}, + {0x1E, 0x14, 0xFB, 0xEC}, + {0xFB, 0xFF, 0xFB, 0xF8}, + {0x07, 0x0C, 0x02, 0x00} + }; + + if (param >= 15) + param = (param % 15); + + hinfc_write(host, 1, HINFC504_DATA_NUM); + + writel(samsung_rr_params[param][0], host->chip->IO_ADDR_R); + hinfc_write(host, 0xA700, HINFC504_ADDRL); + hinfc_write(host, SAMSUNG_RR_CMD, HINFC504_CMD); + hinfc_write(host, HINFC504_WRITE_1CMD_2ADD_DATA, HINFC504_OP); + wait_controller_finished(host); + + writel(samsung_rr_params[param][1], host->chip->IO_ADDR_R); + hinfc_write(host, 0xA400, HINFC504_ADDRL); + hinfc_write(host, SAMSUNG_RR_CMD, HINFC504_CMD); + hinfc_write(host, HINFC504_WRITE_1CMD_2ADD_DATA, HINFC504_OP); + wait_controller_finished(host); + + writel(samsung_rr_params[param][2], host->chip->IO_ADDR_R); + hinfc_write(host, 0xA500, HINFC504_ADDRL); + hinfc_write(host, SAMSUNG_RR_CMD, HINFC504_CMD); + hinfc_write(host, HINFC504_WRITE_1CMD_2ADD_DATA, HINFC504_OP); + wait_controller_finished(host); + + writel(samsung_rr_params[param][3], host->chip->IO_ADDR_R); + hinfc_write(host, 0xA600, HINFC504_ADDRL); + hinfc_write(host, SAMSUNG_RR_CMD, HINFC504_CMD); + hinfc_write(host, HINFC504_WRITE_1CMD_2ADD_DATA, HINFC504_OP); + wait_controller_finished(host); + + return 0; + +#undef SAMSUNG_RR_CMD +} + +static int hinfc504_samsung_set_rr_param(struct hinfc_host *host, int param) +{ + return hinfc504_samsung_set_rr_reg(host, param); +} + +/* micron */ +#define MICRON_RR_ADDR 0x89 + +static int hinfc504_micron_set_rr_reg(struct hinfc_host *host, int rr) +{ +#define MICRON_SET_RR 0xEF + hinfc_write(host, 1, HINFC504_DATA_NUM); + + writel(rr, host->chip->IO_ADDR_W); + hinfc_write(host, MICRON_RR_ADDR, HINFC504_ADDRL); + hinfc_write(host, MICRON_SET_RR, HINFC504_CMD); + hinfc_write(host, HINFC504_WRITE_1CMD_1ADD_DATA, HINFC504_OP); + wait_controller_finished(host); +#undef MICRON_SET_RR + return 0; +} + +static int hinfc504_micron_get_rr_param(struct hinfc_host *host) +{ +#define MICRON_GET_RR 0xEE + hinfc_write(host, 1, HINFC504_DATA_NUM); + + hinfc_write(host, MICRON_RR_ADDR, HINFC504_ADDRL); + hinfc_write(host, MICRON_GET_RR, HINFC504_CMD); + hinfc_write(host, HINFC504_READ_1CMD_1ADD_DATA, HINFC504_OP); + wait_controller_finished(host); + hinfc_write(host, NAND_CMD_READ0, HINFC504_CMD); + hinfc_write(host, HINFC504_READ_1CMD_0ADD_NODATA, HINFC504_OP); +#undef MICRON_GET_RR + return readl(host->chip->IO_ADDR_R) & 0xff; +} + +static int hinfc504_micron_set_rr_param(struct hinfc_host *host, int rr_option) +{ + hinfc504_micron_set_rr_reg(host, rr_option); + return 0; +} + +/* toshiba */ +static int hinfc504_toshiba_24nm_set_rr_reg(struct hinfc_host *host, int param) +{ +#define TOSHIBA_RR_CMD 0x55 + + static char toshiba_rr_param[] = {0x00, 0x04, 0x7c, 0x78, 0x74, 0x08}; + + if (!param) { + host->send_cmd_reset(host, host->chipselect); + return 0; + } + + if (param >= 6) + param = (param % 6); + + hinfc_write(host, 1, HINFC504_DATA_NUM); + + writel(toshiba_rr_param[param], host->chip->IO_ADDR_R); + hinfc_write(host, 0x4, HINFC504_ADDRL); + hinfc_write(host, TOSHIBA_RR_CMD, HINFC504_CMD); + hinfc_write(host, HINFC504_WRITE_1CMD_1ADD_DATA, HINFC504_OP); + wait_controller_finished(host); + + writel(toshiba_rr_param[param], host->chip->IO_ADDR_R); + hinfc_write(host, 0x5, HINFC504_ADDRL); + hinfc_write(host, TOSHIBA_RR_CMD, HINFC504_CMD); + hinfc_write(host, HINFC504_WRITE_1CMD_1ADD_DATA, HINFC504_OP); + wait_controller_finished(host); + + writel(toshiba_rr_param[param], host->chip->IO_ADDR_R); + hinfc_write(host, 0x6, HINFC504_ADDRL); + hinfc_write(host, TOSHIBA_RR_CMD, HINFC504_CMD); + hinfc_write(host, HINFC504_WRITE_1CMD_1ADD_DATA, HINFC504_OP); + wait_controller_finished(host); + + writel(toshiba_rr_param[param], host->chip->IO_ADDR_R); + hinfc_write(host, 0x7, HINFC504_ADDRL); + hinfc_write(host, TOSHIBA_RR_CMD, HINFC504_CMD); + hinfc_write(host, HINFC504_WRITE_1CMD_1ADD_DATA, HINFC504_OP); + wait_controller_finished(host); + + return 0; + +#undef TOSHIBA_RR_CMD +} + +static int hinfc504_toshiba_24nm_set_rr_param(struct hinfc_host *host, + int param) +{ + return hinfc504_toshiba_24nm_set_rr_reg(host, param); +} + +struct read_retry_t read_retry_list[] = { + { + .type = NAND_RR_HYNIX, + .count = 7, + .set_rr_param = hinfc504_hynix_set_rr_param, + .get_rr_param = hinfc504_hynix_get_rr_param, + }, + { + .type = NAND_RR_MICRON, + .count = 8, + .set_rr_param = hinfc504_micron_set_rr_param, + .get_rr_param = hinfc504_micron_get_rr_param, + }, + { + .type = NAND_RR_SAMSUNG, + .count = 15, + .set_rr_param = hinfc504_samsung_set_rr_param, + .get_rr_param = NULL, + }, + { + .type = NAND_RR_TOSHIBA_24nm, + .count = 6, + .set_rr_param = hinfc504_toshiba_24nm_set_rr_param, + .get_rr_param = NULL, + }, + { NAND_RR_NONE, 0, NULL, NULL}, +}; diff --git a/drivers/mtd/nand/hs_nfc/hinfc_common.h b/drivers/mtd/nand/hs_nfc/hinfc_common.h new file mode 100644 index 00000000000..414d8fa2a3f --- /dev/null +++ b/drivers/mtd/nand/hs_nfc/hinfc_common.h @@ -0,0 +1,128 @@ +/* + * Hisilicon NAND Flash controller driver + * + * Copyright © 2012-2013 HiSilicon Technologies Co., Ltd. + * http://www.hisilicon.com + * Copyright © 2012-2013 Linaro Ltd. + * http://www.linaro.org + * + * Author: Mingjun Zhang <zhang.mingjun@linaro.org> + * The initial developer of the original code is Zhiyong Cai + * <caizhiyong@huawei.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef HINFC_COMMONH +#define HINFC_COMMONH +/******************************************************************************/ +#include <linux/mtd/nand.h> +#include <linux/mtd/mtd.h> +#include <linux/sizes.h> + +#define HINFC_VER_300 (0x300) +#define HINFC_VER_301 (0x301) +#define HINFC_VER_310 (0x310) +#define HINFC_VER_504 (0x504) +#define HINFC_VER_505 (0x505) +#define HINFC_VER_600 (0x600) + +/*****************************************************************************/ + +enum ecc_type { + et_ecc_none = 0x00, + et_ecc_1bit = 0x01, + et_ecc_4bit = 0x02, + et_ecc_4bytes = 0x02, + et_ecc_8bytes = 0x03, + et_ecc_24bit1k = 0x04, + et_ecc_40bit1k = 0x05, +}; + +enum page_type { + pt_pagesize_512, + pt_pagesize_2K, + pt_pagesize_4K, + pt_pagesize_8K, + pt_pagesize_16K, +}; + +struct page_page_ecc_info { + enum page_type pagetype; + enum ecc_type ecctype; + int oobsize; + struct nand_ecclayout *layout; +}; + +struct hinfc_host; + +struct read_retry_t { + int type; + int count; + int (*set_rr_param)(struct hinfc_host *host, int param); + int (*get_rr_param)(struct hinfc_host *host); +}; + +enum read_retry_type { + NAND_RR_NONE, + NAND_RR_HYNIX, + NAND_RR_MICRON, + NAND_RR_SAMSUNG, + NAND_RR_TOSHIBA_24nm, +}; + +struct nand_flash_dev_ex { + struct nand_flash_dev flash_dev; + + unsigned char ids[8]; + int oobsize; + int ecctype; + int is_randomizer; + int read_retry_type; +}; + +/*****************************************************************************/ +static inline char *get_ecctype_str(enum ecc_type ecctype) +{ + static char *ecctype_string[] = { + "None", "1bit", "4bits", "8bits", "24bits/1K", + "40bits/1K", "unknown", "unknown"}; + return ecctype_string[(ecctype & 0x07)]; +} +/*****************************************************************************/ + +static inline char *get_pagesize_str(enum page_type pagetype) +{ + static char *pagesize_str[] = { + "512", "2K", "4K", "8K", "16K", "unknown", + "unknown", "unknown"}; + return pagesize_str[pagetype & 0x07]; +} +/*****************************************************************************/ + +static inline unsigned int get_pagesize(enum page_type pagetype) +{ + unsigned int pagesize[] = { SZ_512, SZ_2K, SZ_4K, + SZ_8K, SZ_16K, 0, 0, 0 }; + return pagesize[pagetype & 0x07]; +} + +struct nand_flash_dev *nand_get_flash_type_ex(struct nand_flash_dev_ex + *flash_dev_ex); + +void nand_show_info(struct nand_flash_dev_ex *flash_dev_ex, + struct mtd_info *mtd, char *vendor, char *chipname); + +void nand_show_chip(struct nand_chip *chip); + +extern struct read_retry_t read_retry_list[]; +/******************************************************************************/ +#endif /* HINFC_COMMONH */ diff --git a/drivers/mtd/nand/hs_nfc/hinfc_spl_ids.c b/drivers/mtd/nand/hs_nfc/hinfc_spl_ids.c new file mode 100644 index 00000000000..bb1b1709ce3 --- /dev/null +++ b/drivers/mtd/nand/hs_nfc/hinfc_spl_ids.c @@ -0,0 +1,195 @@ +/* + * Hisilicon NAND Flash controller driver + * + * Copyright © 2012-2013 HiSilicon Technologies Co., Ltd. + * http://www.hisilicon.com + * Copyright © 2012-2013 Linaro Ltd. + * http://www.linaro.org + * + * Author: Mingjun Zhang <zhang.mingjun@linaro.org> + * The initial developer of the original code is Zhiyong Cai + * <caizhiyong@huawei.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/mtd/nand.h> +#include <linux/sizes.h> +#include "hinfc_common.h" + +/*****************************************************************************/ + +#define SZ_768K (SZ_256K + SZ_512K) +#define SZ_4G (0x100000000ULL) +#define SZ_8G (0x200000000ULL) +#define SZ_16G (0x400000000ULL) +#define SZ_64G (0x1000000000ULL) + +/*****************************************************************************/ +struct nand_flash_special_dev { + unsigned char id[8]; + int length; /* length of id. */ + unsigned long long chipsize; + char *name; + + unsigned long pagesize; + unsigned long erasesize; + unsigned long oobsize; + unsigned long options; + int read_retry_type; +}; + +/*****************************************************************************/ +/* + * samsung: 27nm need randomizer, 21nm need read retry; + * micron: 25nm need read retry, datasheet will explain read retry. + * toshaba 32nm need randomizer, 24nm need read retry. + * hynix: 2xnm need read retry. + */ +static struct nand_flash_special_dev nand_flash_special_dev[] = { + /* 1st 2nd 3rd 4th 5th 6th 7th 8th len chipsize(B) + device pagesize erasesize oobsize options */ + /* Micron */ + { {0x2C, 0x64, 0x44, 0x4B, 0xA9, 0x00, 0x00, 0x00}, 8, SZ_8G, + "MT29F64G08CBABA", SZ_8K, SZ_2M, 744, 0, NAND_RR_MICRON}, + { {0x2C, 0x38, 0x00, 0x26, 0x85, 0x00, 0x00, 0x00}, 8, SZ_1G, + "MT29F8G08ABxBA", SZ_4K, SZ_512K, 224, 0, NAND_RR_NONE}, + { {0x2C, 0x48, 0x04, 0x46, 0x85, 0x00, 0x00, 0x00}, 8, SZ_2G, + "MT29F16G08CBABx", SZ_4K, SZ_1M, 224, 0, NAND_RR_NONE}, + { {0x2C, 0x48, 0x04, 0x4A, 0xA5, 0x00, 0x00, 0x00}, 8, SZ_2G, + "MT29F16G08CBACA", SZ_4K, SZ_1M, 224, 0, NAND_RR_NONE}, + { {0x2C, 0x68, 0x04, 0x4A, 0xA9, 0x00, 0x00, 0x00}, 8, SZ_4G, + "MT29F32G08CBACA", SZ_4K, SZ_1M, 224, 0, NAND_RR_NONE}, + { {0x2C, 0x88, 0x04, 0x4B, 0xA9, 0x00, 0x00, 0x00}, 8, SZ_8G, + "MT29F64G08CxxAA", SZ_8K, SZ_2M, 448, 0, NAND_RR_NONE}, + { {0x2C, 0xA8, 0x05, 0xCB, 0xA9, 0x00, 0x00, 0x00}, 8, SZ_16G, + "MT29F256G08CJAAA", SZ_8K, SZ_2M, 448, 0, NAND_RR_NONE}, + { {0x2C, 0x48, 0x00, 0x26, 0xA9, 0x00, 0x00, 0x00}, 5, SZ_2G, + "MT29F16G08ABACA", SZ_4K, SZ_512K, 224, 0, NAND_RR_NONE}, + + /* Toshiba */ + { {0x98, 0xD5, 0x94, 0x32, 0x76, 0x55, 0x00, 0x00}, 6, SZ_2G, + "TC58NVG4D2FTA00", SZ_8K, SZ_1M, 448, 0, NAND_RR_NONE}, + { {0x98, 0xD7, 0x94, 0x32, 0x76, 0x55, 0x00, 0x00}, 6, SZ_8G, + "TH58NVG6D2FTA20", SZ_8K, SZ_1M, 448, 0, NAND_RR_NONE}, + { {0x98, 0xD7, 0x94, 0x32, 0x76, 0x56, 0x08, 0x00}, 6, SZ_4G, + "TC58NVG5D2HTA00 24nm", SZ_8K, SZ_1M, 640, 0, + NAND_RR_TOSHIBA_24nm}, + { {0x98, 0xDE, 0x94, 0x82, 0x76, 0x00, 0x00, 0x00}, 5, SZ_8G, + "TC58NVG6D2GTA00", SZ_8K, SZ_2M, 640, 0, + NAND_RR_NONE}, + { {0x98, 0xDE, 0x84, 0x93, 0x72, 0x57, 0x08, 0x04}, 8, SZ_8G, + "TC58NVG6DCJTA00 19nm", SZ_16K, SZ_4M, 1280, 0, + NAND_RR_TOSHIBA_24nm}, + { {0x98, 0xD7, 0x84, 0x93, 0x72, 0x57, 0x08, 0x04}, 6, SZ_4G, + "TC58TEG5DCJTA00 19nm", SZ_16K, SZ_4M, 1280, 0, + NAND_RR_TOSHIBA_24nm}, + { {0x98, 0xF1, 0x80, 0x15, 0x72, 0x00, 0x00, 0x00}, 5, SZ_128M, + "TC58NVG0S3HTA00", SZ_2K, SZ_128K, 128, 0, NAND_RR_NONE}, + { {0x98, 0xD7, 0x98, 0x92, 0x72, 0x57, 0x08, 0x10}, 6, SZ_4G, + "TC58NVG5T2JTA00 19nm TLC", SZ_8K, SZ_4M, 1024, 0, + NAND_RR_TOSHIBA_24nm}, /* 60bit/1K */ + + /* Samsung K9xxG08UxD: K9LBG08U0D / K9HCG08U1D / K9XDG08U5D */ + { {0xEC, 0xD7, 0x94, 0x7A, 0x54, 0x43, 0x00, 0x00}, 6, SZ_4G, + "K9GBG08U0A 20nm", SZ_8K, SZ_1M, 640, 0, + NAND_RR_SAMSUNG},/* 24bit1k */ + { {0xEC, 0xD7, 0x94, 0x7E, 0x64, 0x44, 0x00, 0x00}, 6, SZ_4G, + "K9GBG08U0B", SZ_8K, SZ_1M, 1024, 0, NAND_RR_SAMSUNG}, + + /* Hynix */ + { {0xAD, 0xD7, 0x94, 0x91, 0x60, 0x44, 0x00, 0x00}, 6, SZ_4G, + "H27UBG8T2C", SZ_8K, SZ_2M, 640, 0, NAND_RR_HYNIX}, + + /* MIRA */ + { {0xC8, 0xD5, 0x14, 0x29, 0x34, 0x01, 0x00, 0x00}, 6, SZ_2G, + "P1UAGA30AT-GCA", SZ_4K, SZ_512K, 218, 0, NAND_RR_NONE}, + /* PowerFlash ASU8GA30IT-G30CA and MIRA PSU8GA30AT-GIA + have the same ID */ + { {0xC8, 0xD3, 0x90, 0x19, 0x34, 0x01, 0x00, 0x00}, 6, SZ_1G, + "PSU8GA30AT-GIA/ASU8GA30IT-G30CA", SZ_4K, SZ_256K, 218, 0, + NAND_RR_NONE}, + { {0x7F, 0x7F, 0x7F, 0x7F, 0xC8, 0xDA, 0x00, 0x15}, 8, SZ_256M, + "PSU2GA30AT", SZ_2K, SZ_128K, 64, 0, NAND_RR_NONE}, + + {},/* may used to store nand chip info passed from bootloader */ + {},/* null terminated */ +}; + +/*****************************************************************************/ +struct nand_flash_dev *nand_get_flash_type_ex(struct nand_flash_dev_ex + *flash_dev_ex) +{ + struct nand_flash_special_dev *spl_dev; + unsigned char *byte = flash_dev_ex->ids; + struct nand_flash_dev *flash_type = &flash_dev_ex->flash_dev; + + pr_info("Chip ID: %02x %02x %02x %02x %02x %02x %02x %02x\n", + byte[0], byte[1], byte[2], byte[3], + byte[4], byte[5], byte[6], byte[7]); + + for (spl_dev = nand_flash_special_dev; spl_dev->length; spl_dev++) { + if (memcmp(byte, spl_dev->id, spl_dev->length)) + continue; + + flash_type->id = byte[1]; + flash_type->name = spl_dev->name; + + flash_type->pagesize = spl_dev->pagesize; + flash_type->chipsize = (unsigned long) + (spl_dev->chipsize >> 20); + flash_type->erasesize = spl_dev->erasesize; + flash_type->options = spl_dev->options; + + flash_dev_ex->oobsize = spl_dev->oobsize; + flash_dev_ex->read_retry_type = spl_dev->read_retry_type; + + return flash_type; + } + + flash_dev_ex->read_retry_type = NAND_RR_NONE; + + return NULL; +} +/*****************************************************************************/ +static char *ultohstr(u32 size, char *buffer) +{ + char *fmt[] = {"%u", "%uK", "%uM", "%uG", "%uT", "%uT"}; + int ix; + + for (ix = 0; (ix < 5) && !(size & 0x3FF) && size; ix++) + size = (size >> 10); + + sprintf(buffer, fmt[ix], size); + return buffer; +} + +void nand_show_info(struct nand_flash_dev_ex *flash_dev_ex, + struct mtd_info *mtd, + char *vendor, + char *chipname) +{ + char buf[20]; + + pr_info("Nand: %s %s ", vendor, chipname); + + if (flash_dev_ex->is_randomizer) + pr_info("Randomizer "); + + if (flash_dev_ex->read_retry_type != NAND_RR_NONE) + pr_info("Read-Retry "); + + pr_info("\n"); + + pr_info("Block:%sB ", ultohstr(mtd->erasesize, buf)); + pr_info("Page:%sB ", ultohstr(mtd->writesize, buf)); + pr_info("OOB:%sB ", ultohstr(flash_dev_ex->oobsize, buf)); + pr_info("ECC:%s ", get_ecctype_str(flash_dev_ex->ecctype)); +} |