2022-01-04 14:52:19

by Neil Armstrong

[permalink] [raw]
Subject: [PATCH 0/4] dmaengine: Add support Oxford Semiconductor OXNAS DMA Engine

This serie adds support for the DMA engine found in Oxford Semiconductor SoCs,
notably in the OX810SE where it's heavily used for SATA transfers.

The driver was on my pipe since 2016 and a courageous person managed to get
the SATA driver work up mainline kernel with this driver, so I cleaned it up
in order to be upstreamed.

I plan to push the last patch through arm-soc when bindings is applied.

Neil Armstrong (4):
dt-bindings: dma: Add bindings for ox810se dma engine
dmaengine: Add Oxford Semiconductor OXNAS DMA Controller
MAINTAINERS: add OX810SE DMA driver files under Oxnas entry
ARM: dts: ox810se: Add DMA Support

.../bindings/dma/oxsemi,ox810se-dma.yaml | 97 ++
MAINTAINERS | 2 +
arch/arm/boot/dts/ox810se-wd-mbwe.dts | 4 +
arch/arm/boot/dts/ox810se.dtsi | 21 +
drivers/dma/Kconfig | 8 +
drivers/dma/Makefile | 1 +
drivers/dma/oxnas_adma.c | 1045 +++++++++++++++++
7 files changed, 1178 insertions(+)
create mode 100644 Documentation/devicetree/bindings/dma/oxsemi,ox810se-dma.yaml
create mode 100644 drivers/dma/oxnas_adma.c


base-commit: fa55b7dcdc43c1aa1ba12bca9d2dd4318c2a0dbf
--
2.25.1



2022-01-04 14:52:20

by Neil Armstrong

[permalink] [raw]
Subject: [PATCH 1/4] dt-bindings: dma: Add bindings for ox810se dma engine

This adds the YAML dt-bindings for the DMA engine found in the
Oxford Semiconductor OX810SE SoC.

Signed-off-by: Neil Armstrong <[email protected]>
---
.../bindings/dma/oxsemi,ox810se-dma.yaml | 97 +++++++++++++++++++
1 file changed, 97 insertions(+)
create mode 100644 Documentation/devicetree/bindings/dma/oxsemi,ox810se-dma.yaml

diff --git a/Documentation/devicetree/bindings/dma/oxsemi,ox810se-dma.yaml b/Documentation/devicetree/bindings/dma/oxsemi,ox810se-dma.yaml
new file mode 100644
index 000000000000..6efa28e8b124
--- /dev/null
+++ b/Documentation/devicetree/bindings/dma/oxsemi,ox810se-dma.yaml
@@ -0,0 +1,97 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/dma/oxsemi,ox810se-dma.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Oxford Semiconductor DMA Controller Device Tree Bindings
+
+maintainers:
+ - Neil Armstrong <[email protected]>
+
+allOf:
+ - $ref: "dma-controller.yaml#"
+
+properties:
+ "#dma-cells":
+ const: 1
+
+ compatible:
+ const: oxsemi,ox810se-dma
+
+ reg:
+ maxItems: 2
+
+ reg-names:
+ items:
+ - const: dma
+ - const: sgdma
+
+ interrupts:
+ maxItems: 5
+
+ clocks:
+ maxItems: 1
+
+ resets:
+ maxItems: 2
+
+ reset-names:
+ items:
+ - const: dma
+ - const: sgdma
+
+ dma-channels: true
+
+ oxsemi,targets-types:
+ description:
+ Table with allowed memory ranges and memory type associated.
+ $ref: "/schemas/types.yaml#/definitions/uint32-matrix"
+ minItems: 4
+ items:
+ items:
+ - description:
+ The first cell defines the memory range start address
+ - description:
+ The first cell defines the memory range end address
+ - description:
+ The third cell represents memory type, 0 for SATA,
+ 1 for DPE RX, 2 for DPE TX, 5 for AUDIO TX, 6 for AUDIO RX,
+ 15 for DRAM MEMORY.
+ enum: [ 0, 1, 2, 5, 6, 15 ]
+
+required:
+ - "#dma-cells"
+ - dma-channels
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - resets
+ - reset-names
+ - oxsemi,targets-types
+
+additionalProperties: false
+
+examples:
+ - |
+ dma: dma-controller@600000 {
+ compatible = "oxsemi,ox810se-dma";
+ reg = <0x600000 0x100000>, <0xc00000 0x100000>;
+ reg-names = "dma", "sgdma";
+ interrupts = <13>, <14>, <15>, <16>, <20>;
+ clocks = <&stdclk 1>;
+ resets = <&reset 8>, <&reset 24>;
+ reset-names = "dma", "sgdma";
+
+ /* Encodes the authorized memory types */
+ oxsemi,targets-types =
+ <0x45900000 0x45a00000 0>, /* SATA */
+ <0x42000000 0x43000000 0>, /* SATA DATA */
+ <0x48000000 0x58000000 15>, /* DDR */
+ <0x58000000 0x58020000 15>; /* SRAM */
+
+ #dma-cells = <1>;
+ dma-channels = <5>;
+ };
+...
--
2.25.1


2022-01-04 14:52:24

by Neil Armstrong

[permalink] [raw]
Subject: [PATCH 2/4] dmaengine: Add Oxford Semiconductor OXNAS DMA Controller

The Oxford Semiconductor OX810SE contains a DMA engine mainly used
for the SATA controller which doesn't have an embedded DMA engine.

This DMA engine support single and scatter-gather memory-to-memory
transfers and device-to-memory/memory-to-device transfers.

Signed-off-by: Neil Armstrong <[email protected]>
---
drivers/dma/Kconfig | 8 +
drivers/dma/Makefile | 1 +
drivers/dma/oxnas_adma.c | 1045 ++++++++++++++++++++++++++++++++++++++
3 files changed, 1054 insertions(+)
create mode 100644 drivers/dma/oxnas_adma.c

diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index 6bcdb4e6a0d1..8925ffd85ac2 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -505,6 +505,14 @@ config OWL_DMA
help
Enable support for the Actions Semi Owl SoCs DMA controller.

