blob: 865b8d815e8bdfa423dec8497e2b87ee765e6b0b [file] [log] [blame] [edit]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdint.h>
#include <time.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <bits/limits.h>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/protocol/platform-bus.h>
#include <ddk/protocol/platform-defs.h>
#include <ddk/protocol/platform-device.h>
#include <ddk/protocol/rawnand.h>
#include <ddk/io-buffer.h>
#include <hw/reg.h>
#include <zircon/assert.h>
#include <zircon/threads.h>
#include <zircon/types.h>
#include <zircon/status.h>
#include <sync/completion.h>
#include <string.h>
#include <soc/aml-common/aml-rawnand.h>
#include "nand.h"
#include "aml_rawnand_api.h"
/*
* Database of settings for the NAND flash devices we support
*/
struct nand_chip_table nand_chip_table[] = {
{ 0xEC, 0xDC, "Samsung", "K9F4G08U0F", { 25, 20, 15 }, true, 512,
0, 0, 0, 0},
};
struct nand_chip_table default_chip =
{
0x00, 0x00, "DEFAULT", "UNKNOWN", { 25, 20, 15 }, true, 512,
0, 0, 0, 0
};
#define NAND_CHIP_TABLE_SIZE \
(sizeof(nand_chip_table)/sizeof(struct nand_chip_table))
/*
* Find the entry in the NAND chip table database based on manufacturer
* id and device id
*/
struct nand_chip_table *
find_nand_chip_table(uint8_t manuf_id, uint8_t device_id)
{
uint32_t i;
for (i = 0 ; i < NAND_CHIP_TABLE_SIZE ; i++)
if (manuf_id == nand_chip_table[i].manufacturer_id &&
device_id == nand_chip_table[i].device_id)
return &nand_chip_table[i];
return NULL;
}
/*
* Generic wait function used by both program (write) and erase
* functionality.
*/
zx_status_t nand_wait(aml_rawnand_t *rawnand, uint32_t timeout_ms)
{
uint64_t total_time = 0;
uint8_t cmd_status;
#if 0
nand_command(rawnand, NAND_CMD_STATUS, -1, -1);
#else
aml_cmd_ctrl(rawnand, NAND_CMD_STATUS,
NAND_CTRL_CLE | NAND_CTRL_CHANGE);
aml_cmd_ctrl(rawnand, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
#endif
while (!((cmd_status = aml_read_byte(rawnand)) & NAND_STATUS_READY)) {
usleep(10);
total_time += 10;
if (total_time > (timeout_ms * 1000)) {
break;
}
}
#if 0
zxlogf(ERROR, "%s: waited %lu usecs\n", __func__, total_time);
zxlogf(ERROR, "%s: cmd_status = %u\n", __func__, cmd_status);
#endif
if (!(cmd_status & NAND_STATUS_READY)) {
zxlogf(ERROR, "nand command wait timed out\n");
return ZX_ERR_TIMED_OUT;
}
if (cmd_status & NAND_STATUS_FAIL) {
zxlogf(ERROR, "%s: nand command returns error\n", __func__);
return ZX_ERR_IO;
}
return ZX_OK;
}
/*
* Send command down to the controller.
*/
void nand_command(aml_rawnand_t *rawnand, unsigned int command,
int column, int page_addr)
{
/* Emulate NAND_CMD_READOOB */
if (command == NAND_CMD_READOOB) {
column += rawnand->writesize;
command = NAND_CMD_READ0;
}
/* Command latch cycle */
aml_cmd_ctrl(rawnand, command, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
if (column != -1 || page_addr != -1) {
int ctrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE;
/* Serially input address */
if (column != -1) {
/* Adjust columns for 16 bit buswidth */
if (rawnand->controller_params.options & NAND_BUSWIDTH_16)
column >>= 1;
aml_cmd_ctrl(rawnand, column, ctrl);
ctrl &= ~NAND_CTRL_CHANGE;
aml_cmd_ctrl(rawnand, column >> 8, ctrl);
}
if (page_addr != -1) {
aml_cmd_ctrl(rawnand, page_addr, ctrl);
aml_cmd_ctrl(rawnand, page_addr >> 8,
NAND_NCE | NAND_ALE);
/* One more address cycle for devices > 128MiB */
if (rawnand->chipsize > 128)
aml_cmd_ctrl(rawnand, page_addr >> 16,
NAND_NCE | NAND_ALE);
}
}
aml_cmd_ctrl(rawnand, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
switch (command) {
case NAND_CMD_ERASE1:
case NAND_CMD_ERASE2:
case NAND_CMD_SEQIN:
case NAND_CMD_PAGEPROG:
return;
case NAND_CMD_RESET:
usleep(rawnand->controller_delay);
aml_cmd_ctrl(rawnand, NAND_CMD_STATUS,
NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
aml_cmd_ctrl(rawnand, NAND_CMD_NONE,
NAND_NCE | NAND_CTRL_CHANGE);
while (!(aml_read_byte(rawnand) & NAND_STATUS_READY))
;
return;
case NAND_CMD_READ0:
aml_cmd_ctrl(rawnand, NAND_CMD_READSTART,
NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
aml_cmd_ctrl(rawnand, NAND_CMD_NONE,
NAND_NCE | NAND_CTRL_CHANGE);
/* This applies to read commands */
default:
usleep(rawnand->controller_delay);
return;
}
}
/*
* Main Read entry point into the NAND. Calls controller specific
* read function.
* data, oob: pointers to user oob/data buffers.
* nandpage : NAND page address to read.
* ecc_correct : Number of ecc corrected bitflips (< 0 indicates
* ecc could not correct all bitflips - caller needs to check that).
* retries : I see read errors (read not completing) reported occasionally
* on different NAND pages. Retry of 3 works around this, in
* fact the first retry addresses the vast majority of the read
* non-completions.
*/
zx_status_t nand_read_page(aml_rawnand_t *rawnand,
void *data,
void *oob,
uint32_t nandpage,
int *ecc_correct,
int retries)
{
zx_status_t status;
retries++;
do {
status = aml_read_page_hwecc(rawnand, data, oob, nandpage,
ecc_correct, false);
if (status != ZX_OK)
zxlogf(ERROR, "%s: Retrying Read@%u\n",
__func__, nandpage);
} while (status != ZX_OK && --retries > 0);
if (status != ZX_OK)
zxlogf(ERROR, "%s: Read error\n", __func__);
return status;
}
zx_status_t nand_read_page0(aml_rawnand_t *rawnand,
void *data,
void *oob,
uint32_t nandpage,
int *ecc_correct,
int retries)
{
zx_status_t status;
retries++;
do {
status = aml_read_page_hwecc(rawnand, data, oob,
nandpage, ecc_correct, true);
} while (status != ZX_OK && --retries > 0);
if (status != ZX_OK)
zxlogf(ERROR, "%s: Read error\n", __func__);
return status;
}
/*
* Main Write entry point into the NAND. Calls controller specific
* write function.
* data, oob: pointers to user oob/data buffers.
* nandpage : NAND page address to read.
*/
zx_status_t nand_write_page(aml_rawnand_t *rawnand,
void *data,
void *oob,
uint32_t nandpage)
{
zx_status_t status;
if (aml_check_write_protect(rawnand)) {
zxlogf(ERROR, "%s: Device is Write Protected, Cannot Erase\n",
__func__);
/* Zircon doesn't have EROFS, this seems closest */
status = ZX_ERR_ACCESS_DENIED;
} else
status = aml_write_page_hwecc(rawnand, data, oob, nandpage, false);
return status;
}
/*
* Main Erase entry point into NAND. Calls controller specific
* erase function.
* nandblock : NAND erase block address.
*/
zx_status_t nand_erase_block(aml_rawnand_t *rawnand,
uint32_t nandpage)
{
zx_status_t status;
if (aml_check_write_protect(rawnand)) {
zxlogf(ERROR, "%s: Device is Write Protected, Cannot Erase\n",
__func__);
/* Zircon doesn't have EROFS, this seems closest */
status = ZX_ERR_ACCESS_DENIED;
} else
status = aml_erase_block(rawnand, nandpage);
return status;
}