Skip to content

Commit

Permalink
spi: mxic: Add support for pipelined ECC operations
Browse files Browse the repository at this point in the history
Some SPI-NAND chips do not have a proper on-die ECC engine providing
error correction/detection. This is particularly an issue on embedded
devices with limited resources because all the computations must
happen in software, unless an external hardware engine is provided.

These external engines are new and can be of two categories: external
or pipelined. Macronix is providing both, the former being already
supported. The second, however, is very SoC implementation dependent
and must be instantiated by the SPI host controller directly.

An entire subsystem has been contributed to support these engines which
makes the insertion into another subsystem such as SPI quite
straightforward without the need for a lot of specific functions.

Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
  • Loading branch information
miquelraynal authored and intel-lab-lkp committed Nov 26, 2021
1 parent 02ab13a commit 0804d6c
Showing 1 changed file with 110 additions and 2 deletions.
112 changes: 110 additions & 2 deletions drivers/spi/spi-mxic.c
Expand Up @@ -12,6 +12,8 @@
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/nand-ecc-mxic.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/spi/spi.h>
Expand Down Expand Up @@ -167,6 +169,7 @@
#define HW_TEST(x) (0xe0 + ((x) * 4))

struct mxic_spi {
struct device *dev;
struct clk *ps_clk;
struct clk *send_clk;
struct clk *send_dly_clk;
Expand All @@ -177,6 +180,12 @@ struct mxic_spi {
dma_addr_t dma;
size_t size;
} linear;

struct {
bool use_pipelined_conf;
struct nand_ecc_engine *pipelined_engine;
void *ctx;
} ecc;
};

static int mxic_spi_clk_enable(struct mxic_spi *mxic)
Expand Down Expand Up @@ -400,7 +409,15 @@ static ssize_t mxic_spi_mem_dirmap_read(struct spi_mem_dirmap_desc *desc,
LMODE_EN,
mxic->regs + LRD_CTRL);

memcpy_fromio(buf, mxic->linear.map, len);
if (mxic->ecc.use_pipelined_conf && desc->info.op_tmpl.ecc_en) {
ret = mxic_ecc_process_data_pipelined(mxic->ecc.pipelined_engine,
NAND_PAGE_READ,
mxic->linear.dma + offs);
if (ret)
return ret;
} else {
memcpy_fromio(buf, mxic->linear.map, len);
}

writel(INT_LRD_DIS, mxic->regs + INT_STS);
writel(0, mxic->regs + LRD_CTRL);
Expand Down Expand Up @@ -436,7 +453,15 @@ static ssize_t mxic_spi_mem_dirmap_write(struct spi_mem_dirmap_desc *desc,
LMODE_EN,
mxic->regs + LWR_CTRL);

memcpy_toio(mxic->linear.map, buf, len);
if (mxic->ecc.use_pipelined_conf && desc->info.op_tmpl.ecc_en) {
ret = mxic_ecc_process_data_pipelined(mxic->ecc.pipelined_engine,
NAND_PAGE_WRITE,
mxic->linear.dma + offs);
if (ret)
return ret;
} else {
memcpy_toio(mxic->linear.map, buf, len);
}

writel(INT_LWR_DIS, mxic->regs + INT_STS);
writel(0, mxic->regs + LWR_CTRL);
Expand Down Expand Up @@ -615,6 +640,80 @@ static int mxic_spi_transfer_one(struct spi_master *master,
return 0;
}

/* ECC wrapper */
static int mxic_spi_mem_ecc_init_ctx(struct nand_device *nand)
{
struct nand_ecc_engine_ops *ops = mxic_ecc_get_pipelined_ops();
struct mxic_spi *mxic = nand->ecc.engine->priv;

mxic->ecc.use_pipelined_conf = true;

return ops->init_ctx(nand);
}

static void mxic_spi_mem_ecc_cleanup_ctx(struct nand_device *nand)
{
struct nand_ecc_engine_ops *ops = mxic_ecc_get_pipelined_ops();
struct mxic_spi *mxic = nand->ecc.engine->priv;

mxic->ecc.use_pipelined_conf = false;

ops->cleanup_ctx(nand);
}

static int mxic_spi_mem_ecc_prepare_io_req(struct nand_device *nand,
struct nand_page_io_req *req)
{
struct nand_ecc_engine_ops *ops = mxic_ecc_get_pipelined_ops();

return ops->prepare_io_req(nand, req);
}

static int mxic_spi_mem_ecc_finish_io_req(struct nand_device *nand,
struct nand_page_io_req *req)
{
struct nand_ecc_engine_ops *ops = mxic_ecc_get_pipelined_ops();

return ops->finish_io_req(nand, req);
}

static struct nand_ecc_engine_ops mxic_spi_mem_ecc_engine_pipelined_ops = {
.init_ctx = mxic_spi_mem_ecc_init_ctx,
.cleanup_ctx = mxic_spi_mem_ecc_cleanup_ctx,
.prepare_io_req = mxic_spi_mem_ecc_prepare_io_req,
.finish_io_req = mxic_spi_mem_ecc_finish_io_req,
};

static void mxic_spi_mem_ecc_remove(struct mxic_spi *mxic)
{
if (mxic->ecc.pipelined_engine) {
mxic_ecc_put_pipelined_engine(mxic->ecc.pipelined_engine);
nand_ecc_unregister_on_host_hw_engine(mxic->ecc.pipelined_engine);
}
}

static int mxic_spi_mem_ecc_probe(struct platform_device *pdev,
struct mxic_spi *mxic)
{
struct nand_ecc_engine *eng;

if (!mxic_ecc_get_pipelined_ops())
return -EOPNOTSUPP;

eng = mxic_ecc_get_pipelined_engine(pdev);
if (IS_ERR(eng))
return PTR_ERR(eng);

eng->dev = &pdev->dev;
eng->integration = NAND_ECC_ENGINE_INTEGRATION_PIPELINED;
eng->ops = &mxic_spi_mem_ecc_engine_pipelined_ops;
eng->priv = mxic;
mxic->ecc.pipelined_engine = eng;
nand_ecc_register_on_host_hw_engine(eng);

return 0;
}

static int __maybe_unused mxic_spi_runtime_suspend(struct device *dev)
{
struct spi_master *master = dev_get_drvdata(dev);
Expand Down Expand Up @@ -660,6 +759,7 @@ static int mxic_spi_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, master);

mxic = spi_master_get_devdata(master);
mxic->dev = &pdev->dev;

master->dev.of_node = pdev->dev.of_node;

Expand Down Expand Up @@ -705,6 +805,12 @@ static int mxic_spi_probe(struct platform_device *pdev)

mxic_spi_hw_init(mxic);

ret = mxic_spi_mem_ecc_probe(pdev, mxic);
if (ret == -EPROBE_DEFER) {
pm_runtime_disable(&pdev->dev);
return ret;
}

ret = spi_register_master(master);
if (ret) {
dev_err(&pdev->dev, "spi_register_master failed\n");
Expand All @@ -717,8 +823,10 @@ static int mxic_spi_probe(struct platform_device *pdev)
static int mxic_spi_remove(struct platform_device *pdev)
{
struct spi_master *master = platform_get_drvdata(pdev);
struct mxic_spi *mxic = spi_master_get_devdata(master);

pm_runtime_disable(&pdev->dev);
mxic_spi_mem_ecc_remove(mxic);
spi_unregister_master(master);

return 0;
Expand Down

0 comments on commit 0804d6c

Please sign in to comment.