+config OXNAS_DMA
+ bool "Oxford Semiconductor OXNAS SoC Family DMA support"
+ select DMA_ENGINE
+ select DMA_VIRTUAL_CHANNELS
+ depends on ARCH_OXNAS || COMPILE_TEST
+ help
+ Support for Oxford Semiconductor OXNAS SoC Family DMA engine
+
config PCH_DMA
tristate "Intel EG20T PCH / LAPIS Semicon IOH(ML7213/ML7223/ML7831) DMA"
depends on PCI && (X86_32 || COMPILE_TEST)
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 616d926cf2a5..ba1111785bb8 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -58,6 +58,7 @@ obj-$(CONFIG_MXS_DMA) += mxs-dma.o
obj-$(CONFIG_MX3_IPU) += ipu/
obj-$(CONFIG_NBPFAXI_DMA) += nbpfaxi.o
obj-$(CONFIG_OWL_DMA) += owl-dma.o
+obj-$(CONFIG_OXNAS_DMA) += oxnas_adma.o
obj-$(CONFIG_PCH_DMA) += pch_dma.o
obj-$(CONFIG_PL330_DMA) += pl330.o
obj-$(CONFIG_PLX_DMA) += plx_dma.o
diff --git a/drivers/dma/oxnas_adma.c b/drivers/dma/oxnas_adma.c
new file mode 100644
index 000000000000..586c23187de1
--- /dev/null
+++ b/drivers/dma/oxnas_adma.c
@@ -0,0 +1,1045 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016 Neil Armstrong <[email protected]>
+ * Copyright (C) 2008 Oxford Semiconductor Ltd
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/semaphore.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/memory.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_dma.h>
+#include <linux/reset.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/bitfield.h>
+#include <linux/dma-mapping.h>
+
+#include "dmaengine.h"
+#include "virt-dma.h"
+
+/* Normal (non-SG) registers */
+#define DMA_REGS_PER_CHANNEL 8
+
+#define DMA_CTRL_STATUS 0x00
+#define DMA_BASE_SRC_ADR 0x04
+#define DMA_BASE_DST_ADR 0x08
+#define DMA_BYTE_CNT 0x0c
+#define DMA_CURRENT_SRC_ADR 0x10
+#define DMA_CURRENT_DST_ADR 0x14
+#define DMA_CURRENT_BYTE_CNT 0x18
+#define DMA_INTR_ID 0x1c
+#define DMA_INTR_CLEAR_REG (DMA_CURRENT_SRC_ADR)
+
+/* 8 quad-sized registers per channel arranged contiguously */
+#define DMA_CALC_REG_ADR(channel, register) (((channel) << 5) + (register))
+
+#define DMA_CTRL_STATUS_FAIR_SHARE_ARB BIT(0)
+#define DMA_CTRL_STATUS_IN_PROGRESS BIT(1)
+#define DMA_CTRL_STATUS_SRC_DREQ GENMASK(5, 2)
+#define DMA_CTRL_STATUS_DEST_DREQ GENMASK(10, 7)
+#define DMA_CTRL_STATUS_INTR BIT(10)
+#define DMA_CTRL_STATUS_NXT_FREE BIT(11)
+#define DMA_CTRL_STATUS_RESET BIT(12)
+#define DMA_CTRL_STATUS_DIR GENMASK(14, 13)
+#define DMA_CTRL_STATUS_SRC_ADR_MODE BIT(15)
+#define DMA_CTRL_STATUS_DEST_ADR_MODE BIT(16)
+#define DMA_CTRL_STATUS_TRANSFER_MODE_A BIT(17)
+#define DMA_CTRL_STATUS_TRANSFER_MODE_B BIT(18)
+#define DMA_CTRL_STATUS_SRC_WIDTH GENMASK(21, 19)
+#define DMA_CTRL_STATUS_DEST_WIDTH GENMASK(24, 22)
+#define DMA_CTRL_STATUS_PAUSE BIT(25)
+#define DMA_CTRL_STATUS_INTERRUPT_ENABLE BIT(26)
+#define DMA_CTRL_STATUS_SOURCE_ADDRESS_FIXED BIT(27)
+#define DMA_CTRL_STATUS_DESTINATION_ADDRESS_FIXED BIT(28)
+#define DMA_CTRL_STATUS_STARVE_LOW_PRIORITY BIT(29)
+#define DMA_CTRL_STATUS_INTR_CLEAR_ENABLE BIT(30)
+
+#define DMA_BYTE_CNT_MASK GENMASK(20, 0)
+#define DMA_BYTE_CNT_INC4_SET_MASK BIT(28)
+#define DMA_BYTE_CNT_HPROT_MASK BIT(29)
+#define DMA_BYTE_CNT_WR_EOT_MASK BIT(30)
+#define DMA_BYTE_CNT_RD_EOT_MASK BIT(31)
+
+#define DMA_INTR_ID_GET_NUM_CHANNELS(reg_contents) (((reg_contents) >> 16) & 0xFF)
+#define DMA_INTR_ID_GET_VERSION(reg_contents) (((reg_contents) >> 24) & 0xFF)
+#define DMA_INTR_ID_INT_MASK GENMASK(MAX_OXNAS_DMA_CHANNELS, 0)
+
+#define DMA_HAS_V4_INTR_CLEAR(version) ((version) > 3)
+
+/* H/W scatter gather controller registers */
+#define OXNAS_DMA_NUM_SG_REGS 4
+
+#define DMA_SG_CONTROL 0x00
+#define DMA_SG_STATUS 0x04
+#define DMA_SG_REQ_PTR 0x08
+#define DMA_SG_RESETS 0x0c
+
+#define DMA_SG_CALC_REG_ADR(channel, register) (((channel) << 4) + (register))
+
+/* SG DMA controller control register field definitions */
+#define DMA_SG_CONTROL_START BIT(0)
+#define DMA_SG_CONTROL_QUEUING_ENABLE BIT(1)
+#define DMA_SG_CONTROL_HBURST_ENABLE BIT(2)
+
+/* SG DMA controller status register field definitions */
+#define DMA_SG_STATUS_ERROR_CODE GENMASK(5, 0)
+#define DMA_SG_STATUS_BUSY BIT(7)
+
+/* SG DMA controller sub-block resets register field definitions */
+#define DMA_SG_RESETS_CONTROL BIT(0)
+#define DMA_SG_RESETS_ARBITER BIT(1)
+#define DMA_SG_RESETS_AHB BIT(2)
+
+/* SG DMA controller qualifier field definitions */
+#define OXNAS_DMA_SG_QUALIFIER GENMASK(15, 0)
+#define OXNAS_DMA_SG_DST_EOT GENMASK(17, 16)
+#define OXNAS_DMA_SG_SRC_EOT GENMASK(22, 20)
+#define OXNAS_DMA_SG_CHANNEL GENMASK(31, 24)
+
+#define OXNAS_DMA_ADR_MASK GENMASK(29, 0)
+#define OXNAS_DMA_MAX_TRANSFER_LENGTH DMA_BYTE_CNT_MASK
+
+/* The available buses to which the DMA controller is attached */
+enum {
+ OXNAS_DMA_SIDE_A = 0,
+ OXNAS_DMA_SIDE_B
+};
+
+/* Direction of data flow between the DMA controller's pair of interfaces */
+enum {
+ OXNAS_DMA_A_TO_A = 0,
+ OXNAS_DMA_B_TO_A,
+ OXNAS_DMA_A_TO_B,
+ OXNAS_DMA_B_TO_B
+};
+
+/* The available data widths */
+enum {
+ OXNAS_DMA_TRANSFER_WIDTH_8BITS = 0,
+ OXNAS_DMA_TRANSFER_WIDTH_16BITS,
+ OXNAS_DMA_TRANSFER_WIDTH_32BITS
+};
+
+/* The mode of the DMA transfer */
+enum {
+ OXNAS_DMA_TRANSFER_MODE_SINGLE = 0,
+ OXNAS_DMA_TRANSFER_MODE_BURST
+};
+
+/* The available transfer targets */
+enum {
+ OXNAS_DMA_DREQ_PATA = 0,
+ OXNAS_DMA_DREQ_SATA = 0,
+ OXNAS_DMA_DREQ_DPE_RX = 1,
+ OXNAS_DMA_DREQ_DPE_TX = 2,
+ OXNAS_DMA_DREQ_AUDIO_TX = 5,
+ OXNAS_DMA_DREQ_AUDIO_RX = 6,
+ OXNAS_DMA_DREQ_MEMORY = 15
+};
+
+enum {
+ OXNAS_DMA_TYPE_SIMPLE = 0,
+ OXNAS_DMA_TYPE_SG,
+};
+
+#define MAX_OXNAS_DMA_CHANNELS 5
+#define MAX_OXNAS_SG_ENTRIES 512
+
+/* Will be exchanged with SG DMA controller */
+struct oxnas_dma_sg_entry {
+ dma_addr_t data_addr; /* physical address of the buffer */
+ unsigned long data_length; /* length of the buffer */
+ dma_addr_t p_next_entry; /* physical address of next descriptor */
+ struct oxnas_dma_sg_entry *next_entry; /* virtual address of the next descriptor */
+ dma_addr_t this_paddr; /* physical address of this descriptor */
+ struct list_head entry; /* Linked list entry */
+} __attribute((aligned(4), packed));
+
+/* Will be exchanged with SG DMA controller */
+struct oxnas_dma_sg_info {
+ unsigned long qualifier;
+ unsigned long control;
+ dma_addr_t p_src_entries; /* physical address of the first source SG desc */
+ dma_addr_t p_dst_entries; /* physical address of the first dest SG desc */
+ struct oxnas_dma_sg_entry *src_entries; /* virtual address of the first source SG desc */
+ struct oxnas_dma_sg_entry *dst_entries; /* virtual address of the first dest SG desc */
+} __attribute((aligned(4), packed));
+
+struct oxnas_dma_sg_data {
+ struct oxnas_dma_sg_entry entries[MAX_OXNAS_SG_ENTRIES];
+ struct oxnas_dma_sg_info infos[MAX_OXNAS_DMA_CHANNELS];
+} __attribute((aligned(4)));
+
+struct oxnas_dma_device;
+struct oxnas_dma_channel;
+
+struct oxnas_dma_desc {
+ struct virt_dma_desc vd;
+ struct oxnas_dma_channel *channel;
+ unsigned long ctrl;
+ unsigned long len;
+ dma_addr_t src_adr;
+ dma_addr_t dst_adr;
+ unsigned int type;
+ struct oxnas_dma_sg_info sg_info;
+ unsigned int entries;
+ struct list_head sg_entries;
+};
+
+struct oxnas_dma_channel {
+ struct virt_dma_chan vc;
+ struct list_head node;
+ struct oxnas_dma_device *dmadev;
+ unsigned int id;
+ unsigned int irq;
+
+ struct dma_slave_config cfg;
+
+ dma_addr_t p_sg_info; /* physical address of the array of sg_info structs */
+ struct oxnas_dma_sg_info *sg_info; /* virtual address of the array of sg_info structs */
+
+ atomic_t active;
+
+ struct oxnas_dma_desc *cur;
+};
+
+struct oxnas_dma_device {
+ struct platform_device *pdev;
+ struct dma_device common;
+ void __iomem *dma_base;
+ void __iomem *sgdma_base;
+ struct reset_control *dma_rst;
+ struct reset_control *sgdma_rst;
+ struct clk *dma_clk;
+
+ unsigned int channels_count;
+
+ struct oxnas_dma_channel channels[MAX_OXNAS_DMA_CHANNELS];
+
+ unsigned int hwversion;
+
+ /* Protects concurrent access to channels */
+ spinlock_t lock;
+ struct tasklet_struct tasklet;
+
+ struct list_head pending;
+
+ struct {
+ dma_addr_t start;
+ dma_addr_t end;
+ unsigned int type;
+ } *authorized_types;
+ unsigned int authorized_types_count;
+
+ struct list_head free_entries;
+ atomic_t free_entries_count;
+ dma_addr_t p_sg_data;
+ struct oxnas_dma_sg_data *sg_data;
+};
+
+static void oxnas_dma_start_next(struct oxnas_dma_channel *channel);
+
+static irqreturn_t oxnas_dma_interrupt(int irq, void *dev_id)
+{
+ struct oxnas_dma_channel *channel = dev_id;
+ struct oxnas_dma_device *dmadev = channel->dmadev;
+ unsigned long error_code;
+ unsigned long flags;
+
+ dev_vdbg(&dmadev->pdev->dev, "irq for channel %d\n", channel->id);
+
+ while (readl(dmadev->dma_base + DMA_CALC_REG_ADR(0, DMA_INTR_ID)) & BIT(channel->id)) {
+ dev_dbg(&dmadev->pdev->dev, "Acking interrupt for channel %u\n",
+ channel->id);
+
+ /* Write to the interrupt clear register to clear interrupt */
+ writel(0, dmadev->dma_base + DMA_CALC_REG_ADR(channel->id, DMA_INTR_CLEAR_REG));
+ }
+
+ if (channel->cur && channel->cur->type == OXNAS_DMA_TYPE_SG) {
+ error_code = readl(dmadev->sgdma_base +
+ DMA_SG_CALC_REG_ADR(channel->id, DMA_SG_STATUS));
+ error_code &= DMA_SG_STATUS_ERROR_CODE;
+
+ /* TOFIX report it to the core */
+ if (error_code)
+ dev_err(&dmadev->pdev->dev, "ch%d: sgdma err %x\n",
+ channel->id, (unsigned int)error_code);
+
+ writel(1, dmadev->sgdma_base + DMA_SG_CALC_REG_ADR(channel->id, DMA_SG_STATUS));
+ }
+
+ spin_lock_irqsave(&channel->vc.lock, flags);
+
+ if (atomic_read(&channel->active)) {
+ struct oxnas_dma_desc *cur = channel->cur;
+
+ oxnas_dma_start_next(channel);
+ if (cur)
+ vchan_cookie_complete(&cur->vd);
+ } else {
+ dev_warn(&dmadev->pdev->dev, "spurious irq for channel %d\n",
+ channel->id);
+ }
+
+ spin_unlock_irqrestore(&channel->vc.lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static void oxnas_dma_start_next(struct oxnas_dma_channel *channel)
+{
+ struct oxnas_dma_device *dmadev = channel->dmadev;
+ struct virt_dma_desc *vd = vchan_next_desc(&channel->vc);
+ struct oxnas_dma_desc *desc;
+ unsigned long ctrl_status;
+
+ if (!vd) {
+ channel->cur = NULL;
+ return;
+ }
+
+ list_del(&vd->node);
+
+ desc = container_of(&vd->tx, struct oxnas_dma_desc, vd.tx);
+ channel->cur = desc;
+
+ if (desc->type == OXNAS_DMA_TYPE_SIMPLE) {
+ /* Write the control/status value to the DMAC */
+ writel(desc->ctrl,
+ dmadev->dma_base + DMA_CALC_REG_ADR(channel->id, DMA_CTRL_STATUS));
+
+ /* Ensure control/status word makes it to the DMAC before
+ * we write address/length info
+ */
+ wmb();
+
+ /* Write the source addresses to the DMAC */
+ writel(desc->src_adr & OXNAS_DMA_ADR_MASK,
+ dmadev->dma_base + DMA_CALC_REG_ADR(channel->id, DMA_BASE_SRC_ADR));
+
+ /* Write the destination addresses to the DMAC */
+ writel(desc->dst_adr & OXNAS_DMA_ADR_MASK,
+ dmadev->dma_base + DMA_CALC_REG_ADR(channel->id, DMA_BASE_DST_ADR));
+
+ /* Write the length, with EOT configuration
+ * for the single transfer
+ */
+ writel(desc->len,
+ dmadev->dma_base + DMA_CALC_REG_ADR(channel->id, DMA_BYTE_CNT));
+
+ /* Ensure adr/len info makes it to DMAC before later modifications to
+ * control/status register due to starting the transfer, which happens in
+ * oxnas_dma_start()
+ */
+ wmb();
+
+ /* Setup channel data */
+ atomic_set(&channel->active, 1);
+
+ /* Single transfer mode, so unpause the DMA controller channel */
+ ctrl_status = readl(dmadev->dma_base +
+ DMA_CALC_REG_ADR(channel->id, DMA_CTRL_STATUS));
+ writel(ctrl_status & ~DMA_CTRL_STATUS_PAUSE,
+ dmadev->dma_base + DMA_CALC_REG_ADR(channel->id, DMA_CTRL_STATUS));
+
+ dev_dbg(&dmadev->pdev->dev, "ch%d: started req %d from %08x to %08x, %lubytes\n",
+ channel->id, vd->tx.cookie,
+ desc->src_adr, desc->dst_adr,
+ desc->len & OXNAS_DMA_MAX_TRANSFER_LENGTH);
+ } else if (desc->type == OXNAS_DMA_TYPE_SG) {
+ /* Write to the SG-DMA channel's reset register to reset the control
+ * in case the previous SG-DMA transfer failed in some way, thus
+ * leaving the SG-DMA controller hung up part way through processing
+ * its SG list. The reset bits are self-clearing
+ */
+ writel(DMA_SG_RESETS_CONTROL,
+ dmadev->sgdma_base + DMA_SG_CALC_REG_ADR(channel->id, DMA_SG_RESETS));
+
+ /* Copy the sg_info structure */
+ memcpy(channel->sg_info, &desc->sg_info, sizeof(struct oxnas_dma_sg_info));
+
+ /* Ensure adr/len info makes it to DMAC before later modifications to
+ * control/status register due to starting the transfer, which happens in
+ * oxnas_dma_start()
+ */
+ wmb();
+
+ /* Write the pointer to the SG info struct into the Request Pointer reg */
+ writel(channel->p_sg_info,
+ dmadev->sgdma_base + DMA_SG_CALC_REG_ADR(channel->id, DMA_SG_REQ_PTR));
+
+ /* Setup channel data */
+ atomic_set(&channel->active, 1);
+
+ /* Start the transfert */
+ writel(DMA_SG_CONTROL_START |
+ DMA_SG_CONTROL_QUEUING_ENABLE |
+ DMA_SG_CONTROL_HBURST_ENABLE,
+ dmadev->sgdma_base + DMA_SG_CALC_REG_ADR(channel->id, DMA_SG_CONTROL));
+
+ dev_dbg(&dmadev->pdev->dev, "ch%d: started %d sg req with %d entries\n",
+ channel->id, vd->tx.cookie,
+ desc->entries);
+ }
+}
+
+static void oxnas_dma_sched(unsigned long data)
+{
+ struct oxnas_dma_device *dmadev = (struct oxnas_dma_device *)data;
+ LIST_HEAD(head);
+
+ spin_lock_irq(&dmadev->lock);
+ list_splice_tail_init(&dmadev->pending, &head);
+ spin_unlock_irq(&dmadev->lock);
+
+ while (!list_empty(&head)) {
+ struct oxnas_dma_channel *ch = list_first_entry(&head,
+ struct oxnas_dma_channel, node);
+
+ spin_lock_irq(&ch->vc.lock);
+
+ list_del_init(&ch->node);
+ oxnas_dma_start_next(ch);
+
+ spin_unlock_irq(&ch->vc.lock);
+ }
+}
+
+static int oxnas_check_address(struct oxnas_dma_device *dmadev, dma_addr_t address)
+{
+ int i;
+
+ for (i = 0 ; i < dmadev->authorized_types_count ; ++i) {
+ if (address >= dmadev->authorized_types[i].start &&
+ address < dmadev->authorized_types[i].end)
+ return dmadev->authorized_types[i].type;
+ }
+
+ return -1;
+}
+
+static struct dma_async_tx_descriptor *oxnas_dma_prep_slave_sg(struct dma_chan *chan,
+ struct scatterlist *sgl,
+ unsigned int sglen,
+ enum dma_transfer_direction dir,
+ unsigned long flags, void *context)
+{
+ struct oxnas_dma_channel *channel = container_of(chan, struct oxnas_dma_channel, vc.chan);
+ struct oxnas_dma_device *dmadev = channel->dmadev;
+ struct oxnas_dma_desc *desc;
+ struct scatterlist *sgent;
+ struct oxnas_dma_sg_entry *entry_mem = NULL, *prev_entry_mem = NULL;
+ struct oxnas_dma_sg_entry *entry_dev = NULL;
+ unsigned int i;
+ int src_memory = OXNAS_DMA_DREQ_MEMORY;
+ int dst_memory = OXNAS_DMA_DREQ_MEMORY;
+
+ if (dir == DMA_DEV_TO_MEM) {
+ src_memory = oxnas_check_address(dmadev, channel->cfg.src_addr);
+ if (src_memory == -1) {
+ dev_err(&dmadev->pdev->dev, "invalid memory address %08x\n",
+ channel->cfg.src_addr);
+ return NULL;
+ }
+
+ if (src_memory == OXNAS_DMA_DREQ_MEMORY) {
+ dev_err(&dmadev->pdev->dev, "In DEV_TO_MEM, src cannot be memory\n");
+ return NULL;
+ }
+ } else if (dir == DMA_MEM_TO_DEV) {
+ dst_memory = oxnas_check_address(dmadev, channel->cfg.dst_addr);
+ if (dst_memory == -1) {
+ dev_err(&dmadev->pdev->dev, "invalid memory address %08x\n",
+ channel->cfg.dst_addr);
+ return NULL;
+ }
+
+ if (dst_memory == OXNAS_DMA_DREQ_MEMORY) {
+ dev_err(&dmadev->pdev->dev, "In MEM_TO_DEV, dst cannot be memory\n");
+ return NULL;
+ }
+ } else {
+ dev_err(&dmadev->pdev->dev, "invalid direction\n");
+ return NULL;
+ }
+
+ if (atomic_read(&dmadev->free_entries_count) < (sglen + 1)) {
+ dev_err(&dmadev->pdev->dev, "Missing sg entries...\n");
+ return NULL;
+ }
+
+ desc = kzalloc(sizeof(*desc), GFP_KERNEL);
+ if (unlikely(!desc))
+ return NULL;
+ desc->channel = channel;
+
+ INIT_LIST_HEAD(&desc->sg_entries);
+ desc->entries = 0;
+
+ /* Device single entry */
+ entry_dev = list_first_entry_or_null(&dmadev->free_entries,
+ struct oxnas_dma_sg_entry, entry);
+ if (!entry_dev) {
+ dev_err(&dmadev->pdev->dev, "Fatal error: Missing dev sg entry...\n");
+ goto entries_cleanup;
+ }
+
+ atomic_dec(&dmadev->free_entries_count);
+ list_move(&entry_dev->entry, &desc->sg_entries);
+ ++desc->entries;
+ dev_dbg(&dmadev->pdev->dev, "got entry %p (%08x)\n", entry_dev, entry_dev->this_paddr);
+
+ entry_dev->next_entry = NULL;
+ entry_dev->p_next_entry = 0;
+ entry_dev->data_length = 0; /* Completed by mem sg entries */
+
+ if (dir == DMA_DEV_TO_MEM) {
+ entry_dev->data_addr = channel->cfg.src_addr & OXNAS_DMA_ADR_MASK;
+ desc->sg_info.src_entries = entry_dev;
+ desc->sg_info.p_src_entries = entry_dev->this_paddr;
+
+ dev_dbg(&dmadev->pdev->dev, "src set %p\n", entry_dev);
+ } else if (dir == DMA_MEM_TO_DEV) {
+ entry_dev->data_addr = channel->cfg.dst_addr & OXNAS_DMA_ADR_MASK;
+ desc->sg_info.dst_entries = entry_dev;
+ desc->sg_info.p_dst_entries = entry_dev->this_paddr;
+
+ dev_dbg(&dmadev->pdev->dev, "dst set %p\n", entry_dev);
+ }
+
+ dev_dbg(&dmadev->pdev->dev, "src = %p (%08x) dst = %p (%08x)\n",
+ desc->sg_info.src_entries, desc->sg_info.p_src_entries,
+ desc->sg_info.dst_entries, desc->sg_info.p_dst_entries);
+
+ /* Memory entries */
+ for_each_sg(sgl, sgent, sglen, i) {
+ entry_mem = list_first_entry_or_null(&dmadev->free_entries,
+ struct oxnas_dma_sg_entry, entry);
+ if (!entry_mem) {
+ dev_err(&dmadev->pdev->dev, "Fatal error: Missing mem sg entries...\n");
+ goto entries_cleanup;
+ }
+
+ atomic_dec(&dmadev->free_entries_count);
+ list_move(&entry_mem->entry, &desc->sg_entries);
+ ++desc->entries;
+ dev_dbg(&dmadev->pdev->dev, "got entry %p (%08x)\n",
+ entry_mem, entry_mem->this_paddr);
+
+ /* Fill the linked list */
+ if (prev_entry_mem) {
+ prev_entry_mem->next_entry = entry_mem;
+ prev_entry_mem->p_next_entry = entry_mem->this_paddr;
+ } else {
+ if (dir == DMA_DEV_TO_MEM) {
+ desc->sg_info.dst_entries = entry_mem;
+ desc->sg_info.p_dst_entries = entry_mem->this_paddr;
+ dev_dbg(&dmadev->pdev->dev, "src set %p\n", entry_mem);
+ } else if (dir == DMA_MEM_TO_DEV) {
+ desc->sg_info.src_entries = entry_mem;
+ desc->sg_info.p_src_entries = entry_mem->this_paddr;
+ dev_dbg(&dmadev->pdev->dev, "dst set %p\n", entry_mem);
+ }
+
+ dev_dbg(&dmadev->pdev->dev, "src = %p (%08x) dst = %p (%08x)\n",
+ desc->sg_info.src_entries, desc->sg_info.p_src_entries,
+ desc->sg_info.dst_entries, desc->sg_info.p_dst_entries);
+ }
+ prev_entry_mem = entry_mem;
+
+ /* Fill the entry from the SG */
+ entry_mem->next_entry = NULL;
+ entry_mem->p_next_entry = 0;
+
+ entry_mem->data_addr = sg_dma_address(sgent) & OXNAS_DMA_ADR_MASK;
+ entry_mem->data_length = sg_dma_len(sgent);
+ dev_dbg(&dmadev->pdev->dev, "sg = %08x len = %d\n",
+ sg_dma_address(sgent), sg_dma_len(sgent));
+
+ /* Add to dev sg length */
+ entry_dev->data_length += sg_dma_len(sgent);
+ }
+ dev_dbg(&dmadev->pdev->dev, "allocated %d sg entries\n", desc->entries);
+
+ desc->sg_info.qualifier = FIELD_PREP(OXNAS_DMA_SG_CHANNEL, channel->id) |
+ OXNAS_DMA_SG_QUALIFIER;
+
+ if (dir == DMA_DEV_TO_MEM)
+ desc->sg_info.qualifier |= FIELD_PREP(OXNAS_DMA_SG_SRC_EOT, 2);
+ else if (dir == DMA_MEM_TO_DEV)
+ desc->sg_info.qualifier |= FIELD_PREP(OXNAS_DMA_SG_DST_EOT, 2);
+
+ desc->sg_info.control = (DMA_CTRL_STATUS_INTERRUPT_ENABLE |
+ DMA_CTRL_STATUS_FAIR_SHARE_ARB |
+ DMA_CTRL_STATUS_INTR_CLEAR_ENABLE);
+ desc->sg_info.control |= FIELD_PREP(DMA_CTRL_STATUS_SRC_DREQ, src_memory);
+ desc->sg_info.control |= FIELD_PREP(DMA_CTRL_STATUS_DEST_DREQ, dst_memory);
+
+ if (dir == DMA_DEV_TO_MEM) {
+ desc->sg_info.control |= DMA_CTRL_STATUS_SRC_ADR_MODE;
+ desc->sg_info.control &= ~DMA_CTRL_STATUS_SOURCE_ADDRESS_FIXED;
+ desc->sg_info.control |= DMA_CTRL_STATUS_DEST_ADR_MODE;
+ desc->sg_info.control &= ~DMA_CTRL_STATUS_DESTINATION_ADDRESS_FIXED;
+ } else if (dir == DMA_MEM_TO_DEV) {
+ desc->sg_info.control |= DMA_CTRL_STATUS_SRC_ADR_MODE;
+ desc->sg_info.control &= ~DMA_CTRL_STATUS_SOURCE_ADDRESS_FIXED;
+ desc->sg_info.control |= DMA_CTRL_STATUS_DEST_ADR_MODE;
+ desc->sg_info.control &= ~DMA_CTRL_STATUS_DESTINATION_ADDRESS_FIXED;
+ }
+ desc->sg_info.control |= DMA_CTRL_STATUS_TRANSFER_MODE_A;
+ desc->sg_info.control |= DMA_CTRL_STATUS_TRANSFER_MODE_B;
+ desc->sg_info.control |= FIELD_PREP(DMA_CTRL_STATUS_DIR, OXNAS_DMA_A_TO_B);
+
+ desc->sg_info.control |= FIELD_PREP(DMA_CTRL_STATUS_SRC_WIDTH,
+ OXNAS_DMA_TRANSFER_WIDTH_32BITS);
+ desc->sg_info.control |= FIELD_PREP(DMA_CTRL_STATUS_DEST_WIDTH,
+ OXNAS_DMA_TRANSFER_WIDTH_32BITS);
+ desc->sg_info.control &= ~DMA_CTRL_STATUS_STARVE_LOW_PRIORITY;
+
+ desc->type = OXNAS_DMA_TYPE_SG;
+
+ return vchan_tx_prep(&channel->vc, &desc->vd, flags);
+
+entries_cleanup:
+ /* Put back all entries in the free entries... */
+ list_splice_tail_init(&desc->sg_entries, &dmadev->free_entries);
+ atomic_add(desc->entries, &dmadev->free_entries_count);
+ dev_dbg(&dmadev->pdev->dev, "freed %d sg entries\n", desc->entries);
+
+ kfree(desc);
+
+ return NULL;
+}
+
+/** Allocate descriptors capable of mapping the requested length of memory */
+static struct dma_async_tx_descriptor *oxnas_dma_prep_dma_memcpy(struct dma_chan *chan,
+ dma_addr_t dst, dma_addr_t src,
+ size_t len, unsigned long flags)
+{
+ struct oxnas_dma_channel *channel = container_of(chan, struct oxnas_dma_channel, vc.chan);
+ struct oxnas_dma_device *dmadev = channel->dmadev;
+ struct oxnas_dma_desc *desc;
+ int src_memory = OXNAS_DMA_DREQ_MEMORY;
+ int dst_memory = OXNAS_DMA_DREQ_MEMORY;
+
+ if (len > OXNAS_DMA_MAX_TRANSFER_LENGTH)
+ return NULL;
+
+ src_memory = oxnas_check_address(dmadev, src);
+ if (src_memory == -1) {
+ dev_err(&dmadev->pdev->dev, "invalid memory address %08x\n", src);
+ return NULL;
+ }
+
+ dst_memory = oxnas_check_address(dmadev, dst);
+ if (dst_memory == -1) {
+ dev_err(&dmadev->pdev->dev, "invalid memory address %08x\n", src);
+ return NULL;
+ }
+
+ desc = kzalloc(sizeof(*desc), GFP_KERNEL);
+ if (unlikely(!desc))
+ return NULL;
+ desc->channel = channel;
+
+ dev_dbg(&dmadev->pdev->dev, "preparing memcpy from %08x to %08x, %lubytes (flags %x)\n",
+ src, dst, (unsigned long)len, (unsigned int)flags);
+
+ /* CTRL STATUS Preparation */
+
+ /* Pause while start */
+ desc->ctrl = DMA_CTRL_STATUS_PAUSE;
+
+ /* Interrupts enabled
+ * High priority
+ * Use new interrupt clearing register
+ */
+ desc->ctrl |= DMA_CTRL_STATUS_INTERRUPT_ENABLE |
+ DMA_CTRL_STATUS_FAIR_SHARE_ARB |
+ DMA_CTRL_STATUS_INTR_CLEAR_ENABLE;
+
+ /* Type Memory */
+ desc->ctrl |= FIELD_PREP(DMA_CTRL_STATUS_SRC_DREQ, src_memory);
+ desc->ctrl |= FIELD_PREP(DMA_CTRL_STATUS_DEST_DREQ, dst_memory);
+
+ /* Setup the transfer direction and burst/single mode for the two DMA busses */
+ desc->ctrl |= DMA_CTRL_STATUS_TRANSFER_MODE_A;
+ desc->ctrl |= DMA_CTRL_STATUS_TRANSFER_MODE_B;
+ desc->ctrl |= FIELD_PREP(DMA_CTRL_STATUS_DIR, OXNAS_DMA_A_TO_B);
+
+ /* Incrementing addresses */
+ desc->ctrl |= DMA_CTRL_STATUS_SRC_ADR_MODE;
+ desc->ctrl &= ~DMA_CTRL_STATUS_SOURCE_ADDRESS_FIXED;
+ desc->ctrl |= DMA_CTRL_STATUS_DEST_ADR_MODE;
+ desc->ctrl &= ~DMA_CTRL_STATUS_DESTINATION_ADDRESS_FIXED;
+
+ /* Set up the width of the transfers on the DMA buses */
+ desc->ctrl |= FIELD_PREP(DMA_CTRL_STATUS_SRC_WIDTH, OXNAS_DMA_TRANSFER_WIDTH_32BITS);
+ desc->ctrl |= FIELD_PREP(DMA_CTRL_STATUS_DEST_WIDTH, OXNAS_DMA_TRANSFER_WIDTH_32BITS);
+
+ /* Setup the priority arbitration scheme */
+ desc->ctrl &= ~DMA_CTRL_STATUS_STARVE_LOW_PRIORITY;
+
+ /* LENGTH and End Of Transfert Preparation */
+ desc->len = len |
+ DMA_BYTE_CNT_INC4_SET_MASK | /* Always enable INC4 transfers */
+ DMA_BYTE_CNT_HPROT_MASK | /* Always enable HPROT assertion */
+ DMA_BYTE_CNT_RD_EOT_MASK; /* EOT at last Read */
+
+ desc->src_adr = src;
+ desc->dst_adr = dst;
+ desc->type = OXNAS_DMA_TYPE_SIMPLE;
+
+ return vchan_tx_prep(&channel->vc, &desc->vd, flags);
+}
+
+static int oxnas_dma_slave_config(struct dma_chan *chan, struct dma_slave_config *cfg)
+{
+ struct oxnas_dma_channel *channel = container_of(chan, struct oxnas_dma_channel, vc.chan);
+
+ memcpy(&channel->cfg, cfg, sizeof(channel->cfg));
+
+ return 0;
+}
+
+static void oxnas_dma_desc_free(struct virt_dma_desc *vd)
+{
+ struct oxnas_dma_desc *desc = container_of(&vd->tx, struct oxnas_dma_desc, vd.tx);
+ struct oxnas_dma_channel *channel = desc->channel;
+ struct oxnas_dma_device *dmadev = channel->dmadev;
+
+ /* Free SG entries */
+ if (desc->type == OXNAS_DMA_TYPE_SG) {
+ list_splice_tail_init(&desc->sg_entries, &dmadev->free_entries);
+ atomic_add(desc->entries, &dmadev->free_entries_count);
+ dev_dbg(&dmadev->pdev->dev, "freed %d sg entries\n", desc->entries);
+ }
+
+ kfree(container_of(vd, struct oxnas_dma_desc, vd));
+}
+
+/** Poll for the DMA channel's active status. There can be multiple transfers
+ * queued with the DMA channel identified by cookies, so should be checking
+ * lists containing all pending transfers and all completed transfers that have
+ * not yet been polled for completion
+ */
+static enum dma_status oxnas_dma_tx_status(struct dma_chan *chan,
+ dma_cookie_t cookie,
+ struct dma_tx_state *txstate)
+{
+ struct oxnas_dma_channel *channel = container_of(chan, struct oxnas_dma_channel, vc.chan);
+ struct virt_dma_desc *vd;
+ enum dma_status ret;
+ unsigned long flags;
+
+ ret = dma_cookie_status(chan, cookie, txstate);
+ if (ret == DMA_COMPLETE || !txstate)
+ return ret;
+
+ spin_lock_irqsave(&channel->vc.lock, flags);
+ vd = vchan_find_desc(&channel->vc, cookie);
+ if (vd) {
+ struct oxnas_dma_desc *desc = container_of(&vd->tx, struct oxnas_dma_desc, vd.tx);
+
+ txstate->residue = desc->len & OXNAS_DMA_MAX_TRANSFER_LENGTH;
+ } else {
+ txstate->residue = 0;
+ }
+ spin_unlock_irqrestore(&channel->vc.lock, flags);
+
+ return ret;
+}
+
+/** To push outstanding transfers to h/w. This should use the list of pending
+ * transfers identified by cookies to select the next transfer and pass this to
+ * the hardware
+ */
+static void oxnas_dma_issue_pending(struct dma_chan *chan)
+{
+ struct oxnas_dma_channel *channel = container_of(chan, struct oxnas_dma_channel, vc.chan);
+ struct oxnas_dma_device *dmadev = channel->dmadev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&channel->vc.lock, flags);
+ if (vchan_issue_pending(&channel->vc) && !channel->cur) {
+ spin_lock(&dmadev->lock);
+
+ if (list_empty(&channel->node))
+ list_add_tail(&channel->node, &dmadev->pending);
+
+ spin_unlock(&dmadev->lock);
+
+ tasklet_schedule(&dmadev->tasklet);
+ }
+
+ spin_unlock_irqrestore(&channel->vc.lock, flags);
+}
+
+static void oxnas_dma_free_chan_resources(struct dma_chan *chan)
+{
+ struct oxnas_dma_channel *channel = container_of(chan, struct oxnas_dma_channel, vc.chan);
+
+ vchan_free_chan_resources(&channel->vc);
+}
+
+static int oxnas_dma_probe(struct platform_device *pdev)
+{
+ struct oxnas_dma_device *dmadev;
+ struct resource *res;
+ int hwid, i, ret;
+
+ dmadev = devm_kzalloc(&pdev->dev, sizeof(struct oxnas_dma_device), GFP_KERNEL);
+ if (!dmadev)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dmadev->dma_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(dmadev->dma_base))
+ return PTR_ERR(dmadev->dma_base);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ dmadev->sgdma_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(dmadev->sgdma_base))
+ return PTR_ERR(dmadev->sgdma_base);
+
+ dmadev->dma_rst = devm_reset_control_get(&pdev->dev, "dma");
+ if (IS_ERR(dmadev->dma_rst))
+ return PTR_ERR(dmadev->dma_rst);
+
+ dmadev->sgdma_rst = devm_reset_control_get(&pdev->dev, "sgdma");
+ if (IS_ERR(dmadev->sgdma_rst))
+ return PTR_ERR(dmadev->sgdma_rst);
+
+ dmadev->dma_clk = devm_clk_get(&pdev->dev, 0);
+ if (IS_ERR(dmadev->dma_clk))
+ return PTR_ERR(dmadev->dma_clk);
+
+ ret = of_property_count_elems_of_size(pdev->dev.of_node, "oxsemi,targets-types", 4);
+ if (ret <= 0 || (ret % 3) != 0) {
+ dev_err(&pdev->dev, "malformed or missing oxsemi,targets-types\n");
+ return -EINVAL;
+ }
+
+ dmadev->authorized_types_count = ret / 3;
+ dmadev->authorized_types = devm_kzalloc(&pdev->dev,
+ sizeof(*dmadev->authorized_types) * dmadev->authorized_types_count, GFP_KERNEL);
+
+ if (!dmadev->authorized_types)
+ return -ENOMEM;
+
+ for (i = 0 ; i < dmadev->authorized_types_count ; ++i) {
+ u32 value;
+
+ ret = of_property_read_u32_index(pdev->dev.of_node,
+ "oxsemi,targets-types",
+ (i * 3), &value);
+ if (ret < 0)
+ return ret;
+
+ dmadev->authorized_types[i].start = value;
+ ret = of_property_read_u32_index(pdev->dev.of_node,
+ "oxsemi,targets-types",
+ (i * 3) + 1, &value);
+ if (ret < 0)
+ return ret;
+
+ dmadev->authorized_types[i].end = value;
+ ret = of_property_read_u32_index(pdev->dev.of_node,
+ "oxsemi,targets-types",
+ (i * 3) + 2, &value);
+ if (ret < 0)
+ return ret;
+
+ dmadev->authorized_types[i].type = value;
+ }
+
+ dev_dbg(&pdev->dev, "Authorized memory ranges :\n");
+ dev_dbg(&pdev->dev, " Start - End = Type\n");
+ for (i = 0 ; i < dmadev->authorized_types_count ; ++i)
+ dev_dbg(&pdev->dev, "0x%08x-0x%08x = %d\n",
+ dmadev->authorized_types[i].start,
+ dmadev->authorized_types[i].end,
+ dmadev->authorized_types[i].type);
+
+ dmadev->pdev = pdev;
+
+ spin_lock_init(&dmadev->lock);
+
+ tasklet_init(&dmadev->tasklet, oxnas_dma_sched, (unsigned long)dmadev);
+ INIT_LIST_HEAD(&dmadev->common.channels);
+ INIT_LIST_HEAD(&dmadev->pending);
+ INIT_LIST_HEAD(&dmadev->free_entries);
+
+ /* Enable HW & Clocks */
+ reset_control_reset(dmadev->dma_rst);
+ reset_control_reset(dmadev->sgdma_rst);
+ clk_prepare_enable(dmadev->dma_clk);
+
+ /* Discover the number of channels available */
+ hwid = readl(dmadev->dma_base + DMA_CALC_REG_ADR(0, DMA_INTR_ID));
+ dmadev->channels_count = DMA_INTR_ID_GET_NUM_CHANNELS(hwid);
+ dmadev->hwversion = DMA_INTR_ID_GET_VERSION(hwid);
+
+ dev_dbg(&pdev->dev, "OXNAS DMA v%x with %d channels\n",
+ dmadev->hwversion, dmadev->channels_count);
+
+ /* Limit channels count */
+ if (dmadev->channels_count > MAX_OXNAS_DMA_CHANNELS)
+ dmadev->channels_count = MAX_OXNAS_DMA_CHANNELS;
+
+ /* Allocate coherent memory for sg descriptors */
+ dmadev->sg_data = dma_alloc_coherent(&pdev->dev, sizeof(struct oxnas_dma_sg_data),
+ &dmadev->p_sg_data, GFP_KERNEL);
+ if (!dmadev->sg_data) {
+ ret = -ENOMEM;
+ goto disable_clks;
+ }
+
+ /* Reset SG descritors */
+ memset(dmadev->sg_data, 0, sizeof(struct oxnas_dma_sg_data));
+ atomic_set(&dmadev->free_entries_count, 0);
+
+ /* Initialize and add all sg entries to the free list */
+ for (i = 0 ; i < MAX_OXNAS_SG_ENTRIES ; ++i) {
+ dmadev->sg_data->entries[i].this_paddr =
+ (dma_addr_t)&(((struct oxnas_dma_sg_data *)dmadev->p_sg_data)->entries[i]);
+ INIT_LIST_HEAD(&dmadev->sg_data->entries[i].entry);
+ list_add_tail(&dmadev->sg_data->entries[i].entry,
+ &dmadev->free_entries);
+ atomic_inc(&dmadev->free_entries_count);
+ }
+
+ /* Init all channels */
+ for (i = 0 ; i < dmadev->channels_count ; ++i) {
+ struct oxnas_dma_channel *ch = &dmadev->channels[i];
+
+ ch->dmadev = dmadev;
+ ch->id = i;
+
+ ch->irq = irq_of_parse_and_map(pdev->dev.of_node, i);
+ if (ch->irq <= 0) {
+ dev_err(&pdev->dev, "invalid irq%d from platform\n", i);
+ goto free_coherent;
+ }
+
+ ret = devm_request_irq(&pdev->dev, ch->irq,
+ oxnas_dma_interrupt, 0,
+ "DMA", ch);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to request irq%d\n", i);
+ goto free_coherent;
+ }
+
+ ch->p_sg_info =
+ (dma_addr_t)&((struct oxnas_dma_sg_data *)dmadev->p_sg_data)->infos[i];
+ ch->sg_info = &dmadev->sg_data->infos[i];
+ memset(ch->sg_info, 0, sizeof(struct oxnas_dma_sg_info));
+
+ atomic_set(&ch->active, 0);
+
+ ch->vc.desc_free = oxnas_dma_desc_free;
+ vchan_init(&ch->vc, &dmadev->common);
+ INIT_LIST_HEAD(&ch->node);
+ }
+
+ platform_set_drvdata(pdev, dmadev);
+
+ dma_cap_set(DMA_MEMCPY, dmadev->common.cap_mask);
+ dmadev->common.chancnt = dmadev->channels_count;
+ dmadev->common.device_free_chan_resources = oxnas_dma_free_chan_resources;
+ dmadev->common.device_tx_status = oxnas_dma_tx_status;
+ dmadev->common.device_issue_pending = oxnas_dma_issue_pending;
+ dmadev->common.device_prep_dma_memcpy = oxnas_dma_prep_dma_memcpy;
+ dmadev->common.device_prep_slave_sg = oxnas_dma_prep_slave_sg;
+ dmadev->common.device_config = oxnas_dma_slave_config;
+ dmadev->common.copy_align = DMAENGINE_ALIGN_4_BYTES;
+ dmadev->common.src_addr_widths = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ dmadev->common.dst_addr_widths = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ dmadev->common.directions = BIT(DMA_MEM_TO_MEM);
+ dmadev->common.residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
+ dmadev->common.dev = &pdev->dev;
+
+ ret = dma_async_device_register(&dmadev->common);
+ if (ret)
+ goto free_coherent;
+
+ ret = of_dma_controller_register(pdev->dev.of_node,
+ of_dma_xlate_by_chan_id,
+ &dmadev->common);
+ if (ret) {
+ dev_warn(&pdev->dev, "Failed to register DMA Controller\n");
+ goto dma_unregister;
+ }
+
+ dev_info(&pdev->dev, "OXNAS DMA Registered\n");
+
+ return 0;
+
+dma_unregister:
+ dma_async_device_unregister(&dmadev->common);
+
+free_coherent:
+ dma_free_coherent(&pdev->dev, sizeof(struct oxnas_dma_sg_data),
+ dmadev->sg_data, dmadev->p_sg_data);
+
+disable_clks:
+ clk_disable_unprepare(dmadev->dma_clk);
+
+ return ret;
+}
+
+static int oxnas_dma_remove(struct platform_device *pdev)
+{
+ struct oxnas_dma_device *dmadev = platform_get_drvdata(pdev);
+
+ dma_async_device_unregister(&dmadev->common);
+
+ dma_free_coherent(&pdev->dev, sizeof(struct oxnas_dma_sg_data),
+ dmadev->sg_data, dmadev->p_sg_data);
+
+ clk_disable_unprepare(dmadev->dma_clk);
+
+ return 0;
+}
+
+static const struct of_device_id oxnas_dma_of_dev_id[] = {
+ { .compatible = "oxsemi,ox810se-dma", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, oxnas_dma_of_dev_id);
+
+static struct platform_driver oxnas_dma_driver = {
+ .probe = oxnas_dma_probe,
+ .remove = oxnas_dma_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "oxnas-dma",
+ .of_match_table = oxnas_dma_of_dev_id,
+ },
+};
+
+static int __init oxnas_dma_init_module(void)
+{
+ return platform_driver_register(&oxnas_dma_driver);
+}
+subsys_initcall(oxnas_dma_init_module);
+
+static void __exit oxnas_dma_exit_module(void)
+{
+ platform_driver_unregister(&oxnas_dma_driver);
+}
+module_exit(oxnas_dma_exit_module);
+
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Neil Armstrong <[email protected]>");
--
2.25.1


