blob: 781c218ba1d1e31ea3af9d54970c5a66bb9eac7c [file] [log] [blame]
/*
* Copyright (c) 2003-2013 Marvell Corporation
*
* Copyright (c) 2009-2010 Micron Technology, Inc.
*
* 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 <common.h>
#include <malloc.h>
#include <spi_flash.h>
#include <nand.h>
#include <linux/bitops.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <asm/errno.h>
#include <asm/io.h>
#include <asm/bitops.h>
#include "spinand.h"
#include <mtd/pxa3xx_bbm.h>
#include <watchdog.h>
#ifndef CONFIG_SF_DEFAULT_SPEED
# define CONFIG_SF_DEFAULT_SPEED 1000000
#endif
#ifndef CONFIG_SF_DEFAULT_MODE
# define CONFIG_SF_DEFAULT_MODE SPI_MODE_3
#endif
#ifndef CONFIG_SPINAND_CS
#define CONFIG_SPINAND_CS 25
#endif
#define FLASH_TIME_OUT (20*CONFIG_SYS_HZ)
#define BUFSIZE (2 * 2048)
/* #define CONFIG_MTD_SPINAND_ONDIEECC 1 */
#ifdef CONFIG_MTD_SPINAND_ONDIEECC
static int enable_hw_ecc;
static int enable_read_hw_ecc;
static struct nand_ecclayout spinand_oob_64 = {
.eccbytes = 24,
.eccpos = {
1, 2, 3, 4, 5, 6,
17, 18, 19, 20, 21, 22,
33, 34, 35, 36, 37, 38,
49, 50, 51, 52, 53, 54, },
.oobavail = 32,
.oobfree = {
{.offset = 8,
.length = 8},
{.offset = 24,
.length = 8},
{.offset = 40,
.length = 8},
{.offset = 56,
.length = 8}, }
};
#endif
extern int spi_dma_xfer(struct spi_slave *slave, const u8 *cmd, size_t cmd_len,
const u32 *dout, u32 *din, size_t data_len);
int spinand_cmd(struct spi_flash *spi_nand, struct spinand_cmd *cmd)
{
char cmdbuf[16], cmd_len = 1;
int ret, datalen = 0;
unsigned long flags = SPI_XFER_BEGIN;
cmdbuf[0] = cmd->cmd;
if (cmd->n_dummy) {
cmdbuf[1] = 0x0;
cmd_len += cmd->n_dummy;
} else if (cmd->addr) {
memcpy(&cmdbuf[1], cmd->addr, cmd->n_addr);
cmd_len += cmd->n_addr;
}
if (cmd->n_rx)
datalen = cmd->n_rx;
else if (cmd->n_tx)
datalen = cmd->n_tx;
else
flags |= SPI_XFER_END;
ret = spi_xfer(spi_nand->spi, cmd_len*8, cmdbuf, NULL, flags);
if (ret) {
debug("SPINAND: Failed to send command (%zu bytes): %d\n",
cmd_len, ret);
} else if (datalen != 0) {
ret = spi_xfer(spi_nand->spi, datalen * 8, cmd->tx_buf,
cmd->rx_buf, SPI_XFER_END);
if (ret)
debug("SPINAND: Failed to transfer %zu bytes of data: %d\n",
datalen, ret);
}
return ret;
}
static int spinand_dma_transfer(struct spi_flash *spi_nand, struct spinand_cmd *cmd)
{
int ret = 0;
char cmdbuf[4], cmd_len = 1;
cmdbuf[0] = cmd->cmd;
if(cmd->addr){
memcpy(&cmdbuf[1], cmd->addr, cmd->n_addr);
cmd_len += cmd->n_addr;
}
if (cmd->n_rx)
ret = spi_dma_xfer(spi_nand->spi, (u8 *)cmdbuf, cmd_len, NULL,
(u32 *)cmd->rx_buf, cmd->n_rx);
if (cmd->n_tx)
ret = spi_dma_xfer(spi_nand->spi, (u8 *)cmdbuf, cmd_len,
(u32 *)cmd->tx_buf, NULL, cmd->n_tx);
if (ret != 0) {
debug("SPINAND: error %d dma trasfer.\n", ret);
return ret;
}
return 0;
}
static int spinand_read_id(struct spi_flash *spi_nand, u8 *id)
{
int retval;
u8 nand_id[2];
struct spinand_cmd cmd = {0};
cmd.cmd = CMD_READ_ID;
cmd.n_dummy = 1;
cmd.n_rx = 2;
cmd.rx_buf = &nand_id[0];
retval = spinand_cmd(spi_nand, &cmd);
if (retval != 0) {
debug("SPINAND: error %d reading id\n", retval);
return retval;
}
id[0] = nand_id[0];
id[1] = nand_id[1];
return 0;
}
static int spinand_read_status(struct spi_flash *spi_nand, uint8_t *status)
{
struct spinand_cmd cmd = {0};
int ret;
cmd.cmd = CMD_READ_REG;
cmd.n_addr = 1;
cmd.addr[0] = REG_STATUS;
cmd.n_rx = 1;
cmd.rx_buf = status;
ret = spinand_cmd(spi_nand, &cmd);
if (ret != 0) {
debug("SPINAND: error %d read status register\n", ret);
return ret;
}
return 0;
}
static int spinand_get_otp(struct spi_flash *spi_nand, u8 *otp)
{
struct spinand_cmd cmd = {0};
int retval;
cmd.cmd = CMD_READ_REG;
cmd.n_addr = 1;
cmd.addr[0] = REG_OTP;
cmd.n_rx = 1;
cmd.rx_buf = otp;
retval = spinand_cmd(spi_nand, &cmd);
if (retval != 0) {
debug("SPINAND: error %d get otp\n", retval);
return retval;
}
return 0;
}
static int spinand_get_protect(struct spi_flash *spi_nand, u8 *otp)
{
struct spinand_cmd cmd = {0};
int retval;
cmd.cmd = CMD_READ_REG;
cmd.n_addr = 1;
cmd.addr[0] = REG_BLOCK_LOCK;
cmd.n_rx = 1;
cmd.rx_buf = otp;
retval = spinand_cmd(spi_nand, &cmd);
if (retval != 0) {
debug("SPINAND error %d get otp\n", retval);
return retval;
}
return 0;
}
static int spinand_set_otp(struct spi_flash *spi_nand, u8 *otp)
{
int retval;
struct spinand_cmd cmd = {0};
cmd.cmd = CMD_WRITE_REG,
cmd.n_addr = 1,
cmd.addr[0] = REG_OTP,
cmd.n_tx = 1,
cmd.tx_buf = otp,
retval = spinand_cmd(spi_nand, &cmd);
if (retval != 0) {
debug("SPINAND error %d set otp\n", retval);
return retval;
}
return 0;
}
#if 0
static int wait_till_ready(struct spi_flash *spi_nand)
{
unsigned long timebase;
int retval;
u8 stat = 0;
timebase = get_timer(0);
do {
WATCHDOG_RESET();
retval = spinand_read_status(spi_nand, &stat);
if (retval < 0)
return -1;
else if (!(stat & 0x1))
break;
} while (get_timer(timebase) < FLASH_TIME_OUT);
if ((stat & 0x1) == 0)
return 0;
return -1;
}
#endif
#ifdef CONFIG_MTD_SPINAND_ONDIEECC
static int spinand_enable_ecc(struct spi_flash *spi_nand)
{
int retval;
u8 otp = 0;
retval = spinand_get_otp(spi_nand, &otp);
if (retval != 0) {
debug("SPINAND get otp error.\n");
return retval;
}
if ((otp & OTP_ECC_MASK) == OTP_ECC_MASK) {
return 0;
} else {
otp |= OTP_ECC_MASK;
retval = spinand_set_otp(spi_nand, &otp);
retval = spinand_get_otp(spi_nand, &otp);
return retval;
}
}
#endif
static int spinand_disable_ecc(struct spi_flash *spi_nand)
{
int retval;
u8 otp = 0;
retval = spinand_get_otp(spi_nand, &otp);
if ((otp & OTP_ECC_MASK) == OTP_ECC_MASK) {
otp &= ~OTP_ECC_MASK;
retval = spinand_set_otp(spi_nand, &otp);
retval = spinand_get_otp(spi_nand, &otp);
return retval;
} else
return 0;
}
static int spinand_write_enable(struct spi_flash *spi_nand)
{
struct spinand_cmd cmd = {0};
cmd.cmd = CMD_WR_ENABLE;
return spinand_cmd(spi_nand, &cmd);
}
static int spinand_read_page_to_cache(struct spi_flash *spi_nand, u16 page_id)
{
struct spinand_cmd cmd = {0};
u16 row;
row = page_id;
cmd.cmd = CMD_READ;
cmd.n_addr = 3;
cmd.addr[1] = (u8)((row & 0xff00) >> 8);
cmd.addr[2] = (u8)(row & 0x00ff);
return spinand_cmd(spi_nand, &cmd);
}
static int spinand_read_from_cache(struct spi_flash *spi_nand, u16 byte_id,
u16 len, u8 *rbuf)
{
struct spinand_cmd cmd = {0};
u16 column;
int ret;
column = byte_id;
cmd.cmd = CMD_READ_RDM;
cmd.n_addr = 3;
cmd.addr[0] = (u8)((column & 0xff00) >> 8);
cmd.addr[1] = (u8)(column & 0x00ff);
cmd.addr[2] = (u8)(0xff);
cmd.n_dummy = 0;
cmd.n_rx = len;
cmd.rx_buf = rbuf;
ret = spinand_dma_transfer(spi_nand, &cmd);
if (ret != 0) {
debug("SPINAND error %d read_from_cache\n", ret);
return ret;
}
return 0;
}
static int spinand_read_page(struct spi_flash *spi_nand, u16 page_id,
u16 offset, u16 len, u8 *rbuf)
{
int ret;
u8 status = 0;
#ifdef CONFIG_MTD_SPINAND_ONDIEECC
if (enable_read_hw_ecc) {
if (spinand_enable_ecc(spi_nand))
debug("SPINAND enable HW ECC failed!");
}
#endif
ret = spinand_read_page_to_cache(spi_nand, page_id);
while (1) {
ret = spinand_read_status(spi_nand, &status);
if (ret < 0) {
debug("SPINAND err %d read status register\n", ret);
return ret;
}
if ((status & STATUS_OIP_MASK) == STATUS_READY) {
if ((status & STATUS_ECC_MASK) == STATUS_ECC_ERROR) {
debug("SPINAND ecc error, page=%d\n", page_id);
return 0;
}
break;
}
}
ret = spinand_read_from_cache(spi_nand, offset, len, rbuf);
if (ret != 0)
debug("SPINAND read from cache failed!!\n");
#ifdef CONFIG_MTD_SPINAND_ONDIEECC
if (enable_read_hw_ecc) {
ret = spinand_disable_ecc(spi_nand);
enable_read_hw_ecc = 0;
}
#endif
return 0;
}
static int spinand_program_data_to_cache(struct spi_flash *spi_nand,
u16 byte_id, u16 len, u8 *wbuf)
{
struct spinand_cmd cmd = {0};
u16 column;
int ret;
column = byte_id;
cmd.cmd = CMD_PROG_PAGE_CLRCACHE;
cmd.n_addr = 2;
cmd.addr[0] = (u8)((column & 0xff00) >> 8);
cmd.addr[1] = (u8)(column & 0x00ff);
cmd.n_tx = len;
cmd.tx_buf = wbuf;
ret = spinand_dma_transfer(spi_nand, &cmd);
if (ret != 0) {
debug("SPINAND error %d read_from_cache\n", ret);
return ret;
}
return 0;
}
static int spinand_program_execute(struct spi_flash *spi_nand, u16 page_id)
{
struct spinand_cmd cmd = {0};
u16 row;
row = page_id;
cmd.cmd = CMD_PROG_PAGE_EXC;
cmd.n_addr = 3;
cmd.addr[1] = (u8)((row & 0xff00) >> 8);
cmd.addr[2] = (u8)(row & 0x00ff);
return spinand_cmd(spi_nand, &cmd);
}
static int spinand_program_page(struct spi_flash *spi_nand,
u16 page_id, u16 offset, u16 len, u8 *buf)
{
int retval;
u8 status = 0;
uint8_t *wbuf;
#ifdef CONFIG_MTD_SPINAND_ONDIEECC
/* unsigned int i j; */
enable_read_hw_ecc = 0;
/* wbuf = malloc(2112);
spinand_read_page(spi_nand, page_id, 0, 0x840, wbuf);
for (i = offset, j = 0; i < len; i++, j++)
wbuf[i] &= buf[j]; */
if (enable_hw_ecc)
retval = spinand_enable_ecc(spi_nand);
#endif
wbuf = buf;
retval = spinand_write_enable(spi_nand);
retval = spinand_program_data_to_cache(spi_nand, offset, len, wbuf);
retval = spinand_program_execute(spi_nand, page_id);
while (1) {
retval = spinand_read_status(spi_nand, &status);
if (retval < 0) {
debug("SPINAND error %d reading status register\n",
retval);
return retval;
}
if ((status & STATUS_OIP_MASK) == STATUS_READY) {
if ((status & STATUS_P_FAIL_MASK) == STATUS_P_FAIL) {
debug("SPINAND program error, page %d\n", page_id);
return -1;
} else
break;
}
}
#ifdef CONFIG_MTD_SPINAND_ONDIEECC
if (enable_hw_ecc) {
retval = spinand_disable_ecc(spi_nand);
enable_hw_ecc = 0;
}
#endif
return 0;
}
static int spinand_erase_block_erase(struct spi_flash *spi_nand, u16 block_id)
{
struct spinand_cmd cmd = {0};
u16 row;
row = block_id;
cmd.cmd = CMD_ERASE_BLK;
cmd.n_addr = 3;
cmd.addr[1] = (u8)((row & 0xff00) >> 8);
cmd.addr[2] = (u8)(row & 0x00ff);
return spinand_cmd(spi_nand, &cmd);
}
static int spinand_erase_block(struct spi_flash *spi_nand, u16 block_id)
{
int retval;
u8 status = 0;
retval = spinand_write_enable(spi_nand);
retval = spinand_erase_block_erase(spi_nand, block_id);
while (1) {
retval = spinand_read_status(spi_nand, &status);
if (retval < 0) {
debug("SPINAND error %d reading status register\n",
(int) retval);
return retval;
}
if ((status & STATUS_OIP_MASK) == STATUS_READY) {
if ((status & STATUS_E_FAIL_MASK) == STATUS_E_FAIL) {
debug("SPINAND erase error,block %d\n",
block_id);
return -1;
} else
break;
}
}
return 0;
}
#ifdef CONFIG_MTD_SPINAND_ONDIEECC
static int spinand_write_page_hwecc(struct mtd_info *mtd,
struct nand_chip *chip, const uint8_t *buf, int oob_required)
{
const uint8_t *p = buf;
int eccsize = chip->ecc.size;
int eccsteps = chip->ecc.steps;
enable_hw_ecc = 1;
chip->write_buf(mtd, p, eccsize * eccsteps);
return 0;
}
static int spinand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
uint8_t *buf, int oob_required, int page)
{
u8 status;
uint8_t *p = buf;
int eccsize = chip->ecc.size;
int eccsteps = chip->ecc.steps;
struct spinand_info *info = (struct spinand_info *)chip->priv;
enable_read_hw_ecc = 1;
chip->read_buf(mtd, p, eccsize * eccsteps);
if (oob_required)
chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
while (1) {
spinand_read_status(info->spi, &status);
if ((status & STATUS_OIP_MASK) == STATUS_READY) {
if ((status & STATUS_ECC_MASK) == STATUS_ECC_ERROR) {
debug("spinand: ECC error\n");
mtd->ecc_stats.failed++;
} else if ((status & STATUS_ECC_MASK) ==
STATUS_ECC_1BIT_CORRECTED)
mtd->ecc_stats.corrected++;
break;
}
}
return 0;
}
#endif
static void spinand_select_chip(struct mtd_info *mtd, int dev)
{
}
static uint8_t spinand_read_byte(struct mtd_info *mtd)
{
struct nand_chip *chip = (struct nand_chip *)mtd->priv;
struct spinand_info *info = (struct spinand_info *)chip->priv;
struct nand_state *state = (struct nand_state *)info->priv;
u8 data;
data = state->buf[state->buf_ptr];
state->buf_ptr++;
return data;
}
static int spinand_wait(struct mtd_info *mtd, struct nand_chip *chip)
{
struct spinand_info *info = (struct spinand_info *)chip->priv;
unsigned long timebase;
u8 status;
timebase = get_timer(0);
do {
WATCHDOG_RESET();
spinand_read_status(info->spi, &status);
if ((status & STATUS_E_FAIL_MASK) == STATUS_E_FAIL)
return NAND_STATUS_FAIL;
if ((status & STATUS_P_FAIL_MASK) == STATUS_P_FAIL)
return NAND_STATUS_FAIL;
if ((status & STATUS_OIP_MASK) == STATUS_READY)
return 0;
} while (get_timer(timebase) < FLASH_TIME_OUT);
return 0;
}
static void spinand_write_buf(struct mtd_info *mtd, const uint8_t *buf,
int len)
{
struct nand_chip *chip = (struct nand_chip *)mtd->priv;
struct spinand_info *info = (struct spinand_info *)chip->priv;
struct nand_state *state = (struct nand_state *)info->priv;
memcpy(state->buf+state->buf_ptr, buf, len);
state->buf_ptr += len;
}
static void spinand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
{
struct nand_chip *chip = (struct nand_chip *)mtd->priv;
struct spinand_info *info = (struct spinand_info *)chip->priv;
struct nand_state *state = (struct nand_state *)info->priv;
memcpy(buf, state->buf+state->buf_ptr, len);
state->buf_ptr += len;
flush_dcache_all();
}
static int spinand_lock_block(struct spi_flash *spi_nand, u8 lock)
{
struct spinand_cmd cmd = {0};
int ret;
u8 otp = 0;
cmd.cmd = CMD_WRITE_REG;
cmd.n_addr = 1;
cmd.addr[0] = REG_BLOCK_LOCK;
cmd.n_tx = 1;
cmd.tx_buf = &lock;
ret = spinand_cmd(spi_nand, &cmd);
if (ret != 0) {
debug("SPINAND: error %d lock block\n", ret);
return ret;
}
ret = spinand_get_protect(spi_nand, &otp);
return otp;
}
static void spinand_cmdfunc(struct mtd_info *mtd, unsigned int command,
int column, int page)
{
struct nand_chip *chip = (struct nand_chip *)mtd->priv;
struct spinand_info *info = (struct spinand_info *)chip->priv;
struct nand_state *state = (struct nand_state *)info->priv;
struct pxa3xx_bbm *pxa3xx_bbm = mtd->bbm;
loff_t addr;
if (pxa3xx_bbm && (command == NAND_CMD_READOOB ||
command == NAND_CMD_READ0 ||
command == NAND_CMD_SEQIN ||
command == NAND_CMD_ERASE1)) {
addr = (loff_t)page << mtd->writesize_shift;
addr = pxa3xx_bbm->search(mtd, addr);
page = addr >> mtd->writesize_shift;
}
switch (command) {
/*
* READ0 - read in first 0x800 bytes
*/
case NAND_CMD_READ1:
case NAND_CMD_READ0:
state->buf_ptr = 0;
spinand_read_page(info->spi, page, 0x0, 0x800, state->buf);
break;
/* READOOB reads only the OOB because no ECC is performed. */
case NAND_CMD_READOOB:
state->buf_ptr = 0;
spinand_read_page(info->spi, page, 0x800, 0x40, state->buf);
break;
case NAND_CMD_RNDOUT:
state->buf_ptr = column;
break;
case NAND_CMD_READID:
state->buf_ptr = 0;
spinand_read_id(info->spi, (u8 *)state->buf);
break;
case NAND_CMD_PARAM:
state->buf_ptr = 0;
break;
/* ERASE1 stores the block and page address */
case NAND_CMD_ERASE1:
spinand_erase_block(info->spi, page);
break;
/* ERASE2 uses the block and page address from ERASE1 */
case NAND_CMD_ERASE2:
break;
/* SEQIN sets up the addr buffer and all registers except the length */
case NAND_CMD_SEQIN:
state->col = column;
state->row = page;
state->buf_ptr = 0;
break;
/* PAGEPROG reuses all of the setup from SEQIN and adds the length */
case NAND_CMD_PAGEPROG:
spinand_program_page(info->spi, state->row, state->col,
state->buf_ptr, state->buf);
break;
case NAND_CMD_STATUS:
spinand_get_otp(info->spi, state->buf);
if (!(state->buf[0] & 0x80))
state->buf[0] = 0x80;
state->buf_ptr = 0;
break;
case NAND_CMD_LOCK:
if (spinand_lock_block(info->spi, BL_ALL_UNLOCKED))
printf("Error: Fail to unlock SPI NAND.\n");
break;
/* RESET command */
case NAND_CMD_RESET:
break;
default:
debug("SPINAND: Unknown CMD: 0x%x\n", command);
}
}
int board_nand_init(struct nand_chip *nand)
{
struct spinand_info *info;
struct spi_flash *spi_nand;
struct nand_state *state;
struct spi_slave *spi;
int ret;
int bus = 0;
unsigned int speed = CONFIG_SF_DEFAULT_SPEED;
unsigned int mode = CONFIG_SF_DEFAULT_MODE;
info = malloc(sizeof(struct spinand_info));
if (!info) {
fprintf(stderr, "Spinand out of memory. (%d bytes)\n",
sizeof(struct spinand_info));
ret = -ENOMEM;
goto err_alloc_info;
}
state = malloc(sizeof(struct nand_state));
if (!state) {
fprintf(stderr, "Spinand out of memory. (%d bytes)\n",
sizeof(struct nand_state));
ret = -ENOMEM;
goto err_alloc_state;
}
spi_nand = malloc(sizeof(struct spi_flash));
if (!spi_nand) {
fprintf(stderr, "Spinand out of memory (%d bytes)\n",
sizeof(struct spi_flash));
ret = -ENOMEM;
goto err_alloc_spinand;
}
spi = malloc(sizeof(struct spi_slave));
if (!spi) {
fprintf(stderr, "Spinand out of memory (%d bytes)\n",
sizeof(struct spi_slave));
ret = -ENOMEM;
goto err_alloc_spi;
}
info->priv = state;
state->buf_ptr = 0;
state->buf = malloc(BUFSIZE);
if (!state->buf) {
fprintf(stderr, "Spinand out of memory (%d bytes)\n",
BUFSIZE);
ret = -ENOMEM;
goto err_alloc_buf;
}
spi_init();
spi = spi_setup_slave(bus, CONFIG_SPINAND_CS, speed, mode);
if (!spi) {
printf("SF: Failed to set up slave\n");
ret = 1;
goto err_alloc;
}
ret = spi_claim_bus(spi);
if (ret) {
debug("SF: Failed to claim SPI bus: %d\n", ret);
ret = 1;
goto err_alloc;
}
spi_nand->spi = spi;
info->spi = spi_nand;
nand->priv = info;
#ifdef CONFIG_MTD_SPINAND_ONDIEECC
nand->ecc.mode = NAND_ECC_HW;
nand->ecc.size = 0x200;
nand->ecc.bytes = 0x6;
nand->ecc.steps = 0x4;
nand->ecc.total = nand->ecc.steps * nand->ecc.bytes;
nand->ecc.layout = &spinand_oob_64;
nand->ecc.read_page = spinand_read_page_hwecc;
nand->ecc.write_page = spinand_write_page_hwecc;
#else
nand->ecc.mode = NAND_ECC_NONE;
ret = spinand_disable_ecc(info->spi);
#endif
nand->read_buf = spinand_read_buf;
nand->write_buf = spinand_write_buf;
nand->read_byte = spinand_read_byte;
nand->cmdfunc = spinand_cmdfunc;
nand->waitfunc = spinand_wait;
nand->options |= NAND_CACHEPRG;
nand->select_chip = spinand_select_chip;
#ifdef CONFIG_BBM
nand->scan_bbt = pxa3xx_scan_bbt;
nand->block_markbad = pxa3xx_block_markbad;
nand->block_bad = pxa3xx_block_bad;
#endif
return 0;
err_alloc:
free(state->buf);
err_alloc_buf:
free(spi);
err_alloc_spi:
free(spi_nand);
err_alloc_spinand:
free(state);
err_alloc_state:
free(info);
err_alloc_info:
return ret;
}