* 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 mx28_apbh_regs *apbh_regs =
- (struct mx28_apbh_regs *)MXS_APBH_BASE;
uint32_t tmp;
int ret;
return tmp;
}
+#ifndef CONFIG_SYS_DCACHE_OFF
+static void mxs_dma_flush_desc(struct mxs_dma_desc *desc)
+{
+ uint32_t addr;
+ uint32_t size;
+
+ addr = (uint32_t)desc;
+ size = roundup(sizeof(struct mxs_dma_desc), MXS_DMA_ALIGNMENT);
+
+ flush_dcache_range(addr, addr + size);
+}
+#else
+static inline void mxs_dma_flush_desc(struct mxs_dma_desc *desc)
+{
+}
+#endif
+
/*
* Enable a DMA channel.
*
*/
static int mxs_dma_enable(int channel)
{
- struct mx28_apbh_regs *apbh_regs =
- (struct mx28_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 mx28_apbh_regs *apbh_regs =
- (struct mx28_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 mx28_apbh_regs *apbh_regs =
- (struct mx28_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 mx28_apbh_regs *apbh_regs =
- (struct mx28_apbh_regs *)MXS_APBH_BASE;
int ret;
ret = mxs_dma_validate_chan(channel);
*/
static int mxs_dma_ack_irq(int channel)
{
- struct mx28_apbh_regs *apbh_regs =
- (struct mx28_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);
* The channel will NOT be released if it's marked "busy" (see
* mxs_dma_enable()).
*/
-static int mxs_dma_release(int channel)
+int mxs_dma_release(int channel)
{
struct mxs_dma_chan *pchan;
int ret;
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
*/
struct mxs_dma_desc *mxs_dma_desc_alloc(void)
{
struct mxs_dma_desc *pdesc;
+ uint32_t size;
- pdesc = memalign(MXS_DMA_ALIGNMENT, sizeof(struct mxs_dma_desc));
+ size = roundup(sizeof(struct mxs_dma_desc), MXS_DMA_ALIGNMENT);
+ pdesc = memalign(MXS_DMA_ALIGNMENT, size);
if (pdesc == NULL)
return NULL;
last->cmd.next = mxs_dma_cmd_address(pdesc);
last->cmd.data |= MXS_DMA_DESC_CHAIN;
+
+ mxs_dma_flush_desc(last);
}
pdesc->flags |= MXS_DMA_DESC_READY;
if (pdesc->flags & MXS_DMA_DESC_FIRST)
pchan->pending_num++;
list_add_tail(&pdesc->node, &pchan->active);
+ mxs_dma_flush_desc(pdesc);
+
return ret;
}
*/
static int mxs_dma_wait_complete(uint32_t timeout, unsigned int chan)
{
- struct mx28_apbh_regs *apbh_regs =
- (struct mx28_apbh_regs *)MXS_APBH_BASE;
int ret;
ret = mxs_dma_validate_chan(chan);
if (ret)
return ret;
- if (mx28_wait_mask_set(&apbh_regs->hw_apbh_ctrl1_reg,
+ if (mxs_wait_mask_set(&apbh_regs->hw_apbh_ctrl1_reg,
1 << chan, timeout)) {
ret = -ETIMEDOUT;
mxs_dma_reset(chan);
*/
int mxs_dma_go(int chan)
{
- uint32_t timeout = 10000;
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.
*/
-int mxs_dma_init(void)
+void mxs_dma_circ_start(int chan, struct mxs_dma_desc *pdesc)
{
- struct mx28_apbh_regs *apbh_regs =
- (struct mx28_apbh_regs *)MXS_APBH_BASE;
- struct mxs_dma_chan *pchan;
- int ret, channel;
+ struct mxs_apbh_regs *apbh_regs =
+ (struct mxs_apbh_regs *)MXS_APBH_BASE;
- mx28_reset_block(&apbh_regs->hw_apbh_ctrl0_reg);
+ 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
writel(APBH_CTRL0_AHB_BURST8_EN,
writel(APBH_CTRL0_APB_BURST_EN,
&apbh_regs->hw_apbh_ctrl0_clr);
#endif
+}
- for (channel = 0; channel < MXS_MAX_DMA_CHANNELS; channel++) {
- pchan = mxs_dma_channels + channel;
- pchan->flags = MXS_DMA_FLAGS_VALID;
-
- ret = mxs_dma_request(channel);
+int mxs_dma_init_channel(int channel)
+{
+ struct mxs_dma_chan *pchan;
+ int ret;
- if (ret) {
- printf("MXS DMA: Can't acquire DMA channel %i\n",
- channel);
+ pchan = mxs_dma_channels + channel;
+ pchan->flags = MXS_DMA_FLAGS_VALID;
- goto err;
- }
+ ret = mxs_dma_request(channel);
- mxs_dma_reset(channel);
- mxs_dma_ack_irq(channel);
+ if (ret) {
+ printf("MXS DMA: Can't acquire DMA channel %i\n",
+ channel);
+ return ret;
}
- return 0;
+ mxs_dma_reset(channel);
+ mxs_dma_ack_irq(channel);
-err:
- while (--channel >= 0)
- mxs_dma_release(channel);
- return ret;
+ return 0;
}