2022-01-04 14:52:26

by Neil Armstrong

[permalink] [raw]
Subject: [PATCH 3/4] MAINTAINERS: add OX810SE DMA driver files under Oxnas entry

Add the DMA driver file and bindings in the Oxnas maintainers entry.

Signed-off-by: Neil Armstrong <[email protected]>
---
MAINTAINERS | 2 ++
1 file changed, 2 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 7a2345ce8521..782332ab0d9b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2371,8 +2371,10 @@ M: Neil Armstrong <[email protected]>
L: [email protected] (moderated for non-subscribers)
L: [email protected] (moderated for non-subscribers)
S: Maintained
+F: Documentation/devicetree/bindings/dma/oxsemi,ox810se-dma.yaml
F: arch/arm/boot/dts/ox8*.dts*
F: arch/arm/mach-oxnas/
+F: drivers/dma/oxnas_adma.c
F: drivers/power/reset/oxnas-restart.c
N: oxnas

--
2.25.1


2022-01-04 14:52:29

by Neil Armstrong

[permalink] [raw]
Subject: [PATCH 4/4] ARM: dts: ox810se: Add DMA Support

This adds the DMA engine node.

Signed-off-by: Neil Armstrong <[email protected]>
---
arch/arm/boot/dts/ox810se-wd-mbwe.dts | 4 ++++
arch/arm/boot/dts/ox810se.dtsi | 21 +++++++++++++++++++++
2 files changed, 25 insertions(+)

