| // SPDX-License-Identifier: GPL-2.0-only |
| |
| #include <linux/completion.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/interrupt.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/platform_device.h> |
| #include <linux/regmap.h> |
| #include <linux/spi/spi.h> |
| #include <linux/spi/spi-mem.h> |
| |
| #define SNAFCFR 0x00 |
| #define SNAFCFR_DMA_IE BIT(20) |
| #define SNAFCCR 0x04 |
| #define SNAFWCMR 0x08 |
| #define SNAFRCMR 0x0c |
| #define SNAFRDR 0x10 |
| #define SNAFWDR 0x14 |
| #define SNAFDTR 0x18 |
| #define SNAFDRSAR 0x1c |
| #define SNAFDIR 0x20 |
| #define SNAFDIR_DMA_IP BIT(0) |
| #define SNAFDLR 0x24 |
| #define SNAFSR 0x40 |
| #define SNAFSR_NFCOS BIT(3) |
| #define SNAFSR_NFDRS BIT(2) |
| #define SNAFSR_NFDWS BIT(1) |
| |
| #define CMR_LEN(len) ((len) - 1) |
| #define CMR_WID(width) (((width) >> 1) << 28) |
| |
| struct rtl_snand { |
| struct device *dev; |
| struct regmap *regmap; |
| struct completion comp; |
| }; |
| |
| static irqreturn_t rtl_snand_irq(int irq, void *data) |
| { |
| struct rtl_snand *snand = data; |
| u32 val = 0; |
| |
| regmap_read(snand->regmap, SNAFSR, &val); |
| if (val & (SNAFSR_NFCOS | SNAFSR_NFDRS | SNAFSR_NFDWS)) |
| return IRQ_NONE; |
| |
| regmap_write(snand->regmap, SNAFDIR, SNAFDIR_DMA_IP); |
| complete(&snand->comp); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static bool rtl_snand_supports_op(struct spi_mem *mem, |
| const struct spi_mem_op *op) |
| { |
| if (!spi_mem_default_supports_op(mem, op)) |
| return false; |
| if (op->cmd.nbytes != 1 || op->cmd.buswidth != 1) |
| return false; |
| return true; |
| } |
| |
| static void rtl_snand_set_cs(struct rtl_snand *snand, int cs, bool active) |
| { |
| u32 val; |
| |
| if (active) |
| val = ~(1 << (4 * cs)); |
| else |
| val = ~0; |
| |
| regmap_write(snand->regmap, SNAFCCR, val); |
| } |
| |
| static int rtl_snand_wait_ready(struct rtl_snand *snand) |
| { |
| u32 val; |
| |
| return regmap_read_poll_timeout(snand->regmap, SNAFSR, val, !(val & SNAFSR_NFCOS), |
| 0, 2 * USEC_PER_MSEC); |
| } |
| |
| static int rtl_snand_xfer_head(struct rtl_snand *snand, int cs, const struct spi_mem_op *op) |
| { |
| int ret; |
| u32 val, len = 0; |
| |
| rtl_snand_set_cs(snand, cs, true); |
| |
| val = op->cmd.opcode << 24; |
| len = 1; |
| if (op->addr.nbytes && op->addr.buswidth == 1) { |
| val |= op->addr.val << ((3 - op->addr.nbytes) * 8); |
| len += op->addr.nbytes; |
| } |
| |
| ret = rtl_snand_wait_ready(snand); |
| if (ret) |
| return ret; |
| |
| ret = regmap_write(snand->regmap, SNAFWCMR, CMR_LEN(len)); |
| if (ret) |
| return ret; |
| |
| ret = regmap_write(snand->regmap, SNAFWDR, val); |
| if (ret) |
| return ret; |
| |
| ret = rtl_snand_wait_ready(snand); |
| if (ret) |
| return ret; |
| |
| if (op->addr.buswidth > 1) { |
| val = op->addr.val << ((3 - op->addr.nbytes) * 8); |
| len = op->addr.nbytes; |
| |
| ret = regmap_write(snand->regmap, SNAFWCMR, |
| CMR_WID(op->addr.buswidth) | CMR_LEN(len)); |
| if (ret) |
| return ret; |
| |
| ret = regmap_write(snand->regmap, SNAFWDR, val); |
| if (ret) |
| return ret; |
| |
| ret = rtl_snand_wait_ready(snand); |
| if (ret) |
| return ret; |
| } |
| |
| if (op->dummy.nbytes) { |
| val = 0; |
| |
| ret = regmap_write(snand->regmap, SNAFWCMR, |
| CMR_WID(op->dummy.buswidth) | CMR_LEN(op->dummy.nbytes)); |
| if (ret) |
| return ret; |
| |
| ret = regmap_write(snand->regmap, SNAFWDR, val); |
| if (ret) |
| return ret; |
| |
| ret = rtl_snand_wait_ready(snand); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static void rtl_snand_xfer_tail(struct rtl_snand *snand, int cs) |
| { |
| rtl_snand_set_cs(snand, cs, false); |
| } |
| |
| static int rtl_snand_xfer(struct rtl_snand *snand, int cs, const struct spi_mem_op *op) |
| { |
| unsigned int pos, nbytes; |
| int ret; |
| u32 val, len = 0; |
| |
| ret = rtl_snand_xfer_head(snand, cs, op); |
| if (ret) |
| goto out_deselect; |
| |
| if (op->data.dir == SPI_MEM_DATA_IN) { |
| pos = 0; |
| len = op->data.nbytes; |
| |
| while (pos < len) { |
| nbytes = len - pos; |
| if (nbytes > 4) |
| nbytes = 4; |
| |
| ret = rtl_snand_wait_ready(snand); |
| if (ret) |
| goto out_deselect; |
| |
| ret = regmap_write(snand->regmap, SNAFRCMR, |
| CMR_WID(op->data.buswidth) | CMR_LEN(nbytes)); |
| if (ret) |
| goto out_deselect; |
| |
| ret = rtl_snand_wait_ready(snand); |
| if (ret) |
| goto out_deselect; |
| |
| ret = regmap_read(snand->regmap, SNAFRDR, &val); |
| if (ret) |
| goto out_deselect; |
| |
| memcpy(op->data.buf.in + pos, &val, nbytes); |
| |
| pos += nbytes; |
| } |
| } else if (op->data.dir == SPI_MEM_DATA_OUT) { |
| pos = 0; |
| len = op->data.nbytes; |
| |
| while (pos < len) { |
| nbytes = len - pos; |
| if (nbytes > 4) |
| nbytes = 4; |
| |
| memcpy(&val, op->data.buf.out + pos, nbytes); |
| |
| pos += nbytes; |
| |
| ret = regmap_write(snand->regmap, SNAFWCMR, CMR_LEN(nbytes)); |
| if (ret) |
| goto out_deselect; |
| |
| ret = regmap_write(snand->regmap, SNAFWDR, val); |
| if (ret) |
| goto out_deselect; |
| |
| ret = rtl_snand_wait_ready(snand); |
| if (ret) |
| goto out_deselect; |
| } |
| } |
| |
| out_deselect: |
| rtl_snand_xfer_tail(snand, cs); |
| |
| if (ret) |
| dev_err(snand->dev, "transfer failed %d\n", ret); |
| |
| return ret; |
| } |
| |
| static int rtl_snand_dma_xfer(struct rtl_snand *snand, int cs, const struct spi_mem_op *op) |
| { |
| unsigned int pos, nbytes; |
| int ret; |
| dma_addr_t buf_dma; |
| enum dma_data_direction dir; |
| u32 trig, len, maxlen; |
| |
| ret = rtl_snand_xfer_head(snand, cs, op); |
| if (ret) |
| goto out_deselect; |
| |
| if (op->data.dir == SPI_MEM_DATA_IN) { |
| maxlen = 2080; |
| dir = DMA_FROM_DEVICE; |
| trig = 0; |
| } else if (op->data.dir == SPI_MEM_DATA_OUT) { |
| maxlen = 520; |
| dir = DMA_TO_DEVICE; |
| trig = 1; |
| } else { |
| ret = -EOPNOTSUPP; |
| goto out_deselect; |
| } |
| |
| buf_dma = dma_map_single(snand->dev, op->data.buf.in, op->data.nbytes, dir); |
| ret = dma_mapping_error(snand->dev, buf_dma); |
| if (ret) |
| goto out_deselect; |
| |
| ret = regmap_write(snand->regmap, SNAFDIR, SNAFDIR_DMA_IP); |
| if (ret) |
| goto out_unmap; |
| |
| ret = regmap_update_bits(snand->regmap, SNAFCFR, SNAFCFR_DMA_IE, SNAFCFR_DMA_IE); |
| if (ret) |
| goto out_unmap; |
| |
| pos = 0; |
| len = op->data.nbytes; |
| |
| while (pos < len) { |
| nbytes = len - pos; |
| if (nbytes > maxlen) |
| nbytes = maxlen; |
| |
| reinit_completion(&snand->comp); |
| |
| ret = regmap_write(snand->regmap, SNAFDRSAR, buf_dma + pos); |
| if (ret) |
| goto out_disable_int; |
| |
| pos += nbytes; |
| |
| ret = regmap_write(snand->regmap, SNAFDLR, |
| CMR_WID(op->data.buswidth) | nbytes); |
| if (ret) |
| goto out_disable_int; |
| |
| ret = regmap_write(snand->regmap, SNAFDTR, trig); |
| if (ret) |
| goto out_disable_int; |
| |
| if (!wait_for_completion_timeout(&snand->comp, usecs_to_jiffies(20000))) |
| ret = -ETIMEDOUT; |
| |
| if (ret) |
| goto out_disable_int; |
| } |
| |
| out_disable_int: |
| regmap_update_bits(snand->regmap, SNAFCFR, SNAFCFR_DMA_IE, 0); |
| out_unmap: |
| dma_unmap_single(snand->dev, buf_dma, op->data.nbytes, dir); |
| out_deselect: |
| rtl_snand_xfer_tail(snand, cs); |
| |
| if (ret) |
| dev_err(snand->dev, "transfer failed %d\n", ret); |
| |
| return ret; |
| } |
| |
| static bool rtl_snand_dma_op(const struct spi_mem_op *op) |
| { |
| switch (op->data.dir) { |
| case SPI_MEM_DATA_IN: |
| case SPI_MEM_DATA_OUT: |
| return op->data.nbytes > 32; |
| default: |
| return false; |
| } |
| } |
| |
| static int rtl_snand_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) |
| { |
| struct rtl_snand *snand = spi_controller_get_devdata(mem->spi->controller); |
| int cs = spi_get_chipselect(mem->spi, 0); |
| |
| dev_dbg(snand->dev, "cs %d op cmd %02x %d:%d, dummy %d:%d, addr %08llx@%d:%d, data %d:%d\n", |
| cs, op->cmd.opcode, |
| op->cmd.buswidth, op->cmd.nbytes, op->dummy.buswidth, |
| op->dummy.nbytes, op->addr.val, op->addr.buswidth, |
| op->addr.nbytes, op->data.buswidth, op->data.nbytes); |
| |
| if (rtl_snand_dma_op(op)) |
| return rtl_snand_dma_xfer(snand, cs, op); |
| else |
| return rtl_snand_xfer(snand, cs, op); |
| } |
| |
| static const struct spi_controller_mem_ops rtl_snand_mem_ops = { |
| .supports_op = rtl_snand_supports_op, |
| .exec_op = rtl_snand_exec_op, |
| }; |
| |
| static const struct of_device_id rtl_snand_match[] = { |
| { .compatible = "realtek,rtl9301-snand" }, |
| { .compatible = "realtek,rtl9302b-snand" }, |
| { .compatible = "realtek,rtl9302c-snand" }, |
| { .compatible = "realtek,rtl9303-snand" }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, rtl_snand_match); |
| |
| static int rtl_snand_probe(struct platform_device *pdev) |
| { |
| struct rtl_snand *snand; |
| struct device *dev = &pdev->dev; |
| struct spi_controller *ctrl; |
| void __iomem *base; |
| const struct regmap_config rc = { |
| .reg_bits = 32, |
| .val_bits = 32, |
| .reg_stride = 4, |
| .cache_type = REGCACHE_NONE, |
| }; |
| int irq, ret; |
| |
| ctrl = devm_spi_alloc_host(dev, sizeof(*snand)); |
| if (!ctrl) |
| return -ENOMEM; |
| |
| snand = spi_controller_get_devdata(ctrl); |
| snand->dev = dev; |
| |
| base = devm_platform_ioremap_resource(pdev, 0); |
| if (IS_ERR(base)) |
| return PTR_ERR(base); |
| |
| snand->regmap = devm_regmap_init_mmio(dev, base, &rc); |
| if (IS_ERR(snand->regmap)) |
| return PTR_ERR(snand->regmap); |
| |
| init_completion(&snand->comp); |
| |
| irq = platform_get_irq(pdev, 0); |
| if (irq < 0) |
| return irq; |
| |
| ret = dma_set_mask(snand->dev, DMA_BIT_MASK(32)); |
| if (ret) |
| return dev_err_probe(dev, ret, "failed to set DMA mask\n"); |
| |
| ret = devm_request_irq(dev, irq, rtl_snand_irq, 0, "rtl-snand", snand); |
| if (ret) |
| return dev_err_probe(dev, ret, "failed to request irq\n"); |
| |
| ctrl->num_chipselect = 2; |
| ctrl->mem_ops = &rtl_snand_mem_ops; |
| ctrl->bits_per_word_mask = SPI_BPW_MASK(8); |
| ctrl->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD; |
| device_set_node(&ctrl->dev, dev_fwnode(dev)); |
| |
| return devm_spi_register_controller(dev, ctrl); |
| } |
| |
| static struct platform_driver rtl_snand_driver = { |
| .driver = { |
| .name = "realtek-rtl-snand", |
| .of_match_table = rtl_snand_match, |
| }, |
| .probe = rtl_snand_probe, |
| }; |
| module_platform_driver(rtl_snand_driver); |
| |
| MODULE_DESCRIPTION("Realtek SPI-NAND Flash Controller Driver"); |
| MODULE_LICENSE("GPL"); |