blob: e5f422740b9721316302593e938f0f691908fd70 [file] [log] [blame]
/*
* Copyright (c) 2015 The Linux Foundation. All rights reserved.
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
* 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 <asm/global_data.h>
#include <nand.h>
#include <linux/mtd/nand.h>
#include <spi_flash.h>
#include <asm/errno.h>
#include "spi_nand_dev.h"
#include <malloc.h>
#include "spi.h"
#include "sf_internal.h"
#include <watchdog.h>
#define CONFIG_SF_DEFAULT_SPEED (48 * 1000 * 1000)
#define TIMEOUT 5000
#define MFID_GIGA 0xc8
#define MFID_ATO 0x9b
#define TOSHIBA_NORM_READ_MASK 0x1F
/* Macronix Specific Defines */
#define MFID_MACRONIX 0xc2
#define MACRONIX_WRAP ((0 & 0x3) << 6)
#define MACRONIX_PLANE ((0 & 0x1) << 4)
#define MACRONIX_NORM_READ_MASK (MACRONIX_WRAP | MACRONIX_PLANE | 0x0F)
/* Fidelix Specific Defines */
#define SET_CMD_PLANE_BIT(plane) (plane << 4)
/* Introduce the 6th bit in the page address and insert the plane ID */
#define INSERT_PLANE_BIT(x, plane) (((x << 1) & 0xFFFFFF80) | (x & 0x3F) \
| (plane << 6))
#define GIGADEVICE_NORM_READ_MASK 0x0F
#define spi_print(...) printf("spi_nand: " __VA_ARGS__)
DECLARE_GLOBAL_DATA_PTR;
struct nand_chip nand_chip[CONFIG_SYS_MAX_NAND_DEVICE];
int verify_3bit_ecc(int status);
int verify_2bit_ecc(int status);
int verify_dummy_ecc(int status);
int verify_2bit_toshiba_ecc(int status);
void gigadevice_norm_read_cmd(u8 *cmd, int column);
void gigadevice_norm_read_cmd_12bit_addr(u8 *cmd, int column);
void macronix_norm_read_cmd(u8 *cmd, int column);
void winbond_norm_read_cmd(u8 *cmd, int column);
void toshiba_norm_read_cmd(u8 *cmd, int column);
void fidelix_norm_read_cmd(u8 *cmd, int column);
int spi_nand_die_select(struct mtd_info *mtd, struct spi_flash *flash,
int die_id);
#define mtd_to_ipq_info(m) ((struct nand_chip *)((m)->priv))->priv
static struct spi_nand_flash_params spi_nand_flash_tbl[] = {
{
.id = { 0xc8, 0xb1, 0x48, 0xc8 },
.page_size = 2048,
.erase_size = 0x00020000,
.no_of_dies = 1,
.prev_die_id = INT_MAX,
.plane_id = 0,
.pages_per_die = 0x10000,
.pages_per_sector = 64,
.nr_sectors = 1024,
.oob_size = 128,
.protec_bpx = 0xC7,
.norm_read_cmd = gigadevice_norm_read_cmd,
.verify_ecc = verify_3bit_ecc,
.die_select = NULL,
.name = "GD5F1GQ4XC",
},
{
.id = { 0xc8, 0xc1, 0xc8, 0xc1 },
.page_size = 2048,
.erase_size = 0x00020000,
.no_of_dies = 1,
.prev_die_id = INT_MAX,
.plane_id = 0,
.pages_per_die = 0x10000,
.pages_per_sector = 64,
.nr_sectors = 1024,
.oob_size = 128,
.protec_bpx = 0xC7,
.norm_read_cmd = gigadevice_norm_read_cmd_12bit_addr,
.verify_ecc = verify_3bit_ecc,
.die_select = NULL,
.name = "GD5F1GQ4R",
},
{
.id = { 0xc8, 0xb4, 0x68, 0xc8 },
.page_size = 4096,
.erase_size = 0x00040000,
.no_of_dies = 1,
.prev_die_id = INT_MAX,
.plane_id = 0,
.pages_per_die = 0x20000,
.pages_per_sector = 64,
.nr_sectors = 2048,
.oob_size = 256,
.protec_bpx = 0xC7,
.norm_read_cmd = gigadevice_norm_read_cmd,
.verify_ecc = verify_3bit_ecc,
.die_select = NULL,
.name = "GD5F4GQ4XC",
},
{
.id = { 0xff, 0x9b, 0x12 , 0x9b },
.page_size = 2048,
.erase_size = 0x00020000,
.no_of_dies = 1,
.prev_die_id = INT_MAX,
.plane_id = 0,
.pages_per_die = 0x10000,
.pages_per_sector = 64,
.nr_sectors = 1024,
.oob_size = 64,
.protec_bpx = 0xC7,
.norm_read_cmd = gigadevice_norm_read_cmd,
.verify_ecc = verify_dummy_ecc,
.die_select = NULL,
.name = "ATO25D1GA",
},
{
.id = { 0x00, 0xc2, 0x12, 0xc2 },
.page_size = 2048,
.erase_size = 0x00020000,
.no_of_dies = 1,
.prev_die_id = INT_MAX,
.plane_id = 0,
.pages_per_die = 0x10000,
.pages_per_sector = 64,
.nr_sectors = 1024,
.oob_size = 64,
.protec_bpx = 0xC7,
.norm_read_cmd = macronix_norm_read_cmd,
.verify_ecc = verify_2bit_ecc,
.die_select = NULL,
.name = "MX35LFxGE4AB",
},
{
.id = { 0x00, 0xef, 0xaa, 0x21 },
.page_size = 2048,
.erase_size = 0x00020000,
.no_of_dies = 1,
.prev_die_id = INT_MAX,
.plane_id = 0,
.pages_per_die = 0x10000,
.pages_per_sector = 64,
.nr_sectors = 1024,
.oob_size = 64,
.protec_bpx = 0x87,
.norm_read_cmd = winbond_norm_read_cmd,
.verify_ecc = verify_2bit_ecc,
.die_select = NULL,
.name = "W25N01GV",
},
{
.id = { 0x00, 0xef, 0xab, 0x21 },
.page_size = 2048,
.erase_size = 0x00020000,
.no_of_dies = 2,
.prev_die_id = INT_MAX,
.plane_id = 0,
.pages_per_die = 0x10000,
.pages_per_sector = 64,
.nr_sectors = 2048,
.oob_size = 64,
.protec_bpx = 0x87,
.norm_read_cmd = winbond_norm_read_cmd,
.verify_ecc = verify_2bit_ecc,
.die_select = spi_nand_die_select,
.name = "W25M02GV",
},
{
.id = { 0x00, 0x98, 0xcd, 0x98 },
.page_size = 4096,
.erase_size = 0x00040000,
.no_of_dies = 1,
.prev_die_id = INT_MAX,
.plane_id = 0,
.pages_per_die = 0x20000,
.pages_per_sector = 64,
.nr_sectors = 2048,
.oob_size = 128,
.protec_bpx = 0xC7,
.norm_read_cmd = toshiba_norm_read_cmd,
.verify_ecc = verify_2bit_toshiba_ecc,
.die_select = NULL,
.name = "TC58CVG2S0F",
},
{
.id = { 0x00, 0xe5, 0x72, 0xe5 },
.page_size = 2048,
.erase_size = 0x00020000,
.no_of_dies = 2,
.prev_die_id = INT_MAX,
.plane_id = 0,
.pages_per_die = 0x10000,
.pages_per_sector = 64,
.nr_sectors = 2048,
.oob_size = 64,
.protec_bpx = 0xC7,
.norm_read_cmd = fidelix_norm_read_cmd,
.verify_ecc = verify_2bit_ecc,
.die_select = NULL,
.name = "FM35X2GA",
},
};
struct spi_nand_flash_params *params;
void spinand_internal_ecc(struct mtd_info *mtd, int enable);
void gigadevice_norm_read_cmd(u8 *cmd, int column)
{
cmd[0] = IPQ40XX_SPINAND_CMD_NORM_READ;
cmd[1] = 0;
cmd[2] = (u8)(column >> 8);
cmd[3] = (u8)(column);
}
void gigadevice_norm_read_cmd_12bit_addr(u8 *cmd, int column)
{
cmd[0] = IPQ40XX_SPINAND_CMD_NORM_READ;
cmd[1] = ((u8)(column >> 8) & GIGADEVICE_NORM_READ_MASK);
cmd[2] = (u8)(column);
cmd[3] = 0;
}
void macronix_norm_read_cmd(u8 *cmd, int column)
{
cmd[0] = IPQ40XX_SPINAND_CMD_NORM_READ;
cmd[1] = ((u8)(column >> 8) & MACRONIX_NORM_READ_MASK);
cmd[2] = (u8)(column);
cmd[3] = 0;
}
void fidelix_norm_read_cmd(u8 *cmd, int column)
{
cmd[0] = IPQ40XX_SPINAND_CMD_NORM_READ;
cmd[1] = ((u8)(column >> 8)) | SET_CMD_PLANE_BIT(params->plane_id);
cmd[2] = (u8)(column);
cmd[3] = 0;
}
void winbond_norm_read_cmd(u8 *cmd, int column)
{
cmd[0] = IPQ40XX_SPINAND_CMD_NORM_READ;
cmd[1] = (u8)(column >> 8);
cmd[2] = (u8)(column);
cmd[3] = 0;
}
void toshiba_norm_read_cmd(u8 *cmd, int column)
{
cmd[0] = IPQ40XX_SPINAND_CMD_NORM_READ;
cmd[1] = ((u8)(column >> 8) & TOSHIBA_NORM_READ_MASK);
cmd[2] = (u8)(column);
cmd[3] = 0;
}
int spi_nand_flash_cmd_poll_bit(struct spi_flash *flash, unsigned long timeout,
u8 cmd, u8 poll_bit, u8 *status)
{
struct spi_slave *spi = flash->spi;
unsigned long timebase;
u8 cmd_buf[2];
cmd_buf[0] = 0x0F;
cmd_buf[1] = cmd;
timebase = get_timer(0);
do {
WATCHDOG_RESET();
spi_flash_cmd_read(spi, cmd_buf, 2, status, 1);
if ((*status & poll_bit) == 0)
break;
} while (get_timer(timebase) < timeout);
if ((*status & poll_bit) == 0)
return 0;
/* Timed out */
debug("SF: time out!\n");
return -1;
}
int spi_nand_flash_cmd_wait_ready(struct spi_flash *flash, u8 status_bit, u8 *status,
unsigned long timeout)
{
return spi_nand_flash_cmd_poll_bit(flash, timeout,
0xC0, status_bit, status);
}
static int spinand_waitfunc(struct mtd_info *mtd, u8 val, u8 *status)
{
struct ipq40xx_spinand_info *info = mtd_to_ipq_info(mtd);
struct spi_flash *flash = info->flash;
int ret;
ret = spi_nand_flash_cmd_wait_ready(flash, val, status, TIMEOUT);
if (ret) {
printf("%s Operation Timeout\n", __func__);
return -1;
}
return 0;
}
static int check_offset(struct mtd_info *mtd, loff_t offs)
{
struct ipq40xx_spinand_info *info = mtd_to_ipq_info(mtd);
struct nand_chip *chip = info->chip;
int ret = 0;
/* Start address must align on block boundary */
if (offs & ((1 << chip->phys_erase_shift) - 1)) {
printf("%s: unaligned address\n", __func__);
ret = -EINVAL;
}
return ret;
}
static int get_die_id(struct ipq40xx_spinand_info *info, u32 page)
{
int die_id;
die_id = page / info->params->pages_per_die;
if (die_id > info->params->no_of_dies) {
printf("Invalid Die ID: %d\n", die_id);
return -EINVAL;
}
return die_id;
}
static int get_plane_id(struct ipq40xx_spinand_info *info, u32 page)
{
if (info->params->no_of_dies == 2 && info->params->die_select == NULL) {
params->plane_id = page / info->params->pages_per_die;
if (params->plane_id > info->params->no_of_dies) {
printf("Invalid Plane ID: %d\n", params->plane_id);
return -EINVAL;
}
}
return 0;
}
int spi_nand_die_select(struct mtd_info *mtd, struct spi_flash *flash,
int die_id)
{
u8 cmd[8];
u8 status;
int ret = 0;
if (die_id < 0)
return -EINVAL;
if (params->prev_die_id == die_id)
return ret;
cmd[0] = IPQ40XX_SPINAND_CMD_DIESELECT;
cmd[1] = die_id;
ret = spi_flash_cmd_write(flash->spi, cmd, 2, NULL, 0);
if (ret) {
printf("%s failed for die select :\n", __func__);
return ret;
}
ret = spinand_waitfunc(mtd, 0x01, &status);
if (ret) {
printf("Operation timeout\n");
return ret;
}
params->prev_die_id = die_id;
return ret;
}
static inline int do_die_select(struct mtd_info *mtd, struct spi_flash *flash,
struct ipq40xx_spinand_info *info, u32 page)
{
u32 ret;
if (info->params->die_select == NULL)
return 0;
ret = info->params->die_select(mtd, flash, get_die_id(info, page));
return ret;
}
static int fidelix_plane_select(u32 page)
{
if (params->no_of_dies == 2 && params->die_select == NULL) {
if (params->plane_id)
page = page - params->pages_per_die;
page = INSERT_PLANE_BIT(page, params->plane_id);
}
return page;
}
static int spi_nand_erase(struct mtd_info *mtd, struct erase_info *instr)
{
u8 cmd[8];
u8 status;
u32 ret;
struct ipq40xx_spinand_info *info = mtd_to_ipq_info(mtd);
struct spi_flash *flash = info->flash;
struct nand_chip *chip = info->chip;
u32 page = (int)(instr->addr >> chip->page_shift);
if (check_offset(mtd, instr->addr))
return -1;
ret = spi_claim_bus(flash->spi);
if (ret) {
printf("SF: Unable to claim SPI bus\n");
return ret;
}
ret = do_die_select(mtd, flash, info, page);
if (ret)
goto out;
if (get_plane_id(info, page))
goto out;
ret = spi_flash_cmd(flash->spi, IPQ40XX_SPINAND_CMD_WREN, NULL, 0);
if (ret) {
printf ("Write enable failed %s\n", __func__);
goto out;
}
ret = spinand_waitfunc(mtd, 0x01, &status);
if (ret) {
goto out;
}
page = fidelix_plane_select(page);
cmd[0] = IPQ40XX_SPINAND_CMD_ERASE;
cmd[1] = (u8)(page >> 16);
cmd[2] = (u8)(page >> 8);
cmd[3] = (u8)(page);
ret = spi_flash_cmd_write(flash->spi, cmd, 4, NULL, 0);
if (ret) {
printf("%s failed for offset:%ld\n", __func__, (long)instr->addr);
goto out;
}
ret = spinand_waitfunc(mtd, 0x01, &status);
if (ret) {
printf("Operation timeout\n");
goto out;
}
if (status & STATUS_E_FAIL) {
printf("Erase operation failed for 0x%x\n", page);
ret = -EIO;
goto out;
}
ret = spi_flash_cmd(flash->spi, IPQ40XX_SPINAND_CMD_WRDI, NULL, 0);
ret = spinand_waitfunc(mtd, 0x01, &status);
if (ret) {
printf("Write disable failed\n");
}
out:
spi_release_bus(flash->spi);
return ret;
}
static int spi_nand_block_isbad(struct mtd_info *mtd, loff_t offs)
{
struct ipq40xx_spinand_info *info = mtd_to_ipq_info(mtd);
struct spi_flash *flash = info->flash;
struct nand_chip *chip = info->chip;
u8 cmd[8];
u8 status;
u32 value = 0xff;
int page, column, ret;
page = (int)(offs >> chip->page_shift) & chip->pagemask;
column = mtd->writesize + chip->badblockpos;
ret = spi_claim_bus(flash->spi);
if (ret) {
printf ("Claim bus failed. %s\n", __func__);
return -1;
}
ret = do_die_select(mtd, flash, info, page);
if (ret)
goto out;
if (get_plane_id(info, page))
goto out;
page = fidelix_plane_select(page);
cmd[0] = IPQ40XX_SPINAND_CMD_READ;
cmd[1] = (u8)(page >> 16);
cmd[2] = (u8)(page >> 8);
cmd[3] = (u8)(page);
ret = spi_flash_cmd_write(flash->spi, cmd, 4, NULL, 0);
if (ret) {
printf("%s: write command failed\n", __func__);
goto out;
}
ret = spinand_waitfunc(mtd, 0x01, &status);
if (ret) {
printf("Operation timeout\n");
goto out;
}
info->params->norm_read_cmd(cmd, column);
ret = spi_flash_cmd_read(flash->spi, cmd, 4, &value, 1);
if (ret) {
printf("%s: read data failed\n", __func__);
goto out;
}
if (value != 0xFF) {
ret = 1;
goto out;
}
out:
spi_release_bus(flash->spi);
return ret;
}
static int spinand_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip,
int page, struct mtd_oob_ops *ops)
{
int column, ret = 0;
u_char *wbuf;
u8 cmd[8], status;
struct ipq40xx_spinand_info *info = mtd_to_ipq_info(mtd);
struct spi_flash *flash = info->flash;
wbuf = chip->oob_poi;
column = mtd->writesize;
ret = do_die_select(mtd, flash, info, page);
if (ret)
goto out;
if (get_plane_id(info, page))
goto out;
ret = spi_flash_cmd(flash->spi, IPQ40XX_SPINAND_CMD_WREN, NULL, 0);
if (ret) {
printf("Write enable failed in %s\n", __func__);
goto out;
}
cmd[0] = IPQ40XX_SPINAND_CMD_PLOAD;
cmd[1] = ((u8)(column >> 8)) | SET_CMD_PLANE_BIT(params->plane_id);
cmd[2] = (u8)(column);
ret = spi_flash_cmd_write(flash->spi, cmd, 3, wbuf, ops->ooblen);
if (ret) {
printf("%s: write command failed\n", __func__);
ret = 1;
goto out;
}
page = fidelix_plane_select(page);
cmd[0] = IPQ40XX_SPINAND_CMD_PROG;
cmd[1] = (u8)(page >> 16);
cmd[2] = (u8)(page >> 8);
cmd[3] = (u8)(page);
ret = spi_flash_cmd_write(flash->spi, cmd, 4, NULL, 0);
if (ret) {
printf("PLOG failed\n");
goto out;
}
ret = spinand_waitfunc(mtd, 0x01, &status);
if (ret) {
if (status)
printf("Program failed\n");
}
ret = spi_flash_cmd(flash->spi, IPQ40XX_SPINAND_CMD_WRDI, NULL, 0);
if (ret)
printf("Write disable failed in %s\n", __func__);
out:
spi_release_bus(flash->spi);
return ret;
}
static void fill_oob_data(struct mtd_info *mtd, uint8_t *oob,
size_t len, struct mtd_oob_ops *ops)
{
struct ipq40xx_spinand_info *info = mtd_to_ipq_info(mtd);
struct nand_chip *chip = info->chip;
memset(chip->oob_poi, 0xff, mtd->oobsize);
memcpy(chip->oob_poi + ops->ooboffs, oob, len);
return;
}
static int spi_nand_write_oob_data(struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops)
{
int page;
struct ipq40xx_spinand_info *info = mtd_to_ipq_info(mtd);
struct nand_chip *chip = info->chip;
/* Shift to get page */
page = (int)(to >> chip->page_shift);
fill_oob_data(mtd, ops->oobbuf, ops->ooblen, ops);
return spinand_write_oob_std(mtd, chip,
page & chip->pagemask, ops);
}
static int spi_nand_block_markbad(struct mtd_info *mtd, loff_t offs)
{
uint8_t buf[2]= { 0, 0 };
int ret;
struct ipq40xx_spinand_info *info = mtd_to_ipq_info(mtd);
struct spi_flash *flash = info->flash;
struct nand_chip *chip = info->chip;
struct mtd_oob_ops ops;
struct erase_info einfo;
ret = spi_nand_block_isbad(mtd, offs);
if (ret) {
if (ret > 0)
return 0;
return ret;
}
/* Attempt erase before marking OOB */
memset(&einfo, 0, sizeof(einfo));
einfo.mtd = mtd;
einfo.addr = offs;
einfo.len = 1 << chip->phys_erase_shift;
spi_nand_erase(mtd, &einfo);
ret = spi_claim_bus(flash->spi);
if (ret) {
printf ("Claim bus failed. %s\n", __func__);
return -1;
}
ops.datbuf = NULL;
ops.oobbuf = buf;
ops.ooboffs = chip->badblockpos;
ops.len = ops.ooblen = 1;
ret = spi_nand_write_oob_data(mtd, offs, &ops);
if (ret)
goto out;
out:
spi_release_bus(flash->spi);
return ret;
}
int verify_3bit_ecc(int status)
{
int ecc_status = (status & SPINAND_3BIT_ECC_MASK);
if (ecc_status == SPINAND_3BIT_ECC_ERROR)
return ECC_ERR;
else if (ecc_status >= SPINAND_3BIT_ECC_BF_THRESHOLD)
return ECC_CORRECTED;
else
return 0;
}
int verify_2bit_ecc(int status)
{
int ecc_status = (status & SPINAND_2BIT_ECC_MASK);
if ((ecc_status == SPINAND_2BIT_ECC_ERROR) ||
(ecc_status == SPINAND_2BIT_ECC_MASK))
return ECC_ERR;
else if (ecc_status == SPINAND_2BIT_ECC_CORRECTED)
return ECC_CORRECTED;
else
return 0;
}
int verify_2bit_toshiba_ecc(int status)
{
int ecc_status = (status & SPINAND_2BIT_ECC_MASK);
if (ecc_status == SPINAND_2BIT_ECC_ERROR)
return ECC_ERR;
else if (ecc_status == SPINAND_2BIT_ECC_CORRECTED_TOSHIBA)
return ECC_CORRECTED;
else
return 0;
}
int verify_dummy_ecc(int status)
{
return 0;
}
static int spi_nand_read_std(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
{
struct ipq40xx_spinand_info *info = mtd_to_ipq_info(mtd);
struct spi_flash *flash = info->flash;
struct nand_chip *chip = info->chip;
u32 ret;
u8 cmd[8];
u8 status;
int realpage, page, readlen, bytes, column, bytes_oob;
int ecc_corrected = 0;
column = mtd->writesize;
realpage = (int)(from >> chip->page_shift);
page = realpage & chip->pagemask;
readlen = ops->len;
ret = spi_claim_bus(flash->spi);
if (ret) {
printf ("Claim bus failed. %s\n", __func__);
return -1;
}
while (1) {
ret = do_die_select(mtd, flash, info, page);
if (ret)
goto out;
if (get_plane_id(info, page))
goto out;
page = fidelix_plane_select(page);
cmd[0] = IPQ40XX_SPINAND_CMD_READ;
cmd[1] = (u8)(page >> 16);
cmd[2] = (u8)(page >> 8);
cmd[3] = (u8)(page);
ret = spi_flash_cmd_write(flash->spi, cmd, 4, NULL, 0);
if (ret) {
printf("%s: write command failed\n", __func__);
goto out;
}
ret = spinand_waitfunc(mtd, 0x01, &status);
if (ret) {
goto out;
}
ret = info->params->verify_ecc(status);
if (ret == ECC_ERR) {
mtd->ecc_stats.failed++;
printf("ecc err(0x%x) for page read\n", status);
ret = -EBADMSG;
goto out;
} else if (ret == ECC_CORRECTED) {
mtd->ecc_stats.corrected++;
ecc_corrected = 1;
}
bytes = ((readlen < mtd->writesize) ? readlen : mtd->writesize);
bytes_oob = ops->ooblen;
/* Read Data */
if (bytes) {
info->params->norm_read_cmd(cmd, 0);
ret = spi_flash_cmd_read(flash->spi, cmd, 4, ops->datbuf, bytes);
if (ret) {
printf("%s: read data failed\n", __func__);
return -1;
}
ops->retlen += bytes;
}
/* Read OOB */
if (bytes_oob) {
info->params->norm_read_cmd(cmd, column);
ret = spi_flash_cmd_read(flash->spi, cmd, 4, ops->oobbuf, ops->ooblen);
if (ret) {
printf("%s: read data failed\n", __func__);
return -1;
}
ops->oobretlen += ops->ooblen;
}
readlen -= bytes;
if (readlen <= 0)
break;
ops->datbuf += bytes;
realpage++;
page = realpage & chip->pagemask;
}
if ((ret == 0) && (ecc_corrected))
ret = -EUCLEAN;
out:
spi_release_bus(flash->spi);
return ret;
}
static int spi_nand_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
struct mtd_oob_ops ops = {0};
u32 ret;
ops.mode = MTD_OPS_AUTO_OOB;
ops.len = len;
ops.datbuf = (uint8_t *)buf;
ret = spi_nand_read_std(mtd, from, &ops);
*retlen = ops.retlen;
return ret;
}
static int spi_nand_read_oob(struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops)
{
u32 ret;
/* Disable ECC */
spinand_internal_ecc(mtd, 0);
ret = spi_nand_read_std(mtd, from,ops);
/* Enable ECC */
spinand_internal_ecc(mtd, 1);
return ret;
}
static int spi_nand_write_std(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops)
{
struct ipq40xx_spinand_info *info = mtd_to_ipq_info(mtd);
struct spi_flash *flash = info->flash;
struct nand_chip *chip = info->chip;
u8 cmd[8];
u8 status;
u32 ret;
const u_char *wbuf;
const u_char *buf;
int realpage, page, bytes, write_len;
write_len = ops->len;
buf = ops->datbuf;
bytes = mtd->writesize;
/* Check whether page is aligned */
if (((to & (mtd->writesize -1)) !=0) ||
((ops->len & (mtd->writesize -1)) != 0)) {
printf("Attempt to write to non page aligned data\n");
return -EINVAL;
}
realpage = (int)(to >> chip->page_shift);
page = realpage & chip->pagemask;
ret = spi_claim_bus(flash->spi);
if (ret) {
printf ("Write enable failed %s\n", __func__);
return -1;
}
while (1) {
wbuf = buf;
ret = do_die_select(mtd, flash, info, page);
if (ret)
goto out;
if (get_plane_id(info, page))
goto out;
ret = spi_flash_cmd(flash->spi, IPQ40XX_SPINAND_CMD_WREN, NULL, 0);
if (ret) {
printf("Write enable failed\n");
goto out;
}
/* buffer to be transmittted here */
cmd[0] = IPQ40XX_SPINAND_CMD_PLOAD;
cmd[1] = SET_CMD_PLANE_BIT(params->plane_id);
cmd[2] = 0;
ret = spi_flash_cmd_write(flash->spi, cmd, 3, wbuf, bytes);
if (ret) {
printf("%s: write command failed\n", __func__);
goto out;
}
page = fidelix_plane_select(page);
cmd[0] = IPQ40XX_SPINAND_CMD_PROG;
cmd[1] = (u8)(page >> 16);
cmd[2] = (u8)(page >> 8);
cmd[3] = (u8)(page);
ret = spi_flash_cmd_write(flash->spi, cmd, 4, NULL, 0);
if (ret) {
printf("PLOG failed\n");
goto out;
}
ret = spinand_waitfunc(mtd, 0x01, &status);
if (ret) {
printf("Operation timeout\n");
goto out;
}
if (status & STATUS_P_FAIL) {
printf("Program failure (0x%x)\n", status);
ret = -EIO;
goto out;
}
ret = spi_flash_cmd(flash->spi, IPQ40XX_SPINAND_CMD_WRDI, NULL, 0);
if (ret) {
printf("Write disable failed\n");
goto out;
}
if (ops->ooblen)
ret = spi_nand_write_oob_data(mtd, to, ops);
ops->retlen += bytes;
write_len -= bytes;
if (!write_len)
break;
buf += bytes;
realpage++;
page = realpage & chip->pagemask;
}
out:
spi_release_bus(flash->spi);
return ret;
}
static int spi_nand_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
struct mtd_oob_ops ops = { 0 };
int ret;
ops.len = len;
ops.datbuf = (uint8_t *)buf;
ret = spi_nand_write_std(mtd, to, &ops);
*retlen = ops.retlen;
return ret;
}
static int spi_nand_write_oob(struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops)
{
int ret;
if(ops->datbuf == NULL)
return -EINVAL;
spinand_internal_ecc(mtd, 0);
ret = spi_nand_write_std(mtd, to, ops);
spinand_internal_ecc(mtd, 1);
return ret;
}
int spi_nand_flash_probe(struct spi_slave *spi, struct spi_flash *flash,
u8 *idcode)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(spi_nand_flash_tbl); i++) {
params = &spi_nand_flash_tbl[i];
if ((params->id[0] == idcode[0]) &&
(params->id[1] == idcode[1]) &&
(params->id[2] == idcode[2]) &&
(params->id[3] == idcode[3])) {
spi_print("%s SF NAND ID %x:%x:%x:%x\n",
__func__, idcode[0], idcode[1], idcode[2], idcode[3]);
break;
}
}
if (i == ARRAY_SIZE(spi_nand_flash_tbl)) {
return -EINVAL;
}
flash->name = params->name;
flash->page_size = params->page_size;
flash->sector_size = params->page_size;
flash->erase_size = params->erase_size;
flash->size = (params->page_size * params->nr_sectors * params->pages_per_sector);
return 0;
}
static int spinand_unlock_protect(struct mtd_info *mtd)
{
struct ipq40xx_spinand_info *info = mtd_to_ipq_info(mtd);
struct spi_flash *flash = info->flash;
int status;
int ret;
int i;
u8 cmd[3];
ret = spi_claim_bus(flash->spi);
if (ret) {
printf ("Write enable failed %s %d\n", __func__, __LINE__);
return -1;
}
cmd[0] = IPQ40XX_SPINAND_CMD_GETFEA;
cmd[1] = IPQ40XX_SPINAND_PROTEC_REG;
ret = spi_flash_cmd_read(flash->spi, cmd, 2, &status, 1);
if (ret) {
printf("Failed to read status register");
goto out;
}
for (i = 0; i < info->params->no_of_dies; i++) {
if (info->params->die_select != NULL) {
ret = info->params->die_select(mtd, flash, i);
if (ret)
goto out;
}
status &= (info->params->protec_bpx);
cmd[0] = IPQ40XX_SPINAND_CMD_SETFEA;
cmd[1] = IPQ40XX_SPINAND_PROTEC_REG;
cmd[2] = (u8)status;
ret = spi_flash_cmd_write(flash->spi, cmd, 3, NULL, 0);
if (ret) {
printf("Failed to unblock sectors\n");
goto out;
}
}
out:
spi_release_bus(flash->spi);
return ret;
}
void spinand_internal_ecc(struct mtd_info *mtd, int enable)
{
struct ipq40xx_spinand_info *info = mtd_to_ipq_info(mtd);
struct spi_flash *flash = info->flash;
int status;
int ret;
int i;
u8 cmd[3];
ret = spi_claim_bus(flash->spi);
if (ret) {
printf ("Write enable failed %s %d\n", __func__, __LINE__);
return;
}
for (i = 0; i < info->params->no_of_dies; i++) {
if (info->params->die_select != NULL) {
ret = info->params->die_select(mtd, flash, i);
if (ret)
goto out;
}
cmd[0] = IPQ40XX_SPINAND_CMD_GETFEA;
cmd[1] = IPQ40XX_SPINAND_FEATURE_REG;
ret = spi_flash_cmd_read(flash->spi, cmd, 2, &status, 1);
if (ret) {
printf("%s: read data failed\n", __func__);
goto out;
}
cmd[0] = IPQ40XX_SPINAND_CMD_SETFEA;
cmd[1] = IPQ40XX_SPINAND_FEATURE_REG;
if (enable) {
cmd[2] = status | IPQ40XX_SPINAND_FEATURE_ECC_EN;
} else {
cmd[2] = status & ~(IPQ40XX_SPINAND_FEATURE_ECC_EN);
}
ret = spi_flash_cmd_write(flash->spi, cmd, 3, NULL, 0);
if (ret) {
printf("Internal ECC enable failed\n");
goto out;
}
}
out:
spi_release_bus(flash->spi);
return;
}
static int spi_nand_scan_bbt_nop(struct mtd_info *mtd)
{
return 0;
}
int spi_nand_init(void)
{
struct spi_flash *flash;
struct mtd_info *mtd;
struct nand_chip *chip;
struct ipq40xx_spinand_info *info;
int ret;
int node;
u8 bus = CONFIG_SF_DEFAULT_BUS;
u8 cs = CONFIG_SF_SPI_NAND_CS;
info = (struct ipq40xx_spinand_info *)malloc(
sizeof(struct ipq40xx_spinand_info));
if (!info) {
printf ("Error in allocating mem\n");
return -ENOMEM;
}
memset(info, '0', sizeof(struct ipq40xx_spinand_info));
node = fdt_path_offset(gd->fdt_blob, "/spi/spi_nand");
if (node >= 0) {
bus = fdtdec_get_uint(gd->fdt_blob,
node, "bus-num", CONFIG_SF_DEFAULT_BUS);
cs = fdtdec_get_uint(gd->fdt_blob,
node, "cs", CONFIG_SF_SPI_NAND_CS);
}
flash = spi_flash_probe(bus, cs, CONFIG_SF_DEFAULT_SPEED,
CONFIG_SF_DEFAULT_MODE);
if (!flash) {
free(info);
spi_print("Id could not be mapped\n");
return -ENODEV;
}
mtd = &nand_info[CONFIG_IPQ_SPI_NAND_INFO_IDX];
chip = &nand_chip[CONFIG_IPQ_SPI_NAND_INFO_IDX];
mtd->priv = chip;
mtd->writesize = flash->page_size;
mtd->writebufsize = mtd->writesize;
mtd->erasesize = params->erase_size;
mtd->oobsize = params->oob_size;
mtd->size = flash->size;
mtd->type = MTD_NANDFLASH;
mtd->flags = MTD_CAP_NANDFLASH;
mtd->_read = spi_nand_read;
mtd->_write = spi_nand_write;
mtd->_erase = spi_nand_erase;
mtd->_read_oob = spi_nand_read_oob;
mtd->_write_oob = spi_nand_write_oob;
mtd->_block_isbad = spi_nand_block_isbad;
mtd->_block_markbad = spi_nand_block_markbad;
chip->page_shift = ffs(mtd->writesize) - 1;
chip->phys_erase_shift = ffs(mtd->erasesize) - 1;
chip->chipsize = flash->size;
chip->pagemask = (chip->chipsize >> chip->page_shift) - 1;
chip->badblockpos = 0;
chip->buffers = kmalloc(sizeof(*chip->buffers), GFP_KERNEL);
chip->oob_poi = chip->buffers->databuf + mtd->writesize;
/* One of the NAND layer functions that the command layer
* tries to access directly.
*/
chip->scan_bbt = spi_nand_scan_bbt_nop;
info->params = params;
info->flash = flash;
info->mtd = mtd;
info->chip = chip;
chip->priv = info;
if ((ret = nand_register(CONFIG_IPQ_SPI_NAND_INFO_IDX)) < 0) {
free(info);
spi_print("Failed to register with MTD subsystem\n");
return ret;
}
ret = spinand_unlock_protect(mtd);
if (ret) {
free(info);
printf("Failed to unlock blocks\n");
return -1;
}
spinand_internal_ecc(mtd, 1);
return 0;
}