diff --git a/arch/arm/boot/dts/ox810se-wd-mbwe.dts b/arch/arm/boot/dts/ox810se-wd-mbwe.dts
index 7e2fcb220aea..19e5d510e425 100644
--- a/arch/arm/boot/dts/ox810se-wd-mbwe.dts
+++ b/arch/arm/boot/dts/ox810se-wd-mbwe.dts
@@ -103,6 +103,10 @@ rtc0: rtc@48 {
};
};

+&dma {
+ status = "okay";
+};
+
&uart1 {
status = "okay";

diff --git a/arch/arm/boot/dts/ox810se.dtsi b/arch/arm/boot/dts/ox810se.dtsi
index 0755e5864c4a..79b2b49dcfbb 100644
--- a/arch/arm/boot/dts/ox810se.dtsi
+++ b/arch/arm/boot/dts/ox810se.dtsi
@@ -334,6 +334,27 @@ timer0: timer@200 {
interrupts = <4 5>;
};
};
+
+ dma: dma-controller@600000 {
+ compatible = "oxsemi,ox810se-dma";
+ reg = <0x600000 0x100000>,
+ <0xc00000 0x100000>;
+ reg-names = "dma", "sgdma";
+ interrupts = <13>, <14>, <15>, <16>, <20>;
+ clocks = <&stdclk 1>;
+ resets = <&reset 8>, <&reset 24>;
+ reset-names = "dma", "sgdma";
+
+ /* Encodes the authorized memory types */
+ oxsemi,targets-types =
+ <0x45900000 0x45a00000 0>, /* SATA */
+ <0x42000000 0x43000000 0>, /* SATA DATA */
+ <0x48000000 0x58000000 15>, /* DDR */
+ <0x58000000 0x58020000 15>; /* SRAM */
+
+ #dma-cells = <1>;
+ dma-channels = <5>;
+ };
};
};
};
--
2.25.1


