774b082ca9
byte positions within the OOB area to support chips with unusual OOB sizes such as 218 or 224 bytes. The table for 128 byte OOB works for these but it assumes 3 bytes of ECC per 256 byte block, and in the case of an ONFI chip the params page may ask for something different. In other words, this is better but not yet perfect.
834 lines
19 KiB
C
834 lines
19 KiB
C
/*-
|
|
* Copyright (C) 2009-2012 Semihalf
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/module.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/callout.h>
|
|
#include <sys/sysctl.h>
|
|
|
|
#include <dev/nand/nand.h>
|
|
#include <dev/nand/nandbus.h>
|
|
#include <dev/nand/nand_ecc_pos.h>
|
|
#include "nfc_if.h"
|
|
#include "nand_if.h"
|
|
#include "nandbus_if.h"
|
|
#include <machine/stdarg.h>
|
|
|
|
#define NAND_RESET_DELAY 1000 /* tRST */
|
|
#define NAND_ERASE_DELAY 3000 /* tBERS */
|
|
#define NAND_PROG_DELAY 700 /* tPROG */
|
|
#define NAND_READ_DELAY 50 /* tR */
|
|
|
|
#define BIT0(x) ((x) & 0x1)
|
|
#define BIT1(x) (BIT0(x >> 1))
|
|
#define BIT2(x) (BIT0(x >> 2))
|
|
#define BIT3(x) (BIT0(x >> 3))
|
|
#define BIT4(x) (BIT0(x >> 4))
|
|
#define BIT5(x) (BIT0(x >> 5))
|
|
#define BIT6(x) (BIT0(x >> 6))
|
|
#define BIT7(x) (BIT0(x >> 7))
|
|
|
|
#define SOFTECC_SIZE 256
|
|
#define SOFTECC_BYTES 3
|
|
|
|
int nand_debug_flag = 0;
|
|
SYSCTL_INT(_debug, OID_AUTO, nand_debug, CTLFLAG_RW, &nand_debug_flag, 0,
|
|
"NAND subsystem debug flag");
|
|
|
|
static void
|
|
nand_tunable_init(void *arg)
|
|
{
|
|
|
|
TUNABLE_INT_FETCH("debug.nand", &nand_debug_flag);
|
|
}
|
|
|
|
SYSINIT(nand_tunables, SI_SUB_VFS, SI_ORDER_ANY, nand_tunable_init, NULL);
|
|
|
|
MALLOC_DEFINE(M_NAND, "NAND", "NAND dynamic data");
|
|
|
|
static void calculate_ecc(const uint8_t *, uint8_t *);
|
|
static int correct_ecc(uint8_t *, uint8_t *, uint8_t *);
|
|
|
|
void
|
|
nand_debug(int level, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
if (!(nand_debug_flag & level))
|
|
return;
|
|
va_start(ap, fmt);
|
|
vprintf(fmt, ap);
|
|
va_end(ap);
|
|
printf("\n");
|
|
}
|
|
|
|
void
|
|
nand_init(struct nand_softc *nand, device_t dev, int ecc_mode,
|
|
int ecc_bytes, int ecc_size, uint16_t *eccposition, char *cdev_name)
|
|
{
|
|
|
|
nand->ecc.eccmode = ecc_mode;
|
|
nand->chip_cdev_name = cdev_name;
|
|
|
|
if (ecc_mode == NAND_ECC_SOFT) {
|
|
nand->ecc.eccbytes = SOFTECC_BYTES;
|
|
nand->ecc.eccsize = SOFTECC_SIZE;
|
|
} else if (ecc_mode != NAND_ECC_NONE) {
|
|
nand->ecc.eccbytes = ecc_bytes;
|
|
nand->ecc.eccsize = ecc_size;
|
|
if (eccposition)
|
|
nand->ecc.eccpositions = eccposition;
|
|
}
|
|
}
|
|
|
|
void
|
|
nand_onfi_set_params(struct nand_chip *chip, struct onfi_chip_params *params)
|
|
{
|
|
struct chip_geom *cg;
|
|
|
|
cg = &chip->chip_geom;
|
|
|
|
init_chip_geom(cg, params->luns, params->blocks_per_lun,
|
|
params->pages_per_block, params->bytes_per_page,
|
|
params->spare_bytes_per_page);
|
|
chip->t_bers = params->t_bers;
|
|
chip->t_prog = params->t_prog;
|
|
chip->t_r = params->t_r;
|
|
chip->t_ccs = params->t_ccs;
|
|
|
|
if (params->features & ONFI_FEAT_16BIT)
|
|
chip->flags |= NAND_16_BIT;
|
|
}
|
|
|
|
void
|
|
nand_set_params(struct nand_chip *chip, struct nand_params *params)
|
|
{
|
|
struct chip_geom *cg;
|
|
uint32_t blocks_per_chip;
|
|
|
|
cg = &chip->chip_geom;
|
|
blocks_per_chip = (params->chip_size << 20) /
|
|
(params->page_size * params->pages_per_block);
|
|
|
|
init_chip_geom(cg, 1, blocks_per_chip,
|
|
params->pages_per_block, params->page_size,
|
|
params->oob_size);
|
|
|
|
chip->t_bers = NAND_ERASE_DELAY;
|
|
chip->t_prog = NAND_PROG_DELAY;
|
|
chip->t_r = NAND_READ_DELAY;
|
|
chip->t_ccs = 0;
|
|
|
|
if (params->flags & NAND_16_BIT)
|
|
chip->flags |= NAND_16_BIT;
|
|
}
|
|
|
|
int
|
|
nand_init_stat(struct nand_chip *chip)
|
|
{
|
|
struct block_stat *blk_stat;
|
|
struct page_stat *pg_stat;
|
|
struct chip_geom *cg;
|
|
uint32_t blks, pgs;
|
|
|
|
cg = &chip->chip_geom;
|
|
blks = cg->blks_per_lun * cg->luns;
|
|
blk_stat = malloc(sizeof(struct block_stat) * blks, M_NAND,
|
|
M_WAITOK | M_ZERO);
|
|
if (!blk_stat)
|
|
return (ENOMEM);
|
|
|
|
pgs = blks * cg->pgs_per_blk;
|
|
pg_stat = malloc(sizeof(struct page_stat) * pgs, M_NAND,
|
|
M_WAITOK | M_ZERO);
|
|
if (!pg_stat) {
|
|
free(blk_stat, M_NAND);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
chip->blk_stat = blk_stat;
|
|
chip->pg_stat = pg_stat;
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
nand_destroy_stat(struct nand_chip *chip)
|
|
{
|
|
|
|
free(chip->pg_stat, M_NAND);
|
|
free(chip->blk_stat, M_NAND);
|
|
}
|
|
|
|
int
|
|
init_chip_geom(struct chip_geom *cg, uint32_t luns, uint32_t blks_per_lun,
|
|
uint32_t pgs_per_blk, uint32_t pg_size, uint32_t oob_size)
|
|
{
|
|
int shift;
|
|
|
|
if (!cg)
|
|
return (-1);
|
|
|
|
cg->luns = luns;
|
|
cg->blks_per_lun = blks_per_lun;
|
|
cg->blks_per_chip = blks_per_lun * luns;
|
|
cg->pgs_per_blk = pgs_per_blk;
|
|
|
|
cg->page_size = pg_size;
|
|
cg->oob_size = oob_size;
|
|
cg->block_size = cg->page_size * cg->pgs_per_blk;
|
|
cg->chip_size = cg->block_size * cg->blks_per_chip;
|
|
|
|
shift = fls(cg->pgs_per_blk - 1);
|
|
cg->pg_mask = (1 << shift) - 1;
|
|
cg->blk_shift = shift;
|
|
|
|
if (cg->blks_per_lun > 0) {
|
|
shift = fls(cg->blks_per_lun - 1);
|
|
cg->blk_mask = ((1 << shift) - 1) << cg->blk_shift;
|
|
} else {
|
|
shift = 0;
|
|
cg->blk_mask = 0;
|
|
}
|
|
|
|
cg->lun_shift = shift + cg->blk_shift;
|
|
shift = fls(cg->luns - 1);
|
|
cg->lun_mask = ((1 << shift) - 1) << cg->lun_shift;
|
|
|
|
nand_debug(NDBG_NAND, "Masks: lun 0x%x blk 0x%x page 0x%x\n"
|
|
"Shifts: lun %d blk %d",
|
|
cg->lun_mask, cg->blk_mask, cg->pg_mask,
|
|
cg->lun_shift, cg->blk_shift);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
nand_row_to_blkpg(struct chip_geom *cg, uint32_t row, uint32_t *lun,
|
|
uint32_t *blk, uint32_t *pg)
|
|
{
|
|
|
|
if (!cg || !lun || !blk || !pg)
|
|
return (-1);
|
|
|
|
if (row & ~(cg->lun_mask | cg->blk_mask | cg->pg_mask)) {
|
|
nand_debug(NDBG_NAND,"Address out of bounds\n");
|
|
return (-1);
|
|
}
|
|
|
|
*lun = (row & cg->lun_mask) >> cg->lun_shift;
|
|
*blk = (row & cg->blk_mask) >> cg->blk_shift;
|
|
*pg = (row & cg->pg_mask);
|
|
|
|
nand_debug(NDBG_NAND,"address %x-%x-%x\n", *lun, *blk, *pg);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int page_to_row(struct chip_geom *cg, uint32_t page, uint32_t *row)
|
|
{
|
|
uint32_t lun, block, pg_in_blk;
|
|
|
|
if (!cg || !row)
|
|
return (-1);
|
|
|
|
block = page / cg->pgs_per_blk;
|
|
pg_in_blk = page % cg->pgs_per_blk;
|
|
|
|
lun = block / cg->blks_per_lun;
|
|
block = block % cg->blks_per_lun;
|
|
|
|
*row = (lun << cg->lun_shift) & cg->lun_mask;
|
|
*row |= ((block << cg->blk_shift) & cg->blk_mask);
|
|
*row |= (pg_in_blk & cg->pg_mask);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
nand_check_page_boundary(struct nand_chip *chip, uint32_t page)
|
|
{
|
|
struct chip_geom* cg;
|
|
|
|
cg = &chip->chip_geom;
|
|
if (page >= (cg->pgs_per_blk * cg->blks_per_lun * cg->luns)) {
|
|
nand_debug(NDBG_GEN,"%s: page number too big %#x\n",
|
|
__func__, page);
|
|
return (1);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
nand_get_chip_param(struct nand_chip *chip, struct chip_param_io *param)
|
|
{
|
|
struct chip_geom *cg;
|
|
|
|
cg = &chip->chip_geom;
|
|
param->page_size = cg->page_size;
|
|
param->oob_size = cg->oob_size;
|
|
|
|
param->blocks = cg->blks_per_lun * cg->luns;
|
|
param->pages_per_block = cg->pgs_per_blk;
|
|
}
|
|
|
|
static uint16_t *
|
|
default_software_ecc_positions(struct nand_chip *chip)
|
|
{
|
|
/* If positions have been set already, use them. */
|
|
if (chip->nand->ecc.eccpositions)
|
|
return (chip->nand->ecc.eccpositions);
|
|
|
|
/*
|
|
* XXX Note that the following logic isn't really sufficient, especially
|
|
* in the ONFI case where the number of ECC bytes can be dictated by
|
|
* values in the parameters page, and that could lead to needing more
|
|
* byte positions than exist within the tables of software-ecc defaults.
|
|
*/
|
|
if (chip->chip_geom.oob_size >= 128)
|
|
return (default_software_ecc_positions_128);
|
|
if (chip->chip_geom.oob_size >= 64)
|
|
return (default_software_ecc_positions_64);
|
|
else if (chip->chip_geom.oob_size >= 16)
|
|
return (default_software_ecc_positions_16);
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
static void
|
|
calculate_ecc(const uint8_t *buf, uint8_t *ecc)
|
|
{
|
|
uint8_t p8, byte;
|
|
int i;
|
|
|
|
memset(ecc, 0, 3);
|
|
|
|
for (i = 0; i < 256; i++) {
|
|
byte = buf[i];
|
|
ecc[0] ^= (BIT0(byte) ^ BIT2(byte) ^ BIT4(byte) ^
|
|
BIT6(byte)) << 2;
|
|
ecc[0] ^= (BIT1(byte) ^ BIT3(byte) ^ BIT5(byte) ^
|
|
BIT7(byte)) << 3;
|
|
ecc[0] ^= (BIT0(byte) ^ BIT1(byte) ^ BIT4(byte) ^
|
|
BIT5(byte)) << 4;
|
|
ecc[0] ^= (BIT2(byte) ^ BIT3(byte) ^ BIT6(byte) ^
|
|
BIT7(byte)) << 5;
|
|
ecc[0] ^= (BIT0(byte) ^ BIT1(byte) ^ BIT2(byte) ^
|
|
BIT3(byte)) << 6;
|
|
ecc[0] ^= (BIT4(byte) ^ BIT5(byte) ^ BIT6(byte) ^
|
|
BIT7(byte)) << 7;
|
|
|
|
p8 = BIT0(byte) ^ BIT1(byte) ^ BIT2(byte) ^
|
|
BIT3(byte) ^ BIT4(byte) ^ BIT5(byte) ^ BIT6(byte) ^
|
|
BIT7(byte);
|
|
|
|
if (p8) {
|
|
ecc[2] ^= (0x1 << BIT0(i));
|
|
ecc[2] ^= (0x4 << BIT1(i));
|
|
ecc[2] ^= (0x10 << BIT2(i));
|
|
ecc[2] ^= (0x40 << BIT3(i));
|
|
|
|
ecc[1] ^= (0x1 << BIT4(i));
|
|
ecc[1] ^= (0x4 << BIT5(i));
|
|
ecc[1] ^= (0x10 << BIT6(i));
|
|
ecc[1] ^= (0x40 << BIT7(i));
|
|
}
|
|
}
|
|
ecc[0] = ~ecc[0];
|
|
ecc[1] = ~ecc[1];
|
|
ecc[2] = ~ecc[2];
|
|
ecc[0] |= 3;
|
|
}
|
|
|
|
static int
|
|
correct_ecc(uint8_t *buf, uint8_t *calc_ecc, uint8_t *read_ecc)
|
|
{
|
|
uint8_t ecc0, ecc1, ecc2, onesnum, bit, byte;
|
|
uint16_t addr = 0;
|
|
|
|
ecc0 = calc_ecc[0] ^ read_ecc[0];
|
|
ecc1 = calc_ecc[1] ^ read_ecc[1];
|
|
ecc2 = calc_ecc[2] ^ read_ecc[2];
|
|
|
|
if (!ecc0 && !ecc1 && !ecc2)
|
|
return (ECC_OK);
|
|
|
|
addr = BIT3(ecc0) | (BIT5(ecc0) << 1) | (BIT7(ecc0) << 2);
|
|
addr |= (BIT1(ecc2) << 3) | (BIT3(ecc2) << 4) |
|
|
(BIT5(ecc2) << 5) | (BIT7(ecc2) << 6);
|
|
addr |= (BIT1(ecc1) << 7) | (BIT3(ecc1) << 8) |
|
|
(BIT5(ecc1) << 9) | (BIT7(ecc1) << 10);
|
|
|
|
onesnum = 0;
|
|
while (ecc0 || ecc1 || ecc2) {
|
|
if (ecc0 & 1)
|
|
onesnum++;
|
|
if (ecc1 & 1)
|
|
onesnum++;
|
|
if (ecc2 & 1)
|
|
onesnum++;
|
|
|
|
ecc0 >>= 1;
|
|
ecc1 >>= 1;
|
|
ecc2 >>= 1;
|
|
}
|
|
|
|
if (onesnum == 11) {
|
|
/* Correctable error */
|
|
bit = addr & 7;
|
|
byte = addr >> 3;
|
|
buf[byte] ^= (1 << bit);
|
|
return (ECC_CORRECTABLE);
|
|
} else if (onesnum == 1) {
|
|
/* ECC error */
|
|
return (ECC_ERROR_ECC);
|
|
} else {
|
|
/* Uncorrectable error */
|
|
return (ECC_UNCORRECTABLE);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
nand_softecc_get(device_t dev, uint8_t *buf, int pagesize, uint8_t *ecc)
|
|
{
|
|
int steps = pagesize / SOFTECC_SIZE;
|
|
int i = 0, j = 0;
|
|
|
|
for (; i < (steps * SOFTECC_BYTES);
|
|
i += SOFTECC_BYTES, j += SOFTECC_SIZE) {
|
|
calculate_ecc(&buf[j], &ecc[i]);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
nand_softecc_correct(device_t dev, uint8_t *buf, int pagesize,
|
|
uint8_t *readecc, uint8_t *calcecc)
|
|
{
|
|
int steps = pagesize / SOFTECC_SIZE;
|
|
int i = 0, j = 0, ret = 0;
|
|
|
|
for (i = 0; i < (steps * SOFTECC_BYTES);
|
|
i += SOFTECC_BYTES, j += SOFTECC_SIZE) {
|
|
ret += correct_ecc(&buf[j], &calcecc[i], &readecc[i]);
|
|
if (ret < 0)
|
|
return (ret);
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
static int
|
|
offset_to_page(struct chip_geom *cg, uint32_t offset)
|
|
{
|
|
|
|
return (offset / cg->page_size);
|
|
}
|
|
|
|
int
|
|
nand_read_pages(struct nand_chip *chip, uint32_t offset, void *buf,
|
|
uint32_t len)
|
|
{
|
|
struct chip_geom *cg;
|
|
struct nand_ecc_data *eccd;
|
|
struct page_stat *pg_stat;
|
|
device_t nandbus;
|
|
void *oob = NULL;
|
|
uint8_t *ptr;
|
|
uint16_t *eccpos = NULL;
|
|
uint32_t page, num, steps = 0;
|
|
int i, retval = 0, needwrite;
|
|
|
|
nand_debug(NDBG_NAND,"%p read page %x[%x]", chip, offset, len);
|
|
cg = &chip->chip_geom;
|
|
eccd = &chip->nand->ecc;
|
|
page = offset_to_page(cg, offset);
|
|
num = len / cg->page_size;
|
|
|
|
if (eccd->eccmode != NAND_ECC_NONE) {
|
|
steps = cg->page_size / eccd->eccsize;
|
|
eccpos = default_software_ecc_positions(chip);
|
|
oob = malloc(cg->oob_size, M_NAND, M_WAITOK);
|
|
}
|
|
|
|
nandbus = device_get_parent(chip->dev);
|
|
NANDBUS_LOCK(nandbus);
|
|
NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num);
|
|
|
|
ptr = (uint8_t *)buf;
|
|
while (num--) {
|
|
pg_stat = &(chip->pg_stat[page]);
|
|
|
|
if (NAND_READ_PAGE(chip->dev, page, ptr, cg->page_size, 0)) {
|
|
retval = ENXIO;
|
|
break;
|
|
}
|
|
|
|
if (eccd->eccmode != NAND_ECC_NONE) {
|
|
if (NAND_GET_ECC(chip->dev, ptr, eccd->ecccalculated,
|
|
&needwrite)) {
|
|
retval = ENXIO;
|
|
break;
|
|
}
|
|
nand_debug(NDBG_ECC,"%s: ECC calculated:",
|
|
__func__);
|
|
if (nand_debug_flag & NDBG_ECC)
|
|
for (i = 0; i < (eccd->eccbytes * steps); i++)
|
|
printf("%x ", eccd->ecccalculated[i]);
|
|
|
|
nand_debug(NDBG_ECC,"\n");
|
|
|
|
if (NAND_READ_OOB(chip->dev, page, oob, cg->oob_size,
|
|
0)) {
|
|
retval = ENXIO;
|
|
break;
|
|
}
|
|
for (i = 0; i < (eccd->eccbytes * steps); i++)
|
|
eccd->eccread[i] = ((uint8_t *)oob)[eccpos[i]];
|
|
|
|
nand_debug(NDBG_ECC,"%s: ECC read:", __func__);
|
|
if (nand_debug_flag & NDBG_ECC)
|
|
for (i = 0; i < (eccd->eccbytes * steps); i++)
|
|
printf("%x ", eccd->eccread[i]);
|
|
nand_debug(NDBG_ECC,"\n");
|
|
|
|
retval = NAND_CORRECT_ECC(chip->dev, ptr, eccd->eccread,
|
|
eccd->ecccalculated);
|
|
|
|
nand_debug(NDBG_ECC, "NAND_CORRECT_ECC() returned %d",
|
|
retval);
|
|
|
|
if (retval == 0)
|
|
pg_stat->ecc_stat.ecc_succeded++;
|
|
else if (retval > 0) {
|
|
pg_stat->ecc_stat.ecc_corrected += retval;
|
|
retval = ECC_CORRECTABLE;
|
|
} else {
|
|
pg_stat->ecc_stat.ecc_failed++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
pg_stat->page_read++;
|
|
page++;
|
|
ptr += cg->page_size;
|
|
}
|
|
|
|
NANDBUS_UNLOCK(nandbus);
|
|
|
|
if (oob)
|
|
free(oob, M_NAND);
|
|
|
|
return (retval);
|
|
}
|
|
|
|
int
|
|
nand_read_pages_raw(struct nand_chip *chip, uint32_t offset, void *buf,
|
|
uint32_t len)
|
|
{
|
|
struct chip_geom *cg;
|
|
device_t nandbus;
|
|
uint8_t *ptr;
|
|
uint32_t page, num, end, begin = 0, begin_off;
|
|
int retval = 0;
|
|
|
|
cg = &chip->chip_geom;
|
|
page = offset_to_page(cg, offset);
|
|
begin_off = offset - page * cg->page_size;
|
|
if (begin_off) {
|
|
begin = cg->page_size - begin_off;
|
|
len -= begin;
|
|
}
|
|
num = len / cg->page_size;
|
|
end = len % cg->page_size;
|
|
|
|
nandbus = device_get_parent(chip->dev);
|
|
NANDBUS_LOCK(nandbus);
|
|
NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num);
|
|
|
|
ptr = (uint8_t *)buf;
|
|
if (begin_off) {
|
|
if (NAND_READ_PAGE(chip->dev, page, ptr, begin, begin_off)) {
|
|
NANDBUS_UNLOCK(nandbus);
|
|
return (ENXIO);
|
|
}
|
|
|
|
page++;
|
|
ptr += begin;
|
|
}
|
|
|
|
while (num--) {
|
|
if (NAND_READ_PAGE(chip->dev, page, ptr, cg->page_size, 0)) {
|
|
NANDBUS_UNLOCK(nandbus);
|
|
return (ENXIO);
|
|
}
|
|
|
|
page++;
|
|
ptr += cg->page_size;
|
|
}
|
|
|
|
if (end)
|
|
if (NAND_READ_PAGE(chip->dev, page, ptr, end, 0)) {
|
|
NANDBUS_UNLOCK(nandbus);
|
|
return (ENXIO);
|
|
}
|
|
|
|
NANDBUS_UNLOCK(nandbus);
|
|
|
|
return (retval);
|
|
}
|
|
|
|
|
|
int
|
|
nand_prog_pages(struct nand_chip *chip, uint32_t offset, uint8_t *buf,
|
|
uint32_t len)
|
|
{
|
|
struct chip_geom *cg;
|
|
struct page_stat *pg_stat;
|
|
struct nand_ecc_data *eccd;
|
|
device_t nandbus;
|
|
uint32_t page, num;
|
|
uint8_t *oob = NULL;
|
|
uint16_t *eccpos = NULL;
|
|
int steps = 0, i, needwrite, err = 0;
|
|
|
|
nand_debug(NDBG_NAND,"%p prog page %x[%x]", chip, offset, len);
|
|
|
|
eccd = &chip->nand->ecc;
|
|
cg = &chip->chip_geom;
|
|
page = offset_to_page(cg, offset);
|
|
num = len / cg->page_size;
|
|
|
|
if (eccd->eccmode != NAND_ECC_NONE) {
|
|
steps = cg->page_size / eccd->eccsize;
|
|
oob = malloc(cg->oob_size, M_NAND, M_WAITOK);
|
|
eccpos = default_software_ecc_positions(chip);
|
|
}
|
|
|
|
nandbus = device_get_parent(chip->dev);
|
|
NANDBUS_LOCK(nandbus);
|
|
NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num);
|
|
|
|
while (num--) {
|
|
if (NAND_PROGRAM_PAGE(chip->dev, page, buf, cg->page_size, 0)) {
|
|
err = ENXIO;
|
|
break;
|
|
}
|
|
|
|
if (eccd->eccmode != NAND_ECC_NONE) {
|
|
if (NAND_GET_ECC(chip->dev, buf, &eccd->ecccalculated,
|
|
&needwrite)) {
|
|
err = ENXIO;
|
|
break;
|
|
}
|
|
nand_debug(NDBG_ECC,"ECC calculated:");
|
|
if (nand_debug_flag & NDBG_ECC)
|
|
for (i = 0; i < (eccd->eccbytes * steps); i++)
|
|
printf("%x ", eccd->ecccalculated[i]);
|
|
|
|
nand_debug(NDBG_ECC,"\n");
|
|
|
|
if (needwrite) {
|
|
if (NAND_READ_OOB(chip->dev, page, oob, cg->oob_size,
|
|
0)) {
|
|
err = ENXIO;
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < (eccd->eccbytes * steps); i++)
|
|
oob[eccpos[i]] = eccd->ecccalculated[i];
|
|
|
|
if (NAND_PROGRAM_OOB(chip->dev, page, oob,
|
|
cg->oob_size, 0)) {
|
|
err = ENXIO;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
pg_stat = &(chip->pg_stat[page]);
|
|
pg_stat->page_written++;
|
|
|
|
page++;
|
|
buf += cg->page_size;
|
|
}
|
|
|
|
NANDBUS_UNLOCK(nandbus);
|
|
|
|
if (oob)
|
|
free(oob, M_NAND);
|
|
|
|
return (err);
|
|
}
|
|
|
|
int
|
|
nand_prog_pages_raw(struct nand_chip *chip, uint32_t offset, void *buf,
|
|
uint32_t len)
|
|
{
|
|
struct chip_geom *cg;
|
|
device_t nandbus;
|
|
uint8_t *ptr;
|
|
uint32_t page, num, end, begin = 0, begin_off;
|
|
int retval = 0;
|
|
|
|
cg = &chip->chip_geom;
|
|
page = offset_to_page(cg, offset);
|
|
begin_off = offset - page * cg->page_size;
|
|
if (begin_off) {
|
|
begin = cg->page_size - begin_off;
|
|
len -= begin;
|
|
}
|
|
num = len / cg->page_size;
|
|
end = len % cg->page_size;
|
|
|
|
nandbus = device_get_parent(chip->dev);
|
|
NANDBUS_LOCK(nandbus);
|
|
NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num);
|
|
|
|
ptr = (uint8_t *)buf;
|
|
if (begin_off) {
|
|
if (NAND_PROGRAM_PAGE(chip->dev, page, ptr, begin, begin_off)) {
|
|
NANDBUS_UNLOCK(nandbus);
|
|
return (ENXIO);
|
|
}
|
|
|
|
page++;
|
|
ptr += begin;
|
|
}
|
|
|
|
while (num--) {
|
|
if (NAND_PROGRAM_PAGE(chip->dev, page, ptr, cg->page_size, 0)) {
|
|
NANDBUS_UNLOCK(nandbus);
|
|
return (ENXIO);
|
|
}
|
|
|
|
page++;
|
|
ptr += cg->page_size;
|
|
}
|
|
|
|
if (end)
|
|
retval = NAND_PROGRAM_PAGE(chip->dev, page, ptr, end, 0);
|
|
|
|
NANDBUS_UNLOCK(nandbus);
|
|
|
|
return (retval);
|
|
}
|
|
|
|
int
|
|
nand_read_oob(struct nand_chip *chip, uint32_t page, void *buf,
|
|
uint32_t len)
|
|
{
|
|
device_t nandbus;
|
|
int retval = 0;
|
|
|
|
nandbus = device_get_parent(chip->dev);
|
|
NANDBUS_LOCK(nandbus);
|
|
NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num);
|
|
|
|
retval = NAND_READ_OOB(chip->dev, page, buf, len, 0);
|
|
|
|
NANDBUS_UNLOCK(nandbus);
|
|
|
|
return (retval);
|
|
}
|
|
|
|
|
|
int
|
|
nand_prog_oob(struct nand_chip *chip, uint32_t page, void *buf,
|
|
uint32_t len)
|
|
{
|
|
device_t nandbus;
|
|
int retval = 0;
|
|
|
|
nandbus = device_get_parent(chip->dev);
|
|
NANDBUS_LOCK(nandbus);
|
|
NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num);
|
|
|
|
retval = NAND_PROGRAM_OOB(chip->dev, page, buf, len, 0);
|
|
|
|
NANDBUS_UNLOCK(nandbus);
|
|
|
|
return (retval);
|
|
}
|
|
|
|
int
|
|
nand_erase_blocks(struct nand_chip *chip, off_t offset, size_t len)
|
|
{
|
|
device_t nandbus;
|
|
struct chip_geom *cg;
|
|
uint32_t block, num_blocks;
|
|
int err = 0;
|
|
|
|
cg = &chip->chip_geom;
|
|
if ((offset % cg->block_size) || (len % cg->block_size))
|
|
return (EINVAL);
|
|
|
|
block = offset / cg->block_size;
|
|
num_blocks = len / cg->block_size;
|
|
nand_debug(NDBG_NAND,"%p erase blocks %d[%d]", chip, block, num_blocks);
|
|
|
|
nandbus = device_get_parent(chip->dev);
|
|
NANDBUS_LOCK(nandbus);
|
|
NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num);
|
|
|
|
while (num_blocks--) {
|
|
if (!nand_check_bad_block(chip, block)) {
|
|
if (NAND_ERASE_BLOCK(chip->dev, block)) {
|
|
nand_debug(NDBG_NAND,"%p erase blocks %d error",
|
|
chip, block);
|
|
nand_mark_bad_block(chip, block);
|
|
err = ENXIO;
|
|
}
|
|
} else
|
|
err = ENXIO;
|
|
|
|
block++;
|
|
};
|
|
|
|
NANDBUS_UNLOCK(nandbus);
|
|
|
|
if (err)
|
|
nand_update_bbt(chip);
|
|
|
|
return (err);
|
|
}
|
|
|
|
MODULE_VERSION(nand, 1);
|