2011-02-27 00:47:42 +08:00
|
|
|
/*
|
|
|
|
* Copyright 2011 Freescale Semiconductor, Inc. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Refer to drivers/dma/imx-sdma.c
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
|
|
* published by the Free Software Foundation.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/mm.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/clk.h>
|
|
|
|
#include <linux/wait.h>
|
|
|
|
#include <linux/sched.h>
|
|
|
|
#include <linux/semaphore.h>
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/dma-mapping.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/dmaengine.h>
|
|
|
|
#include <linux/delay.h>
|
2012-05-04 20:12:17 +08:00
|
|
|
#include <linux/module.h>
|
2012-05-04 20:12:15 +08:00
|
|
|
#include <linux/stmp_device.h>
|
2012-05-04 20:12:17 +08:00
|
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/of_device.h>
|
2013-02-26 09:42:09 +08:00
|
|
|
#include <linux/of_dma.h>
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
#include <asm/irq.h>
|
|
|
|
|
2012-03-07 06:34:26 +08:00
|
|
|
#include "dmaengine.h"
|
|
|
|
|
2011-02-27 00:47:42 +08:00
|
|
|
/*
|
|
|
|
* NOTE: The term "PIO" throughout the mxs-dma implementation means
|
|
|
|
* PIO mode of mxs apbh-dma and apbx-dma. With this working mode,
|
|
|
|
* dma can program the controller registers of peripheral devices.
|
|
|
|
*/
|
|
|
|
|
2012-05-10 06:23:26 +08:00
|
|
|
#define dma_is_apbh(mxs_dma) ((mxs_dma)->type == MXS_DMA_APBH)
|
|
|
|
#define apbh_is_old(mxs_dma) ((mxs_dma)->dev_id == IMX23_DMA)
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
#define HW_APBHX_CTRL0 0x000
|
|
|
|
#define BM_APBH_CTRL0_APB_BURST8_EN (1 << 29)
|
|
|
|
#define BM_APBH_CTRL0_APB_BURST_EN (1 << 28)
|
|
|
|
#define BP_APBH_CTRL0_RESET_CHANNEL 16
|
|
|
|
#define HW_APBHX_CTRL1 0x010
|
|
|
|
#define HW_APBHX_CTRL2 0x020
|
|
|
|
#define HW_APBHX_CHANNEL_CTRL 0x030
|
|
|
|
#define BP_APBHX_CHANNEL_CTRL_RESET_CHANNEL 16
|
2012-05-07 14:14:08 +08:00
|
|
|
/*
|
|
|
|
* The offset of NXTCMDAR register is different per both dma type and version,
|
|
|
|
* while stride for each channel is all the same 0x70.
|
|
|
|
*/
|
|
|
|
#define HW_APBHX_CHn_NXTCMDAR(d, n) \
|
|
|
|
(((dma_is_apbh(d) && apbh_is_old(d)) ? 0x050 : 0x110) + (n) * 0x70)
|
|
|
|
#define HW_APBHX_CHn_SEMA(d, n) \
|
|
|
|
(((dma_is_apbh(d) && apbh_is_old(d)) ? 0x080 : 0x140) + (n) * 0x70)
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* ccw bits definitions
|
|
|
|
*
|
|
|
|
* COMMAND: 0..1 (2)
|
|
|
|
* CHAIN: 2 (1)
|
|
|
|
* IRQ: 3 (1)
|
|
|
|
* NAND_LOCK: 4 (1) - not implemented
|
|
|
|
* NAND_WAIT4READY: 5 (1) - not implemented
|
|
|
|
* DEC_SEM: 6 (1)
|
|
|
|
* WAIT4END: 7 (1)
|
|
|
|
* HALT_ON_TERMINATE: 8 (1)
|
|
|
|
* TERMINATE_FLUSH: 9 (1)
|
|
|
|
* RESERVED: 10..11 (2)
|
|
|
|
* PIO_NUM: 12..15 (4)
|
|
|
|
*/
|
|
|
|
#define BP_CCW_COMMAND 0
|
|
|
|
#define BM_CCW_COMMAND (3 << 0)
|
|
|
|
#define CCW_CHAIN (1 << 2)
|
|
|
|
#define CCW_IRQ (1 << 3)
|
|
|
|
#define CCW_DEC_SEM (1 << 6)
|
|
|
|
#define CCW_WAIT4END (1 << 7)
|
|
|
|
#define CCW_HALT_ON_TERM (1 << 8)
|
|
|
|
#define CCW_TERM_FLUSH (1 << 9)
|
|
|
|
#define BP_CCW_PIO_NUM 12
|
|
|
|
#define BM_CCW_PIO_NUM (0xf << 12)
|
|
|
|
|
|
|
|
#define BF_CCW(value, field) (((value) << BP_CCW_##field) & BM_CCW_##field)
|
|
|
|
|
|
|
|
#define MXS_DMA_CMD_NO_XFER 0
|
|
|
|
#define MXS_DMA_CMD_WRITE 1
|
|
|
|
#define MXS_DMA_CMD_READ 2
|
|
|
|
#define MXS_DMA_CMD_DMA_SENSE 3 /* not implemented */
|
|
|
|
|
|
|
|
struct mxs_dma_ccw {
|
|
|
|
u32 next;
|
|
|
|
u16 bits;
|
|
|
|
u16 xfer_bytes;
|
|
|
|
#define MAX_XFER_BYTES 0xff00
|
|
|
|
u32 bufaddr;
|
|
|
|
#define MXS_PIO_WORDS 16
|
|
|
|
u32 pio_words[MXS_PIO_WORDS];
|
|
|
|
};
|
|
|
|
|
2012-09-04 12:04:25 +08:00
|
|
|
#define CCW_BLOCK_SIZE (4 * PAGE_SIZE)
|
|
|
|
#define NUM_CCW (int)(CCW_BLOCK_SIZE / sizeof(struct mxs_dma_ccw))
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
struct mxs_dma_chan {
|
|
|
|
struct mxs_dma_engine *mxs_dma;
|
|
|
|
struct dma_chan chan;
|
|
|
|
struct dma_async_tx_descriptor desc;
|
|
|
|
struct tasklet_struct tasklet;
|
2013-01-08 09:48:39 +08:00
|
|
|
unsigned int chan_irq;
|
2011-02-27 00:47:42 +08:00
|
|
|
struct mxs_dma_ccw *ccw;
|
|
|
|
dma_addr_t ccw_phys;
|
2011-12-08 16:15:43 +08:00
|
|
|
int desc_count;
|
2011-02-27 00:47:42 +08:00
|
|
|
enum dma_status status;
|
|
|
|
unsigned int flags;
|
|
|
|
#define MXS_DMA_SG_LOOP (1 << 0)
|
|
|
|
};
|
|
|
|
|
|
|
|
#define MXS_DMA_CHANNELS 16
|
|
|
|
#define MXS_DMA_CHANNELS_MASK 0xffff
|
|
|
|
|
2012-05-10 06:23:26 +08:00
|
|
|
enum mxs_dma_devtype {
|
|
|
|
MXS_DMA_APBH,
|
|
|
|
MXS_DMA_APBX,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum mxs_dma_id {
|
|
|
|
IMX23_DMA,
|
|
|
|
IMX28_DMA,
|
|
|
|
};
|
|
|
|
|
2011-02-27 00:47:42 +08:00
|
|
|
struct mxs_dma_engine {
|
2012-05-10 06:23:26 +08:00
|
|
|
enum mxs_dma_id dev_id;
|
|
|
|
enum mxs_dma_devtype type;
|
2011-02-27 00:47:42 +08:00
|
|
|
void __iomem *base;
|
|
|
|
struct clk *clk;
|
|
|
|
struct dma_device dma_device;
|
|
|
|
struct device_dma_parameters dma_parms;
|
|
|
|
struct mxs_dma_chan mxs_chans[MXS_DMA_CHANNELS];
|
2013-02-26 09:42:09 +08:00
|
|
|
struct platform_device *pdev;
|
|
|
|
unsigned int nr_channels;
|
2011-02-27 00:47:42 +08:00
|
|
|
};
|
|
|
|
|
2012-05-10 06:23:26 +08:00
|
|
|
struct mxs_dma_type {
|
|
|
|
enum mxs_dma_id id;
|
|
|
|
enum mxs_dma_devtype type;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct mxs_dma_type mxs_dma_types[] = {
|
|
|
|
{
|
|
|
|
.id = IMX23_DMA,
|
|
|
|
.type = MXS_DMA_APBH,
|
|
|
|
}, {
|
|
|
|
.id = IMX23_DMA,
|
|
|
|
.type = MXS_DMA_APBX,
|
|
|
|
}, {
|
|
|
|
.id = IMX28_DMA,
|
|
|
|
.type = MXS_DMA_APBH,
|
|
|
|
}, {
|
|
|
|
.id = IMX28_DMA,
|
|
|
|
.type = MXS_DMA_APBX,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct platform_device_id mxs_dma_ids[] = {
|
|
|
|
{
|
|
|
|
.name = "imx23-dma-apbh",
|
|
|
|
.driver_data = (kernel_ulong_t) &mxs_dma_types[0],
|
|
|
|
}, {
|
|
|
|
.name = "imx23-dma-apbx",
|
|
|
|
.driver_data = (kernel_ulong_t) &mxs_dma_types[1],
|
|
|
|
}, {
|
|
|
|
.name = "imx28-dma-apbh",
|
|
|
|
.driver_data = (kernel_ulong_t) &mxs_dma_types[2],
|
|
|
|
}, {
|
|
|
|
.name = "imx28-dma-apbx",
|
|
|
|
.driver_data = (kernel_ulong_t) &mxs_dma_types[3],
|
|
|
|
}, {
|
|
|
|
/* end of list */
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-05-04 20:12:17 +08:00
|
|
|
static const struct of_device_id mxs_dma_dt_ids[] = {
|
|
|
|
{ .compatible = "fsl,imx23-dma-apbh", .data = &mxs_dma_ids[0], },
|
|
|
|
{ .compatible = "fsl,imx23-dma-apbx", .data = &mxs_dma_ids[1], },
|
|
|
|
{ .compatible = "fsl,imx28-dma-apbh", .data = &mxs_dma_ids[2], },
|
|
|
|
{ .compatible = "fsl,imx28-dma-apbx", .data = &mxs_dma_ids[3], },
|
|
|
|
{ /* sentinel */ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, mxs_dma_dt_ids);
|
|
|
|
|
2012-05-10 06:23:26 +08:00
|
|
|
static struct mxs_dma_chan *to_mxs_dma_chan(struct dma_chan *chan)
|
|
|
|
{
|
|
|
|
return container_of(chan, struct mxs_dma_chan, chan);
|
|
|
|
}
|
|
|
|
|
2011-02-27 00:47:42 +08:00
|
|
|
static void mxs_dma_reset_chan(struct mxs_dma_chan *mxs_chan)
|
|
|
|
{
|
|
|
|
struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;
|
|
|
|
int chan_id = mxs_chan->chan.chan_id;
|
|
|
|
|
2012-05-07 14:14:08 +08:00
|
|
|
if (dma_is_apbh(mxs_dma) && apbh_is_old(mxs_dma))
|
2011-02-27 00:47:42 +08:00
|
|
|
writel(1 << (chan_id + BP_APBH_CTRL0_RESET_CHANNEL),
|
2012-05-04 20:12:15 +08:00
|
|
|
mxs_dma->base + HW_APBHX_CTRL0 + STMP_OFFSET_REG_SET);
|
2011-02-27 00:47:42 +08:00
|
|
|
else
|
|
|
|
writel(1 << (chan_id + BP_APBHX_CHANNEL_CTRL_RESET_CHANNEL),
|
2012-05-04 20:12:15 +08:00
|
|
|
mxs_dma->base + HW_APBHX_CHANNEL_CTRL + STMP_OFFSET_REG_SET);
|
2011-02-27 00:47:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void mxs_dma_enable_chan(struct mxs_dma_chan *mxs_chan)
|
|
|
|
{
|
|
|
|
struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;
|
|
|
|
int chan_id = mxs_chan->chan.chan_id;
|
|
|
|
|
|
|
|
/* set cmd_addr up */
|
|
|
|
writel(mxs_chan->ccw_phys,
|
2012-05-07 14:14:08 +08:00
|
|
|
mxs_dma->base + HW_APBHX_CHn_NXTCMDAR(mxs_dma, chan_id));
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
/* write 1 to SEMA to kick off the channel */
|
2012-05-07 14:14:08 +08:00
|
|
|
writel(1, mxs_dma->base + HW_APBHX_CHn_SEMA(mxs_dma, chan_id));
|
2011-02-27 00:47:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void mxs_dma_disable_chan(struct mxs_dma_chan *mxs_chan)
|
|
|
|
{
|
2013-10-16 23:21:30 +08:00
|
|
|
mxs_chan->status = DMA_COMPLETE;
|
2011-02-27 00:47:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void mxs_dma_pause_chan(struct mxs_dma_chan *mxs_chan)
|
|
|
|
{
|
|
|
|
struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;
|
|
|
|
int chan_id = mxs_chan->chan.chan_id;
|
|
|
|
|
|
|
|
/* freeze the channel */
|
2012-05-07 14:14:08 +08:00
|
|
|
if (dma_is_apbh(mxs_dma) && apbh_is_old(mxs_dma))
|
2011-02-27 00:47:42 +08:00
|
|
|
writel(1 << chan_id,
|
2012-05-04 20:12:15 +08:00
|
|
|
mxs_dma->base + HW_APBHX_CTRL0 + STMP_OFFSET_REG_SET);
|
2011-02-27 00:47:42 +08:00
|
|
|
else
|
|
|
|
writel(1 << chan_id,
|
2012-05-04 20:12:15 +08:00
|
|
|
mxs_dma->base + HW_APBHX_CHANNEL_CTRL + STMP_OFFSET_REG_SET);
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
mxs_chan->status = DMA_PAUSED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mxs_dma_resume_chan(struct mxs_dma_chan *mxs_chan)
|
|
|
|
{
|
|
|
|
struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;
|
|
|
|
int chan_id = mxs_chan->chan.chan_id;
|
|
|
|
|
|
|
|
/* unfreeze the channel */
|
2012-05-07 14:14:08 +08:00
|
|
|
if (dma_is_apbh(mxs_dma) && apbh_is_old(mxs_dma))
|
2011-02-27 00:47:42 +08:00
|
|
|
writel(1 << chan_id,
|
2012-05-04 20:12:15 +08:00
|
|
|
mxs_dma->base + HW_APBHX_CTRL0 + STMP_OFFSET_REG_CLR);
|
2011-02-27 00:47:42 +08:00
|
|
|
else
|
|
|
|
writel(1 << chan_id,
|
2012-05-04 20:12:15 +08:00
|
|
|
mxs_dma->base + HW_APBHX_CHANNEL_CTRL + STMP_OFFSET_REG_CLR);
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
mxs_chan->status = DMA_IN_PROGRESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static dma_cookie_t mxs_dma_tx_submit(struct dma_async_tx_descriptor *tx)
|
|
|
|
{
|
2012-03-07 06:34:46 +08:00
|
|
|
return dma_cookie_assign(tx);
|
2011-02-27 00:47:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void mxs_dma_tasklet(unsigned long data)
|
|
|
|
{
|
|
|
|
struct mxs_dma_chan *mxs_chan = (struct mxs_dma_chan *) data;
|
|
|
|
|
|
|
|
if (mxs_chan->desc.callback)
|
|
|
|
mxs_chan->desc.callback(mxs_chan->desc.callback_param);
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t mxs_dma_int_handler(int irq, void *dev_id)
|
|
|
|
{
|
|
|
|
struct mxs_dma_engine *mxs_dma = dev_id;
|
|
|
|
u32 stat1, stat2;
|
|
|
|
|
|
|
|
/* completion status */
|
|
|
|
stat1 = readl(mxs_dma->base + HW_APBHX_CTRL1);
|
|
|
|
stat1 &= MXS_DMA_CHANNELS_MASK;
|
2012-05-04 20:12:15 +08:00
|
|
|
writel(stat1, mxs_dma->base + HW_APBHX_CTRL1 + STMP_OFFSET_REG_CLR);
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
/* error status */
|
|
|
|
stat2 = readl(mxs_dma->base + HW_APBHX_CTRL2);
|
2012-05-04 20:12:15 +08:00
|
|
|
writel(stat2, mxs_dma->base + HW_APBHX_CTRL2 + STMP_OFFSET_REG_CLR);
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* When both completion and error of termination bits set at the
|
|
|
|
* same time, we do not take it as an error. IOW, it only becomes
|
2011-12-08 16:15:41 +08:00
|
|
|
* an error we need to handle here in case of either it's (1) a bus
|
2011-02-27 00:47:42 +08:00
|
|
|
* error or (2) a termination error with no completion.
|
|
|
|
*/
|
|
|
|
stat2 = ((stat2 >> MXS_DMA_CHANNELS) & stat2) | /* (1) */
|
|
|
|
(~(stat2 >> MXS_DMA_CHANNELS) & stat2 & ~stat1); /* (2) */
|
|
|
|
|
|
|
|
/* combine error and completion status for checking */
|
|
|
|
stat1 = (stat2 << MXS_DMA_CHANNELS) | stat1;
|
|
|
|
while (stat1) {
|
|
|
|
int channel = fls(stat1) - 1;
|
|
|
|
struct mxs_dma_chan *mxs_chan =
|
|
|
|
&mxs_dma->mxs_chans[channel % MXS_DMA_CHANNELS];
|
|
|
|
|
|
|
|
if (channel >= MXS_DMA_CHANNELS) {
|
|
|
|
dev_dbg(mxs_dma->dma_device.dev,
|
|
|
|
"%s: error in channel %d\n", __func__,
|
|
|
|
channel - MXS_DMA_CHANNELS);
|
|
|
|
mxs_chan->status = DMA_ERROR;
|
|
|
|
mxs_dma_reset_chan(mxs_chan);
|
|
|
|
} else {
|
|
|
|
if (mxs_chan->flags & MXS_DMA_SG_LOOP)
|
|
|
|
mxs_chan->status = DMA_IN_PROGRESS;
|
|
|
|
else
|
2013-10-16 23:21:30 +08:00
|
|
|
mxs_chan->status = DMA_COMPLETE;
|
2011-02-27 00:47:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
stat1 &= ~(1 << channel);
|
|
|
|
|
2013-10-16 23:21:30 +08:00
|
|
|
if (mxs_chan->status == DMA_COMPLETE)
|
2012-03-07 06:35:07 +08:00
|
|
|
dma_cookie_complete(&mxs_chan->desc);
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
/* schedule tasklet on this channel */
|
|
|
|
tasklet_schedule(&mxs_chan->tasklet);
|
|
|
|
}
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mxs_dma_alloc_chan_resources(struct dma_chan *chan)
|
|
|
|
{
|
|
|
|
struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan);
|
|
|
|
struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;
|
|
|
|
int ret;
|
|
|
|
|
2012-09-04 12:04:25 +08:00
|
|
|
mxs_chan->ccw = dma_alloc_coherent(mxs_dma->dma_device.dev,
|
|
|
|
CCW_BLOCK_SIZE, &mxs_chan->ccw_phys,
|
|
|
|
GFP_KERNEL);
|
2011-02-27 00:47:42 +08:00
|
|
|
if (!mxs_chan->ccw) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto err_alloc;
|
|
|
|
}
|
|
|
|
|
2012-09-04 12:04:25 +08:00
|
|
|
memset(mxs_chan->ccw, 0, CCW_BLOCK_SIZE);
|
2011-02-27 00:47:42 +08:00
|
|
|
|
2011-06-30 16:06:33 +08:00
|
|
|
if (mxs_chan->chan_irq != NO_IRQ) {
|
|
|
|
ret = request_irq(mxs_chan->chan_irq, mxs_dma_int_handler,
|
|
|
|
0, "mxs-dma", mxs_dma);
|
|
|
|
if (ret)
|
|
|
|
goto err_irq;
|
|
|
|
}
|
2011-02-27 00:47:42 +08:00
|
|
|
|
2011-12-20 13:54:00 +08:00
|
|
|
ret = clk_prepare_enable(mxs_dma->clk);
|
2011-02-27 00:47:42 +08:00
|
|
|
if (ret)
|
|
|
|
goto err_clk;
|
|
|
|
|
|
|
|
mxs_dma_reset_chan(mxs_chan);
|
|
|
|
|
|
|
|
dma_async_tx_descriptor_init(&mxs_chan->desc, chan);
|
|
|
|
mxs_chan->desc.tx_submit = mxs_dma_tx_submit;
|
|
|
|
|
|
|
|
/* the descriptor is ready */
|
|
|
|
async_tx_ack(&mxs_chan->desc);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_clk:
|
|
|
|
free_irq(mxs_chan->chan_irq, mxs_dma);
|
|
|
|
err_irq:
|
2012-09-04 12:04:25 +08:00
|
|
|
dma_free_coherent(mxs_dma->dma_device.dev, CCW_BLOCK_SIZE,
|
2011-02-27 00:47:42 +08:00
|
|
|
mxs_chan->ccw, mxs_chan->ccw_phys);
|
|
|
|
err_alloc:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mxs_dma_free_chan_resources(struct dma_chan *chan)
|
|
|
|
{
|
|
|
|
struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan);
|
|
|
|
struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;
|
|
|
|
|
|
|
|
mxs_dma_disable_chan(mxs_chan);
|
|
|
|
|
|
|
|
free_irq(mxs_chan->chan_irq, mxs_dma);
|
|
|
|
|
2012-09-04 12:04:25 +08:00
|
|
|
dma_free_coherent(mxs_dma->dma_device.dev, CCW_BLOCK_SIZE,
|
2011-02-27 00:47:42 +08:00
|
|
|
mxs_chan->ccw, mxs_chan->ccw_phys);
|
|
|
|
|
2011-12-20 13:54:00 +08:00
|
|
|
clk_disable_unprepare(mxs_dma->clk);
|
2011-02-27 00:47:42 +08:00
|
|
|
}
|
|
|
|
|
mxs-dma : rewrite the last parameter of mxs_dma_prep_slave_sg()
[1] Background :
The GPMI does ECC read page operation with a DMA chain consist of three DMA
Command Structures. The middle one of the chain is used to enable the BCH,
and read out the NAND page.
The WAIT4END(wait for command end) is a comunication signal between
the GPMI and MXS-DMA.
[2] The current DMA code sets the WAIT4END bit at the last one, such as:
+-----+ +-----+ +-----+
| cmd | ------------> | cmd | ------------------> | cmd |
+-----+ +-----+ +-----+
^
|
|
set WAIT4END here
This chain works fine in the mx23/mx28.
[3] But in the new GPMI version (used in MX50/MX60), the WAIT4END bit should
be set not only at the last DMA Command Structure,
but also at the middle one, such as:
+-----+ +-----+ +-----+
| cmd | ------------> | cmd | ------------------> | cmd |
+-----+ +-----+ +-----+
^ ^
| |
| |
set WAIT4END here too set WAIT4END here
If we do not set WAIT4END, the BCH maybe stalls in "ECC reading page" state.
In the next ECC write page operation, a DMA-timeout occurs.
This has been catched in the MX6Q board.
[4] In order to fix the bug, rewrite the last parameter of mxs_dma_prep_slave_sg(),
and use the dma_ctrl_flags:
---------------------------------------------------------
DMA_PREP_INTERRUPT : append a new DMA Command Structrue.
DMA_CTRL_ACK : set the WAIT4END bit for this DMA Command Structure.
---------------------------------------------------------
[5] changes to the relative drivers:
<1> For mxs-mmc driver, just use the new flags, do not change any logic.
<2> For gpmi-nand driver, and use the new flags to set the DMA
chain, especially for ecc read page.
Acked-by: Shawn Guo <shawn.guo@linaro.org>
Signed-off-by: Huang Shijie <b32955@freescale.com>
Acked-by: Vinod Koul <vinod.koul@linux.intel.com>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
2012-02-16 14:17:33 +08:00
|
|
|
/*
|
|
|
|
* How to use the flags for ->device_prep_slave_sg() :
|
|
|
|
* [1] If there is only one DMA command in the DMA chain, the code should be:
|
|
|
|
* ......
|
|
|
|
* ->device_prep_slave_sg(DMA_CTRL_ACK);
|
|
|
|
* ......
|
|
|
|
* [2] If there are two DMA commands in the DMA chain, the code should be
|
|
|
|
* ......
|
|
|
|
* ->device_prep_slave_sg(0);
|
|
|
|
* ......
|
|
|
|
* ->device_prep_slave_sg(DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
|
|
|
* ......
|
|
|
|
* [3] If there are more than two DMA commands in the DMA chain, the code
|
|
|
|
* should be:
|
|
|
|
* ......
|
|
|
|
* ->device_prep_slave_sg(0); // First
|
|
|
|
* ......
|
|
|
|
* ->device_prep_slave_sg(DMA_PREP_INTERRUPT [| DMA_CTRL_ACK]);
|
|
|
|
* ......
|
|
|
|
* ->device_prep_slave_sg(DMA_PREP_INTERRUPT | DMA_CTRL_ACK); // Last
|
|
|
|
* ......
|
|
|
|
*/
|
2011-02-27 00:47:42 +08:00
|
|
|
static struct dma_async_tx_descriptor *mxs_dma_prep_slave_sg(
|
|
|
|
struct dma_chan *chan, struct scatterlist *sgl,
|
2011-10-14 01:04:23 +08:00
|
|
|
unsigned int sg_len, enum dma_transfer_direction direction,
|
2012-03-31 08:31:56 +08:00
|
|
|
unsigned long flags, void *context)
|
2011-02-27 00:47:42 +08:00
|
|
|
{
|
|
|
|
struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan);
|
|
|
|
struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;
|
|
|
|
struct mxs_dma_ccw *ccw;
|
|
|
|
struct scatterlist *sg;
|
2013-01-08 09:48:39 +08:00
|
|
|
u32 i, j;
|
2011-02-27 00:47:42 +08:00
|
|
|
u32 *pio;
|
mxs-dma : rewrite the last parameter of mxs_dma_prep_slave_sg()
[1] Background :
The GPMI does ECC read page operation with a DMA chain consist of three DMA
Command Structures. The middle one of the chain is used to enable the BCH,
and read out the NAND page.
The WAIT4END(wait for command end) is a comunication signal between
the GPMI and MXS-DMA.
[2] The current DMA code sets the WAIT4END bit at the last one, such as:
+-----+ +-----+ +-----+
| cmd | ------------> | cmd | ------------------> | cmd |
+-----+ +-----+ +-----+
^
|
|
set WAIT4END here
This chain works fine in the mx23/mx28.
[3] But in the new GPMI version (used in MX50/MX60), the WAIT4END bit should
be set not only at the last DMA Command Structure,
but also at the middle one, such as:
+-----+ +-----+ +-----+
| cmd | ------------> | cmd | ------------------> | cmd |
+-----+ +-----+ +-----+
^ ^
| |
| |
set WAIT4END here too set WAIT4END here
If we do not set WAIT4END, the BCH maybe stalls in "ECC reading page" state.
In the next ECC write page operation, a DMA-timeout occurs.
This has been catched in the MX6Q board.
[4] In order to fix the bug, rewrite the last parameter of mxs_dma_prep_slave_sg(),
and use the dma_ctrl_flags:
---------------------------------------------------------
DMA_PREP_INTERRUPT : append a new DMA Command Structrue.
DMA_CTRL_ACK : set the WAIT4END bit for this DMA Command Structure.
---------------------------------------------------------
[5] changes to the relative drivers:
<1> For mxs-mmc driver, just use the new flags, do not change any logic.
<2> For gpmi-nand driver, and use the new flags to set the DMA
chain, especially for ecc read page.
Acked-by: Shawn Guo <shawn.guo@linaro.org>
Signed-off-by: Huang Shijie <b32955@freescale.com>
Acked-by: Vinod Koul <vinod.koul@linux.intel.com>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
2012-02-16 14:17:33 +08:00
|
|
|
bool append = flags & DMA_PREP_INTERRUPT;
|
2011-12-08 16:15:43 +08:00
|
|
|
int idx = append ? mxs_chan->desc_count : 0;
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
if (mxs_chan->status == DMA_IN_PROGRESS && !append)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (sg_len + (append ? idx : 0) > NUM_CCW) {
|
|
|
|
dev_err(mxs_dma->dma_device.dev,
|
|
|
|
"maximum number of sg exceeded: %d > %d\n",
|
|
|
|
sg_len, NUM_CCW);
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
|
|
|
|
mxs_chan->status = DMA_IN_PROGRESS;
|
|
|
|
mxs_chan->flags = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the sg is prepared with append flag set, the sg
|
|
|
|
* will be appended to the last prepared sg.
|
|
|
|
*/
|
|
|
|
if (append) {
|
|
|
|
BUG_ON(idx < 1);
|
|
|
|
ccw = &mxs_chan->ccw[idx - 1];
|
|
|
|
ccw->next = mxs_chan->ccw_phys + sizeof(*ccw) * idx;
|
|
|
|
ccw->bits |= CCW_CHAIN;
|
|
|
|
ccw->bits &= ~CCW_IRQ;
|
|
|
|
ccw->bits &= ~CCW_DEC_SEM;
|
|
|
|
} else {
|
|
|
|
idx = 0;
|
|
|
|
}
|
|
|
|
|
2011-12-13 23:48:03 +08:00
|
|
|
if (direction == DMA_TRANS_NONE) {
|
2011-02-27 00:47:42 +08:00
|
|
|
ccw = &mxs_chan->ccw[idx++];
|
|
|
|
pio = (u32 *) sgl;
|
|
|
|
|
|
|
|
for (j = 0; j < sg_len;)
|
|
|
|
ccw->pio_words[j++] = *pio++;
|
|
|
|
|
|
|
|
ccw->bits = 0;
|
|
|
|
ccw->bits |= CCW_IRQ;
|
|
|
|
ccw->bits |= CCW_DEC_SEM;
|
mxs-dma : rewrite the last parameter of mxs_dma_prep_slave_sg()
[1] Background :
The GPMI does ECC read page operation with a DMA chain consist of three DMA
Command Structures. The middle one of the chain is used to enable the BCH,
and read out the NAND page.
The WAIT4END(wait for command end) is a comunication signal between
the GPMI and MXS-DMA.
[2] The current DMA code sets the WAIT4END bit at the last one, such as:
+-----+ +-----+ +-----+
| cmd | ------------> | cmd | ------------------> | cmd |
+-----+ +-----+ +-----+
^
|
|
set WAIT4END here
This chain works fine in the mx23/mx28.
[3] But in the new GPMI version (used in MX50/MX60), the WAIT4END bit should
be set not only at the last DMA Command Structure,
but also at the middle one, such as:
+-----+ +-----+ +-----+
| cmd | ------------> | cmd | ------------------> | cmd |
+-----+ +-----+ +-----+
^ ^
| |
| |
set WAIT4END here too set WAIT4END here
If we do not set WAIT4END, the BCH maybe stalls in "ECC reading page" state.
In the next ECC write page operation, a DMA-timeout occurs.
This has been catched in the MX6Q board.
[4] In order to fix the bug, rewrite the last parameter of mxs_dma_prep_slave_sg(),
and use the dma_ctrl_flags:
---------------------------------------------------------
DMA_PREP_INTERRUPT : append a new DMA Command Structrue.
DMA_CTRL_ACK : set the WAIT4END bit for this DMA Command Structure.
---------------------------------------------------------
[5] changes to the relative drivers:
<1> For mxs-mmc driver, just use the new flags, do not change any logic.
<2> For gpmi-nand driver, and use the new flags to set the DMA
chain, especially for ecc read page.
Acked-by: Shawn Guo <shawn.guo@linaro.org>
Signed-off-by: Huang Shijie <b32955@freescale.com>
Acked-by: Vinod Koul <vinod.koul@linux.intel.com>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
2012-02-16 14:17:33 +08:00
|
|
|
if (flags & DMA_CTRL_ACK)
|
|
|
|
ccw->bits |= CCW_WAIT4END;
|
2011-02-27 00:47:42 +08:00
|
|
|
ccw->bits |= CCW_HALT_ON_TERM;
|
|
|
|
ccw->bits |= CCW_TERM_FLUSH;
|
|
|
|
ccw->bits |= BF_CCW(sg_len, PIO_NUM);
|
|
|
|
ccw->bits |= BF_CCW(MXS_DMA_CMD_NO_XFER, COMMAND);
|
|
|
|
} else {
|
|
|
|
for_each_sg(sgl, sg, sg_len, i) {
|
2012-04-26 02:50:52 +08:00
|
|
|
if (sg_dma_len(sg) > MAX_XFER_BYTES) {
|
2011-02-27 00:47:42 +08:00
|
|
|
dev_err(mxs_dma->dma_device.dev, "maximum bytes for sg entry exceeded: %d > %d\n",
|
2012-04-26 02:50:52 +08:00
|
|
|
sg_dma_len(sg), MAX_XFER_BYTES);
|
2011-02-27 00:47:42 +08:00
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
|
|
|
|
ccw = &mxs_chan->ccw[idx++];
|
|
|
|
|
|
|
|
ccw->next = mxs_chan->ccw_phys + sizeof(*ccw) * idx;
|
|
|
|
ccw->bufaddr = sg->dma_address;
|
2012-04-26 02:50:52 +08:00
|
|
|
ccw->xfer_bytes = sg_dma_len(sg);
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
ccw->bits = 0;
|
|
|
|
ccw->bits |= CCW_CHAIN;
|
|
|
|
ccw->bits |= CCW_HALT_ON_TERM;
|
|
|
|
ccw->bits |= CCW_TERM_FLUSH;
|
2011-10-14 01:04:23 +08:00
|
|
|
ccw->bits |= BF_CCW(direction == DMA_DEV_TO_MEM ?
|
2011-02-27 00:47:42 +08:00
|
|
|
MXS_DMA_CMD_WRITE : MXS_DMA_CMD_READ,
|
|
|
|
COMMAND);
|
|
|
|
|
|
|
|
if (i + 1 == sg_len) {
|
|
|
|
ccw->bits &= ~CCW_CHAIN;
|
|
|
|
ccw->bits |= CCW_IRQ;
|
|
|
|
ccw->bits |= CCW_DEC_SEM;
|
mxs-dma : rewrite the last parameter of mxs_dma_prep_slave_sg()
[1] Background :
The GPMI does ECC read page operation with a DMA chain consist of three DMA
Command Structures. The middle one of the chain is used to enable the BCH,
and read out the NAND page.
The WAIT4END(wait for command end) is a comunication signal between
the GPMI and MXS-DMA.
[2] The current DMA code sets the WAIT4END bit at the last one, such as:
+-----+ +-----+ +-----+
| cmd | ------------> | cmd | ------------------> | cmd |
+-----+ +-----+ +-----+
^
|
|
set WAIT4END here
This chain works fine in the mx23/mx28.
[3] But in the new GPMI version (used in MX50/MX60), the WAIT4END bit should
be set not only at the last DMA Command Structure,
but also at the middle one, such as:
+-----+ +-----+ +-----+
| cmd | ------------> | cmd | ------------------> | cmd |
+-----+ +-----+ +-----+
^ ^
| |
| |
set WAIT4END here too set WAIT4END here
If we do not set WAIT4END, the BCH maybe stalls in "ECC reading page" state.
In the next ECC write page operation, a DMA-timeout occurs.
This has been catched in the MX6Q board.
[4] In order to fix the bug, rewrite the last parameter of mxs_dma_prep_slave_sg(),
and use the dma_ctrl_flags:
---------------------------------------------------------
DMA_PREP_INTERRUPT : append a new DMA Command Structrue.
DMA_CTRL_ACK : set the WAIT4END bit for this DMA Command Structure.
---------------------------------------------------------
[5] changes to the relative drivers:
<1> For mxs-mmc driver, just use the new flags, do not change any logic.
<2> For gpmi-nand driver, and use the new flags to set the DMA
chain, especially for ecc read page.
Acked-by: Shawn Guo <shawn.guo@linaro.org>
Signed-off-by: Huang Shijie <b32955@freescale.com>
Acked-by: Vinod Koul <vinod.koul@linux.intel.com>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
2012-02-16 14:17:33 +08:00
|
|
|
if (flags & DMA_CTRL_ACK)
|
|
|
|
ccw->bits |= CCW_WAIT4END;
|
2011-02-27 00:47:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-12-08 16:15:43 +08:00
|
|
|
mxs_chan->desc_count = idx;
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
return &mxs_chan->desc;
|
|
|
|
|
|
|
|
err_out:
|
|
|
|
mxs_chan->status = DMA_ERROR;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct dma_async_tx_descriptor *mxs_dma_prep_dma_cyclic(
|
|
|
|
struct dma_chan *chan, dma_addr_t dma_addr, size_t buf_len,
|
2012-03-09 04:35:13 +08:00
|
|
|
size_t period_len, enum dma_transfer_direction direction,
|
2012-09-14 20:05:47 +08:00
|
|
|
unsigned long flags, void *context)
|
2011-02-27 00:47:42 +08:00
|
|
|
{
|
|
|
|
struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan);
|
|
|
|
struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;
|
2013-01-08 09:48:39 +08:00
|
|
|
u32 num_periods = buf_len / period_len;
|
|
|
|
u32 i = 0, buf = 0;
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
if (mxs_chan->status == DMA_IN_PROGRESS)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
mxs_chan->status = DMA_IN_PROGRESS;
|
|
|
|
mxs_chan->flags |= MXS_DMA_SG_LOOP;
|
|
|
|
|
|
|
|
if (num_periods > NUM_CCW) {
|
|
|
|
dev_err(mxs_dma->dma_device.dev,
|
|
|
|
"maximum number of sg exceeded: %d > %d\n",
|
|
|
|
num_periods, NUM_CCW);
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (period_len > MAX_XFER_BYTES) {
|
|
|
|
dev_err(mxs_dma->dma_device.dev,
|
|
|
|
"maximum period size exceeded: %d > %d\n",
|
|
|
|
period_len, MAX_XFER_BYTES);
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (buf < buf_len) {
|
|
|
|
struct mxs_dma_ccw *ccw = &mxs_chan->ccw[i];
|
|
|
|
|
|
|
|
if (i + 1 == num_periods)
|
|
|
|
ccw->next = mxs_chan->ccw_phys;
|
|
|
|
else
|
|
|
|
ccw->next = mxs_chan->ccw_phys + sizeof(*ccw) * (i + 1);
|
|
|
|
|
|
|
|
ccw->bufaddr = dma_addr;
|
|
|
|
ccw->xfer_bytes = period_len;
|
|
|
|
|
|
|
|
ccw->bits = 0;
|
|
|
|
ccw->bits |= CCW_CHAIN;
|
|
|
|
ccw->bits |= CCW_IRQ;
|
|
|
|
ccw->bits |= CCW_HALT_ON_TERM;
|
|
|
|
ccw->bits |= CCW_TERM_FLUSH;
|
2011-10-14 01:04:23 +08:00
|
|
|
ccw->bits |= BF_CCW(direction == DMA_DEV_TO_MEM ?
|
2011-02-27 00:47:42 +08:00
|
|
|
MXS_DMA_CMD_WRITE : MXS_DMA_CMD_READ, COMMAND);
|
|
|
|
|
|
|
|
dma_addr += period_len;
|
|
|
|
buf += period_len;
|
|
|
|
|
|
|
|
i++;
|
|
|
|
}
|
2011-12-08 16:15:43 +08:00
|
|
|
mxs_chan->desc_count = i;
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
return &mxs_chan->desc;
|
|
|
|
|
|
|
|
err_out:
|
|
|
|
mxs_chan->status = DMA_ERROR;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mxs_dma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
|
|
|
|
unsigned long arg)
|
|
|
|
{
|
|
|
|
struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan);
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case DMA_TERMINATE_ALL:
|
2011-07-19 12:09:56 +08:00
|
|
|
mxs_dma_reset_chan(mxs_chan);
|
2011-12-08 16:15:44 +08:00
|
|
|
mxs_dma_disable_chan(mxs_chan);
|
2011-02-27 00:47:42 +08:00
|
|
|
break;
|
|
|
|
case DMA_PAUSE:
|
|
|
|
mxs_dma_pause_chan(mxs_chan);
|
|
|
|
break;
|
|
|
|
case DMA_RESUME:
|
|
|
|
mxs_dma_resume_chan(mxs_chan);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ret = -ENOSYS;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum dma_status mxs_dma_tx_status(struct dma_chan *chan,
|
|
|
|
dma_cookie_t cookie, struct dma_tx_state *txstate)
|
|
|
|
{
|
|
|
|
struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan);
|
|
|
|
|
2013-05-27 20:14:32 +08:00
|
|
|
dma_set_tx_state(txstate, chan->completed_cookie, chan->cookie, 0);
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
return mxs_chan->status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mxs_dma_issue_pending(struct dma_chan *chan)
|
|
|
|
{
|
2012-04-11 13:29:31 +08:00
|
|
|
struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan);
|
|
|
|
|
|
|
|
mxs_dma_enable_chan(mxs_chan);
|
2011-02-27 00:47:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int __init mxs_dma_init(struct mxs_dma_engine *mxs_dma)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2011-12-20 13:54:00 +08:00
|
|
|
ret = clk_prepare_enable(mxs_dma->clk);
|
2011-02-27 00:47:42 +08:00
|
|
|
if (ret)
|
2011-12-08 16:15:42 +08:00
|
|
|
return ret;
|
2011-02-27 00:47:42 +08:00
|
|
|
|
2012-05-04 20:12:15 +08:00
|
|
|
ret = stmp_reset_block(mxs_dma->base);
|
2011-02-27 00:47:42 +08:00
|
|
|
if (ret)
|
|
|
|
goto err_out;
|
|
|
|
|
|
|
|
/* enable apbh burst */
|
2012-05-07 14:14:08 +08:00
|
|
|
if (dma_is_apbh(mxs_dma)) {
|
2011-02-27 00:47:42 +08:00
|
|
|
writel(BM_APBH_CTRL0_APB_BURST_EN,
|
2012-05-04 20:12:15 +08:00
|
|
|
mxs_dma->base + HW_APBHX_CTRL0 + STMP_OFFSET_REG_SET);
|
2011-02-27 00:47:42 +08:00
|
|
|
writel(BM_APBH_CTRL0_APB_BURST8_EN,
|
2012-05-04 20:12:15 +08:00
|
|
|
mxs_dma->base + HW_APBHX_CTRL0 + STMP_OFFSET_REG_SET);
|
2011-02-27 00:47:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* enable irq for all the channels */
|
|
|
|
writel(MXS_DMA_CHANNELS_MASK << MXS_DMA_CHANNELS,
|
2012-05-04 20:12:15 +08:00
|
|
|
mxs_dma->base + HW_APBHX_CTRL1 + STMP_OFFSET_REG_SET);
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
err_out:
|
2012-01-18 10:40:24 +08:00
|
|
|
clk_disable_unprepare(mxs_dma->clk);
|
2011-02-27 00:47:42 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2013-02-26 09:42:09 +08:00
|
|
|
struct mxs_dma_filter_param {
|
|
|
|
struct device_node *of_node;
|
|
|
|
unsigned int chan_id;
|
|
|
|
};
|
|
|
|
|
|
|
|
static bool mxs_dma_filter_fn(struct dma_chan *chan, void *fn_param)
|
|
|
|
{
|
|
|
|
struct mxs_dma_filter_param *param = fn_param;
|
|
|
|
struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan);
|
|
|
|
struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;
|
|
|
|
int chan_irq;
|
|
|
|
|
|
|
|
if (mxs_dma->dma_device.dev->of_node != param->of_node)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (chan->chan_id != param->chan_id)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
chan_irq = platform_get_irq(mxs_dma->pdev, param->chan_id);
|
|
|
|
if (chan_irq < 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
mxs_chan->chan_irq = chan_irq;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-05-25 03:37:27 +08:00
|
|
|
static struct dma_chan *mxs_dma_xlate(struct of_phandle_args *dma_spec,
|
2013-02-26 09:42:09 +08:00
|
|
|
struct of_dma *ofdma)
|
|
|
|
{
|
|
|
|
struct mxs_dma_engine *mxs_dma = ofdma->of_dma_data;
|
|
|
|
dma_cap_mask_t mask = mxs_dma->dma_device.cap_mask;
|
|
|
|
struct mxs_dma_filter_param param;
|
|
|
|
|
|
|
|
if (dma_spec->args_count != 1)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
param.of_node = ofdma->of_node;
|
|
|
|
param.chan_id = dma_spec->args[0];
|
|
|
|
|
|
|
|
if (param.chan_id >= mxs_dma->nr_channels)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return dma_request_channel(mask, mxs_dma_filter_fn, ¶m);
|
|
|
|
}
|
|
|
|
|
2011-02-27 00:47:42 +08:00
|
|
|
static int __init mxs_dma_probe(struct platform_device *pdev)
|
|
|
|
{
|
2013-02-26 09:42:09 +08:00
|
|
|
struct device_node *np = pdev->dev.of_node;
|
2012-05-04 20:12:17 +08:00
|
|
|
const struct platform_device_id *id_entry;
|
|
|
|
const struct of_device_id *of_id;
|
|
|
|
const struct mxs_dma_type *dma_type;
|
2011-02-27 00:47:42 +08:00
|
|
|
struct mxs_dma_engine *mxs_dma;
|
|
|
|
struct resource *iores;
|
|
|
|
int ret, i;
|
|
|
|
|
2013-02-25 14:57:26 +08:00
|
|
|
mxs_dma = devm_kzalloc(&pdev->dev, sizeof(*mxs_dma), GFP_KERNEL);
|
2011-02-27 00:47:42 +08:00
|
|
|
if (!mxs_dma)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2013-02-26 09:42:09 +08:00
|
|
|
ret = of_property_read_u32(np, "dma-channels", &mxs_dma->nr_channels);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(&pdev->dev, "failed to read dma-channels\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-05-04 20:12:17 +08:00
|
|
|
of_id = of_match_device(mxs_dma_dt_ids, &pdev->dev);
|
|
|
|
if (of_id)
|
|
|
|
id_entry = of_id->data;
|
|
|
|
else
|
|
|
|
id_entry = platform_get_device_id(pdev);
|
|
|
|
|
|
|
|
dma_type = (struct mxs_dma_type *)id_entry->driver_data;
|
2012-05-10 06:23:26 +08:00
|
|
|
mxs_dma->type = dma_type->type;
|
2012-05-04 20:12:17 +08:00
|
|
|
mxs_dma->dev_id = dma_type->id;
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
2013-02-25 14:57:26 +08:00
|
|
|
mxs_dma->base = devm_ioremap_resource(&pdev->dev, iores);
|
|
|
|
if (IS_ERR(mxs_dma->base))
|
|
|
|
return PTR_ERR(mxs_dma->base);
|
2011-02-27 00:47:42 +08:00
|
|
|
|
2013-02-25 14:57:26 +08:00
|
|
|
mxs_dma->clk = devm_clk_get(&pdev->dev, NULL);
|
|
|
|
if (IS_ERR(mxs_dma->clk))
|
|
|
|
return PTR_ERR(mxs_dma->clk);
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
dma_cap_set(DMA_SLAVE, mxs_dma->dma_device.cap_mask);
|
|
|
|
dma_cap_set(DMA_CYCLIC, mxs_dma->dma_device.cap_mask);
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&mxs_dma->dma_device.channels);
|
|
|
|
|
|
|
|
/* Initialize channel parameters */
|
|
|
|
for (i = 0; i < MXS_DMA_CHANNELS; i++) {
|
|
|
|
struct mxs_dma_chan *mxs_chan = &mxs_dma->mxs_chans[i];
|
|
|
|
|
|
|
|
mxs_chan->mxs_dma = mxs_dma;
|
|
|
|
mxs_chan->chan.device = &mxs_dma->dma_device;
|
2012-03-07 06:36:27 +08:00
|
|
|
dma_cookie_init(&mxs_chan->chan);
|
2011-02-27 00:47:42 +08:00
|
|
|
|
|
|
|
tasklet_init(&mxs_chan->tasklet, mxs_dma_tasklet,
|
|
|
|
(unsigned long) mxs_chan);
|
|
|
|
|
|
|
|
|
|
|
|
/* Add the channel to mxs_chan list */
|
|
|
|
list_add_tail(&mxs_chan->chan.device_node,
|
|
|
|
&mxs_dma->dma_device.channels);
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = mxs_dma_init(mxs_dma);
|
|
|
|
if (ret)
|
2013-02-25 14:57:26 +08:00
|
|
|
return ret;
|
2011-02-27 00:47:42 +08:00
|
|
|
|
2013-02-26 09:42:09 +08:00
|
|
|
mxs_dma->pdev = pdev;
|
2011-02-27 00:47:42 +08:00
|
|
|
mxs_dma->dma_device.dev = &pdev->dev;
|
|
|
|
|
|
|
|
/* mxs_dma gets 65535 bytes maximum sg size */
|
|
|
|
mxs_dma->dma_device.dev->dma_parms = &mxs_dma->dma_parms;
|
|
|
|
dma_set_max_seg_size(mxs_dma->dma_device.dev, MAX_XFER_BYTES);
|
|
|
|
|
|
|
|
mxs_dma->dma_device.device_alloc_chan_resources = mxs_dma_alloc_chan_resources;
|
|
|
|
mxs_dma->dma_device.device_free_chan_resources = mxs_dma_free_chan_resources;
|
|
|
|
mxs_dma->dma_device.device_tx_status = mxs_dma_tx_status;
|
|
|
|
mxs_dma->dma_device.device_prep_slave_sg = mxs_dma_prep_slave_sg;
|
|
|
|
mxs_dma->dma_device.device_prep_dma_cyclic = mxs_dma_prep_dma_cyclic;
|
|
|
|
mxs_dma->dma_device.device_control = mxs_dma_control;
|
|
|
|
mxs_dma->dma_device.device_issue_pending = mxs_dma_issue_pending;
|
|
|
|
|
|
|
|
ret = dma_async_device_register(&mxs_dma->dma_device);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(mxs_dma->dma_device.dev, "unable to register\n");
|
2013-02-25 14:57:26 +08:00
|
|
|
return ret;
|
2011-02-27 00:47:42 +08:00
|
|
|
}
|
|
|
|
|
2013-02-26 09:42:09 +08:00
|
|
|
ret = of_dma_controller_register(np, mxs_dma_xlate, mxs_dma);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(mxs_dma->dma_device.dev,
|
|
|
|
"failed to register controller\n");
|
|
|
|
dma_async_device_unregister(&mxs_dma->dma_device);
|
|
|
|
}
|
|
|
|
|
2011-02-27 00:47:42 +08:00
|
|
|
dev_info(mxs_dma->dma_device.dev, "initialized\n");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct platform_driver mxs_dma_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "mxs-dma",
|
2012-05-04 20:12:17 +08:00
|
|
|
.of_match_table = mxs_dma_dt_ids,
|
2011-02-27 00:47:42 +08:00
|
|
|
},
|
2012-05-10 06:23:26 +08:00
|
|
|
.id_table = mxs_dma_ids,
|
2011-02-27 00:47:42 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static int __init mxs_dma_module_init(void)
|
|
|
|
{
|
|
|
|
return platform_driver_probe(&mxs_dma_driver, mxs_dma_probe);
|
|
|
|
}
|
|
|
|
subsys_initcall(mxs_dma_module_init);
|