2022-01-12 01:20:54

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH 1/4] dt-bindings: dma: Add bindings for ox810se dma engine

On Tue, Jan 04, 2022 at 03:52:03PM +0100, Neil Armstrong wrote:
> This adds the YAML dt-bindings for the DMA engine found in the
> Oxford Semiconductor OX810SE SoC.
>
> Signed-off-by: Neil Armstrong <[email protected]>
> ---
> .../bindings/dma/oxsemi,ox810se-dma.yaml | 97 +++++++++++++++++++
> 1 file changed, 97 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/dma/oxsemi,ox810se-dma.yaml
>
> diff --git a/Documentation/devicetree/bindings/dma/oxsemi,ox810se-dma.yaml b/Documentation/devicetree/bindings/dma/oxsemi,ox810se-dma.yaml
> new file mode 100644
> index 000000000000..6efa28e8b124
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/dma/oxsemi,ox810se-dma.yaml
> @@ -0,0 +1,97 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/dma/oxsemi,ox810se-dma.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Oxford Semiconductor DMA Controller Device Tree Bindings
> +
> +maintainers:
> + - Neil Armstrong <[email protected]>
> +
> +allOf:
> + - $ref: "dma-controller.yaml#"
> +
> +properties:
> + "#dma-cells":
> + const: 1
> +
> + compatible:
> + const: oxsemi,ox810se-dma
> +
> + reg:
> + maxItems: 2
> +
> + reg-names:
> + items:
> + - const: dma
> + - const: sgdma
> +
> + interrupts:
> + maxItems: 5

