From bc5a5f89e9458f1b945e8c17ce1bbe541da152bf Mon Sep 17 00:00:00 2001 From: heyuanjie87 Date: Tue, 18 Sep 2018 18:41:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=B0=E7=9A=84mtd?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/drivers/Kconfig | 15 +- components/drivers/include/drivers/mtd.h | 118 ++++ components/drivers/include/drivers/mtdnand.h | 118 ++++ components/drivers/include/drivers/mtdnor.h | 48 ++ components/drivers/include/rtdevice.h | 7 + components/drivers/mtd/SConscript | 11 + components/drivers/mtd/mtd.c | 225 +++++++ components/drivers/mtd/mtdnand.c | 636 +++++++++++++++++++ components/drivers/mtd/mtdnor.c | 87 +++ 9 files changed, 1264 insertions(+), 1 deletion(-) create mode 100644 components/drivers/include/drivers/mtd.h create mode 100644 components/drivers/include/drivers/mtdnand.h create mode 100644 components/drivers/include/drivers/mtdnor.h create mode 100644 components/drivers/mtd/mtd.c create mode 100644 components/drivers/mtd/mtdnand.c create mode 100644 components/drivers/mtd/mtdnor.c diff --git a/components/drivers/Kconfig b/components/drivers/Kconfig index e2d290032b..e88a9b1335 100755 --- a/components/drivers/Kconfig +++ b/components/drivers/Kconfig @@ -90,6 +90,20 @@ config RT_USING_MTD_NAND default n endif +config RT_USING_MTD + bool "Using Memory Technology Device (MTD)" + default n + + if RT_USING_MTD + config MTD_USING_NOR + bool "Using MTD Nor Flash device" + default n + + config MTD_USING_NAND + bool "Using MTD Nand Flash device" + default n + endif + config RT_USING_RTC bool "Using RTC device drivers" default n @@ -138,7 +152,6 @@ config RT_USING_SDIO config RT_MMCSD_MAX_PARTITION int "mmcsd max partition" default 16 - config RT_SDIO_DEBUG bool "Enable SDIO debug log output" default n diff --git a/components/drivers/include/drivers/mtd.h b/components/drivers/include/drivers/mtd.h new file mode 100644 index 0000000000..64c24bca3a --- /dev/null +++ b/components/drivers/include/drivers/mtd.h @@ -0,0 +1,118 @@ +/* + * File : mtd.h + * This file is part of RT-Thread RTOS + * COPYRIGHT (C) 2012, Shanghai Real-Thread Technology Co., Ltd + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2018-7-5 heyuanjie the first version + */ + +#ifndef __MTD_H__ +#define __MTD_H__ + +#include +#include +#include + +#define MTD_TYPE_NOR 1 +#define MTD_TYPE_NAND 2 + + /** + * MTD operation modes + * + * @MTD_OPM_PLACE_OOB: OOB data are placed at the given offset (default) + * @MTD_OPM_AUTO_OOB: OOB data are automatically placed at the free areas + * @MTD_OPM_RAW: data are transferred as-is, with no error correction; + */ +enum mtd_opm +{ + MTD_OPM_PLACE_OOB = 0, + MTD_OPM_AUTO_OOB = 1, + MTD_OPM_RAW = 2, +}; + +#ifndef loff_t +typedef long loff_t; +#endif + +struct mtd_oob_region +{ + uint8_t offset; + uint8_t length; +}; + +typedef struct mtd_info +{ + struct rt_device parent; + + const struct mtd_ops *ops; + + uint16_t oob_size; + uint16_t sector_size; /* Minimal writable flash unit size */ + uint32_t block_size:28; /* Erase size for the device */ + uint32_t type:4; + + size_t size; /* Total size of the MTD */ + loff_t offset; /* At which this MTD starts, from the beginning of the MEMORY */ + struct mtd_info *master; + + void *priv; +}rt_mtd_t; + +struct mtd_io_desc +{ + uint8_t mode; /* operation mode(enum mtd_opm) */ + uint8_t ooblen; /* number of oob bytes to write/read */ + uint8_t oobretlen; /* number of oob bytes written/read */ + uint8_t ooboffs; /* offset in the oob area */ + uint8_t *oobbuf; + + size_t datlen; /* number of data bytes to write/read */ + size_t datretlen; /* number of data bytes written/read */ + uint8_t *datbuf; /* if NULL only oob are read/written */ +}; + +struct mtd_ops +{ + int(*erase)(rt_mtd_t *mtd, loff_t addr, size_t len); /* return 0 if success */ + int(*read) (rt_mtd_t *mtd, loff_t from, struct mtd_io_desc *ops); /* return 0 if success */ + int(*write) (rt_mtd_t *mtd, loff_t to, struct mtd_io_desc *ops); /* return 0 if success */ + int(*isbad) (rt_mtd_t *mtd, uint32_t block); /* return 1 if bad, 0 not bad */ + int(*markbad) (rt_mtd_t *mtd, uint32_t block); /* return 0 if success */ +}; + +struct mtd_part +{ + const char *name; /* name of the MTD partion */ + loff_t offset; /* start addr of partion */ + size_t size; /* size of partion */ +}; + +int rt_mtd_erase(rt_mtd_t *mtd, loff_t addr, size_t size); +int rt_mtd_block_erase(rt_mtd_t *mtd, uint32_t block); +int rt_mtd_read_oob(rt_mtd_t *mtd, loff_t from, struct mtd_io_desc *desc); +int rt_mtd_write_oob(rt_mtd_t *mtd, loff_t to, struct mtd_io_desc *desc); +int rt_mtd_read(rt_mtd_t *mtd, loff_t from, uint8_t *buf, size_t len); +int rt_mtd_write(rt_mtd_t *mtd, loff_t to, const uint8_t *buf, size_t len); +int rt_mtd_block_markbad(rt_mtd_t *mtd, uint32_t block); +int rt_mtd_block_isbad(rt_mtd_t *mtd, uint32_t block); + +rt_mtd_t* rt_mtd_get(const char *name); +int rt_mtd_register(rt_mtd_t *master, const struct mtd_part *parts, int np); + +#endif diff --git a/components/drivers/include/drivers/mtdnand.h b/components/drivers/include/drivers/mtdnand.h new file mode 100644 index 0000000000..5371cd1fb3 --- /dev/null +++ b/components/drivers/include/drivers/mtdnand.h @@ -0,0 +1,118 @@ +/* + * File : nand.h + * This file is part of RT-Thread RTOS + * COPYRIGHT (C) 2006 - 2012, RT-Thread Development Team + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2018-7-8 heyuanjie the first version + */ + +/* + * COPYRIGHT (C) 2012, Shanghai Real Thread + */ + +#ifndef _MTDNAND_H_ +#define _MTDNAND_H_ + +#include "mtd.h" + + /* Status bits */ +#define NAND_STATUS_FAIL 0x01 +#define NAND_STATUS_FAIL_N1 0x02 +#define NAND_STATUS_WP 0x80 + +typedef enum +{ + NAND_CMD_PAGE_RD, /* read data to chip's page buffer,do WaitBusy after this cmd in low driver */ + NAND_CMD_PAGE_WR0, /* write data to chip's page buffer */ + NAND_CMD_PAGE_WR1, /* do flash programe */ + NAND_CMD_BLK_ERASE, /* erase block */ + NAND_CMD_ECC_EN, /* enable gen HWECC */ + NAND_CMD_ECC_DIS /* disable gen HWECC */ +} nand_cmd_t; + +typedef enum +{ + NAND_ECCM_NONE, + NAND_ECCM_HW, +} nand_eccmode_t; + +struct nand_chip; +struct nand_ops; + +/** +* struct nand_buffers - buffer structure for read/write +* @ecccalc: buffer pointer for calculated ECC, size is oobsize. +* @ecccode: buffer pointer for ECC read from flash, size is oobsize. +* +*/ +struct nand_buffers +{ + uint8_t *ecccalc; + uint8_t *ecccode; +}; + +struct nand_ecc +{ + uint8_t mode; /* nand_eccmode_t */ + uint8_t bytes; /* gen ecc bytes per ecc step(usually 3) */ + uint16_t stepsize:12; /* min 256 */ + uint16_t _step:4; /* */ + + /* driver must set the two interface if HWECC */ + void (*calculate)(struct nand_chip *chip, const uint8_t *dat, uint8_t *ecc_code); + /* return max bit flips if can't correct,return -1 ECC is error(0 success) */ + int(*correct)(struct nand_chip *chip, uint8_t *dat, uint8_t *read_ecc, uint8_t *calc_ecc); + /* ignore if NONECC */ + const struct mtd_oob_region *layout; +}; + +typedef struct nand_chip +{ + rt_mtd_t parent; + + /* driver must init these */ + const struct nand_ops *ops; + struct nand_ecc ecc; + const struct mtd_oob_region *freelayout; + + /* driver do not touch */ + struct nand_buffers buffers; + uint8_t *oob_poi; + uint8_t *pagebuf; + uint32_t size; + uint16_t oobsize; + uint8_t pages_pb; + uint16_t page_size; + int(*read_page)(struct nand_chip *chip, uint8_t *buf, int oob_required, int page); + int(*write_page)(struct nand_chip *chip, const uint8_t *buf, int oob_required, int page); +}rt_nand_t; + +struct nand_ops +{ + int(*cmdfunc)(rt_nand_t *nand, int cmd, int page, int offset); /* send nand operation cmd, return Status bits(0 success), + if nand is busy please wait in low driver */ + int(*read_buf)(rt_nand_t *nand, uint8_t *buf, int len); /* read data from nand chip's page buffer */ + int(*write_buf)(rt_nand_t *nand, const uint8_t *buf, int len);/* write data to nand chip's page buffer */ + int(*isbad)(rt_nand_t *nand, uint32_t blk); /* if NULL OOB[0] used as bad mark(not 0xff is bad) */ + int(*markbad)(rt_nand_t *nand, uint32_t blk); /* if NULL OOB[0] used as bad mark(set to 0x00) */ +}; + +int rt_mtd_nand_init(rt_nand_t *nand, int blk_size, int page_size, int blks_pc, int oob_size); + +#endif /* MTD_NAND_H_ */ diff --git a/components/drivers/include/drivers/mtdnor.h b/components/drivers/include/drivers/mtdnor.h new file mode 100644 index 0000000000..de57c2d2d5 --- /dev/null +++ b/components/drivers/include/drivers/mtdnor.h @@ -0,0 +1,48 @@ +/* + * File : mtdnor.h + * This file is part of RT-Thread RTOS + * COPYRIGHT (C) 2012, Shanghai Real-Thread Technology Co., Ltd + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2018-8-30 heyuanjie the first version + */ + +#ifndef __MTDNOR_H__ +#define __MTDNOR_H__ + +#include + +struct nor_ops; + +typedef struct +{ + rt_mtd_t parent; + + const struct nor_ops *ops; /* operations interface */ +}rt_nor_t; + +struct nor_ops +{ + int (*erase)(rt_nor_t *nor, loff_t addr, size_t len); /* return success erased len or error code */ + int (*read)(rt_nor_t *nor, loff_t addr, uint8_t *buf, size_t len); /* return success data size or error code */ + int (*write)(rt_nor_t *nor, loff_t addr, const uint8_t *buf, size_t len); /* return success data size or error code */ +}; + +int rt_mtd_nor_init(rt_nor_t *nor, int blksize); + +#endif diff --git a/components/drivers/include/rtdevice.h b/components/drivers/include/rtdevice.h index eb28714891..4207ddc0ee 100644 --- a/components/drivers/include/rtdevice.h +++ b/components/drivers/include/rtdevice.h @@ -117,6 +117,13 @@ extern "C" { #include "drivers/rt_drv_pwm.h" #endif +#ifdef MTD_USING_NOR +#include "drivers/mtdnor.h" +#endif +#ifdef MTD_USING_NAND +#include "drivers/mtdnand.h" +#endif + #ifdef __cplusplus } #endif diff --git a/components/drivers/mtd/SConscript b/components/drivers/mtd/SConscript index bc2bd54218..462c38194c 100644 --- a/components/drivers/mtd/SConscript +++ b/components/drivers/mtd/SConscript @@ -9,6 +9,17 @@ depend = [] CPPPATH = [cwd + '/../include'] group = [] +if GetDepend(['RT_USING_MTD']): + src += ['mtd.c'] + depend += ['RT_USING_MTD'] + + if GetDepend(['MTD_USING_NOR']): + src += ['mtdnor.c'] + depend += ['MTD_USING_NOR'] + if GetDepend(['MTD_USING_NAND']): + src += ['mtdnand.c'] + depend += ['MTD_USING_NAND'] + if GetDepend(['RT_USING_MTD_NOR']): src += ['mtd_nor.c'] depend += ['RT_USING_MTD_NOR'] diff --git a/components/drivers/mtd/mtd.c b/components/drivers/mtd/mtd.c new file mode 100644 index 0000000000..38af5c85e9 --- /dev/null +++ b/components/drivers/mtd/mtd.c @@ -0,0 +1,225 @@ +#include + +static rt_mtd_t* mtd_part_alloc(rt_mtd_t *master, const struct mtd_part *part) +{ + rt_mtd_t *slave; + + slave = rt_malloc(sizeof(rt_mtd_t)); + if (slave == RT_NULL) + goto out; + + slave->master = master; + + *slave = *master; + slave->size = part->size; + slave->offset = part->offset; + +out: + + return slave; +} + +rt_mtd_t* rt_mtd_get(const char *name) +{ + rt_mtd_t *mtd; + + mtd = (rt_mtd_t *)rt_device_find(name); + if (mtd == RT_NULL) + return RT_NULL; + + if (mtd->parent.type != RT_Device_Class_MTD) + return RT_NULL; + + return mtd; +} + +/* + * Register MTD driver + * + * @parts partion description + * @np number of partitions + * @return number of unregistered partitions + * +*/ +int rt_mtd_register(rt_mtd_t *master, const struct mtd_part *parts, int np) +{ + int ret; + rt_mtd_t *slave; + + master->master = master; + master->parent.type = RT_Device_Class_MTD; + + if (np > 0) + { + master->offset = parts->offset; + master->size = parts->size; + + ret = rt_device_register((rt_device_t)master, parts->name, 0); + if (ret != 0) + goto _out; + + np --; + parts ++; + } + + while (np > 0) + { + slave = mtd_part_alloc(master, parts); + if (!slave) + break; + ret = rt_device_register((rt_device_t)slave, parts->name, 0); + if (ret) + break; + parts ++; + np --; + } + +_out: + return np; +} + +int rt_mtd_block_erase(rt_mtd_t *mtd, uint32_t block) +{ + uint32_t total_blks; + loff_t addr; + + total_blks = mtd->size/mtd->block_size; + if (block >= total_blks) + return -EINVAL; + addr = mtd->offset + mtd->block_size * block; + + return mtd->ops->erase(mtd->master, addr, mtd->block_size); +} + +int rt_mtd_block_isbad(rt_mtd_t *mtd, uint32_t block) +{ + uint32_t total_blks, offset_blk; + + if (!mtd->ops->isbad) + return 0; + + total_blks = mtd->size / mtd->block_size; + if (block >= total_blks) + return -EINVAL; + offset_blk = mtd->offset / mtd->block_size; + + return mtd->ops->isbad(mtd->master, block + offset_blk); +} + +int rt_mtd_block_markbad(rt_mtd_t *mtd, uint32_t block) +{ + uint32_t total_blks, offset_blk; + + if (!mtd->ops->markbad) + return -EOPNOTSUPP; + + total_blks = mtd->size / mtd->block_size; + if (block >= total_blks) + return -EINVAL; + offset_blk = mtd->offset / mtd->block_size; + + return mtd->ops->markbad(mtd->master, block + offset_blk); +} + +int rt_mtd_erase(rt_mtd_t *mtd, loff_t addr, size_t size) +{ + if (addr > mtd->size || (addr + size) > mtd->size) + return -EINVAL; + addr += mtd->offset; + + return mtd->ops->erase(mtd->master, addr, size); +} + +/* +* Read data only +* +* @from offset to read from +* @return success size or error code +*/ +int rt_mtd_read(rt_mtd_t *mtd, loff_t from, uint8_t *buf, size_t len) +{ + int ret; + struct mtd_io_desc desc = {0}; + + if (from < 0 || from >= (loff_t)mtd->size || len > mtd->size - from) + return -EINVAL; + if (!len) + return 0; + + desc.datbuf = buf; + desc.datlen = len; + ret = mtd->ops->read(mtd->master, from + mtd->offset, &desc); + if (ret) + return ret; + + return desc.datretlen; +} + +/** +* Write data only +* +* @to offset to write from +* @return success size or error code +*/ +int rt_mtd_write(rt_mtd_t *mtd, loff_t to, const uint8_t *buf, size_t len) +{ + int ret; + struct mtd_io_desc desc = {0}; + + if (to < 0 || to >= (loff_t)mtd->size || len > mtd->size - to) + return -EINVAL; + if (!mtd->ops->write) + return -EROFS; + if (!len) + return 0; + + desc.datbuf = (uint8_t*)buf; + desc.datlen = len; + ret = mtd->ops->write(mtd->master, to + mtd->offset, &desc); + if (ret) + return ret; + + return desc.datretlen; +} + +/** +* Read data and/or out-of-band +* +* @from offset to read from +* @desc sector operation description structure +* @return error code, 0 success +*/ +int rt_mtd_read_oob(rt_mtd_t *mtd, loff_t from, struct mtd_io_desc *desc) +{ + desc->datretlen = 0; + desc->oobretlen = 0; + + if (from < 0 || from >= (loff_t)mtd->size) + return -EINVAL; + + if (desc->datbuf && (desc->datlen > (mtd->size - from))) + return -EINVAL; + + return mtd->ops->read(mtd->master, from + mtd->offset, desc); +} + +/** +* Write data and/or out-of-band +* +* @to offset to read from +* @desc sector operation description structure +* @return error code, 0 success +*/ +int rt_mtd_write_oob(rt_mtd_t *mtd, loff_t to, struct mtd_io_desc *desc) +{ + desc->datretlen = 0; + desc->oobretlen = 0; + + if (to < 0 || to >= (loff_t)mtd->size) + return -EINVAL; + + if (desc->datbuf && (desc->datlen >(mtd->size - to))) + return -EINVAL; + + return mtd->ops->write(mtd->master, to + mtd->offset, desc); +} diff --git a/components/drivers/mtd/mtdnand.c b/components/drivers/mtd/mtdnand.c new file mode 100644 index 0000000000..cb5cf4e734 --- /dev/null +++ b/components/drivers/mtd/mtdnand.c @@ -0,0 +1,636 @@ +#include + +#define MTDTONAND(x) ((rt_nand_t*)(x)) +#define NOTALIGNED(x) ((x & (chip->page_size - 1)) != 0) +#ifndef min +#define min(a,b) (a>b? b:a) +#endif + +static uint8_t *nand_fill_oob(rt_nand_t *chip, uint8_t *oob, size_t len, struct mtd_io_desc *desc) +{ + rt_memset(chip->oob_poi, 0xff, chip->oobsize); + + switch (desc->mode) + { + case MTD_OPM_PLACE_OOB: + case MTD_OPM_RAW: + rt_memcpy(chip->oob_poi + desc->ooboffs, oob, len); + return oob + len; + + case MTD_OPM_AUTO_OOB: + { + const struct mtd_oob_region *free = chip->freelayout; + uint32_t boffs; + size_t bytes; + + bytes = min(len, free->length); + boffs = free->offset; + + rt_memcpy(chip->oob_poi + boffs, oob, bytes); + oob += bytes; + + return oob; + } + } + + return NULL; +} + +static uint8_t *nand_transfer_oob(rt_nand_t *chip, uint8_t *oob, struct mtd_io_desc *desc, size_t len) +{ + switch (desc->mode) + { + case MTD_OPM_PLACE_OOB: + case MTD_OPM_RAW: + rt_memcpy(oob, chip->oob_poi + desc->ooboffs, len); + return oob + len; + + case MTD_OPM_AUTO_OOB: + { + struct mtd_oob_region *free = (struct mtd_oob_region *)chip->freelayout; + uint32_t boffs = 0, roffs = desc->ooboffs; + size_t bytes = 0; + + for (; free->length && len; free++, len -= bytes) + { + /* Read request not from offset 0? */ + if (roffs) + { + if (roffs >= free->length) + { + roffs -= free->length; + continue; + } + boffs = free->offset + roffs; + bytes = min(len, (free->length - roffs)); + roffs = 0; + } + else + { + bytes = min(len, free->length); + boffs = free->offset; + } + + rt_memcpy(oob, chip->oob_poi + boffs, bytes); + oob += bytes; + } + + return oob; + } + } + + return NULL; +} + +static int nand_read_page_raw(rt_nand_t *chip, uint8_t *buf, int oob_required, int page) +{ + chip->ops->read_buf(chip, buf, chip->page_size); + + if (oob_required) + chip->ops->read_buf(chip, chip->oob_poi, chip->oobsize); + + return 0; +} + +static int nand_write_page_raw(rt_nand_t *chip, const uint8_t *buf, int oob_required, int page) +{ + chip->ops->write_buf(chip, buf, chip->page_size); + + if (oob_required) + chip->ops->write_buf(chip, chip->oob_poi, chip->oobsize); + + return 0; +} + +static int nand_write_page_hwecc(rt_nand_t *chip, const uint8_t *buf, int oob_required, int page) +{ + uint16_t i; + uint16_t stepsize = chip->ecc.stepsize; + uint16_t eccbytes = chip->ecc.bytes; + uint16_t eccsteps = chip->ecc._step; + uint16_t eccpos = chip->ecc.layout->offset; + uint8_t *ecc_calc = chip->buffers.ecccalc; + const uint8_t *p = buf; + + for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += stepsize) + { + chip->ops->cmdfunc(chip, NAND_CMD_ECC_EN, 0, 0); + chip->ops->write_buf(chip, p, stepsize); + chip->ecc.calculate(chip, p, &ecc_calc[i]); + chip->ops->cmdfunc(chip, NAND_CMD_ECC_DIS, 0, 0); + } + + rt_memcpy(&chip->oob_poi[eccpos], ecc_calc, chip->ecc.layout->length); + + chip->ops->write_buf(chip, chip->oob_poi, chip->oobsize); + + return 0; +} + +static int nand_read_page_hwecc(rt_nand_t *chip, uint8_t *buf, int oob_required, int page) +{ + uint16_t i; + uint16_t eccsize = chip->ecc.stepsize; + uint16_t eccbytes = chip->ecc.bytes; + uint16_t eccsteps = chip->ecc._step; + uint16_t eccpos = chip->ecc.layout->offset; + uint8_t *p = buf; + uint8_t *ecc_calc = chip->buffers.ecccalc; + uint8_t *ecc_code = chip->buffers.ecccode; + int ret = 0; + + for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) + { + chip->ops->cmdfunc(chip, NAND_CMD_ECC_EN, 0, 0); + chip->ops->read_buf(chip, p, eccsize); + chip->ecc.calculate(chip, p, &ecc_calc[i]); + chip->ops->cmdfunc(chip, NAND_CMD_ECC_DIS, 0, 0); + } + + chip->ops->read_buf(chip, chip->oob_poi, chip->oobsize); + rt_memcpy(ecc_code, &chip->oob_poi[eccpos], chip->ecc.layout->length); + + eccsteps = chip->ecc._step; + p = buf; + + for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) + { + int stat; + + stat = chip->ecc.correct(chip, p, &ecc_code[i], &ecc_calc[i]); + if (stat != 0) + ret = -1; + } + + return ret; +} + +static int nand_write_page(rt_nand_t *chip, const uint8_t *buf, + int oob_required, int page, int raw) +{ + int status; + + chip->ops->cmdfunc(chip, NAND_CMD_PAGE_WR0, page, 0x00); + + if (raw) + { + nand_write_page_raw(chip, buf, oob_required, page); + } + else + { + chip->write_page(chip, buf, oob_required, page); + } + + status = chip->ops->cmdfunc(chip, NAND_CMD_PAGE_WR1, -1, -1); + + return status; +} + +static int nand_do_read_desc(rt_nand_t *chip, loff_t from, struct mtd_io_desc *desc) +{ + int page, bytes; + char oob_required; + char ecc_fail = 0; + int ret = 0; + uint32_t readlen = desc->datlen; + uint16_t oobreadlen = desc->ooblen; + uint16_t max_oobsize = desc->mode == MTD_OPM_AUTO_OOB ? + chip->freelayout->length : chip->oobsize; + + uint8_t *oob, *buf, *notalign = 0; + + /* Reject reads, which are not page aligned */ + if (NOTALIGNED(from)) + { + return -EINVAL; + } + + buf = desc->datbuf; + if (NOTALIGNED(desc->datlen) && !chip->pagebuf) + { + chip->pagebuf = rt_malloc(chip->page_size); + if (!chip->pagebuf) + return -ENOMEM; + } + + page = (int)(from / chip->page_size); + + oob = desc->oobbuf; + oob_required = oob ? 1 : 0; + + while (1) + { + bytes = min(chip->page_size, readlen); + + chip->ops->cmdfunc(chip, NAND_CMD_PAGE_RD, page, 0x00); + if (NOTALIGNED(bytes)) + { + notalign = buf; + buf = chip->pagebuf; + } + /* + * Now read the page into the buffer. Absent an error, + * the read methods return max bitflips per ecc step. + */ + if (desc->mode == MTD_OPM_RAW) + { + ret = nand_read_page_raw(chip, buf, oob_required, page); + } + else + { + ret = chip->read_page(chip, buf, oob_required, page); + } + + if (ret != 0) + { + ret = -EBADMSG; + break; + } + + if (oob) + { + int toread = min(oobreadlen, max_oobsize); + + if (toread) + { + oob = nand_transfer_oob(chip, oob, desc, toread); + oobreadlen -= toread; + } + } + + if (notalign) + { + rt_memcpy(notalign, buf, bytes); + } + + buf += bytes; + readlen -= bytes; + + if (!readlen) + break; + + page++; + } + + desc->datretlen = desc->datlen - (size_t)readlen; + if (oob) + desc->oobretlen = desc->ooblen - oobreadlen; + + return ret; +} + +/* + * write with ECC + * +*/ +static int nand_do_write_desc(rt_nand_t *chip, loff_t to, struct mtd_io_desc *desc) +{ + int page; + uint16_t writelen = desc->datlen; + uint16_t oob_required = desc->oobbuf ? 1 : 0; + uint16_t oobwritelen = desc->ooblen; + uint16_t oobmaxlen = desc->mode == MTD_OPM_AUTO_OOB ? + chip->freelayout->length : chip->oobsize; + + uint8_t *oob = desc->oobbuf; + uint8_t *buf = desc->datbuf; + int ret; + + if (!writelen) + return 0; + + /* Reject writes, which are not page aligned */ + if (NOTALIGNED(to)) + { + return -EINVAL; + } + + page = (int)(to / chip->page_size); + + /* Don't allow multipage oob writes with offset */ + if (oob && desc->ooboffs && (desc->ooboffs + desc->ooblen > oobmaxlen)) + { + ret = -EINVAL; + goto err_out; + } + + if (NOTALIGNED(desc->datlen) && !chip->pagebuf) + { + chip->pagebuf = rt_malloc(chip->page_size); + if (!chip->pagebuf) + return -ENOMEM; + } + + while (1) + { + uint16_t bytes = min(chip->page_size, writelen); + + if (oob) + { + size_t len = min(oobwritelen, oobmaxlen); + oob = nand_fill_oob(chip, oob, len, desc); + oobwritelen -= len; + } + else + { + /* We still need to erase leftover OOB data */ + rt_memset(chip->oob_poi, 0xff, chip->oobsize); + } + + if (NOTALIGNED(bytes)) + { + uint8_t *dbtmp = buf; + buf = chip->pagebuf; + rt_memset(&buf[bytes], 0xff, chip->page_size - bytes); + rt_memcpy(buf, dbtmp, bytes); + } + ret = nand_write_page(chip, buf, oob_required, page, (desc->mode == MTD_OPM_RAW)); + if (ret) + break; + + writelen -= bytes; + if (!writelen) + break; + + buf += bytes; + page++; + } + + desc->datretlen = desc->datlen - writelen; + if (oob) + desc->oobretlen = desc->ooblen; + +err_out: + + return ret; +} + +static int nand_read_oob_std(rt_nand_t *chip, int page) +{ + chip->ops->cmdfunc(chip, NAND_CMD_PAGE_RD, page, chip->page_size); + chip->ops->read_buf(chip, chip->oob_poi, chip->oobsize); + + return 0; +} + +/* + * read one page of OOB +*/ +static int nand_only_read_oob(rt_nand_t *chip, loff_t from, struct mtd_io_desc *desc) +{ + int page; + int readlen = desc->ooblen; + int len; + uint8_t *buf = desc->oobbuf; + int ret = 0; + + if (desc->mode == MTD_OPM_AUTO_OOB) + len = chip->freelayout->length; + else + len = chip->oobsize; + + if (desc->ooboffs >= len) //attempt to start read outside oob + { + return -EINVAL; + } + + page = (int)(from / chip->page_size); + + ret = nand_read_oob_std(chip, page); + if (ret == 0) + { + len = min(len, readlen); + buf = nand_transfer_oob(chip, buf, desc, len); + desc->oobretlen = len; + } + + return ret; +} + +static int nand_write_oob_std(rt_nand_t *chip, int page) +{ + int status; + + chip->ops->cmdfunc(chip, NAND_CMD_PAGE_WR0, page, chip->page_size); + chip->ops->write_buf(chip, chip->oob_poi, chip->oobsize); + /* Send command to program the OOB data */ + status = chip->ops->cmdfunc(chip, NAND_CMD_PAGE_WR1, -1, -1); + + return status & NAND_STATUS_FAIL ? -EIO : 0; +} + +static int nand_only_write_oob(rt_nand_t *chip, loff_t to, struct mtd_io_desc *desc) +{ + int page, ret, len; + + if (desc->mode == MTD_OPM_AUTO_OOB) + len = chip->freelayout->length; + else + len = chip->oobsize; + + /* Do not allow write past end of page */ + if ((desc->ooboffs + desc->ooblen) > len) + { + return -EINVAL; + } + + if (desc->ooblen == 0) + { + return -EINVAL; + } + + /* get page */ + page = (int)(to / chip->page_size); + + nand_fill_oob(chip, desc->oobbuf, desc->ooblen, desc); + + ret = nand_write_oob_std(chip, page); + if (ret == 0) + desc->oobretlen = len; + + return ret; +} + +static int nand_erase(rt_mtd_t *mtd, loff_t addr, size_t size) +{ + rt_nand_t *chip; + int status; + int page; + uint32_t blksize; + + chip = MTDTONAND(mtd); + blksize = mtd->block_size; + page = addr / chip->page_size; + + while (size >= blksize) + { + status = chip->ops->cmdfunc(chip, NAND_CMD_BLK_ERASE, page, 0); + if (status & NAND_STATUS_FAIL) + { + break; + } + size -= blksize; + page += chip->pages_pb; + } + + return size; +} + +static int nand_read(rt_mtd_t *mtd, loff_t from, struct mtd_io_desc *desc) +{ + int ret = -ENOTSUP; + rt_nand_t *chip; + + chip = MTDTONAND(mtd); + + switch (desc->mode) + { + case MTD_OPM_PLACE_OOB: + case MTD_OPM_AUTO_OOB: + case MTD_OPM_RAW: + break; + + default: + goto out; + } + + if (!desc->datbuf || !desc->datlen) + ret = nand_only_read_oob(chip, from, desc); + else + ret = nand_do_read_desc(chip, from, desc); + +out: + + return ret; +} + +static int nand_write(rt_mtd_t *mtd, loff_t to, struct mtd_io_desc *desc) +{ + int ret = -ENOTSUP; + rt_nand_t *chip; + + chip = MTDTONAND(mtd); + + switch (desc->mode) + { + case MTD_OPM_PLACE_OOB: + case MTD_OPM_AUTO_OOB: + case MTD_OPM_RAW: + break; + + default: + goto out; + } + + if (!desc->datbuf || !desc->datlen) + ret = nand_only_write_oob(chip, to, desc); + else + ret = nand_do_write_desc(chip, to, desc); + +out: + + return ret; +} + +static int nand_block_isbad(rt_mtd_t *mtd, uint32_t blk) +{ + int ret; + rt_nand_t *chip = MTDTONAND(mtd); + + if (chip->ops->isbad) + { + ret = chip->ops->isbad(chip, blk); + } + else + { + int page; + + page = blk * chip->pages_pb; + nand_read_oob_std(chip, page); + ret = chip->oob_poi[0] != 0xFF; + } + + return ret; +} + +static int nand_block_markbad(rt_mtd_t *mtd, uint32_t blk) +{ + int ret; + rt_nand_t *chip; + + chip = MTDTONAND(mtd); + + if (chip->ops->markbad) + { + ret = chip->ops->markbad(chip, blk); + } + else + { + int page; + + page = blk * chip->pages_pb; + rt_memset(chip->oob_poi, 0xff, chip->oobsize); + chip->oob_poi[0] = 0; + ret = nand_write_oob_std(chip, page); + } + + return ret; +} + +static const struct mtd_ops _ops = +{ + nand_erase, + nand_read, + nand_write, + nand_block_isbad, + nand_block_markbad, +}; + +int rt_mtd_nand_init(rt_nand_t *nand, int blk_size, int page_size, int blks_pc, int oob_size) +{ + uint8_t *buf; + + buf = rt_malloc(oob_size * 3); + if (buf == RT_NULL) + return -ENOMEM; + + nand->oob_poi = buf; + buf += oob_size; + nand->buffers.ecccalc = buf; + buf += oob_size; + nand->buffers.ecccode = buf; + nand->pagebuf = 0; /* alloc when unaligen access */ + + nand->size = blk_size * blks_pc; + nand->pages_pb = blk_size / page_size; + nand->ecc._step = page_size / nand->ecc.stepsize; + nand->page_size = page_size; + nand->oobsize = oob_size; + + nand->parent.type = MTD_TYPE_NAND; + nand->parent.ops = &_ops; + nand->parent.sector_size = page_size; + nand->parent.block_size = blk_size; + nand->parent.oob_size = oob_size; + + switch (nand->ecc.mode) + { + case NAND_ECCM_NONE: + { + nand->read_page = nand_read_page_raw; + nand->write_page = nand_write_page_raw; + }break; + case NAND_ECCM_HW: + { + nand->read_page = nand_read_page_hwecc; + nand->write_page = nand_write_page_hwecc; + }break; + default: + { + rt_free(buf); + return -1; + } + } + + return 0; +} diff --git a/components/drivers/mtd/mtdnor.c b/components/drivers/mtd/mtdnor.c new file mode 100644 index 0000000000..34c9ad6a4a --- /dev/null +++ b/components/drivers/mtd/mtdnor.c @@ -0,0 +1,87 @@ +/* + * File : mtdnor.c + * This file is part of RT-Thread RTOS + * COPYRIGHT (C) 2012, Shanghai Real-Thread Technology Co., Ltd + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2018-8-30 heyuanjie the first version + */ + +#include + +#ifdef MTD_USING_NOR +static int _nor_erase(rt_mtd_t *mtd, loff_t addr, size_t len) +{ + rt_nor_t *nor; + + nor = (rt_nor_t *)mtd; + return nor->ops->erase(nor, addr, len); +} + +static int _nor_read(rt_mtd_t *mtd, loff_t from, struct mtd_io_desc *desc) +{ + rt_nor_t *nor; + int ret; + + nor = (rt_nor_t *)mtd; + ret = nor->ops->read(nor, from, desc->datbuf, desc->datlen); + if (ret > 0) + { + desc->datretlen = ret; + ret = 0; + } + + return ret; +} + +static int _nor_write(rt_mtd_t *mtd, loff_t to, struct mtd_io_desc *desc) +{ + rt_nor_t *nor; + int ret; + + nor = (rt_nor_t *)mtd; + ret = nor->ops->write(nor, to, desc->datbuf, desc->datlen); + if (ret > 0) + { + desc->datretlen = ret; + ret = 0; + } + + return ret; +} + +static const struct mtd_ops _ops = +{ + _nor_erase, + _nor_read, + _nor_write, + 0, + 0 +}; + +int rt_mtd_nor_init(rt_nor_t *nor, int blksize) +{ + nor->parent.sector_size = 1; + nor->parent.block_size = blksize; + nor->parent.ops = &_ops; + nor->parent.type = MTD_TYPE_NOR; + nor->parent.oob_size = 0; + + return 0; +} +#endif -- GitLab