diff options
Diffstat (limited to 'drivers/spi/spi-mxic.c')
| -rw-r--r-- | drivers/spi/spi-mxic.c | 429 |
1 files changed, 335 insertions, 94 deletions
diff --git a/drivers/spi/spi-mxic.c b/drivers/spi/spi-mxic.c index e41ae6ef0f8a..eeaea6a5e310 100644 --- a/drivers/spi/spi-mxic.c +++ b/drivers/spi/spi-mxic.c @@ -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> @@ -145,8 +147,8 @@ #define LWR_SUSP_CTRL_EN BIT(31) #define DMAS_CTRL 0x9c -#define DMAS_CTRL_DIR_READ BIT(31) -#define DMAS_CTRL_EN BIT(30) +#define DMAS_CTRL_EN BIT(31) +#define DMAS_CTRL_DIR_READ BIT(30) #define DATA_STROB 0xa0 #define DATA_STROB_EDO_EN BIT(2) @@ -167,11 +169,23 @@ #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; void __iomem *regs; u32 cur_speed_hz; + struct { + void __iomem *map; + 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) @@ -275,11 +289,62 @@ static void mxic_spi_hw_init(struct mxic_spi *mxic) writel(0, mxic->regs + HC_EN); writel(0, mxic->regs + LRD_CFG); writel(0, mxic->regs + LRD_CTRL); - writel(HC_CFG_NIO(1) | HC_CFG_TYPE(0, HC_CFG_TYPE_SPI_NAND) | + writel(HC_CFG_NIO(1) | HC_CFG_TYPE(0, HC_CFG_TYPE_SPI_NOR) | HC_CFG_SLV_ACT(0) | HC_CFG_MAN_CS_EN | HC_CFG_IDLE_SIO_LVL(1), mxic->regs + HC_CFG); } +static u32 mxic_spi_prep_hc_cfg(struct spi_device *spi, u32 flags, + bool swap16) +{ + int nio = 1; + + if (spi->mode & (SPI_TX_OCTAL | SPI_RX_OCTAL)) + nio = 8; + else if (spi->mode & (SPI_TX_QUAD | SPI_RX_QUAD)) + nio = 4; + else if (spi->mode & (SPI_TX_DUAL | SPI_RX_DUAL)) + nio = 2; + + if (swap16) + flags &= ~HC_CFG_DATA_PASS; + else + flags |= HC_CFG_DATA_PASS; + + return flags | HC_CFG_NIO(nio) | + HC_CFG_TYPE(spi_get_chipselect(spi, 0), HC_CFG_TYPE_SPI_NOR) | + HC_CFG_SLV_ACT(spi_get_chipselect(spi, 0)) | HC_CFG_IDLE_SIO_LVL(1); +} + +static u32 mxic_spi_mem_prep_op_cfg(const struct spi_mem_op *op, + unsigned int data_len) +{ + u32 cfg = OP_CMD_BYTES(op->cmd.nbytes) | + OP_CMD_BUSW(fls(op->cmd.buswidth) - 1) | + (op->cmd.dtr ? OP_CMD_DDR : 0); + + if (op->addr.nbytes) + cfg |= OP_ADDR_BYTES(op->addr.nbytes) | + OP_ADDR_BUSW(fls(op->addr.buswidth) - 1) | + (op->addr.dtr ? OP_ADDR_DDR : 0); + + if (op->dummy.nbytes) + cfg |= OP_DUMMY_CYC(op->dummy.nbytes); + + /* Direct mapping data.nbytes field is not populated */ + if (data_len) { + cfg |= OP_DATA_BUSW(fls(op->data.buswidth) - 1) | + (op->data.dtr ? OP_DATA_DDR : 0); + if (op->data.dir == SPI_MEM_DATA_IN) { + cfg |= OP_READ; + if (op->data.dtr) + cfg |= OP_DQS_EN; + } + } + + return cfg; +} + static int mxic_spi_data_xfer(struct mxic_spi *mxic, const void *txbuf, void *rxbuf, unsigned int len) { @@ -304,25 +369,21 @@ static int mxic_spi_data_xfer(struct mxic_spi *mxic, const void *txbuf, writel(data, mxic->regs + TXD(nbytes % 4)); + ret = readl_poll_timeout(mxic->regs + INT_STS, sts, + sts & INT_TX_EMPTY, 0, USEC_PER_SEC); + if (ret) + return ret; + + ret = readl_poll_timeout(mxic->regs + INT_STS, sts, + sts & INT_RX_NOT_EMPTY, 0, + USEC_PER_SEC); + if (ret) + return ret; + + data = readl(mxic->regs + RXD); if (rxbuf) { - ret = readl_poll_timeout(mxic->regs + INT_STS, sts, - sts & INT_TX_EMPTY, 0, - USEC_PER_SEC); - if (ret) - return ret; - - ret = readl_poll_timeout(mxic->regs + INT_STS, sts, - sts & INT_RX_NOT_EMPTY, 0, - USEC_PER_SEC); - if (ret) - return ret; - - data = readl(mxic->regs + RXD); data >>= (8 * (4 - nbytes)); memcpy(rxbuf + pos, &data, nbytes); - WARN_ON(readl(mxic->regs + INT_STS) & INT_RX_NOT_EMPTY); - } else { - readl(mxic->regs + RXD); } WARN_ON(readl(mxic->regs + INT_STS) & INT_RX_NOT_EMPTY); @@ -332,11 +393,100 @@ static int mxic_spi_data_xfer(struct mxic_spi *mxic, const void *txbuf, return 0; } +static ssize_t mxic_spi_mem_dirmap_read(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, void *buf) +{ + struct mxic_spi *mxic = spi_controller_get_devdata(desc->mem->spi->controller); + int ret; + u32 sts; + + if (WARN_ON(offs + desc->info.offset + len > U32_MAX)) + return -EINVAL; + + writel(mxic_spi_prep_hc_cfg(desc->mem->spi, 0, desc->info.op_tmpl.data.swap16), + mxic->regs + HC_CFG); + + writel(mxic_spi_mem_prep_op_cfg(&desc->info.op_tmpl, len), + mxic->regs + LRD_CFG); + writel(desc->info.offset + offs, mxic->regs + LRD_ADDR); + len = min_t(size_t, len, mxic->linear.size); + writel(len, mxic->regs + LRD_RANGE); + writel(LMODE_CMD0(desc->info.op_tmpl.cmd.opcode) | + LMODE_SLV_ACT(spi_get_chipselect(desc->mem->spi, 0)) | + LMODE_EN, + mxic->regs + LRD_CTRL); + + if (mxic->ecc.use_pipelined_conf && desc->info.op_tmpl.data.ecc) { + 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); + + ret = readl_poll_timeout(mxic->regs + INT_STS, sts, + sts & INT_LRD_DIS, 0, USEC_PER_SEC); + if (ret) + return ret; + + return len; +} + +static ssize_t mxic_spi_mem_dirmap_write(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, + const void *buf) +{ + struct mxic_spi *mxic = spi_controller_get_devdata(desc->mem->spi->controller); + u32 sts; + int ret; + + if (WARN_ON(offs + desc->info.offset + len > U32_MAX)) + return -EINVAL; + + writel(mxic_spi_prep_hc_cfg(desc->mem->spi, 0, desc->info.op_tmpl.data.swap16), + mxic->regs + HC_CFG); + + writel(mxic_spi_mem_prep_op_cfg(&desc->info.op_tmpl, len), + mxic->regs + LWR_CFG); + writel(desc->info.offset + offs, mxic->regs + LWR_ADDR); + len = min_t(size_t, len, mxic->linear.size); + writel(len, mxic->regs + LWR_RANGE); + writel(LMODE_CMD0(desc->info.op_tmpl.cmd.opcode) | + LMODE_SLV_ACT(spi_get_chipselect(desc->mem->spi, 0)) | + LMODE_EN, + mxic->regs + LWR_CTRL); + + if (mxic->ecc.use_pipelined_conf && desc->info.op_tmpl.data.ecc) { + 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); + + ret = readl_poll_timeout(mxic->regs + INT_STS, sts, + sts & INT_LWR_DIS, 0, USEC_PER_SEC); + if (ret) + return ret; + + return len; +} + static bool mxic_spi_mem_supports_op(struct spi_mem *mem, const struct spi_mem_op *op) { - if (op->data.buswidth > 4 || op->addr.buswidth > 4 || - op->dummy.buswidth > 4 || op->cmd.buswidth > 4) + if (op->data.buswidth > 8 || op->addr.buswidth > 8 || + op->dummy.buswidth > 8 || op->cmd.buswidth > 8) return false; if (op->data.nbytes && op->dummy.nbytes && @@ -346,54 +496,51 @@ static bool mxic_spi_mem_supports_op(struct spi_mem *mem, if (op->addr.nbytes > 7) return false; - return true; + return spi_mem_default_supports_op(mem, op); +} + +static int mxic_spi_mem_dirmap_create(struct spi_mem_dirmap_desc *desc) +{ + struct mxic_spi *mxic = spi_controller_get_devdata(desc->mem->spi->controller); + + if (!mxic->linear.map) + return -EOPNOTSUPP; + + if (desc->info.offset + desc->info.length > U32_MAX) + return -EINVAL; + + if (!mxic_spi_mem_supports_op(desc->mem, &desc->info.op_tmpl)) + return -EOPNOTSUPP; + + return 0; } static int mxic_spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) { - struct mxic_spi *mxic = spi_master_get_devdata(mem->spi->master); - int nio = 1, i, ret; - u32 ss_ctrl; - u8 addr[8]; + struct mxic_spi *mxic = spi_controller_get_devdata(mem->spi->controller); + int i, ret; + u8 addr[8], cmd[2]; - ret = mxic_spi_set_freq(mxic, mem->spi->max_speed_hz); + ret = mxic_spi_set_freq(mxic, op->max_freq); if (ret) return ret; - if (mem->spi->mode & (SPI_TX_QUAD | SPI_RX_QUAD)) - nio = 4; - else if (mem->spi->mode & (SPI_TX_DUAL | SPI_RX_DUAL)) - nio = 2; - - writel(HC_CFG_NIO(nio) | - HC_CFG_TYPE(mem->spi->chip_select, HC_CFG_TYPE_SPI_NOR) | - HC_CFG_SLV_ACT(mem->spi->chip_select) | HC_CFG_IDLE_SIO_LVL(1) | - HC_CFG_MAN_CS_EN, + writel(mxic_spi_prep_hc_cfg(mem->spi, HC_CFG_MAN_CS_EN, op->data.swap16), mxic->regs + HC_CFG); - writel(HC_EN_BIT, mxic->regs + HC_EN); - - ss_ctrl = OP_CMD_BYTES(1) | OP_CMD_BUSW(fls(op->cmd.buswidth) - 1); - - if (op->addr.nbytes) - ss_ctrl |= OP_ADDR_BYTES(op->addr.nbytes) | - OP_ADDR_BUSW(fls(op->addr.buswidth) - 1); - - if (op->dummy.nbytes) - ss_ctrl |= OP_DUMMY_CYC(op->dummy.nbytes); - if (op->data.nbytes) { - ss_ctrl |= OP_DATA_BUSW(fls(op->data.buswidth) - 1); - if (op->data.dir == SPI_MEM_DATA_IN) - ss_ctrl |= OP_READ; - } + writel(HC_EN_BIT, mxic->regs + HC_EN); - writel(ss_ctrl, mxic->regs + SS_CTRL(mem->spi->chip_select)); + writel(mxic_spi_mem_prep_op_cfg(op, op->data.nbytes), + mxic->regs + SS_CTRL(spi_get_chipselect(mem->spi, 0))); writel(readl(mxic->regs + HC_CFG) | HC_CFG_MAN_CS_ASSERT, mxic->regs + HC_CFG); - ret = mxic_spi_data_xfer(mxic, &op->cmd.opcode, NULL, 1); + for (i = 0; i < op->cmd.nbytes; i++) + cmd[i] = op->cmd.opcode >> (8 * (op->cmd.nbytes - i - 1)); + + ret = mxic_spi_data_xfer(mxic, cmd, NULL, op->cmd.nbytes); if (ret) goto out; @@ -426,11 +573,21 @@ out: static const struct spi_controller_mem_ops mxic_spi_mem_ops = { .supports_op = mxic_spi_mem_supports_op, .exec_op = mxic_spi_mem_exec_op, + .dirmap_create = mxic_spi_mem_dirmap_create, + .dirmap_read = mxic_spi_mem_dirmap_read, + .dirmap_write = mxic_spi_mem_dirmap_write, +}; + +static const struct spi_controller_mem_caps mxic_spi_mem_caps = { + .dtr = true, + .ecc = true, + .swap16 = true, + .per_op_freq = true, }; static void mxic_spi_set_cs(struct spi_device *spi, bool lvl) { - struct mxic_spi *mxic = spi_master_get_devdata(spi->master); + struct mxic_spi *mxic = spi_controller_get_devdata(spi->controller); if (!lvl) { writel(readl(mxic->regs + HC_CFG) | HC_CFG_MAN_CS_EN, @@ -445,11 +602,11 @@ static void mxic_spi_set_cs(struct spi_device *spi, bool lvl) } } -static int mxic_spi_transfer_one(struct spi_master *master, +static int mxic_spi_transfer_one(struct spi_controller *host, struct spi_device *spi, struct spi_transfer *t) { - struct mxic_spi *mxic = spi_master_get_devdata(master); + struct mxic_spi *mxic = spi_controller_get_devdata(host); unsigned int busw = OP_BUSW_1; int ret; @@ -485,16 +642,89 @@ static int mxic_spi_transfer_one(struct spi_master *master, if (ret) return ret; - spi_finalize_current_transfer(master); + spi_finalize_current_transfer(host); + + return 0; +} + +/* ECC wrapper */ +static int mxic_spi_mem_ecc_init_ctx(struct nand_device *nand) +{ + const 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) +{ + const 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) +{ + const 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) +{ + const struct nand_ecc_engine_ops *ops = mxic_ecc_get_pipelined_ops(); + + return ops->finish_io_req(nand, req); +} + +static const 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 platform_device *pdev = to_platform_device(dev); - struct spi_master *master = platform_get_drvdata(pdev); - struct mxic_spi *mxic = spi_master_get_devdata(master); + struct spi_controller *host = dev_get_drvdata(dev); + struct mxic_spi *mxic = spi_controller_get_devdata(host); mxic_spi_clk_disable(mxic); clk_disable_unprepare(mxic->ps_clk); @@ -504,9 +734,8 @@ static int __maybe_unused mxic_spi_runtime_suspend(struct device *dev) static int __maybe_unused mxic_spi_runtime_resume(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct spi_master *master = platform_get_drvdata(pdev); - struct mxic_spi *mxic = spi_master_get_devdata(master); + struct spi_controller *host = dev_get_drvdata(dev); + struct mxic_spi *mxic = spi_controller_get_devdata(host); int ret; ret = clk_prepare_enable(mxic->ps_clk); @@ -525,20 +754,21 @@ static const struct dev_pm_ops mxic_spi_dev_pm_ops = { static int mxic_spi_probe(struct platform_device *pdev) { - struct spi_master *master; + struct spi_controller *host; struct resource *res; struct mxic_spi *mxic; int ret; - master = spi_alloc_master(&pdev->dev, sizeof(struct mxic_spi)); - if (!master) + host = devm_spi_alloc_host(&pdev->dev, sizeof(struct mxic_spi)); + if (!host) return -ENOMEM; - platform_set_drvdata(pdev, master); + platform_set_drvdata(pdev, host); - mxic = spi_master_get_devdata(master); + mxic = spi_controller_get_devdata(host); + mxic->dev = &pdev->dev; - master->dev.of_node = pdev->dev.of_node; + host->dev.of_node = pdev->dev.of_node; mxic->ps_clk = devm_clk_get(&pdev->dev, "ps_clk"); if (IS_ERR(mxic->ps_clk)) @@ -552,49 +782,60 @@ static int mxic_spi_probe(struct platform_device *pdev) if (IS_ERR(mxic->send_dly_clk)) return PTR_ERR(mxic->send_dly_clk); - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); - mxic->regs = devm_ioremap_resource(&pdev->dev, res); + mxic->regs = devm_platform_ioremap_resource_byname(pdev, "regs"); if (IS_ERR(mxic->regs)) return PTR_ERR(mxic->regs); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dirmap"); + mxic->linear.map = devm_ioremap_resource(&pdev->dev, res); + if (!IS_ERR(mxic->linear.map)) { + mxic->linear.dma = res->start; + mxic->linear.size = resource_size(res); + } else { + mxic->linear.map = NULL; + } + pm_runtime_enable(&pdev->dev); - master->auto_runtime_pm = true; + host->auto_runtime_pm = true; - master->num_chipselect = 1; - master->mem_ops = &mxic_spi_mem_ops; + host->num_chipselect = 1; + host->mem_ops = &mxic_spi_mem_ops; + host->mem_caps = &mxic_spi_mem_caps; - master->set_cs = mxic_spi_set_cs; - master->transfer_one = mxic_spi_transfer_one; - master->bits_per_word_mask = SPI_BPW_MASK(8); - master->mode_bits = SPI_CPOL | SPI_CPHA | - SPI_RX_DUAL | SPI_TX_DUAL | - SPI_RX_QUAD | SPI_TX_QUAD; + host->set_cs = mxic_spi_set_cs; + host->transfer_one = mxic_spi_transfer_one; + host->bits_per_word_mask = SPI_BPW_MASK(8); + host->mode_bits = SPI_CPOL | SPI_CPHA | + SPI_RX_DUAL | SPI_TX_DUAL | + SPI_RX_QUAD | SPI_TX_QUAD | + SPI_RX_OCTAL | SPI_TX_OCTAL; mxic_spi_hw_init(mxic); - ret = spi_register_master(master); - if (ret) { - dev_err(&pdev->dev, "spi_register_master failed\n"); - goto err_put_master; + ret = mxic_spi_mem_ecc_probe(pdev, mxic); + if (ret == -EPROBE_DEFER) { + pm_runtime_disable(&pdev->dev); + return ret; } - return 0; - -err_put_master: - spi_master_put(master); - pm_runtime_disable(&pdev->dev); + ret = spi_register_controller(host); + if (ret) { + dev_err(&pdev->dev, "spi_register_controller failed\n"); + pm_runtime_disable(&pdev->dev); + mxic_spi_mem_ecc_remove(mxic); + } return ret; } -static int mxic_spi_remove(struct platform_device *pdev) +static void mxic_spi_remove(struct platform_device *pdev) { - struct spi_master *master = platform_get_drvdata(pdev); + struct spi_controller *host = platform_get_drvdata(pdev); + struct mxic_spi *mxic = spi_controller_get_devdata(host); pm_runtime_disable(&pdev->dev); - spi_unregister_master(master); - - return 0; + mxic_spi_mem_ecc_remove(mxic); + spi_unregister_controller(host); } static const struct of_device_id mxic_spi_of_ids[] = { |
