* Based on code from LTIB:
* Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved.
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * SPDX-License-Identifier: GPL-2.0+
*/
#include <linux/list.h>
#include <asm/arch/clock.h>
#include <asm/arch/imx-regs.h>
#include <asm/arch/sys_proto.h>
-#include <asm/arch/dma.h>
+#include <asm/imx-common/dma.h>
+#include <asm/imx-common/regs-apbh.h>
static struct mxs_dma_chan mxs_dma_channels[MXS_MAX_DMA_CHANNELS];
+static struct mxs_apbh_regs *apbh_regs = (struct mxs_apbh_regs *)MXS_APBH_BASE;
/*
* Test is the DMA channel is valid channel
{
struct mxs_dma_chan *pchan;
- if ((channel < 0) || (channel >= MXS_MAX_DMA_CHANNELS))
+ if ((channel < 0) || (channel >= MXS_MAX_DMA_CHANNELS)) {
+ printf("Invalid DMA channel %d\n", channel);
return -EINVAL;
+ }
pchan = mxs_dma_channels + channel;
- if (!(pchan->flags & MXS_DMA_FLAGS_ALLOCATED))
+ if (!(pchan->flags & MXS_DMA_FLAGS_ALLOCATED)) {
+ printf("DMA channel %d not allocated\n", channel);
return -EINVAL;
+ }
return 0;
}
*/
static int mxs_dma_read_semaphore(int channel)
{
- struct mxs_apbh_regs *apbh_regs =
- (struct mxs_apbh_regs *)MXS_APBH_BASE;
uint32_t tmp;
int ret;
}
#ifndef CONFIG_SYS_DCACHE_OFF
-void mxs_dma_flush_desc(struct mxs_dma_desc *desc)
+static void mxs_dma_flush_desc(struct mxs_dma_desc *desc)
{
uint32_t addr;
uint32_t size;
flush_dcache_range(addr, addr + size);
}
#else
-inline void mxs_dma_flush_desc(struct mxs_dma_desc *desc) {}
+static inline void mxs_dma_flush_desc(struct mxs_dma_desc *desc)
+{
+}
#endif
/*
*/
static int mxs_dma_enable(int channel)
{
- struct mxs_apbh_regs *apbh_regs =
- (struct mxs_apbh_regs *)MXS_APBH_BASE;
unsigned int sem;
struct mxs_dma_chan *pchan;
struct mxs_dma_desc *pdesc;
pdesc = list_first_entry(&pchan->active, struct mxs_dma_desc, node);
if (pdesc == NULL)
- return -EFAULT;
+ return -EINVAL;
if (pchan->flags & MXS_DMA_FLAGS_BUSY) {
if (!(pdesc->cmd.data & MXS_DMA_DESC_CHAIN))
} else {
pchan->active_num += pchan->pending_num;
pchan->pending_num = 0;
+ writel(1 << (channel + APBH_CTRL0_CLKGATE_CHANNEL_OFFSET),
+ &apbh_regs->hw_apbh_ctrl0_clr);
writel(mxs_dma_cmd_address(pdesc),
&apbh_regs->ch[channel].hw_apbh_ch_nxtcmdar);
writel(pchan->active_num,
&apbh_regs->ch[channel].hw_apbh_ch_sema);
- writel(1 << (channel + APBH_CTRL0_CLKGATE_CHANNEL_OFFSET),
- &apbh_regs->hw_apbh_ctrl0_clr);
}
pchan->flags |= MXS_DMA_FLAGS_BUSY;
static int mxs_dma_disable(int channel)
{
struct mxs_dma_chan *pchan;
- struct mxs_apbh_regs *apbh_regs =
- (struct mxs_apbh_regs *)MXS_APBH_BASE;
int ret;
ret = mxs_dma_validate_chan(channel);
pchan = mxs_dma_channels + channel;
- if (!(pchan->flags & MXS_DMA_FLAGS_BUSY))
+ if ((pchan->flags & MXS_DMA_FLAGS_BUSY)) {
+ printf("%s: DMA channel %d busy\n", __func__, channel);
return -EINVAL;
-
+ }
writel(1 << (channel + APBH_CTRL0_CLKGATE_CHANNEL_OFFSET),
&apbh_regs->hw_apbh_ctrl0_set);
-
- pchan->flags &= ~MXS_DMA_FLAGS_BUSY;
pchan->active_num = 0;
pchan->pending_num = 0;
list_splice_init(&pchan->active, &pchan->done);
*/
static int mxs_dma_reset(int channel)
{
- struct mxs_apbh_regs *apbh_regs =
- (struct mxs_apbh_regs *)MXS_APBH_BASE;
int ret;
+#if defined(CONFIG_SOC_MX23)
+ uint32_t *setreg = &apbh_regs->hw_apbh_ctrl0_set;
+ uint32_t offset = APBH_CTRL0_RESET_CHANNEL_OFFSET;
+#elif (defined(CONFIG_SOC_MX28) || defined(CONFIG_SOC_MX6))
+ uint32_t *setreg = &apbh_regs->hw_apbh_channel_ctrl_set;
+ uint32_t offset = APBH_CHANNEL_CTRL_RESET_CHANNEL_OFFSET;
+#endif
ret = mxs_dma_validate_chan(channel);
if (ret)
return ret;
- writel(1 << (channel + APBH_CHANNEL_CTRL_RESET_CHANNEL_OFFSET),
- &apbh_regs->hw_apbh_channel_ctrl_set);
+ writel(1 << (channel + offset), setreg);
return 0;
}
*/
static int mxs_dma_enable_irq(int channel, int enable)
{
- struct mxs_apbh_regs *apbh_regs =
- (struct mxs_apbh_regs *)MXS_APBH_BASE;
int ret;
ret = mxs_dma_validate_chan(channel);
*/
static int mxs_dma_ack_irq(int channel)
{
- struct mxs_apbh_regs *apbh_regs =
- (struct mxs_apbh_regs *)MXS_APBH_BASE;
int ret;
ret = mxs_dma_validate_chan(channel);
pchan->flags |= MXS_DMA_FLAGS_ALLOCATED;
pchan->active_num = 0;
pchan->pending_num = 0;
+ pchan->timeout = 10000000;
INIT_LIST_HEAD(&pchan->active);
INIT_LIST_HEAD(&pchan->done);
return 0;
}
+/*
+ * Set the timeout for any DMA operation started with mxs_dma_go()
+ * The timeout value given is in milliseconds
+ */
+int mxs_dma_set_timeout(int channel, unsigned long timeout)
+{
+ int ret;
+ struct mxs_dma_chan *pchan;
+
+ ret = mxs_dma_validate_chan(channel);
+ if (ret)
+ return ret;
+
+ pchan = &mxs_dma_channels[channel];
+
+ if (pchan->flags & MXS_DMA_FLAGS_BUSY)
+ return -EBUSY;
+
+ if (timeout > ~0UL / 1000)
+ return -EINVAL;
+
+ pchan->timeout = timeout;
+ return 0;
+}
+
+unsigned long mxs_dma_get_timeout(int channel)
+{
+ int ret;
+ struct mxs_dma_chan *pchan;
+
+ ret = mxs_dma_validate_chan(channel);
+ if (ret)
+ return 0;
+
+ pchan = &mxs_dma_channels[channel];
+ return pchan->timeout;
+}
+
/*
* Allocate DMA descriptor
*/
*/
static int mxs_dma_wait_complete(uint32_t timeout, unsigned int chan)
{
- struct mxs_apbh_regs *apbh_regs =
- (struct mxs_apbh_regs *)MXS_APBH_BASE;
int ret;
ret = mxs_dma_validate_chan(chan);
*/
int mxs_dma_go(int chan)
{
- uint32_t timeout = 10000000;
int ret;
-
+ struct mxs_dma_chan *pchan;
LIST_HEAD(tmp_desc_list);
+ ret = mxs_dma_validate_chan(chan);
+ if (ret)
+ return ret;
+
+ pchan = &mxs_dma_channels[chan];
+
mxs_dma_enable_irq(chan, 1);
- mxs_dma_enable(chan);
+ ret = mxs_dma_enable(chan);
+ if (ret) {
+ mxs_dma_enable_irq(chan, 0);
+ return ret;
+ }
/* Wait for DMA to finish. */
- ret = mxs_dma_wait_complete(timeout, chan);
+ ret = mxs_dma_wait_complete(pchan->timeout * 1000, chan);
/* Clear out the descriptors we just ran. */
mxs_dma_finish(chan, &tmp_desc_list);
}
/*
- * Initialize the DMA hardware
+ * Execute a continuously running circular DMA descriptor.
+ * NOTE: This is not intended for general use, but rather
+ * for the LCD driver in Smart-LCD mode. It allows
+ * continuous triggering of the RUN bit there.
*/
-void mxs_dma_init(void)
+void mxs_dma_circ_start(int chan, struct mxs_dma_desc *pdesc)
{
struct mxs_apbh_regs *apbh_regs =
(struct mxs_apbh_regs *)MXS_APBH_BASE;
+ mxs_dma_flush_desc(pdesc);
+
+ mxs_dma_enable_irq(chan, 1);
+
+ writel(mxs_dma_cmd_address(pdesc),
+ &apbh_regs->ch[chan].hw_apbh_ch_nxtcmdar);
+ writel(1, &apbh_regs->ch[chan].hw_apbh_ch_sema);
+ writel(1 << (chan + APBH_CTRL0_CLKGATE_CHANNEL_OFFSET),
+ &apbh_regs->hw_apbh_ctrl0_clr);
+}
+
+/*
+ * Initialize the DMA hardware
+ */
+void mxs_dma_init(void)
+{
mxs_reset_block(&apbh_regs->hw_apbh_ctrl0_reg);
#ifdef CONFIG_APBH_DMA_BURST8