Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
linux
Commits
69ca36b3
Commit
69ca36b3
authored
Dec 31, 2018
by
Vinod Koul
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'topic/uniphier' into for-linus
parents
990beed9
32e74aab
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
544 additions
and
0 deletions
+544
-0
Documentation/devicetree/bindings/dma/uniphier-mio-dmac.txt
Documentation/devicetree/bindings/dma/uniphier-mio-dmac.txt
+25
-0
MAINTAINERS
MAINTAINERS
+1
-0
drivers/dma/Kconfig
drivers/dma/Kconfig
+11
-0
drivers/dma/Makefile
drivers/dma/Makefile
+1
-0
drivers/dma/uniphier-mdmac.c
drivers/dma/uniphier-mdmac.c
+506
-0
No files found.
Documentation/devicetree/bindings/dma/uniphier-mio-dmac.txt
0 → 100644
View file @
69ca36b3
UniPhier Media IO DMA controller
This works as an external DMA engine for SD/eMMC controllers etc.
found in UniPhier LD4, Pro4, sLD8 SoCs.
Required properties:
- compatible: should be "socionext,uniphier-mio-dmac".
- reg: offset and length of the register set for the device.
- interrupts: a list of interrupt specifiers associated with the DMA channels.
- clocks: a single clock specifier.
- #dma-cells: should be <1>. The single cell represents the channel index.
Example:
dmac: dma-controller@5a000000 {
compatible = "socionext,uniphier-mio-dmac";
reg = <0x5a000000 0x1000>;
interrupts = <0 68 4>, <0 68 4>, <0 69 4>, <0 70 4>,
<0 71 4>, <0 72 4>, <0 73 4>, <0 74 4>;
clocks = <&mio_clk 7>;
#dma-cells = <1>;
};
Note:
In the example above, "interrupts = <0 68 4>, <0 68 4>, ..." is not a typo.
The first two channels share a single interrupt line.
MAINTAINERS
View file @
69ca36b3
...
...
@@ -2256,6 +2256,7 @@ F: arch/arm/mm/cache-uniphier.c
F: arch/arm64/boot/dts/socionext/uniphier*
F: drivers/bus/uniphier-system-bus.c
F: drivers/clk/uniphier/
F: drivers/dmaengine/uniphier-mdmac.c
F: drivers/gpio/gpio-uniphier.c
F: drivers/i2c/busses/i2c-uniphier*
F: drivers/irqchip/irq-uniphier-aidet.c
...
...
drivers/dma/Kconfig
View file @
69ca36b3
...
...
@@ -587,6 +587,17 @@ config TIMB_DMA
help
Enable support for the Timberdale FPGA DMA engine.
config UNIPHIER_MDMAC
tristate "UniPhier MIO DMAC"
depends on ARCH_UNIPHIER || COMPILE_TEST
depends on OF
select DMA_ENGINE
select DMA_VIRTUAL_CHANNELS
help
Enable support for the MIO DMAC (Media I/O DMA controller) on the
UniPhier platform. This DMA controller is used as the external
DMA engine of the SD/eMMC controllers of the LD4, Pro4, sLD8 SoCs.
config XGENE_DMA
tristate "APM X-Gene DMA support"
depends on ARCH_XGENE || COMPILE_TEST
...
...
drivers/dma/Makefile
View file @
69ca36b3
...
...
@@ -70,6 +70,7 @@ obj-$(CONFIG_TXX9_DMAC) += txx9dmac.o
obj-$(CONFIG_TEGRA20_APB_DMA)
+=
tegra20-apb-dma.o
obj-$(CONFIG_TEGRA210_ADMA)
+=
tegra210-adma.o
obj-$(CONFIG_TIMB_DMA)
+=
timb_dma.o
obj-$(CONFIG_UNIPHIER_MDMAC)
+=
uniphier-mdmac.o
obj-$(CONFIG_XGENE_DMA)
+=
xgene-dma.o
obj-$(CONFIG_ZX_DMA)
+=
zx_dma.o
obj-$(CONFIG_ST_FDMA)
+=
st_fdma.o
...
...
drivers/dma/uniphier-mdmac.c
0 → 100644
View file @
69ca36b3
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (C) 2018 Socionext Inc.
// Author: Masahiro Yamada <yamada.masahiro@socionext.com>
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_dma.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/types.h>
#include "virt-dma.h"
/* registers common for all channels */
#define UNIPHIER_MDMAC_CMD 0x000
/* issue DMA start/abort */
#define UNIPHIER_MDMAC_CMD_ABORT BIT(31)
/* 1: abort, 0: start */
/* per-channel registers */
#define UNIPHIER_MDMAC_CH_OFFSET 0x100
#define UNIPHIER_MDMAC_CH_STRIDE 0x040
#define UNIPHIER_MDMAC_CH_IRQ_STAT 0x010
/* current hw status (RO) */
#define UNIPHIER_MDMAC_CH_IRQ_REQ 0x014
/* latched STAT (WOC) */
#define UNIPHIER_MDMAC_CH_IRQ_EN 0x018
/* IRQ enable mask */
#define UNIPHIER_MDMAC_CH_IRQ_DET 0x01c
/* REQ & EN (RO) */
#define UNIPHIER_MDMAC_CH_IRQ__ABORT BIT(13)
#define UNIPHIER_MDMAC_CH_IRQ__DONE BIT(1)
#define UNIPHIER_MDMAC_CH_SRC_MODE 0x020
/* mode of source */
#define UNIPHIER_MDMAC_CH_DEST_MODE 0x024
/* mode of destination */
#define UNIPHIER_MDMAC_CH_MODE__ADDR_INC (0 << 4)
#define UNIPHIER_MDMAC_CH_MODE__ADDR_DEC (1 << 4)
#define UNIPHIER_MDMAC_CH_MODE__ADDR_FIXED (2 << 4)
#define UNIPHIER_MDMAC_CH_SRC_ADDR 0x028
/* source address */
#define UNIPHIER_MDMAC_CH_DEST_ADDR 0x02c
/* destination address */
#define UNIPHIER_MDMAC_CH_SIZE 0x030
/* transfer bytes */
#define UNIPHIER_MDMAC_SLAVE_BUSWIDTHS \
(BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \
BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \
BIT(DMA_SLAVE_BUSWIDTH_3_BYTES) | \
BIT(DMA_SLAVE_BUSWIDTH_4_BYTES))
struct
uniphier_mdmac_desc
{
struct
virt_dma_desc
vd
;
struct
scatterlist
*
sgl
;
unsigned
int
sg_len
;
unsigned
int
sg_cur
;
enum
dma_transfer_direction
dir
;
};
struct
uniphier_mdmac_chan
{
struct
virt_dma_chan
vc
;
struct
uniphier_mdmac_device
*
mdev
;
struct
uniphier_mdmac_desc
*
md
;
void
__iomem
*
reg_ch_base
;
unsigned
int
chan_id
;
};
struct
uniphier_mdmac_device
{
struct
dma_device
ddev
;
struct
clk
*
clk
;
void
__iomem
*
reg_base
;
struct
uniphier_mdmac_chan
channels
[
0
];
};
static
struct
uniphier_mdmac_chan
*
to_uniphier_mdmac_chan
(
struct
virt_dma_chan
*
vc
)
{
return
container_of
(
vc
,
struct
uniphier_mdmac_chan
,
vc
);
}
static
struct
uniphier_mdmac_desc
*
to_uniphier_mdmac_desc
(
struct
virt_dma_desc
*
vd
)
{
return
container_of
(
vd
,
struct
uniphier_mdmac_desc
,
vd
);
}
/* mc->vc.lock must be held by caller */
static
struct
uniphier_mdmac_desc
*
uniphier_mdmac_next_desc
(
struct
uniphier_mdmac_chan
*
mc
)
{
struct
virt_dma_desc
*
vd
;
vd
=
vchan_next_desc
(
&
mc
->
vc
);
if
(
!
vd
)
{
mc
->
md
=
NULL
;
return
NULL
;
}
list_del
(
&
vd
->
node
);
mc
->
md
=
to_uniphier_mdmac_desc
(
vd
);
return
mc
->
md
;
}
/* mc->vc.lock must be held by caller */
static
void
uniphier_mdmac_handle
(
struct
uniphier_mdmac_chan
*
mc
,
struct
uniphier_mdmac_desc
*
md
)
{
struct
uniphier_mdmac_device
*
mdev
=
mc
->
mdev
;
struct
scatterlist
*
sg
;
u32
irq_flag
=
UNIPHIER_MDMAC_CH_IRQ__DONE
;
u32
src_mode
,
src_addr
,
dest_mode
,
dest_addr
,
chunk_size
;
sg
=
&
md
->
sgl
[
md
->
sg_cur
];
if
(
md
->
dir
==
DMA_MEM_TO_DEV
)
{
src_mode
=
UNIPHIER_MDMAC_CH_MODE__ADDR_INC
;
src_addr
=
sg_dma_address
(
sg
);
dest_mode
=
UNIPHIER_MDMAC_CH_MODE__ADDR_FIXED
;
dest_addr
=
0
;
}
else
{
src_mode
=
UNIPHIER_MDMAC_CH_MODE__ADDR_FIXED
;
src_addr
=
0
;
dest_mode
=
UNIPHIER_MDMAC_CH_MODE__ADDR_INC
;
dest_addr
=
sg_dma_address
(
sg
);
}
chunk_size
=
sg_dma_len
(
sg
);
writel
(
src_mode
,
mc
->
reg_ch_base
+
UNIPHIER_MDMAC_CH_SRC_MODE
);
writel
(
dest_mode
,
mc
->
reg_ch_base
+
UNIPHIER_MDMAC_CH_DEST_MODE
);
writel
(
src_addr
,
mc
->
reg_ch_base
+
UNIPHIER_MDMAC_CH_SRC_ADDR
);
writel
(
dest_addr
,
mc
->
reg_ch_base
+
UNIPHIER_MDMAC_CH_DEST_ADDR
);
writel
(
chunk_size
,
mc
->
reg_ch_base
+
UNIPHIER_MDMAC_CH_SIZE
);
/* write 1 to clear */
writel
(
irq_flag
,
mc
->
reg_ch_base
+
UNIPHIER_MDMAC_CH_IRQ_REQ
);
writel
(
irq_flag
,
mc
->
reg_ch_base
+
UNIPHIER_MDMAC_CH_IRQ_EN
);
writel
(
BIT
(
mc
->
chan_id
),
mdev
->
reg_base
+
UNIPHIER_MDMAC_CMD
);
}
/* mc->vc.lock must be held by caller */
static
void
uniphier_mdmac_start
(
struct
uniphier_mdmac_chan
*
mc
)
{
struct
uniphier_mdmac_desc
*
md
;
md
=
uniphier_mdmac_next_desc
(
mc
);
if
(
md
)
uniphier_mdmac_handle
(
mc
,
md
);
}
/* mc->vc.lock must be held by caller */
static
int
uniphier_mdmac_abort
(
struct
uniphier_mdmac_chan
*
mc
)
{
struct
uniphier_mdmac_device
*
mdev
=
mc
->
mdev
;
u32
irq_flag
=
UNIPHIER_MDMAC_CH_IRQ__ABORT
;
u32
val
;
/* write 1 to clear */
writel
(
irq_flag
,
mc
->
reg_ch_base
+
UNIPHIER_MDMAC_CH_IRQ_REQ
);
writel
(
UNIPHIER_MDMAC_CMD_ABORT
|
BIT
(
mc
->
chan_id
),
mdev
->
reg_base
+
UNIPHIER_MDMAC_CMD
);
/*
* Abort should be accepted soon. We poll the bit here instead of
* waiting for the interrupt.
*/
return
readl_poll_timeout
(
mc
->
reg_ch_base
+
UNIPHIER_MDMAC_CH_IRQ_REQ
,
val
,
val
&
irq_flag
,
0
,
20
);
}
static
irqreturn_t
uniphier_mdmac_interrupt
(
int
irq
,
void
*
dev_id
)
{
struct
uniphier_mdmac_chan
*
mc
=
dev_id
;
struct
uniphier_mdmac_desc
*
md
;
irqreturn_t
ret
=
IRQ_HANDLED
;
u32
irq_stat
;
spin_lock
(
&
mc
->
vc
.
lock
);
irq_stat
=
readl
(
mc
->
reg_ch_base
+
UNIPHIER_MDMAC_CH_IRQ_DET
);
/*
* Some channels share a single interrupt line. If the IRQ status is 0,
* this is probably triggered by a different channel.
*/
if
(
!
irq_stat
)
{
ret
=
IRQ_NONE
;
goto
out
;
}
/* write 1 to clear */
writel
(
irq_stat
,
mc
->
reg_ch_base
+
UNIPHIER_MDMAC_CH_IRQ_REQ
);
/*
* UNIPHIER_MDMAC_CH_IRQ__DONE interrupt is asserted even when the DMA
* is aborted. To distinguish the normal completion and the abort,
* check mc->md. If it is NULL, we are aborting.
*/
md
=
mc
->
md
;
if
(
!
md
)
goto
out
;
md
->
sg_cur
++
;
if
(
md
->
sg_cur
>=
md
->
sg_len
)
{
vchan_cookie_complete
(
&
md
->
vd
);
md
=
uniphier_mdmac_next_desc
(
mc
);
if
(
!
md
)
goto
out
;
}
uniphier_mdmac_handle
(
mc
,
md
);
out:
spin_unlock
(
&
mc
->
vc
.
lock
);
return
ret
;
}
static
void
uniphier_mdmac_free_chan_resources
(
struct
dma_chan
*
chan
)
{
vchan_free_chan_resources
(
to_virt_chan
(
chan
));
}
static
struct
dma_async_tx_descriptor
*
uniphier_mdmac_prep_slave_sg
(
struct
dma_chan
*
chan
,
struct
scatterlist
*
sgl
,
unsigned
int
sg_len
,
enum
dma_transfer_direction
direction
,
unsigned
long
flags
,
void
*
context
)
{
struct
virt_dma_chan
*
vc
=
to_virt_chan
(
chan
);
struct
uniphier_mdmac_desc
*
md
;
if
(
!
is_slave_direction
(
direction
))
return
NULL
;
md
=
kzalloc
(
sizeof
(
*
md
),
GFP_NOWAIT
);
if
(
!
md
)
return
NULL
;
md
->
sgl
=
sgl
;
md
->
sg_len
=
sg_len
;
md
->
dir
=
direction
;
return
vchan_tx_prep
(
vc
,
&
md
->
vd
,
flags
);
}
static
int
uniphier_mdmac_terminate_all
(
struct
dma_chan
*
chan
)
{
struct
virt_dma_chan
*
vc
=
to_virt_chan
(
chan
);
struct
uniphier_mdmac_chan
*
mc
=
to_uniphier_mdmac_chan
(
vc
);
unsigned
long
flags
;
int
ret
=
0
;
LIST_HEAD
(
head
);
spin_lock_irqsave
(
&
vc
->
lock
,
flags
);
if
(
mc
->
md
)
{
vchan_terminate_vdesc
(
&
mc
->
md
->
vd
);
mc
->
md
=
NULL
;
ret
=
uniphier_mdmac_abort
(
mc
);
}
vchan_get_all_descriptors
(
vc
,
&
head
);
spin_unlock_irqrestore
(
&
vc
->
lock
,
flags
);
vchan_dma_desc_free_list
(
vc
,
&
head
);
return
ret
;
}
static
void
uniphier_mdmac_synchronize
(
struct
dma_chan
*
chan
)
{
vchan_synchronize
(
to_virt_chan
(
chan
));
}
static
enum
dma_status
uniphier_mdmac_tx_status
(
struct
dma_chan
*
chan
,
dma_cookie_t
cookie
,
struct
dma_tx_state
*
txstate
)
{
struct
virt_dma_chan
*
vc
;
struct
virt_dma_desc
*
vd
;
struct
uniphier_mdmac_chan
*
mc
;
struct
uniphier_mdmac_desc
*
md
=
NULL
;
enum
dma_status
stat
;
unsigned
long
flags
;
int
i
;
stat
=
dma_cookie_status
(
chan
,
cookie
,
txstate
);
/* Return immediately if we do not need to compute the residue. */
if
(
stat
==
DMA_COMPLETE
||
!
txstate
)
return
stat
;
vc
=
to_virt_chan
(
chan
);
spin_lock_irqsave
(
&
vc
->
lock
,
flags
);
mc
=
to_uniphier_mdmac_chan
(
vc
);
if
(
mc
->
md
&&
mc
->
md
->
vd
.
tx
.
cookie
==
cookie
)
{
/* residue from the on-flight chunk */
txstate
->
residue
=
readl
(
mc
->
reg_ch_base
+
UNIPHIER_MDMAC_CH_SIZE
);
md
=
mc
->
md
;
}
if
(
!
md
)
{
vd
=
vchan_find_desc
(
vc
,
cookie
);
if
(
vd
)
md
=
to_uniphier_mdmac_desc
(
vd
);
}
if
(
md
)
{
/* residue from the queued chunks */
for
(
i
=
md
->
sg_cur
;
i
<
md
->
sg_len
;
i
++
)
txstate
->
residue
+=
sg_dma_len
(
&
md
->
sgl
[
i
]);
}
spin_unlock_irqrestore
(
&
vc
->
lock
,
flags
);
return
stat
;
}
static
void
uniphier_mdmac_issue_pending
(
struct
dma_chan
*
chan
)
{
struct
virt_dma_chan
*
vc
=
to_virt_chan
(
chan
);
struct
uniphier_mdmac_chan
*
mc
=
to_uniphier_mdmac_chan
(
vc
);
unsigned
long
flags
;
spin_lock_irqsave
(
&
vc
->
lock
,
flags
);
if
(
vchan_issue_pending
(
vc
)
&&
!
mc
->
md
)
uniphier_mdmac_start
(
mc
);
spin_unlock_irqrestore
(
&
vc
->
lock
,
flags
);
}
static
void
uniphier_mdmac_desc_free
(
struct
virt_dma_desc
*
vd
)
{
kfree
(
to_uniphier_mdmac_desc
(
vd
));
}
static
int
uniphier_mdmac_chan_init
(
struct
platform_device
*
pdev
,
struct
uniphier_mdmac_device
*
mdev
,
int
chan_id
)
{
struct
device
*
dev
=
&
pdev
->
dev
;
struct
uniphier_mdmac_chan
*
mc
=
&
mdev
->
channels
[
chan_id
];
char
*
irq_name
;
int
irq
,
ret
;
irq
=
platform_get_irq
(
pdev
,
chan_id
);
if
(
irq
<
0
)
{
dev_err
(
&
pdev
->
dev
,
"failed to get IRQ number for ch%d
\n
"
,
chan_id
);
return
irq
;
}
irq_name
=
devm_kasprintf
(
dev
,
GFP_KERNEL
,
"uniphier-mio-dmac-ch%d"
,
chan_id
);
if
(
!
irq_name
)
return
-
ENOMEM
;
ret
=
devm_request_irq
(
dev
,
irq
,
uniphier_mdmac_interrupt
,
IRQF_SHARED
,
irq_name
,
mc
);
if
(
ret
)
return
ret
;
mc
->
mdev
=
mdev
;
mc
->
reg_ch_base
=
mdev
->
reg_base
+
UNIPHIER_MDMAC_CH_OFFSET
+
UNIPHIER_MDMAC_CH_STRIDE
*
chan_id
;
mc
->
chan_id
=
chan_id
;
mc
->
vc
.
desc_free
=
uniphier_mdmac_desc_free
;
vchan_init
(
&
mc
->
vc
,
&
mdev
->
ddev
);
return
0
;
}
static
int
uniphier_mdmac_probe
(
struct
platform_device
*
pdev
)
{
struct
device
*
dev
=
&
pdev
->
dev
;
struct
uniphier_mdmac_device
*
mdev
;
struct
dma_device
*
ddev
;
struct
resource
*
res
;
int
nr_chans
,
ret
,
i
;
nr_chans
=
platform_irq_count
(
pdev
);
if
(
nr_chans
<
0
)
return
nr_chans
;
ret
=
dma_set_mask
(
dev
,
DMA_BIT_MASK
(
32
));
if
(
ret
)
return
ret
;
mdev
=
devm_kzalloc
(
dev
,
struct_size
(
mdev
,
channels
,
nr_chans
),
GFP_KERNEL
);
if
(
!
mdev
)
return
-
ENOMEM
;
res
=
platform_get_resource
(
pdev
,
IORESOURCE_MEM
,
0
);
mdev
->
reg_base
=
devm_ioremap_resource
(
dev
,
res
);
if
(
IS_ERR
(
mdev
->
reg_base
))
return
PTR_ERR
(
mdev
->
reg_base
);
mdev
->
clk
=
devm_clk_get
(
dev
,
NULL
);
if
(
IS_ERR
(
mdev
->
clk
))
{
dev_err
(
dev
,
"failed to get clock
\n
"
);
return
PTR_ERR
(
mdev
->
clk
);
}
ret
=
clk_prepare_enable
(
mdev
->
clk
);
if
(
ret
)
return
ret
;
ddev
=
&
mdev
->
ddev
;
ddev
->
dev
=
dev
;
dma_cap_set
(
DMA_PRIVATE
,
ddev
->
cap_mask
);
ddev
->
src_addr_widths
=
UNIPHIER_MDMAC_SLAVE_BUSWIDTHS
;
ddev
->
dst_addr_widths
=
UNIPHIER_MDMAC_SLAVE_BUSWIDTHS
;
ddev
->
directions
=
BIT
(
DMA_MEM_TO_DEV
)
|
BIT
(
DMA_DEV_TO_MEM
);
ddev
->
residue_granularity
=
DMA_RESIDUE_GRANULARITY_SEGMENT
;
ddev
->
device_free_chan_resources
=
uniphier_mdmac_free_chan_resources
;
ddev
->
device_prep_slave_sg
=
uniphier_mdmac_prep_slave_sg
;
ddev
->
device_terminate_all
=
uniphier_mdmac_terminate_all
;
ddev
->
device_synchronize
=
uniphier_mdmac_synchronize
;
ddev
->
device_tx_status
=
uniphier_mdmac_tx_status
;
ddev
->
device_issue_pending
=
uniphier_mdmac_issue_pending
;
INIT_LIST_HEAD
(
&
ddev
->
channels
);
for
(
i
=
0
;
i
<
nr_chans
;
i
++
)
{
ret
=
uniphier_mdmac_chan_init
(
pdev
,
mdev
,
i
);
if
(
ret
)
goto
disable_clk
;
}
ret
=
dma_async_device_register
(
ddev
);
if
(
ret
)
goto
disable_clk
;
ret
=
of_dma_controller_register
(
dev
->
of_node
,
of_dma_xlate_by_chan_id
,
ddev
);
if
(
ret
)
goto
unregister_dmac
;
platform_set_drvdata
(
pdev
,
mdev
);
return
0
;
unregister_dmac:
dma_async_device_unregister
(
ddev
);
disable_clk:
clk_disable_unprepare
(
mdev
->
clk
);
return
ret
;
}
static
int
uniphier_mdmac_remove
(
struct
platform_device
*
pdev
)
{
struct
uniphier_mdmac_device
*
mdev
=
platform_get_drvdata
(
pdev
);
struct
dma_chan
*
chan
;
int
ret
;
/*
* Before reaching here, almost all descriptors have been freed by the
* ->device_free_chan_resources() hook. However, each channel might
* be still holding one descriptor that was on-flight at that moment.
* Terminate it to make sure this hardware is no longer running. Then,
* free the channel resources once again to avoid memory leak.
*/
list_for_each_entry
(
chan
,
&
mdev
->
ddev
.
channels
,
device_node
)
{
ret
=
dmaengine_terminate_sync
(
chan
);
if
(
ret
)
return
ret
;
uniphier_mdmac_free_chan_resources
(
chan
);
}
of_dma_controller_free
(
pdev
->
dev
.
of_node
);
dma_async_device_unregister
(
&
mdev
->
ddev
);
clk_disable_unprepare
(
mdev
->
clk
);
return
0
;
}
static
const
struct
of_device_id
uniphier_mdmac_match
[]
=
{
{
.
compatible
=
"socionext,uniphier-mio-dmac"
},
{
/* sentinel */
}
};
MODULE_DEVICE_TABLE
(
of
,
uniphier_mdmac_match
);
static
struct
platform_driver
uniphier_mdmac_driver
=
{
.
probe
=
uniphier_mdmac_probe
,
.
remove
=
uniphier_mdmac_remove
,
.
driver
=
{
.
name
=
"uniphier-mio-dmac"
,
.
of_match_table
=
uniphier_mdmac_match
,
},
};
module_platform_driver
(
uniphier_mdmac_driver
);
MODULE_AUTHOR
(
"Masahiro Yamada <yamada.masahiro@socionext.com>"
);
MODULE_DESCRIPTION
(
"UniPhier MIO DMAC driver"
);
MODULE_LICENSE
(
"GPL v2"
);
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment