mtd: spinand: Use the spi-mem dirmap API
Make use of the spi-mem direct mapping API to let advanced controllers optimize read/write operations when they support direct mapping. Signed-off-by: Boris Brezillon <bbrezillon@kernel.org> Cc: Stefan Roese <sr@denx.de> Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com> Tested-by: Stefan Roese <sr@denx.de>
This commit is contained in:
committed by
Miquel Raynal
parent
9e98c678c2
commit
981d1aa069
@@ -19,21 +19,6 @@
|
|||||||
#include <linux/spi/spi.h>
|
#include <linux/spi/spi.h>
|
||||||
#include <linux/spi/spi-mem.h>
|
#include <linux/spi/spi-mem.h>
|
||||||
|
|
||||||
static void spinand_cache_op_adjust_colum(struct spinand_device *spinand,
|
|
||||||
const struct nand_page_io_req *req,
|
|
||||||
u16 *column)
|
|
||||||
{
|
|
||||||
struct nand_device *nand = spinand_to_nand(spinand);
|
|
||||||
unsigned int shift;
|
|
||||||
|
|
||||||
if (nand->memorg.planes_per_lun < 2)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* The plane number is passed in MSB just above the column address */
|
|
||||||
shift = fls(nand->memorg.pagesize);
|
|
||||||
*column |= req->pos.plane << shift;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
|
static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
|
||||||
{
|
{
|
||||||
struct spi_mem_op op = SPINAND_GET_FEATURE_OP(reg,
|
struct spi_mem_op op = SPINAND_GET_FEATURE_OP(reg,
|
||||||
@@ -227,27 +212,21 @@ static int spinand_load_page_op(struct spinand_device *spinand,
|
|||||||
static int spinand_read_from_cache_op(struct spinand_device *spinand,
|
static int spinand_read_from_cache_op(struct spinand_device *spinand,
|
||||||
const struct nand_page_io_req *req)
|
const struct nand_page_io_req *req)
|
||||||
{
|
{
|
||||||
struct spi_mem_op op = *spinand->op_templates.read_cache;
|
|
||||||
struct nand_device *nand = spinand_to_nand(spinand);
|
struct nand_device *nand = spinand_to_nand(spinand);
|
||||||
struct mtd_info *mtd = nanddev_to_mtd(nand);
|
struct mtd_info *mtd = nanddev_to_mtd(nand);
|
||||||
struct nand_page_io_req adjreq = *req;
|
struct spi_mem_dirmap_desc *rdesc;
|
||||||
unsigned int nbytes = 0;
|
unsigned int nbytes = 0;
|
||||||
void *buf = NULL;
|
void *buf = NULL;
|
||||||
u16 column = 0;
|
u16 column = 0;
|
||||||
int ret;
|
ssize_t ret;
|
||||||
|
|
||||||
if (req->datalen) {
|
if (req->datalen) {
|
||||||
adjreq.datalen = nanddev_page_size(nand);
|
|
||||||
adjreq.dataoffs = 0;
|
|
||||||
adjreq.databuf.in = spinand->databuf;
|
|
||||||
buf = spinand->databuf;
|
buf = spinand->databuf;
|
||||||
nbytes = adjreq.datalen;
|
nbytes = nanddev_page_size(nand);
|
||||||
|
column = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req->ooblen) {
|
if (req->ooblen) {
|
||||||
adjreq.ooblen = nanddev_per_page_oobsize(nand);
|
|
||||||
adjreq.ooboffs = 0;
|
|
||||||
adjreq.oobbuf.in = spinand->oobbuf;
|
|
||||||
nbytes += nanddev_per_page_oobsize(nand);
|
nbytes += nanddev_per_page_oobsize(nand);
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
buf = spinand->oobbuf;
|
buf = spinand->oobbuf;
|
||||||
@@ -255,28 +234,19 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spinand_cache_op_adjust_colum(spinand, &adjreq, &column);
|
rdesc = spinand->dirmaps[req->pos.plane].rdesc;
|
||||||
op.addr.val = column;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Some controllers are limited in term of max RX data size. In this
|
|
||||||
* case, just repeat the READ_CACHE operation after updating the
|
|
||||||
* column.
|
|
||||||
*/
|
|
||||||
while (nbytes) {
|
while (nbytes) {
|
||||||
op.data.buf.in = buf;
|
ret = spi_mem_dirmap_read(rdesc, column, nbytes, buf);
|
||||||
op.data.nbytes = nbytes;
|
if (ret < 0)
|
||||||
ret = spi_mem_adjust_op_size(spinand->spimem, &op);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
ret = spi_mem_exec_op(spinand->spimem, &op);
|
if (!ret || ret > nbytes)
|
||||||
if (ret)
|
return -EIO;
|
||||||
return ret;
|
|
||||||
|
|
||||||
buf += op.data.nbytes;
|
nbytes -= ret;
|
||||||
nbytes -= op.data.nbytes;
|
column += ret;
|
||||||
op.addr.val += op.data.nbytes;
|
buf += ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req->datalen)
|
if (req->datalen)
|
||||||
@@ -300,14 +270,12 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand,
|
|||||||
static int spinand_write_to_cache_op(struct spinand_device *spinand,
|
static int spinand_write_to_cache_op(struct spinand_device *spinand,
|
||||||
const struct nand_page_io_req *req)
|
const struct nand_page_io_req *req)
|
||||||
{
|
{
|
||||||
struct spi_mem_op op = *spinand->op_templates.write_cache;
|
|
||||||
struct nand_device *nand = spinand_to_nand(spinand);
|
struct nand_device *nand = spinand_to_nand(spinand);
|
||||||
struct mtd_info *mtd = nanddev_to_mtd(nand);
|
struct mtd_info *mtd = nanddev_to_mtd(nand);
|
||||||
struct nand_page_io_req adjreq = *req;
|
struct spi_mem_dirmap_desc *wdesc;
|
||||||
|
unsigned int nbytes, column = 0;
|
||||||
void *buf = spinand->databuf;
|
void *buf = spinand->databuf;
|
||||||
unsigned int nbytes;
|
ssize_t ret;
|
||||||
u16 column = 0;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Looks like PROGRAM LOAD (AKA write cache) does not necessarily reset
|
* Looks like PROGRAM LOAD (AKA write cache) does not necessarily reset
|
||||||
@@ -318,12 +286,6 @@ static int spinand_write_to_cache_op(struct spinand_device *spinand,
|
|||||||
*/
|
*/
|
||||||
nbytes = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand);
|
nbytes = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand);
|
||||||
memset(spinand->databuf, 0xff, nbytes);
|
memset(spinand->databuf, 0xff, nbytes);
|
||||||
adjreq.dataoffs = 0;
|
|
||||||
adjreq.datalen = nanddev_page_size(nand);
|
|
||||||
adjreq.databuf.out = spinand->databuf;
|
|
||||||
adjreq.ooblen = nanddev_per_page_oobsize(nand);
|
|
||||||
adjreq.ooboffs = 0;
|
|
||||||
adjreq.oobbuf.out = spinand->oobbuf;
|
|
||||||
|
|
||||||
if (req->datalen)
|
if (req->datalen)
|
||||||
memcpy(spinand->databuf + req->dataoffs, req->databuf.out,
|
memcpy(spinand->databuf + req->dataoffs, req->databuf.out,
|
||||||
@@ -340,42 +302,19 @@ static int spinand_write_to_cache_op(struct spinand_device *spinand,
|
|||||||
req->ooblen);
|
req->ooblen);
|
||||||
}
|
}
|
||||||
|
|
||||||
spinand_cache_op_adjust_colum(spinand, &adjreq, &column);
|
wdesc = spinand->dirmaps[req->pos.plane].wdesc;
|
||||||
|
|
||||||
op = *spinand->op_templates.write_cache;
|
|
||||||
op.addr.val = column;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Some controllers are limited in term of max TX data size. In this
|
|
||||||
* case, split the operation into one LOAD CACHE and one or more
|
|
||||||
* LOAD RANDOM CACHE.
|
|
||||||
*/
|
|
||||||
while (nbytes) {
|
while (nbytes) {
|
||||||
op.data.buf.out = buf;
|
ret = spi_mem_dirmap_write(wdesc, column, nbytes, buf);
|
||||||
op.data.nbytes = nbytes;
|
if (ret < 0)
|
||||||
|
|
||||||
ret = spi_mem_adjust_op_size(spinand->spimem, &op);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
ret = spi_mem_exec_op(spinand->spimem, &op);
|
if (!ret || ret > nbytes)
|
||||||
if (ret)
|
return -EIO;
|
||||||
return ret;
|
|
||||||
|
|
||||||
buf += op.data.nbytes;
|
nbytes -= ret;
|
||||||
nbytes -= op.data.nbytes;
|
column += ret;
|
||||||
op.addr.val += op.data.nbytes;
|
buf += ret;
|
||||||
|
|
||||||
/*
|
|
||||||
* We need to use the RANDOM LOAD CACHE operation if there's
|
|
||||||
* more than one iteration, because the LOAD operation might
|
|
||||||
* reset the cache to 0xff.
|
|
||||||
*/
|
|
||||||
if (nbytes) {
|
|
||||||
column = op.addr.val;
|
|
||||||
op = *spinand->op_templates.update_cache;
|
|
||||||
op.addr.val = column;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -755,6 +694,59 @@ static int spinand_mtd_block_isreserved(struct mtd_info *mtd, loff_t offs)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int spinand_create_dirmap(struct spinand_device *spinand,
|
||||||
|
unsigned int plane)
|
||||||
|
{
|
||||||
|
struct nand_device *nand = spinand_to_nand(spinand);
|
||||||
|
struct spi_mem_dirmap_info info = {
|
||||||
|
.length = nanddev_page_size(nand) +
|
||||||
|
nanddev_per_page_oobsize(nand),
|
||||||
|
};
|
||||||
|
struct spi_mem_dirmap_desc *desc;
|
||||||
|
|
||||||
|
/* The plane number is passed in MSB just above the column address */
|
||||||
|
info.offset = plane << fls(nand->memorg.pagesize);
|
||||||
|
|
||||||
|
info.op_tmpl = *spinand->op_templates.update_cache;
|
||||||
|
desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
|
||||||
|
spinand->spimem, &info);
|
||||||
|
if (IS_ERR(desc))
|
||||||
|
return PTR_ERR(desc);
|
||||||
|
|
||||||
|
spinand->dirmaps[plane].wdesc = desc;
|
||||||
|
|
||||||
|
info.op_tmpl = *spinand->op_templates.read_cache;
|
||||||
|
desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
|
||||||
|
spinand->spimem, &info);
|
||||||
|
if (IS_ERR(desc))
|
||||||
|
return PTR_ERR(desc);
|
||||||
|
|
||||||
|
spinand->dirmaps[plane].rdesc = desc;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int spinand_create_dirmaps(struct spinand_device *spinand)
|
||||||
|
{
|
||||||
|
struct nand_device *nand = spinand_to_nand(spinand);
|
||||||
|
int i, ret;
|
||||||
|
|
||||||
|
spinand->dirmaps = devm_kzalloc(&spinand->spimem->spi->dev,
|
||||||
|
sizeof(*spinand->dirmaps) *
|
||||||
|
nand->memorg.planes_per_lun,
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!spinand->dirmaps)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
for (i = 0; i < nand->memorg.planes_per_lun; i++) {
|
||||||
|
ret = spinand_create_dirmap(spinand, i);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct nand_ops spinand_ops = {
|
static const struct nand_ops spinand_ops = {
|
||||||
.erase = spinand_erase,
|
.erase = spinand_erase,
|
||||||
.markbad = spinand_markbad,
|
.markbad = spinand_markbad,
|
||||||
@@ -1012,6 +1004,14 @@ static int spinand_init(struct spinand_device *spinand)
|
|||||||
goto err_free_bufs;
|
goto err_free_bufs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret = spinand_create_dirmaps(spinand);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(dev,
|
||||||
|
"Failed to create direct mappings for read/write operations (err = %d)\n",
|
||||||
|
ret);
|
||||||
|
goto err_manuf_cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
/* After power up, all blocks are locked, so unlock them here. */
|
/* After power up, all blocks are locked, so unlock them here. */
|
||||||
for (i = 0; i < nand->memorg.ntargets; i++) {
|
for (i = 0; i < nand->memorg.ntargets; i++) {
|
||||||
ret = spinand_select_target(spinand, i);
|
ret = spinand_select_target(spinand, i);
|
||||||
|
@@ -302,6 +302,11 @@ struct spinand_info {
|
|||||||
__VA_ARGS__ \
|
__VA_ARGS__ \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct spinand_dirmap {
|
||||||
|
struct spi_mem_dirmap_desc *wdesc;
|
||||||
|
struct spi_mem_dirmap_desc *rdesc;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct spinand_device - SPI NAND device instance
|
* struct spinand_device - SPI NAND device instance
|
||||||
* @base: NAND device instance
|
* @base: NAND device instance
|
||||||
@@ -341,6 +346,8 @@ struct spinand_device {
|
|||||||
const struct spi_mem_op *update_cache;
|
const struct spi_mem_op *update_cache;
|
||||||
} op_templates;
|
} op_templates;
|
||||||
|
|
||||||
|
struct spinand_dirmap *dirmaps;
|
||||||
|
|
||||||
int (*select_target)(struct spinand_device *spinand,
|
int (*select_target)(struct spinand_device *spinand,
|
||||||
unsigned int target);
|
unsigned int target);
|
||||||
unsigned int cur_target;
|
unsigned int cur_target;
|
||||||
|
Reference in New Issue
Block a user