1562 lines
40 KiB
C
1562 lines
40 KiB
C
/**
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
* aw_rawnand_nfc.c
|
|
*
|
|
* (C) Copyright 2020 - 2021
|
|
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
* cuizhikui <cuizhikui@allwinnertech.com>
|
|
*
|
|
*/
|
|
#include <linux/clk.h>
|
|
#include <linux/of.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/mtd/mtd.h>
|
|
#include <linux/mtd/aw-rawnand.h>
|
|
#include <linux/kernel.h>
|
|
#include <asm/io.h>
|
|
#include "aw_rawnand_nfc.h"
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/of_address.h>
|
|
|
|
struct aw_nand_host aw_host;
|
|
|
|
#define MBUS_GATE 0x0804
|
|
#define NAND0_CFG 0x0810
|
|
#define NAND1_CFG 0x0814
|
|
#define NAND_GATE 0x082C
|
|
|
|
#define CCM_NAND_CTRL_M (0xF << 0)
|
|
#define CCM_NAND_CTRL_CM(x) ((x) - 1)
|
|
#define CCM_NAND_CTRL_N (0x3 << 8)
|
|
#define CCM_NAND_CTRL_CN(x) ((x) << 8)
|
|
#define CCM_NAND_CTRL_ENABLE (0x1 << 31)
|
|
#define CCM_NAND_SRC_SELECT (0x7 << 24)
|
|
#define CCM_NAND_SRC_CSELECT(x) (((x)& 0x7) << 24)
|
|
|
|
static struct aw_nand_host *get_host(void)
|
|
{
|
|
return &aw_host;
|
|
}
|
|
|
|
void aw_nfc_reg_dump(struct nfc_reg *reg)
|
|
{
|
|
uint32_t i = 0;
|
|
for (i = 0; i < 12; i++) {
|
|
printk("[%p : %08x]\n", reg->ctl + i, readl(reg->ctl + i));
|
|
}
|
|
|
|
for (i = 0; i < 7; i++) {
|
|
printk("[%p : %08x]\n", reg->ecc_ctl + i, readl(reg->ecc_ctl + i));
|
|
}
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
printk("[%p : %08x]\n", reg->err_cnt[0] + i, readl(reg->err_cnt[0] + i));
|
|
}
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
printk("[%p : %08x]\n", reg->user_data_len_base + i,
|
|
readl(reg->user_data_len_base + i));
|
|
}
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
printk("[%p : %08x]\n", reg->user_data_base + i,
|
|
readl(reg->user_data_base + i));
|
|
}
|
|
|
|
printk("[%p : %08x]\n", reg->spare_area, readl(reg->spare_area));
|
|
printk("[%p : %08x]\n", reg->pat_id, readl(reg->pat_id));
|
|
#if 0
|
|
volatile unsigned *mdclk = (volatile unsigned *)(0x03001810);
|
|
volatile unsigned *mcclk = (volatile unsigned *)(0x03001814);
|
|
volatile unsigned *mbus_gate = (volatile unsigned *)(0x03001804);
|
|
volatile unsigned *per0 = (volatile unsigned *)(0x03001020);
|
|
volatile unsigned *mbus = (volatile unsigned *)(0x03001540);
|
|
printk("[%p : %08x]\n", mbus_gate, readl(mbus_gate));
|
|
printk("[%p : %08x]\n", per0, readl(per0));
|
|
printk("[%p : %08x]\n", mbus, readl(mbus));
|
|
printk("[%p : %08x]\n", mdclk, readl(mdclk));
|
|
printk("[%p : %08x]\n", mcclk, readl(mcclk));
|
|
#endif
|
|
}
|
|
|
|
|
|
static void aw_nfc_reg_prepare(struct nfc_reg *reg)
|
|
{
|
|
struct aw_nand_host *host = container_of(reg, struct aw_nand_host, nfc_reg);
|
|
int i = 0;
|
|
AWRAWNAND_TRACE_NFC("Enter %s reg@%p\n", __func__, reg);
|
|
|
|
AWRAWNAND_TRACE_NFC("host:%p\n", host, host);
|
|
AWRAWNAND_TRACE_NFC("nfc:%p\n", &host->nfc_reg);
|
|
|
|
reg->ctl = (volatile uint32_t *)((uint8_t *)host->base + 0x0000);
|
|
reg->sta = (volatile uint32_t *)((uint8_t *)host->base + 0x0004);
|
|
reg->int_ctl = (volatile uint32_t *)((uint8_t *)host->base + 0x0008);
|
|
reg->timing_ctl = (volatile uint32_t *)((uint8_t *)host->base + 0x000c);
|
|
reg->timing_cfg = (volatile uint32_t *)((uint8_t *)host->base + 0x0010);
|
|
reg->addr_low = (volatile uint32_t *)((uint8_t *)host->base + 0x0014);
|
|
reg->addr_high = (volatile uint32_t *)((uint8_t *)host->base + 0x0018);
|
|
reg->data_block_mask = (volatile uint32_t *)((uint8_t *)host->base + 0x001c);
|
|
reg->cnt = (volatile uint32_t *)((uint8_t *)host->base + 0x0020);
|
|
reg->cmd = (volatile uint32_t *)((uint8_t *)host->base + 0x0024);
|
|
reg->read_cmd_set = (volatile uint32_t *)((uint8_t *)host->base + 0x0028);
|
|
reg->write_cmd_set = (volatile uint32_t *)((uint8_t *)host->base + 0x002c);
|
|
reg->ecc_ctl = (volatile uint32_t *)((uint8_t *)host->base + 0x0034);
|
|
reg->ecc_sta = (volatile uint32_t *)((uint8_t *)host->base + 0x0038);
|
|
reg->data_pattern_sta = (volatile uint32_t *)((uint8_t *)host->base + 0x003c);
|
|
reg->efr = (volatile uint32_t *)((uint8_t *)host->base + 0x0040);
|
|
reg->rdata_sta_ctl = (volatile uint32_t *)((uint8_t *)host->base + 0x0044);
|
|
reg->rdata_sta_0 = (volatile uint32_t *)((uint8_t *)host->base + 0x0048);
|
|
reg->rdata_sta_1 = (volatile uint32_t *)((uint8_t *)host->base + 0x004c);
|
|
for (i = 0; i < MAX_ERR_CNT; i++)
|
|
reg->err_cnt[i] = (volatile uint32_t *)((uint8_t *)host->base + 0x0050+(i * 4));
|
|
reg->user_data_len_base = (volatile uint32_t *)((uint8_t *)host->base + 0x0070);
|
|
reg->user_data_base = (volatile uint32_t *)((uint8_t *)host->base + 0x0080);
|
|
reg->efnand_sta = (volatile uint32_t *)((uint8_t *)host->base + 0x0110);
|
|
reg->spare_area = (volatile uint32_t *)((uint8_t *)host->base + 0x0114);
|
|
reg->pat_id = (volatile uint32_t *)((uint8_t *)host->base + 0x0118);
|
|
reg->ddr2_spec_ctl = (volatile uint32_t *)((uint8_t *)host->base + 0x011c);
|
|
reg->ndma_mode_ctl = (volatile uint32_t *)((uint8_t *)host->base + 0x0120);
|
|
reg->mbus_dma_dlba = (volatile uint32_t *)((uint8_t *)host->base + 0x0200);
|
|
reg->mbus_dma_sta = (volatile uint32_t *)((uint8_t *)host->base + 0x0204);
|
|
reg->mdma_int_mask = (volatile uint32_t *)((uint8_t *)host->base + 0x0208);
|
|
reg->mdma_cur_desc_addr = (volatile uint32_t *)((uint8_t *)host->base + 0x020c);
|
|
reg->mdma_cur_buf_addr = (volatile uint32_t *)((uint8_t *)host->base + 0x0210);
|
|
reg->dma_cnt = (volatile uint32_t *)((uint8_t *)host->base + 0x0214);
|
|
reg->ver = (volatile uint32_t *)((uint8_t *)host->base + 0x02f0);
|
|
reg->ram0_base = (volatile uint32_t *)((uint8_t *)host->base + 0x0400);
|
|
reg->ram1_base = (volatile uint32_t *)((uint8_t *)host->base + 0x0800);
|
|
|
|
}
|
|
|
|
|
|
static int aw_host_set_pin(struct aw_nand_host *host)
|
|
{
|
|
struct pinctrl *pinctrl = NULL;
|
|
|
|
pinctrl = pinctrl_get_select(host->dev, "default");
|
|
if (IS_ERR_OR_NULL(pinctrl)) {
|
|
awrawnand_err("set nand0 pin fail\n");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void noraml_req_get_addr(struct aw_nfc_normal_req *req, uint32_t *addr_low,
|
|
uint32_t *addr_high)
|
|
{
|
|
int i = 0;
|
|
|
|
for (i = 0; i < req->op.cmd_with_addr.addr_cycles; i++) {
|
|
if (i < 4)
|
|
(*addr_low) |= (req->op.cmd_with_addr.addr[i] << (i * 8));
|
|
else
|
|
(*addr_high) |= (req->op.cmd_with_addr.addr[i] << ((i - 4) * 8));
|
|
}
|
|
}
|
|
|
|
static int aw_host_nfc_wait_cmd_fifo_empty(struct nfc_reg *nfc)
|
|
{
|
|
int ret = -ETIMEDOUT;
|
|
|
|
unsigned long timeout = jiffies + msecs_to_jiffies(60000);
|
|
|
|
do {
|
|
if (!(readl(nfc->sta) & NFC_CMD_FIFO_STATUS)) {
|
|
ret = 0;
|
|
goto out;
|
|
} else
|
|
cond_resched();
|
|
|
|
} while (time_before(jiffies, timeout));
|
|
|
|
if (ret) {
|
|
awrawnand_err("wait cmd fifo empty timeout 1s status@%p %x\n",
|
|
nfc->sta, readl(nfc->sta));
|
|
aw_nfc_reg_dump(nfc);
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int aw_host_nfc_wait_fsm_idle(struct nfc_reg *nfc)
|
|
{
|
|
int ret = -ETIMEDOUT;
|
|
|
|
unsigned long timeout = jiffies +msecs_to_jiffies(60000);
|
|
|
|
do {
|
|
if (!(readl(nfc->sta) & NFC_STA)) {
|
|
ret = 0;
|
|
goto out;
|
|
} else
|
|
cond_resched();
|
|
|
|
} while (time_before(jiffies, timeout));
|
|
|
|
if (ret) {
|
|
awrawnand_err("wait nfc fsm idle timeout 1s status@%p %x\n",
|
|
nfc->sta, readl(nfc->sta));
|
|
aw_nfc_reg_dump(nfc);
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int aw_host_nfc_wait_cmd_finish(struct nfc_reg *nfc)
|
|
{
|
|
int ret = -ETIMEDOUT;
|
|
/*AWRAWNAND_TRACE_NFC("Enter %s\n", __func__);*/
|
|
|
|
unsigned long timeout = jiffies + msecs_to_jiffies(60000);
|
|
|
|
do {
|
|
if ((readl(nfc->sta) & NFC_CMD_INT_FLAG)) {
|
|
ret = 0;
|
|
goto out;
|
|
} else
|
|
cond_resched();
|
|
|
|
} while (time_before(jiffies, timeout));
|
|
|
|
if (ret)
|
|
awrawnand_err("wait nfc wait cmd finish 10s timeout[%p:%x]\n",
|
|
nfc->sta, readl(nfc->sta));
|
|
out:
|
|
/*write 1 to clear CMD INT FLAG*/
|
|
writel(NFC_CMD_INT_FLAG, nfc->sta);
|
|
/*AWRAWNAND_TRACE_NFC("Exit %s\n", __func__);*/
|
|
return ret;
|
|
}
|
|
#if 0
|
|
static int aw_host_nfc_wait_B2R_int(struct nfc_reg *nfc)
|
|
{
|
|
int ret = -ETIMEDOUT;
|
|
uint32_t cfg = 0;
|
|
|
|
unsigned long timeout = jiffies + msecs_to_jiffies(60000);
|
|
|
|
do {
|
|
if ((readl(nfc->sta) & NFC_RB_B2R)) {
|
|
ret = 0;
|
|
goto out;
|
|
} else
|
|
cond_resched();
|
|
|
|
} while (time_before(jiffies, timeout));
|
|
|
|
if (ret)
|
|
awrawnand_err("wait nfc wait B2R 2s timeout [%x: %x]\n",
|
|
nfc->sta, readl(nfc->sta));
|
|
out:
|
|
/*write 1 to clear CMD B2R FLAG*/
|
|
cfg = readl(nfc->sta);
|
|
cfg |= NFC_RB_B2R;
|
|
writel(cfg, nfc->sta);
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static bool aw_host_nfc_wait_rb_ready(struct aw_nand_chip *chip, struct aw_nand_host *host)
|
|
{
|
|
int ret = 0;
|
|
uint32_t val = 0;
|
|
int chip_no = chip->selected_chip.chip_no;
|
|
unsigned long timeout = 0;
|
|
|
|
AWRAWNAND_TRACE_NFC("Enter %s\n", __func__);
|
|
|
|
timeout = jiffies + msecs_to_jiffies(60000);
|
|
|
|
do {
|
|
val = readl(host->nfc_reg.sta);
|
|
if (chip->selected_chip.chip_no != -1) {
|
|
#ifndef DBG_NFC
|
|
ret = ((val & NFC_RB_STATE(chip->selected_chip.ceinfo[chip_no].relate_rb_no)));
|
|
#else
|
|
ret = ((val & NFC_RB_STATE(chip->selected_chip.ceinfo[chip_no].relate_rb_no))
|
|
&& (val & NFC_RB_B2R));
|
|
#endif
|
|
if (ret) {
|
|
break;
|
|
} else
|
|
cond_resched();
|
|
}
|
|
} while (time_before(jiffies, timeout));
|
|
|
|
AWRAWNAND_TRACE_NFC("Exit %s %s status[%p:%x]\n", __func__, ret ? "ready" : "busy",
|
|
host->nfc_reg.sta, readl(host->nfc_reg.sta));
|
|
return ret ? true : false;
|
|
}
|
|
|
|
static bool aw_host_nfc_rb_ready(struct aw_nand_chip *chip, struct aw_nand_host *host)
|
|
{
|
|
int ret = 0;
|
|
uint32_t val = 0;
|
|
int chip_no = chip->selected_chip.chip_no;
|
|
|
|
AWRAWNAND_TRACE_NFC("Enter %s\n", __func__);
|
|
|
|
AWRAWNAND_TRACE_NFC("selected chip_no:%d rb_no:%d\n", chip_no,
|
|
chip->selected_chip.ceinfo[chip_no].relate_rb_no);
|
|
|
|
val = readl(host->nfc_reg.sta);
|
|
if (chip->selected_chip.chip_no != -1) {
|
|
#ifndef DBG_NFC
|
|
ret = (val & NFC_RB_STATE(chip->selected_chip.ceinfo[chip_no].relate_rb_no));
|
|
#else
|
|
ret = ((val & NFC_RB_STATE(chip->selected_chip.ceinfo[chip_no].relate_rb_no))
|
|
&& (val & NFC_RB_B2R));
|
|
#endif
|
|
}
|
|
AWRAWNAND_TRACE_NFC("Exit %s %s status[%p:%x]\n", __func__, ret ? "ready" : "busy",
|
|
host->nfc_reg.sta, readl(host->nfc_reg.sta));
|
|
return ret ? true : false;
|
|
}
|
|
|
|
|
|
static int aw_host_wait_dma_ready_flag_timeout(struct aw_nand_host *host, uint32_t timeout_ms)
|
|
{
|
|
int ret = -ETIMEDOUT;
|
|
|
|
unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms);
|
|
|
|
do {
|
|
if (host->dma_ready_flag) {
|
|
ret = 0;
|
|
goto out;
|
|
} else
|
|
cond_resched();
|
|
|
|
} while (time_before(jiffies, timeout));
|
|
|
|
if (ret) {
|
|
awrawnand_err("wait dma ready int tiemout status[%p:%x]\n",
|
|
&host->nfc_reg.sta, readl(host->nfc_reg.sta));
|
|
}
|
|
out:
|
|
return ret;
|
|
|
|
}
|
|
static int aw_host_nfc_wait_status_timeout(struct nfc_reg *nfc, uint32_t mark, uint32_t value, uint32_t timeout_ms)
|
|
{
|
|
int ret = -ETIMEDOUT;
|
|
unsigned long timeout = 0;
|
|
|
|
AWRAWNAND_TRACE_NFC("Enter %s\n", __func__);
|
|
|
|
timeout = jiffies + msecs_to_jiffies(timeout_ms);
|
|
|
|
|
|
do {
|
|
if ((readl(nfc->sta) & mark) == value) {
|
|
ret = 0;
|
|
goto out;
|
|
} else
|
|
cond_resched();
|
|
|
|
} while (time_before(jiffies, timeout));
|
|
|
|
if (ret)
|
|
awrawnand_err("wait nfc wait status 10s timeout[%x:%x:%p:%x]\n",
|
|
mark, value, nfc->sta, readl(nfc->sta));
|
|
out:
|
|
AWRAWNAND_TRACE_NFC("Exit %s ret@%d sta[%x:%x]\n", __func__, ret, nfc->sta, readl(nfc->sta));
|
|
return ret;
|
|
}
|
|
static int aw_host_nfc_noraml_op_cmd(struct aw_nand_chip *chip, struct aw_nfc_normal_req *req)
|
|
{
|
|
struct aw_nand_host *host = awnand_chip_to_host(chip);
|
|
struct nfc_reg *nfc = &host->nfc_reg;
|
|
uint32_t cmd_cfg = 0;
|
|
|
|
int ret = 0;
|
|
|
|
AWRAWNAND_TRACE_NFC("Enter %s\n", __func__);
|
|
|
|
if (aw_host_nfc_wait_cmd_fifo_empty(nfc) || aw_host_nfc_wait_fsm_idle(nfc)) {
|
|
awrawnand_warn("cmd@%02x wait fifo or fsm timeout\n",
|
|
req->op.cmd.code);
|
|
ret = -ETIMEDOUT;
|
|
goto out;
|
|
}
|
|
|
|
/*configure first command*/
|
|
cmd_cfg |= req->op.cmd.code;
|
|
cmd_cfg |= NFC_SEND_CMD1;
|
|
|
|
|
|
if (req->wait_rb) {
|
|
cmd_cfg |= NFC_WAIT_FLAG;
|
|
}
|
|
|
|
writel(cmd_cfg, nfc->cmd);
|
|
|
|
AWRAWNAND_TRACE_NFC("cmd_cfg@%x reg_cmd@%p %x\n", cmd_cfg, nfc->cmd, readl(nfc->cmd));
|
|
|
|
ret = aw_host_nfc_wait_cmd_finish(nfc);
|
|
if (ret) {
|
|
awrawnand_err("cmd@%d send fail\n", req->op.cmd.code);
|
|
}
|
|
|
|
|
|
AWRAWNAND_TRACE_NFC("Exit %s\n", __func__);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int aw_host_nfc_noraml_op_cmd_with_addr(struct aw_nand_chip *chip, struct aw_nfc_normal_req *req)
|
|
{
|
|
struct aw_nand_host *host = awnand_chip_to_host(chip);
|
|
struct nfc_reg *nfc = &host->nfc_reg;
|
|
uint32_t low = 0;
|
|
uint32_t high = 0;
|
|
uint32_t cmd_cfg = 0;
|
|
|
|
int ret = 0;
|
|
AWRAWNAND_TRACE_NFC("Enter %s\n", __func__);
|
|
|
|
if (aw_host_nfc_wait_cmd_fifo_empty(nfc) || aw_host_nfc_wait_fsm_idle(nfc)) {
|
|
awrawnand_warn("cmd@%02x wait fifo or fsm timeout\n",
|
|
req->op.cmd.code);
|
|
ret = -ETIMEDOUT;
|
|
goto out;
|
|
}
|
|
|
|
/*configure first command*/
|
|
cmd_cfg |= req->op.cmd_with_addr.code;
|
|
cmd_cfg |= NFC_SEND_CMD1;
|
|
|
|
|
|
if (req->wait_rb) {
|
|
cmd_cfg |= NFC_WAIT_FLAG;
|
|
}
|
|
|
|
/*configure address*/
|
|
noraml_req_get_addr(req, &low, &high);
|
|
writel(low, nfc->addr_low);
|
|
if (req->op.cmd_with_addr.addr_cycles > 4)
|
|
writel(high, nfc->addr_high);
|
|
/*configure send's address number*/
|
|
cmd_cfg |= NFC_ADR_NUM(req->op.cmd_with_addr.addr_cycles);
|
|
cmd_cfg |= NFC_SEND_ADR;
|
|
|
|
writel(cmd_cfg, nfc->cmd);
|
|
AWRAWNAND_TRACE_NFC("cmd_cfg@%d cmd[%p:%x] addr_low:[%p:%x] addr_high:[%p:%x]\n", cmd_cfg, nfc->cmd, readl(nfc->cmd), nfc->addr_low, readl(nfc->addr_low), nfc->addr_high, readl(nfc->addr_high));
|
|
|
|
ret = aw_host_nfc_wait_cmd_finish(nfc);
|
|
if (ret) {
|
|
awrawnand_err("cmd@%d send fail\n", req->op.cmd.code);
|
|
}
|
|
|
|
AWRAWNAND_TRACE_NFC("Exit %s\n", __func__);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int aw_host_nfc_noraml_op_cmd_with_addr_data(struct aw_nand_chip *chip, struct aw_nfc_normal_req *req)
|
|
{
|
|
struct aw_nand_host *host = awnand_chip_to_host(chip);
|
|
struct nfc_reg *nfc = &host->nfc_reg;
|
|
uint32_t cmd_cfg = 0;
|
|
uint32_t ctl_cfg = 0;
|
|
uint32_t low = 0;
|
|
uint32_t high = 0;
|
|
|
|
int ret = 0;
|
|
|
|
AWRAWNAND_TRACE_NFC("Enter %s\n", __func__);
|
|
if (unlikely(req->op.cmd_with_addr_data.len > SZ_1K))
|
|
goto out_exceed_fail;
|
|
|
|
if (unlikely(req->op.cmd_with_addr_data.len == 0))
|
|
goto out_do_nothing;
|
|
|
|
if (unlikely(req->op.cmd_with_addr_data.in == NULL)
|
|
&& unlikely(req->op.cmd_with_addr_data.out == NULL))
|
|
goto out_invalid_paramter;
|
|
|
|
if (aw_host_nfc_wait_cmd_fifo_empty(nfc) || aw_host_nfc_wait_fsm_idle(nfc)) {
|
|
ret = -ETIMEDOUT;
|
|
goto out_fifo_fsm_fail;
|
|
}
|
|
|
|
/*configure first command*/
|
|
cmd_cfg |= req->op.cmd_with_addr_data.code;
|
|
cmd_cfg |= NFC_SEND_CMD1;
|
|
|
|
|
|
if (req->wait_rb) {
|
|
cmd_cfg |= NFC_WAIT_FLAG;
|
|
}
|
|
|
|
/*configure address*/
|
|
noraml_req_get_addr(req, &low, &high);
|
|
low = (req->op.cmd_with_addr_data.addr[0] | (req->op.cmd_with_addr_data.addr[1] << 8)
|
|
| (req->op.cmd_with_addr_data.addr[2] << 16) |
|
|
(req->op.cmd_with_addr_data.addr[3] << 24));
|
|
writel(low, nfc->addr_low);
|
|
if (req->op.cmd_with_addr.addr_cycles > 4) {
|
|
high = req->op.cmd_with_addr_data.addr[5];
|
|
writel(high, nfc->addr_high);
|
|
}
|
|
|
|
/*configure send's address number*/
|
|
if (req->op.cmd_with_addr_data.addr_cycles) {
|
|
cmd_cfg |= NFC_ADR_NUM(req->op.cmd_with_addr_data.addr_cycles);
|
|
cmd_cfg |= NFC_SEND_ADR;
|
|
}
|
|
|
|
|
|
writel((req->op.cmd_with_addr_data.len & 0x3ff), nfc->cnt);
|
|
|
|
ctl_cfg = readl(nfc->ctl);
|
|
ctl_cfg &= ~NFC_RAM_METHOD_DMA;
|
|
writel(ctl_cfg, nfc->ctl);
|
|
|
|
|
|
if (req->op.cmd_with_addr_data.direct == FLASH_WRITE) {
|
|
cmd_cfg |= NFC_ACCESS_DIR;
|
|
memcpy_toio(nfc->ram0_base, req->op.cmd_with_addr_data.out,
|
|
req->op.cmd_with_addr_data.len);
|
|
}
|
|
|
|
|
|
cmd_cfg |= NFC_DATA_TRANS;
|
|
writel(cmd_cfg, nfc->cmd);
|
|
|
|
AWRAWNAND_TRACE_NFC("cmd_cfg@%x cmd[%p:%x] ctl[%p:%x] cnt[%p:%x]\n",
|
|
cmd_cfg, nfc->cmd, readl(nfc->cmd),
|
|
nfc->ctl, readl(nfc->ctl), nfc->cnt, readl(nfc->cnt));
|
|
|
|
ret = aw_host_nfc_wait_cmd_finish(nfc);
|
|
if (ret)
|
|
goto out_read_data_fail;
|
|
|
|
|
|
if ((req->op.cmd_with_addr_data.direct == FLASH_READ) && req->op.cmd_with_addr_data.in) {
|
|
/*AWRAWNAND_TRACE_NFC("ram:%x %x\n", readl(nfc->ram0_base), readl(nfc->ram0_base + 1));*/
|
|
memcpy_fromio(req->op.cmd_with_addr_data.in, nfc->ram0_base,
|
|
req->op.cmd_with_addr_data.len);
|
|
}
|
|
|
|
|
|
AWRAWNAND_TRACE_NFC("Exit %s\n", __func__);
|
|
return ret;
|
|
|
|
out_exceed_fail:
|
|
awrawnand_err("req len@%d exceed @%d\n", req->op.cmd_with_addr_data.len, SZ_1K);
|
|
return -EINVAL;
|
|
out_do_nothing:
|
|
awrawnand_err("req len@0 , do nothing\n");
|
|
return 0;
|
|
out_invalid_paramter:
|
|
awrawnand_err("invalid parameter\n");
|
|
return -EINVAL;
|
|
out_read_data_fail:
|
|
awrawnand_err("read data fail\n");
|
|
return ret;
|
|
out_fifo_fsm_fail:
|
|
awrawnand_err("fifo or fsm is not empty\n");
|
|
return ret;
|
|
}
|
|
|
|
static int aw_host_nfc_normal_op(struct aw_nand_chip *chip, struct aw_nfc_normal_req *req)
|
|
{
|
|
int ret = 0;
|
|
|
|
AWRAWNAND_TRACE_NFC("Enter %s req type@%d\n", __func__, req->type);
|
|
|
|
switch (req->type) {
|
|
case CMD:
|
|
ret = aw_host_nfc_noraml_op_cmd(chip, req);
|
|
break;
|
|
case CMD_WITH_ADDR:
|
|
ret = aw_host_nfc_noraml_op_cmd_with_addr(chip, req);
|
|
break;
|
|
case CMD_WITH_ADDR_DATA:
|
|
ret = aw_host_nfc_noraml_op_cmd_with_addr_data(chip, req);
|
|
break;
|
|
default:
|
|
awrawnand_err("don't support normal req type@%d\n", req->type);
|
|
break;
|
|
}
|
|
|
|
AWRAWNAND_TRACE_NFC("Exit %s\n", __func__);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int aw_host_nfc_dma_config_start(struct aw_nand_host *host, uint8_t rw, void *addr, unsigned int len)
|
|
{
|
|
|
|
struct nfc_reg *nfc = &host->nfc_reg;
|
|
uint32_t cfg = 0;
|
|
enum dma_data_direction dir = rw ? DMA_TO_DEVICE : DMA_BIDIRECTIONAL;
|
|
int ret = 0;
|
|
|
|
/*aw_host_flush_dcache(addr, len);*/
|
|
AWRAWNAND_TRACE_NFC("Enter %s rw@%d addr@%p len@%u\n", __func__, rw, addr, len);
|
|
|
|
if (host->dma_type == MBUS_DMA) {
|
|
host->dma_addr = dma_map_single(host->dev, addr, len, dir);
|
|
if (dma_mapping_error(host->dev, host->dma_addr)) {
|
|
awrawnand_err("dma mapping err\n");
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
/*config use mbus dma*/
|
|
cfg = readl(nfc->ctl);
|
|
/*use dma*/
|
|
cfg &= ~NFC_DMA_TYPE;
|
|
/*use mbus dma*/
|
|
cfg |= NFC_RAM_METHOD_DMA;
|
|
writel(cfg, nfc->ctl);
|
|
|
|
host->nfc_dma_desc_cpu[0].bcnt = 0;
|
|
host->nfc_dma_desc_cpu[0].bcnt |= NFC_DESC_BSIZE(len);
|
|
host->nfc_dma_desc_cpu[0].buff = (unsigned int)host->dma_addr;
|
|
|
|
host->nfc_dma_desc_cpu[0].cfg = 0;
|
|
host->nfc_dma_desc_cpu[0].cfg |= NFC_DESC_FIRST_FLAG;
|
|
host->nfc_dma_desc_cpu[0].cfg |= NFC_DESC_LAST_FLAG;
|
|
host->nfc_dma_desc_cpu[0].next = &host->nfc_dma_desc_cpu[0];
|
|
|
|
host->desc_addr = dma_map_single(host->dev, (void *)&host->nfc_dma_desc_cpu[0], 1024, DMA_TO_DEVICE);
|
|
if (dma_mapping_error(host->dev, host->desc_addr)) {
|
|
awrawnand_err("dma mapping err\n");
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (host->use_dma_int)
|
|
aw_host_nfc_dma_int_enable(&host->nfc_reg);
|
|
|
|
writel((uint32_t)host->desc_addr, nfc->mbus_dma_dlba);
|
|
|
|
} else {
|
|
; /*to do*/
|
|
}
|
|
|
|
out:
|
|
AWRAWNAND_TRACE_NFC("Exit %s\n", __func__);
|
|
return 0;
|
|
|
|
}
|
|
|
|
static void aw_host_rb_wake_up(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void aw_host_dma_wake_up(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
void aw_host_nfc_do_nand_interrupt(void)
|
|
{
|
|
struct aw_nand_host *host = &aw_host;
|
|
if (aw_host_nfc_rb_b2r_int_occur_check(&host->nfc_reg)) {
|
|
aw_host_nfc_rb_b2r_intstatus_clear(&host->nfc_reg);
|
|
aw_host_nfc_rb_b2r_int_disable(&host->nfc_reg);
|
|
host->rb_ready_flag = 1;
|
|
aw_host_rb_wake_up();
|
|
}
|
|
|
|
if (host->use_dma_int) {
|
|
if (aw_host_nfc_dma_int_occur_check(&host->nfc_reg)) {
|
|
aw_host_nfc_dma_intstatus_clear(&host->nfc_reg);
|
|
aw_host_nfc_dma_int_disable(&host->nfc_reg);
|
|
host->dma_ready_flag = 1;
|
|
aw_host_dma_wake_up();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static int aw_host_nfc_dma_wait_end(struct aw_nand_host *host, uint8_t rw, void *addr, unsigned int len)
|
|
{
|
|
int ret = 0;
|
|
enum dma_data_direction dir = rw ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
|
|
|
|
AWRAWNAND_TRACE_NFC("Enter %s\n", __func__);
|
|
if (host->use_dma_int) {
|
|
if (host->dma_ready_flag == 1)
|
|
goto dma_int_end;
|
|
if (!aw_host_wait_dma_ready_flag_timeout(host, 10000))
|
|
goto dma_int_end;
|
|
|
|
}
|
|
|
|
ret = aw_host_nfc_wait_status_timeout(&host->nfc_reg,
|
|
NFC_DMA_INT_FLAG, NFC_DMA_INT_FLAG, 60000);
|
|
if (ret)
|
|
aw_nfc_reg_dump(&host->nfc_reg);
|
|
|
|
if (host->dma_ready_flag == 1)
|
|
host->dma_ready_flag = 0;
|
|
else {
|
|
aw_host_nfc_dma_intstatus_clear(&host->nfc_reg);
|
|
aw_host_nfc_dma_int_disable(&host->nfc_reg);
|
|
}
|
|
|
|
dma_int_end:
|
|
|
|
dma_unmap_single(host->dev, host->dma_addr, len, dir);
|
|
dma_unmap_single(host->dev, host->desc_addr, 1024, DMA_TO_DEVICE);
|
|
|
|
AWRAWNAND_TRACE_NFC("Exit %s\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
/*len is align to ecc block size(1KB)*/
|
|
static int aw_host_nfc_check_ecc_status(struct nfc_reg *nfc, uint32_t len)
|
|
{
|
|
|
|
struct aw_nand_host *host = awnand_nfc_to_host(nfc);
|
|
|
|
uint8_t ecc_limit = ecc_limit_tab[NFC_ECC_GET(readl(nfc->ecc_ctl))];
|
|
uint32_t ecc_block_cnt = B_TO_KB(len);
|
|
uint32_t ecc_block_mask = ((1 << ecc_block_cnt) - 1);
|
|
uint32_t ecc_cnt_w[MAX_ERR_CNT];
|
|
uint8_t ecc_cnt = 0;
|
|
int i = 0;
|
|
AWRAWNAND_TRACE_NFC("Enter %s len@%d\n", __func__, len);
|
|
|
|
if (readl(nfc->ecc_sta) & ecc_block_mask) {
|
|
awrawnand_err("status[%p:%x]\n", nfc->ecc_sta, readl(nfc->ecc_sta));
|
|
aw_nfc_reg_dump(nfc);
|
|
return ECC_ERR;
|
|
}
|
|
|
|
/*check ecc limit*/
|
|
for (i = 0; i < MAX_ERR_CNT; i++) {
|
|
ecc_cnt_w[i] = readl(nfc->err_cnt[i]);
|
|
}
|
|
|
|
for (i = 0; i < ecc_block_cnt; i++) {
|
|
ecc_cnt = (uint8_t)(ecc_cnt_w[i >> 2] >> ((i % 4) << 3));
|
|
if (ecc_cnt > ecc_limit) {
|
|
AWRAWNAND_TRACE_NFC("Exit %s ret@ECC_LIMIT\n", __func__);
|
|
host->bitflips = ecc_cnt;
|
|
awrawnand_info("ecc limit@%d\n", ecc_cnt);
|
|
return ECC_LIMIT;
|
|
}
|
|
}
|
|
|
|
host->bitflips = ecc_cnt;
|
|
AWRAWNAND_TRACE_NFC("Exit %s ret@ECC_GOOG\n", __func__);
|
|
return ECC_GOOD;
|
|
}
|
|
|
|
static bool aw_host_nfc_is_blank_page(struct nfc_reg *nfc, uint32_t len)
|
|
{
|
|
uint32_t ecc_block_cnt = B_TO_KB(len);
|
|
uint32_t ecc_block_mask = ((1 << ecc_block_cnt) - 1);
|
|
|
|
if ((readl(nfc->ecc_ctl) & NFC_ECC_EXCEPTION) &&
|
|
(!(readl(nfc->ecc_sta) & ecc_block_mask)) &&
|
|
((readl(nfc->pat_id) & ecc_block_mask) == ecc_block_mask))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static void aw_host_nfc_get_spare_data(struct nfc_reg *nfc, uint8_t *spare, uint8_t len)
|
|
{
|
|
uint8_t cnt = (len >> 2);
|
|
uint8_t i = 0;
|
|
uint32_t val = 0;
|
|
|
|
if (likely(spare)) {
|
|
for (i = 0; i < cnt; i++) {
|
|
val = readl(nfc->user_data_base + i);
|
|
spare[i * 4 + 0] = ((val >> 0) & 0xff);
|
|
spare[i * 4 + 1] = ((val >> 8) & 0xff);
|
|
spare[i * 4 + 2] = ((val >> 16) & 0xff);
|
|
spare[i * 4 + 3] = ((val >> 24) & 0xff);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int aw_host_nfc_get_dummy_byte(int real_page_size, int ecc_mode, int ecc_block_cnt, int user_data_len)
|
|
{
|
|
int ecc_code_size = 0;
|
|
int valid_size = 0;
|
|
int dummy_byte = 0;
|
|
uint8_t ecc_bits_per_ecc_block = 0;
|
|
|
|
AWRAWNAND_TRACE_NFC("Enter %s [%d:%d:%d:%d]\n", __func__, real_page_size, ecc_mode, ecc_block_cnt, user_data_len);
|
|
|
|
ecc_bits_per_ecc_block = ecc_bits_tbl[ecc_mode];
|
|
ecc_code_size = ((14 * ecc_bits_per_ecc_block / 8) * ecc_block_cnt);
|
|
/*ecc block size : 1024Byte*/
|
|
valid_size = ecc_code_size + user_data_len + (ecc_block_cnt << 10);
|
|
dummy_byte = real_page_size - valid_size;
|
|
AWRAWNAND_TRACE_NFC("Exit %s dummy_byte@%d\n", __func__, dummy_byte);
|
|
|
|
return dummy_byte;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static void dump_batch_req(struct aw_nfc_batch_req *req)
|
|
{
|
|
printk("req type@%d\n", req->type);
|
|
printk("req layout@%d\n", req->layout);
|
|
printk("req plane@%d\n", req->plane);
|
|
printk("req first cmd@%x\n", req->cmd.val.first);
|
|
printk("req snd cmd@%x\n", req->cmd.val.snd);
|
|
printk("req rnd1 cmd@%x\n", req->cmd.val.rnd1);
|
|
printk("req rnd2 cmd@%x\n", req->cmd.val.rnd2);
|
|
printk("req addr page@%u\n", req->addr.page);
|
|
printk("req addr cycles@%d\n", req->addr.row_cycles);
|
|
printk("req data type@%d\n", req->data.type);
|
|
printk("req data main len@%d\n", req->data.main_len);
|
|
printk("req data spare len@%d\n", req->data.spare_len);
|
|
printk("req data main@%p\n", req->data.main);
|
|
printk("req data spare@%p\n", req->data.spare);
|
|
}
|
|
#endif
|
|
|
|
|
|
static int aw_host_nfc_batch_op(struct aw_nand_chip *chip, struct aw_nfc_batch_req *req)
|
|
{
|
|
struct aw_nand_host *host = awnand_chip_to_host(chip);
|
|
struct nfc_reg *nfc = &host->nfc_reg;
|
|
/*int rb_no = aw_host_nfc_get_selected_rb_no(nfc);*/
|
|
uint32_t page = req->addr.page;
|
|
uint32_t page_in_block = page & chip->pages_per_blk_mask;
|
|
|
|
int ret = 0;
|
|
uint32_t val = 0;
|
|
uint8_t col1 = 0, col2 = 0, row1 = 0, row2 = 0, row3 = 0;
|
|
uint32_t low = 0, high = 0;
|
|
uint32_t read_set0 = 0;
|
|
uint32_t cmd = 0;
|
|
uint32_t ecc_block = 0, ecc_block_bitmap = 0;
|
|
uint8_t spare[MAX_SPARE_SIZE];
|
|
int dummy_byte = 0;
|
|
uint8_t default_spare[MAX_SPARE_SIZE];
|
|
int real_pagesize = chip->real_pagesize;
|
|
int ecc_mode = chip->ecc_mode;
|
|
int w_ecc_block_cnt = B_TO_KB(chip->pagesize);
|
|
int w_user_data_len = chip->avalid_sparesize;
|
|
|
|
|
|
AWRAWNAND_TRACE_NFC("Enter %s\n", __func__);
|
|
/*dump_batch_req(req);*/
|
|
|
|
if (req->data.type == ONLY_SPARE) {
|
|
/*data layout interleave mode, ecc and oob store in spare area
|
|
* need to read main data and spare data to do ecc,though user only read spare*/
|
|
req->data.main_len = SZ_1K;
|
|
ecc_block = B_TO_KB(chip->pagesize);
|
|
ecc_block_bitmap = ((1 << ecc_block) - 1);
|
|
/*one ecc_block can attach to 32 bytes*/
|
|
if (req->data.spare_len < 32)
|
|
ecc_block_bitmap = 0x1;
|
|
/*to do : ecc_block_bitmap from req.data.spare_len*/
|
|
} else {
|
|
ecc_block = B_TO_KB(req->data.main_len);
|
|
ecc_block_bitmap = ((1 << ecc_block) - 1);
|
|
}
|
|
|
|
val = readl(nfc->sta);
|
|
val &= (NFC_RB_B2R | NFC_CMD_INT_FLAG | NFC_DMA_INT_FLAG);
|
|
val |= readl(nfc->sta);
|
|
writel(val, nfc->sta);
|
|
|
|
/*set ecc mode, randomizer,exception,pipeline*/
|
|
if (!chip->operate_boot0) {
|
|
aw_host_nfc_set_ecc_mode(nfc, chip->ecc_mode);
|
|
aw_host_nfc_ecc_enable(nfc, 1);
|
|
if (chip->random) {
|
|
aw_host_nfc_randomize_enable(nfc, page_in_block);
|
|
} else
|
|
aw_host_nfc_randomize_disable(nfc);
|
|
} else {
|
|
aw_host_nfc_set_ecc_mode(nfc, chip->boot0_ecc_mode);
|
|
aw_host_nfc_ecc_enable(nfc, 1);
|
|
aw_host_nfc_randomize_enable(nfc, page);
|
|
}
|
|
|
|
/*configure addr*/
|
|
row1 = (page & 0xff);
|
|
row2 = ((page >> 8) & 0xff);
|
|
row3 = ((page >> 16) & 0xff);
|
|
|
|
low = (col1 | (col2 << 8) | (row1 << 16) | (row2 << 24));
|
|
high |= row3;
|
|
|
|
writel(low, nfc->addr_low);
|
|
writel(high, nfc->addr_high);
|
|
|
|
cmd |= NFC_ADR_NUM((req->addr.row_cycles + 2));
|
|
cmd |= NFC_SEND_ADR;
|
|
|
|
|
|
cmd |= NFC_SEND_CMD2;
|
|
cmd |= NFC_SEND_CMD1;
|
|
cmd |= NFC_DATA_TRANS;
|
|
|
|
/*default:batch mode use dma*/
|
|
if (req->data.type != ONLY_SPARE)
|
|
cmd |= NFC_DATA_SWAP_METHOD;
|
|
|
|
if (req->layout == SEQUENCE) {
|
|
cmd |= NFC_SEQ;
|
|
}
|
|
|
|
cmd |= NFC_BATCH_OP;
|
|
/*configure read command set*/
|
|
if (req->type == FLASH_READ) {
|
|
/*cmd |= req->cmd.r.READ0;*/
|
|
cmd |= req->cmd.val.first;
|
|
cmd |= NFC_WAIT_FLAG;
|
|
|
|
/*
|
|
*read_set0 = ((req->cmd.r.READSTART << 0) | (req->cmd.r.RNOUT << 8) |
|
|
* (req->cmd.r.RNOUTSTART << 16));
|
|
*/
|
|
|
|
read_set0 = ((req->cmd.val.snd << 0) | (req->cmd.val.rnd1 << 8) |
|
|
(req->cmd.val.rnd2 << 16));
|
|
writel(read_set0, nfc->read_cmd_set);
|
|
|
|
/*configure user data len*/
|
|
if (req->data.spare_len) {
|
|
aw_host_nfc_set_user_data_len(nfc, req->data.spare_len);
|
|
memset(default_spare, 0x99, req->data.spare_len);
|
|
aw_host_nfc_set_user_data(nfc, default_spare, req->data.spare_len);
|
|
} else {
|
|
aw_host_nfc_set_user_data_len(nfc, chip->avalid_sparesize);
|
|
memset(default_spare, 0x99, chip->avalid_sparesize);
|
|
aw_host_nfc_set_user_data(nfc, default_spare, chip->avalid_sparesize);
|
|
}
|
|
|
|
} else {
|
|
|
|
/*configure user data len*/
|
|
if (req->data.spare_len) {
|
|
memset(spare, 0xff, MAX_SPARE_SIZE);
|
|
/*data.spare_len should be less than MAX_SPARE_SIZE or equal*/
|
|
memcpy(spare, req->data.spare, req->data.spare_len);
|
|
/*avalid_sparesize maximum equal to MAX_SPARE_SIZE*/
|
|
aw_host_nfc_set_user_data(nfc, spare, req->data.spare_len);
|
|
} else {
|
|
aw_host_nfc_set_user_data(nfc, host->spare_default, chip->avalid_sparesize);
|
|
}
|
|
|
|
if (!chip->operate_boot0)
|
|
aw_host_nfc_set_user_data_len(nfc, req->data.spare_len);
|
|
else
|
|
aw_host_nfc_set_boot0_user_data_len(nfc, req->data.spare_len);
|
|
|
|
cmd |= NFC_ACCESS_DIR;
|
|
/*cmd |= req->cmd.w.SEQIN;*/
|
|
cmd |= req->cmd.val.first;
|
|
|
|
/*writel((req->cmd.w.PAGEPROG | (req->cmd.w.RNDIN << 8)), nfc->write_cmd_set);*/
|
|
writel((req->cmd.val.snd | (req->cmd.val.rnd1 << 8)), nfc->write_cmd_set);
|
|
|
|
dummy_byte = aw_host_nfc_get_dummy_byte(real_pagesize, ecc_mode, w_ecc_block_cnt, w_user_data_len);
|
|
if (dummy_byte > 0)
|
|
aw_host_nfc_set_dummy_byte(nfc, dummy_byte);
|
|
|
|
}
|
|
|
|
if (aw_host_nfc_wait_cmd_fifo_empty(nfc) || aw_host_nfc_wait_fsm_idle(nfc)) {
|
|
ret = -ETIMEDOUT;
|
|
awrawnand_err("fifo or fsm is not empty\n");
|
|
goto out_err;
|
|
}
|
|
|
|
|
|
if (req->data.type != ONLY_SPARE) {
|
|
aw_host_nfc_dma_config_start(host, req->type, req->data.main, req->data.main_len);
|
|
/*write command*/
|
|
writel(ecc_block_bitmap, nfc->data_block_mask);
|
|
writel(cmd, nfc->cmd);
|
|
/*aw_nfc_reg_dump(nfc);*/
|
|
ret = aw_host_nfc_dma_wait_end(host, req->type, req->data.main, req->data.main_len);
|
|
if (ret) {
|
|
awrawnand_err("%s wait dma end fail\n", __func__);
|
|
goto out_err;
|
|
}
|
|
} else {
|
|
writel(ecc_block_bitmap, nfc->data_block_mask);
|
|
/*write command*/
|
|
writel(cmd, nfc->cmd);
|
|
}
|
|
|
|
ret = aw_host_nfc_wait_cmd_finish(nfc);
|
|
if (ret) {
|
|
awrawnand_err("wait cmd finish fail\n");
|
|
goto out_err;
|
|
}
|
|
|
|
if (host->use_rb_int) {
|
|
/*to do*/
|
|
} else {
|
|
ret = aw_host_nfc_wait_rb_ready(chip, host);
|
|
if (!ret) {
|
|
awrawnand_err("wait rb ready fail\n");
|
|
aw_nfc_reg_dump(nfc);
|
|
ret = -ETIMEDOUT;
|
|
goto out_err;
|
|
}
|
|
ret = 0;
|
|
}
|
|
|
|
if (req->type == FLASH_READ) {
|
|
ret = aw_host_nfc_check_ecc_status(nfc, req->data.main_len);
|
|
if (ret == ECC_GOOD) {
|
|
int len = req->data.main_len ? req->data.main_len : req->data.spare_len;
|
|
len = ((len < 1024) ? 1024 : len);
|
|
if (aw_host_nfc_is_blank_page(nfc, len)) {
|
|
AWRAWNAND_TRACE_NFC("%s-%d page%d is blank\n", __func__, __LINE__, page);
|
|
memset(req->data.main, 0xff, req->data.main_len);
|
|
memset(req->data.spare, 0xff, req->data.spare_len);
|
|
} else {
|
|
aw_host_nfc_get_spare_data(nfc, spare, MAX_SPARE_SIZE);
|
|
memcpy(req->data.spare, spare, req->data.spare_len);
|
|
}
|
|
} else {
|
|
aw_host_nfc_get_spare_data(nfc, spare, MAX_SPARE_SIZE);
|
|
memcpy(req->data.spare, spare, req->data.spare_len);
|
|
}
|
|
|
|
chip->bitflips = host->bitflips;
|
|
|
|
aw_host_nfc_ecc_disable(nfc);
|
|
if (chip->random) {
|
|
aw_host_nfc_randomize_disable(nfc);
|
|
}
|
|
} else {
|
|
aw_host_nfc_ecc_disable(nfc);
|
|
if (chip->random) {
|
|
aw_host_nfc_randomize_disable(nfc);
|
|
}
|
|
aw_host_nfc_set_dummy_byte(nfc, 0);
|
|
}
|
|
|
|
|
|
AWRAWNAND_TRACE_NFC("Exit %s\n", __func__);
|
|
return ret;
|
|
|
|
out_err:
|
|
aw_host_nfc_ecc_disable(nfc);
|
|
if (chip->random) {
|
|
aw_host_nfc_randomize_disable(nfc);
|
|
}
|
|
if (req->type == FLASH_WRITE)
|
|
aw_host_nfc_set_dummy_byte(nfc, 0);
|
|
AWRAWNAND_TRACE_NFC("Exit %s\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
static int aw_host_set_mdclk(struct aw_nand_host *host, uint32_t mdclk)
|
|
{
|
|
long rate = 0;
|
|
int ret = 0;
|
|
|
|
/*ndfc frequency division inside, so if external flash frequency is n, should set ndfc clk src 2n*/
|
|
rate = clk_round_rate(host->mdclk, 2 * mdclk * 1000000);
|
|
|
|
ret = clk_set_rate(host->mdclk, rate);
|
|
if (ret)
|
|
awrawnand_err("set nand0 clk@%u MHz fail\n", mdclk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int aw_host_set_mcclk(struct aw_nand_host *host, uint32_t mcclk)
|
|
{
|
|
long rate = 0;
|
|
int ret = 0;
|
|
|
|
rate = clk_round_rate(host->mcclk, mcclk * 1000000);
|
|
|
|
ret = clk_set_rate(host->mcclk, rate);
|
|
if (ret)
|
|
awrawnand_err("set nand0 clk@%u MHz fail\n", mcclk);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
static int aw_host_clk_init(struct aw_nand_host *host)
|
|
{
|
|
int ret = 0;
|
|
long rate = 0;
|
|
|
|
rate = clk_get_rate(host->pclk);
|
|
awrawnand_dbg("get pll rate@%ld Hz\n", rate);
|
|
|
|
|
|
ret = clk_set_parent(host->mdclk, host->pclk);
|
|
if (ret) {
|
|
awrawnand_err("set nand0 clk parent to pll fail\n");
|
|
goto out;
|
|
}
|
|
|
|
rate = clk_round_rate(host->mdclk, host->mdclk_val * 1000000);
|
|
awrawnand_info("rate@%ld\n", rate);
|
|
ret = clk_set_rate(host->mdclk, rate);
|
|
if (ret) {
|
|
awrawnand_err("set nand0 rate@%ld fail\n", rate);
|
|
goto out;
|
|
}
|
|
|
|
ret = clk_prepare_enable(host->mdclk);
|
|
if (ret) {
|
|
awrawnand_err("enable nand0 clk fail\n");
|
|
goto out;
|
|
}
|
|
|
|
ret = clk_set_parent(host->mcclk, host->pclk);
|
|
if (ret) {
|
|
awrawnand_err("set nand0 ecc engine clk parent to pll fail\n");
|
|
goto out;
|
|
}
|
|
|
|
rate = clk_round_rate(host->mcclk, host->mcclk_val * 1000000);
|
|
ret = clk_set_rate(host->mdclk, rate);
|
|
if (ret) {
|
|
awrawnand_err("set nand0 ecc engine rate@%ld fail\n", rate);
|
|
goto out;
|
|
}
|
|
|
|
ret = clk_prepare_enable(host->mcclk);
|
|
if (ret) {
|
|
awrawnand_err("enable nand0 ecc engin clk fail\n");
|
|
goto out;
|
|
}
|
|
|
|
host->clk_rate = host->mdclk_val;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int aw_host_set_clk(struct aw_nand_host *host, uint32_t mdclk,
|
|
uint32_t mcclk)
|
|
{
|
|
int ret = 0;
|
|
|
|
AWRAWNAND_TRACE_NFC("Enter %s [%d:%d]\n", __func__, mdclk, mcclk);
|
|
|
|
|
|
if (host->clk_rate == mdclk)
|
|
goto out;
|
|
|
|
if (!host->init) {
|
|
ret = aw_host_clk_init(host);
|
|
if (ret) {
|
|
awrawnand_err("host clk init fail\n");
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
ret = aw_host_set_mdclk(host, mdclk);
|
|
if (ret) {
|
|
awrawnand_err("set mdclk fail\n");
|
|
goto out;
|
|
}
|
|
|
|
ret = aw_host_set_mcclk(host, mcclk);
|
|
if (ret) {
|
|
awrawnand_err("set mcclk fail\n");
|
|
goto out;
|
|
}
|
|
|
|
host->clk_rate = mdclk;
|
|
|
|
awrawnand_print("awrawnand(mtd):mdclk:%ld mcclk:%ld\n",
|
|
clk_get_rate(host->mdclk), clk_get_rate(host->mcclk));
|
|
out:
|
|
AWRAWNAND_TRACE_NFC("Exit %s\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
static int aw_host_nfc_init(struct nfc_reg *nfc)
|
|
{
|
|
int ret = 0;
|
|
|
|
AWRAWNAND_TRACE_NFC("Enter %s\n", __func__);
|
|
ret = aw_host_nfc_reset(nfc);
|
|
if (ret) {
|
|
awrawnand_err("aw nfc reset fail@%d\n", ret);
|
|
goto out;
|
|
}
|
|
aw_host_nfc_ctl_init(nfc);
|
|
aw_host_nfc_spare_area_init(nfc);
|
|
aw_host_nfc_efr_init(nfc);
|
|
|
|
aw_host_nfc_randomize_disable(nfc);
|
|
aw_host_nfc_timing_init(nfc);
|
|
|
|
AWRAWNAND_TRACE_NFC("Exit %s\n", __func__);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static inline int aw_host_resource_init(struct aw_nand_host *host)
|
|
{
|
|
int ret = 0;
|
|
struct device *dev = host->dev;
|
|
const char *vcc_nand = NULL;
|
|
const char *vcc_io = NULL;
|
|
|
|
ret = of_property_read_string(dev->of_node, "nand0_regulator1",
|
|
&vcc_nand);
|
|
if (ret) {
|
|
awrawnand_err("fail to get vcc nand in device tree\n");
|
|
goto out;
|
|
}
|
|
|
|
host->vcc_nand = regulator_get(NULL, vcc_nand);
|
|
if (IS_ERR_OR_NULL(host->vcc_nand)) {
|
|
awrawnand_err("fail to get regulator vcc-nand\n");
|
|
goto out;
|
|
}
|
|
|
|
ret = of_property_read_string(dev->of_node, "nand0_regulator2",
|
|
&vcc_io);
|
|
if (ret) {
|
|
awrawnand_err("fail to get vcc io in device tree\n");
|
|
goto out;
|
|
}
|
|
|
|
host->vcc_io = regulator_get(NULL, vcc_io);
|
|
if (IS_ERR_OR_NULL(host->vcc_io)) {
|
|
awrawnand_err("fail to get regulator vcc-nand\n");
|
|
goto out;
|
|
}
|
|
|
|
host->pclk = of_clk_get(dev->of_node, 0);
|
|
if (IS_ERR_OR_NULL(host->pclk)) {
|
|
awrawnand_err("fail to get pll clk\n");
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
host->mdclk = of_clk_get(dev->of_node, 1);
|
|
if (IS_ERR_OR_NULL(host->mdclk)) {
|
|
awrawnand_err("fail to get nfc clk\n");
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
host->mcclk = of_clk_get(dev->of_node, 2);
|
|
if (IS_ERR_OR_NULL(host->mcclk)) {
|
|
awrawnand_err("fail to get nfc ecc engine clk\n");
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
host->base = (void __iomem *)of_iomap(dev->of_node, 0);
|
|
if (host->base == NULL) {
|
|
awrawnand_err("fail to get nfc base reg\n");
|
|
goto out;
|
|
}
|
|
|
|
host->mdclk_val = 10;
|
|
host->mcclk_val = 20;
|
|
host->dma_type = MBUS_DMA;
|
|
host->use_dma_int = 0;
|
|
host->spare_default = kzalloc(MAX_SPARE_SIZE, GFP_KERNEL);
|
|
if (!host->spare_default) {
|
|
awrawnand_err("kzalloc spare default fail\n");
|
|
goto out;
|
|
}
|
|
|
|
memset(host->spare_default, 0xff, MAX_SPARE_SIZE);
|
|
|
|
host->nfc_dma_desc_cpu = kmalloc(
|
|
sizeof(struct aw_nfc_dma_desc) * NFC_DMA_DESC_MAX_NUM, GFP_KERNEL);
|
|
if (host->nfc_dma_desc_cpu == NULL) {
|
|
awrawnand_err("kmalloc for dma desc fail\n");
|
|
ret = -ENOMEM;
|
|
kfree(host->spare_default);
|
|
goto out;
|
|
}
|
|
|
|
host->nfc_dma_desc = host->nfc_dma_desc_cpu;
|
|
|
|
host->normal_op = aw_host_nfc_normal_op;
|
|
host->batch_op = aw_host_nfc_batch_op;
|
|
host->rb_ready = aw_host_nfc_rb_ready;
|
|
|
|
aw_nfc_reg_prepare(&host->nfc_reg);
|
|
|
|
AWRAWNAND_TRACE_NFC("bus_gate:%08x\n", host->bus_gate);
|
|
AWRAWNAND_TRACE_NFC("mbus_gate:%08x\n", host->mbus_gate);
|
|
AWRAWNAND_TRACE_NFC("mdclk:%08x\n", host->mdclk);
|
|
AWRAWNAND_TRACE_NFC("mcclk:%08x\n", host->mcclk);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static inline void aw_host_resource_destroy(struct aw_nand_host *host)
|
|
{
|
|
if (host->spare_default)
|
|
kfree(host->spare_default);
|
|
|
|
if (host->nfc_dma_desc_cpu)
|
|
kfree(host->nfc_dma_desc_cpu);
|
|
|
|
|
|
host->nfc_dma_desc = NULL;
|
|
|
|
}
|
|
|
|
static int aw_nfc_pagesize_set(struct nfc_reg *nfc, int pagesize)
|
|
{
|
|
int ret = 0;
|
|
uint32_t cfg = 0;
|
|
AWRAWNAND_TRACE_NFC("Enter %s pagesize@%d\n", __func__, pagesize);
|
|
|
|
cfg = readl(nfc->ctl);
|
|
|
|
cfg &= ~NFC_PAGE_SHIFT_MSK;
|
|
|
|
switch (B_TO_KB(pagesize)) {
|
|
case 1:
|
|
cfg |= NFC_PAGE_SIZE_1KB;
|
|
break;
|
|
case 2:
|
|
cfg |= NFC_PAGE_SIZE_2KB;
|
|
break;
|
|
case 4:
|
|
cfg |= NFC_PAGE_SIZE_4KB;
|
|
break;
|
|
case 8:
|
|
cfg |= NFC_PAGE_SIZE_8KB;
|
|
break;
|
|
case 16:
|
|
cfg |= NFC_PAGE_SIZE_16KB;
|
|
break;
|
|
case 32:
|
|
cfg |= NFC_PAGE_SIZE_32KB;
|
|
break;
|
|
default:
|
|
awrawnand_err("Don't support pagesize@%d\n", pagesize);
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
writel(cfg, nfc->ctl);
|
|
writel(pagesize, nfc->spare_area);
|
|
|
|
AWRAWNAND_TRACE_NFC("Exit %s ctl[%p:%x] spare_area[%p:%x]\n", __func__,
|
|
nfc->ctl, readl(nfc->ctl), nfc->spare_area, readl(nfc->spare_area));
|
|
return ret;
|
|
}
|
|
|
|
static void aw_nfc_ecc_mode_set(struct nfc_reg *nfc, int ecc_mode)
|
|
{
|
|
uint32_t cfg = 0;
|
|
AWRAWNAND_TRACE_NFC("Enter %s ecc_mode@%d\n", __func__, ecc_mode);
|
|
|
|
cfg = readl(nfc->ecc_ctl);
|
|
cfg &= ~NFC_ECC_MODE_MSK;
|
|
cfg |= ecc_mode;
|
|
writel(cfg, nfc->ecc_ctl);
|
|
AWRAWNAND_TRACE_NFC("Exit %s ecc ctl[%p:%x]\n", __func__, nfc->ecc_ctl, readl(nfc->ecc_ctl));
|
|
|
|
}
|
|
|
|
static void aw_nfc_set_interface(struct nfc_reg *nfc, enum rawnand_data_interface_type type,
|
|
uint32_t timing_ctl, uint32_t timing_cfg)
|
|
{
|
|
uint32_t cfg = 0;
|
|
AWRAWNAND_TRACE_NFC("Enter %s type@%x timing_ctl@%x timing_cfg@%x\n", __func__,
|
|
type, timing_ctl, timing_cfg);
|
|
|
|
/*set ctl interface*/
|
|
cfg = readl(nfc->ctl);
|
|
cfg &= ~NFC_DATA_INTERFACE_TYPE_MSK;
|
|
cfg |= (type & 0x3);
|
|
writel(cfg, nfc->ctl);
|
|
|
|
writel(timing_ctl, nfc->timing_ctl);
|
|
writel(timing_cfg, nfc->timing_cfg);
|
|
AWRAWNAND_TRACE_NFC("Exit %s ctl[%p: %x] timing_ctl[%p: %x] timing_cfg[%p: %x]\n",
|
|
__func__, nfc->ctl, nfc->timing_ctl, nfc->timing_cfg);
|
|
}
|
|
|
|
static int aw_host_set_clk_itf_sdr(struct aw_nand_host *host)
|
|
{
|
|
struct aw_nand_chip *chip = awnand_host_to_chip(host);
|
|
struct nfc_reg *nfc = &host->nfc_reg;
|
|
uint32_t mdclk = chip->clk_rate;
|
|
uint32_t mcclk = mdclk * 2;
|
|
uint32_t cfg = 0;
|
|
int ret = 0;
|
|
|
|
AWRAWNAND_TRACE_NFC("Enter %s mdclk@%d mcclk@%d\n", __func__, mdclk, mcclk);
|
|
|
|
/*set NAND FLASH Type*/
|
|
cfg = readl(nfc->ctl);
|
|
cfg &= ~NFC_DATA_INTERFACE_TYPE_MSK;
|
|
cfg |= NFC_DATA_INTERFACE_TYPE_SDR;
|
|
writel(cfg, nfc->ctl);
|
|
|
|
/*set READ PIPE to EDO*/
|
|
cfg = readl(nfc->timing_ctl);
|
|
cfg &= ~NFC_TIMING_CTL_PIPE_MSK;
|
|
cfg |= NFC_TIMING_SDR_EDO;
|
|
writel(cfg, nfc->timing_ctl);
|
|
|
|
ret = aw_host_set_clk(host, mdclk, mcclk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int aw_host_set_interface(struct aw_nand_host *host)
|
|
{
|
|
int ret = 0;
|
|
struct aw_nand_chip *chip = awnand_host_to_chip(host);
|
|
struct mtd_info *mtd = awnand_chip_to_mtd(chip);
|
|
|
|
int c = 0;
|
|
AWRAWNAND_TRACE_NFC("Enter %s\n", __func__);
|
|
|
|
if (RAWNAND_HAS_ONLY_TOGGLE(chip)) {
|
|
/*init toggle ddr interface with classic clock cfg(20MHz)*/
|
|
if (NAND_DATA_ITF_TYPE_TOGGLE_DDR1_2(chip)) {
|
|
ret = aw_host_set_clk(host, 20, 20*2);
|
|
if (ret)
|
|
goto out;
|
|
host->nf_type = RAWNAND_TOGGLE_DDR;
|
|
host->timing_ctl = NFC_TIMING_DDR_PIPE_SEL(0x2);
|
|
host->timing_ctl |= NFC_TIMING_DC_SEL(0x1f);
|
|
|
|
/*1. default value : 0x95
|
|
*2. bit16 tCCS=1 for micron l85a, nvddr-100mHZ*/
|
|
host->timing_cfg = 0x10095;
|
|
|
|
aw_nfc_set_interface(&host->nfc_reg, host->nf_type, host->timing_ctl, host->timing_cfg);
|
|
}
|
|
}
|
|
|
|
if (RAWNAND_NEED_CHANGE_TO_SDR(chip)) {
|
|
int feature_addr = TOGGLE_INTERFACE_CHANGE_ADDR;
|
|
uint8_t p[4] = {1, 0, 0, 0};
|
|
for (c = 0; c < chip->chips; c++) {
|
|
chip->select_chip(mtd, c);
|
|
ret = chip->data_interface.set_feature(chip, feature_addr, p);
|
|
chip->select_chip(mtd, -1);
|
|
}
|
|
}
|
|
|
|
if (RAWNAND_HAS_ITF_SDR(chip)) {
|
|
ret = aw_host_set_clk_itf_sdr(host);
|
|
|
|
}
|
|
if (RAWNAND_HAS_ITF_ONFI_DDR(chip)) {
|
|
/*to do*/
|
|
}
|
|
|
|
if (RAWNAND_HAS_ITF_TOGGLE_DDR(chip)) {
|
|
/*to do*/
|
|
}
|
|
|
|
out:
|
|
AWRAWNAND_TRACE_NFC("Exit %s\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
int aw_host_init_tail(struct aw_nand_host *host)
|
|
{
|
|
|
|
int ret = 0;
|
|
struct aw_nand_chip *chip = awnand_host_to_chip(host);
|
|
int pagesize = 1 << chip->pagesize_shift;
|
|
|
|
/*update pagesize*/
|
|
aw_nfc_pagesize_set(&host->nfc_reg, pagesize);
|
|
/*update ecc mode*/
|
|
aw_nfc_ecc_mode_set(&host->nfc_reg, chip->ecc_mode);
|
|
|
|
ret = aw_host_set_interface(host);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int aw_host_requlator_request(struct aw_nand_host *host)
|
|
{
|
|
if (regulator_enable(host->vcc_nand)) {
|
|
awrawnand_err("request vcc nand fail\n");
|
|
return -EPERM;
|
|
}
|
|
|
|
if (regulator_enable(host->vcc_io)) {
|
|
awrawnand_err("request vcc io fail\n");
|
|
return -EPERM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int aw_host_init(struct device *dev)
|
|
{
|
|
struct aw_nand_host *host = get_host();
|
|
int ret = 0;
|
|
|
|
memset(&aw_host, 0, sizeof(struct aw_nand_host));
|
|
host->priv = &awnand_chip;
|
|
host->init = false;
|
|
host->dev = dev;
|
|
|
|
|
|
ret = aw_host_resource_init(host);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = aw_host_requlator_request(host);
|
|
if (ret)
|
|
goto out;
|
|
|
|
/*set pin*/
|
|
ret = aw_host_set_pin(host);
|
|
if (ret) {
|
|
awrawnand_err("set pin fail err@%d\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
/*set clock*/
|
|
ret = aw_host_set_clk(host, host->mdclk_val, host->mcclk_val);
|
|
if (ret) {
|
|
awrawnand_err("set clk fail err@%d\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
|
|
ret = aw_host_nfc_init(&host->nfc_reg);
|
|
if (ret) {
|
|
awrawnand_err("host nfc fail err@%d\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
host->init = true;
|
|
return ret;
|
|
|
|
out:
|
|
pr_err("%s-%d err@%d fail\n", __func__, __LINE__, ret);
|
|
memset(&aw_host, 0, sizeof(struct aw_nand_host));
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(aw_host_init);
|
|
|
|
void aw_host_exit(struct aw_nand_host *host)
|
|
{
|
|
aw_host_resource_destroy(host);
|
|
}
|
|
EXPORT_SYMBOL_GPL(aw_host_exit);
|