@@ -79,6 +79,12 @@
#define NFC_REG_H6_MDMA_BUF_ADDR 0x0210
#define NFC_REG_H6_MDMA_CNT 0x0214
+#define NFC_H6_MDMA_STA_DESC0_COMPLETE BIT(0)
+
+#define NFC_MDMA_DESC_LAST BIT(2)
+#define NFC_MDMA_DESC_FIRST BIT(3)
+#define NFC_MDMA_DESC_SIZE_MASK GENMASK(15, 0)
+
#define NFC_RAM0_BASE 0x0400
#define NFC_RAM1_BASE 0x0800
@@ -267,12 +273,19 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand)
return container_of(nand, struct sunxi_nand_chip, nand);
}
+struct sunxi_nfc_mdma_desc {
+ __le32 config;
+ __le32 size;
+ __le32 buf;
+ __le32 next;
+} __packed __aligned(4);
+
/*
* NAND Controller capabilities structure: stores NAND controller capabilities
* for distinction between compatible strings.
*
- * @has_mdma: Use mbus dma mode, otherwise general dma
- * through MBUS on A23/A33 needs extra configuration.
+ * @has_mdma: Use A23/A33-style MBUS DMA registers
+ * @has_mdma_desc: MBUS DMA uses H6-style descriptors
* @has_ecc_block_512: If the ECC can handle 512B or only 1024B chunks
* @has_ecc_clk: If the controller needs an ECC clock.
* @has_mbus_clk: If the controller needs a mbus clock.
@@ -304,6 +317,7 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand)
*/
struct sunxi_nfc_caps {
bool has_mdma;
+ bool has_mdma_desc;
bool has_ecc_block_512;
bool has_ecc_clk;
bool has_mbus_clk;
@@ -346,6 +360,9 @@ struct sunxi_nfc_caps {
* controller
* @complete: a completion object used to wait for NAND controller events
* @dmac: the DMA channel attached to the NAND controller
+ * @use_mdma: use an internal MBUS DMA backend
+ * @mdma_desc: H6-style MBUS DMA descriptor
+ * @mdma_desc_dma: DMA address of @mdma_desc
* @caps: NAND Controller capabilities
*/
struct sunxi_nfc {
@@ -362,6 +379,9 @@ struct sunxi_nfc {
struct list_head chips;
struct completion complete;
struct dma_chan *dmac;
+ bool use_mdma;
+ struct sunxi_nfc_mdma_desc *mdma_desc;
+ dma_addr_t mdma_desc_dma;
const struct sunxi_nfc_caps *caps;
};
@@ -466,7 +486,10 @@ static int sunxi_nfc_dma_op_prepare(struct sunxi_nfc *nfc, const void *buf,
{
struct dma_async_tx_descriptor *dmad;
enum dma_transfer_direction tdir;
+ dma_addr_t buf_dma;
dma_cookie_t dmat;
+ int len = chunksize * nchunks;
+ u32 data_blocks = nchunks;
int ret;
if (ddir == DMA_FROM_DEVICE)
@@ -474,12 +497,21 @@ static int sunxi_nfc_dma_op_prepare(struct sunxi_nfc *nfc, const void *buf,
else
tdir = DMA_MEM_TO_DEV;
- sg_init_one(sg, buf, nchunks * chunksize);
+ sg_init_one(sg, buf, len);
ret = dma_map_sg(nfc->dev, sg, 1, ddir);
if (!ret)
return -ENOMEM;
- if (!nfc->caps->has_mdma) {
+ buf_dma = sg_dma_address(sg);
+
+ if (nfc->mdma_desc &&
+ (len > NFC_MDMA_DESC_SIZE_MASK || !IS_ALIGNED(len, 8) ||
+ !IS_ALIGNED(buf_dma, 4))) {
+ ret = -EINVAL;
+ goto err_unmap_buf;
+ }
+
+ if (!nfc->use_mdma) {
dmad = dmaengine_prep_slave_sg(nfc->dmac, sg, 1, tdir, DMA_CTRL_ACK);
if (!dmad) {
ret = -EINVAL;
@@ -489,14 +521,35 @@ static int sunxi_nfc_dma_op_prepare(struct sunxi_nfc *nfc, const void *buf,
writel(readl(nfc->regs + NFC_REG_CTL) | NFC_RAM_METHOD,
nfc->regs + NFC_REG_CTL);
- writel(nchunks, nfc->regs + NFC_REG_SECTOR_NUM);
+
+ /* H6/H616 use one enable bit per ECC data block. */
+ if (nfc->caps->has_mdma_desc)
+ data_blocks = GENMASK(nchunks - 1, 0);
+ writel(data_blocks, nfc->regs + NFC_REG_SECTOR_NUM);
writel(chunksize, nfc->regs + NFC_REG_CNT);
- if (nfc->caps->has_mdma) {
+ if (nfc->use_mdma)
writel(readl(nfc->regs + NFC_REG_CTL) & ~NFC_DMA_TYPE_NORMAL,
nfc->regs + NFC_REG_CTL);
- writel(chunksize * nchunks, nfc->regs + NFC_REG_MDMA_CNT);
- writel(sg_dma_address(sg), nfc->regs + NFC_REG_MDMA_ADDR);
+
+ if (nfc->mdma_desc) {
+ struct sunxi_nfc_mdma_desc *desc = nfc->mdma_desc;
+
+ desc->config = cpu_to_le32(NFC_MDMA_DESC_FIRST |
+ NFC_MDMA_DESC_LAST);
+ desc->size = cpu_to_le32(len);
+ /* Descriptor words are little-endian DMA memory, not MMIO. */
+ desc->buf = cpu_to_le32(buf_dma);
+ desc->next = cpu_to_le32(nfc->mdma_desc_dma);
+
+ writel(NFC_H6_MDMA_STA_DESC0_COMPLETE,
+ nfc->regs + NFC_REG_H6_MDMA_STA);
+ dma_wmb();
+ writel(nfc->mdma_desc_dma,
+ nfc->regs + NFC_REG_H6_MDMA_DLBA_REG);
+ } else if (nfc->caps->has_mdma) {
+ writel(len, nfc->regs + NFC_REG_MDMA_CNT);
+ writel(buf_dma, nfc->regs + NFC_REG_MDMA_ADDR);
} else {
dmat = dmaengine_submit(dmad);
@@ -525,6 +578,14 @@ static void sunxi_nfc_dma_op_cleanup(struct sunxi_nfc *nfc,
nfc->regs + NFC_REG_CTL);
}
+static void sunxi_nfc_dma_op_abort(struct sunxi_nfc *nfc)
+{
+ if (nfc->use_mdma)
+ sunxi_nfc_rst(nfc);
+ else
+ dmaengine_terminate_all(nfc->dmac);
+}
+
static void sunxi_nfc_select_chip(struct nand_chip *nand, unsigned int cs)
{
struct mtd_info *mtd = nand_to_mtd(nand);
@@ -1202,7 +1263,7 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
wait = NFC_CMD_INT_FLAG;
- if (nfc->caps->has_mdma)
+ if (nfc->use_mdma)
wait |= NFC_DMA_INT_FLAG;
else
dma_async_issue_pending(nfc->dmac);
@@ -1211,8 +1272,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
nfc->regs + NFC_REG_CMD);
ret = sunxi_nfc_wait_events(nfc, wait, false, 0);
- if (ret && !nfc->caps->has_mdma)
- dmaengine_terminate_all(nfc->dmac);
+ if (ret)
+ sunxi_nfc_dma_op_abort(nfc);
sunxi_nfc_randomizer_disable(nand);
sunxi_nfc_hw_ecc_disable(nand);
@@ -1613,7 +1674,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
wait = NFC_CMD_INT_FLAG;
- if (nfc->caps->has_mdma)
+ if (nfc->use_mdma)
wait |= NFC_DMA_INT_FLAG;
else
dma_async_issue_pending(nfc->dmac);
@@ -1623,8 +1684,8 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
nfc->regs + NFC_REG_CMD);
ret = sunxi_nfc_wait_events(nfc, wait, false, 0);
- if (ret && !nfc->caps->has_mdma)
- dmaengine_terminate_all(nfc->dmac);
+ if (ret)
+ sunxi_nfc_dma_op_abort(nfc);
sunxi_nfc_randomizer_disable(nand);
sunxi_nfc_hw_ecc_disable(nand);
@@ -2073,11 +2134,13 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
ecc->write_oob = sunxi_nfc_hw_ecc_write_oob;
mtd_set_ooblayout(mtd, &sunxi_nand_ooblayout_ops);
- if (nfc->dmac || nfc->caps->has_mdma) {
+ if (nfc->dmac || nfc->use_mdma) {
ecc->read_page = sunxi_nfc_hw_ecc_read_page_dma;
ecc->read_subpage = sunxi_nfc_hw_ecc_read_subpage_dma;
ecc->write_page = sunxi_nfc_hw_ecc_write_page_dma;
nand->options |= NAND_USES_DMA;
+ if (nfc->mdma_desc)
+ nand->buf_align = 4;
} else {
ecc->read_page = sunxi_nfc_hw_ecc_read_page;
ecc->read_subpage = sunxi_nfc_hw_ecc_read_subpage;
@@ -2426,8 +2489,32 @@ static int sunxi_nfc_dma_init(struct sunxi_nfc *nfc, struct resource *r)
{
int ret;
- if (nfc->caps->has_mdma)
+ if (nfc->caps->has_mdma_desc) {
+ ret = dma_set_mask_and_coherent(nfc->dev, DMA_BIT_MASK(32));
+ if (ret) {
+ dev_warn(nfc->dev,
+ "failed to set MBUS DMA mask, using PIO: %d\n",
+ ret);
+ return 0;
+ }
+
+ nfc->mdma_desc =
+ dmam_alloc_coherent(nfc->dev, sizeof(*nfc->mdma_desc),
+ &nfc->mdma_desc_dma, GFP_KERNEL);
+ if (!nfc->mdma_desc) {
+ dev_warn(nfc->dev,
+ "failed to allocate MBUS DMA descriptor, using PIO\n");
+ return 0;
+ }
+
+ nfc->use_mdma = true;
return 0;
+ }
+
+ if (nfc->caps->has_mdma) {
+ nfc->use_mdma = true;
+ return 0;
+ }
nfc->dmac = dma_request_chan(nfc->dev, "rxtx");
if (IS_ERR(nfc->dmac)) {
@@ -2620,6 +2707,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_a23_caps = {
};
static const struct sunxi_nfc_caps sunxi_nfc_h616_caps = {
+ .has_mdma_desc = true,
.has_ecc_clk = true,
.has_mbus_clk = true,
.reg_io_data = NFC_REG_A23_IO_DATA,