Need to define what each one is.

> +
> + clocks:
> + maxItems: 1
> +
> + resets:
> + maxItems: 2
> +
> + reset-names:
> + items:
> + - const: dma
> + - const: sgdma
> +
> + dma-channels: true

Constraints on number of channels?

> +
> + oxsemi,targets-types:
> + description:
> + Table with allowed memory ranges and memory type associated.
> + $ref: "/schemas/types.yaml#/definitions/uint32-matrix"
> + minItems: 4
> + items:
> + items:
> + - description:
> + The first cell defines the memory range start address
> + - description:
> + The first cell defines the memory range end address
> + - description:
> + The third cell represents memory type, 0 for SATA,
> + 1 for DPE RX, 2 for DPE TX, 5 for AUDIO TX, 6 for AUDIO RX,
> + 15 for DRAM MEMORY.
> + enum: [ 0, 1, 2, 5, 6, 15 ]
> +
> +required:
> + - "#dma-cells"
> + - dma-channels
> + - compatible
> + - reg
> + - interrupts
> + - clocks
> + - resets
> + - reset-names
> + - oxsemi,targets-types
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + dma: dma-controller@600000 {

Drop unused labels.

> + compatible = "oxsemi,ox810se-dma";
> + reg = <0x600000 0x100000>, <0xc00000 0x100000>;
> + reg-names = "dma", "sgdma";
> + interrupts = <13>, <14>, <15>, <16>, <20>;
> + clocks = <&stdclk 1>;
> + resets = <&reset 8>, <&reset 24>;
> + reset-names = "dma", "sgdma";
> +
> + /* Encodes the authorized memory types */
> + oxsemi,targets-types =
> + <0x45900000 0x45a00000 0>, /* SATA */
> + <0x42000000 0x43000000 0>, /* SATA DATA */
> + <0x48000000 0x58000000 15>, /* DDR */
> + <0x58000000 0x58020000 15>; /* SRAM */
> +
> + #dma-cells = <1>;
> + dma-channels = <5>;
> + };
> +...
> --
> 2.25.1
>
>