// SPDX-License-Identifier: GPL-2.0-only #include #include #include #include #include #include #include #include #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");