blob: eeac32998d49daa1b0e0dea0747cec11f79a9643 [file] [log] [blame]
// 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 <stdlib.h>
#include <unistd.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 <zircon/assert.h>
#include <zircon/threads.h>
#include <zircon/types.h>
#include <zircon/status.h>
#include <sync/completion.h>
#include <string.h>
#include "onfi.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 },
/* TODO: This works. but doublecheck Toshiba nand_timings from datasheet */
{ 0x98, 0xDC, "Toshiba", "TC58NVG2S0F", { 25, 20, /* 15 */ 25 }, 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)
{
for (uint32_t 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;
}
/*
* onfi_wait() and onfi_command() are generic ONFI protocol compliant.
*
* Generic wait function used by both program (write) and erase
* functionality.
*/
zx_status_t onfi_wait(raw_nand_protocol_t *proto, uint32_t timeout_ms)
{
uint64_t total_time = 0;
uint8_t cmd_status;
raw_nand_cmd_ctrl(proto, NAND_CMD_STATUS,
NAND_CTRL_CLE | NAND_CTRL_CHANGE);
raw_nand_cmd_ctrl(proto, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
while (!((cmd_status = raw_nand_read_byte(proto)) & NAND_STATUS_READY)) {
usleep(10);
total_time += 10;
if (total_time > (timeout_ms * 1000)) {
break;
}
}
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 onfi command down to the controller.
*/
void onfi_command(raw_nand_protocol_t *proto, uint32_t command,
int32_t column, int32_t page_addr,
uint32_t capacity_mb, uint32_t controller_delay_us,
int buswidth_16)
{
raw_nand_cmd_ctrl(proto, command, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
if (column != -1 || page_addr != -1) {
uint32_t ctrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE;
if (column != -1) {
/* 16 bit buswidth ? */
if (buswidth_16)
column >>= 1;
raw_nand_cmd_ctrl(proto, column, ctrl);
ctrl &= ~NAND_CTRL_CHANGE;
raw_nand_cmd_ctrl(proto, column >> 8, ctrl);
}
if (page_addr != -1) {
raw_nand_cmd_ctrl(proto, page_addr, ctrl);
raw_nand_cmd_ctrl(proto, page_addr >> 8,
NAND_NCE | NAND_ALE);
/* one more address cycle for devices > 128M */
if (capacity_mb > 128)
raw_nand_cmd_ctrl(proto, page_addr >> 16,
NAND_NCE | NAND_ALE);
}
}
raw_nand_cmd_ctrl(proto, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
if (command == NAND_CMD_ERASE1 || command == NAND_CMD_ERASE2 ||
command == NAND_CMD_SEQIN || command == NAND_CMD_PAGEPROG)
return;
if (command == NAND_CMD_RESET) {
usleep(controller_delay_us);
raw_nand_cmd_ctrl(proto, NAND_CMD_STATUS,
NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
raw_nand_cmd_ctrl(proto, NAND_CMD_NONE,
NAND_NCE | NAND_CTRL_CHANGE);
/* We have to busy loop until ready */
while (!(raw_nand_read_byte(proto) & NAND_STATUS_READY))
;
return;
}
if (command == NAND_CMD_READ0) {
raw_nand_cmd_ctrl(proto, NAND_CMD_READSTART,
NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
raw_nand_cmd_ctrl(proto, NAND_CMD_NONE,
NAND_NCE | NAND_CTRL_CHANGE);
}
usleep(controller_delay_us);
}