aboutsummaryrefslogblamecommitdiffstats
path: root/arch/arm/mach-ep93xx/dma-m2p.c
blob: dbcac9c40a28883df558565be3c7e52c37426053 (plain) (tree)


































                                                                          
                     




















































































































































































































































































































































































                                                                               
/*
 * arch/arm/mach-ep93xx/dma-m2p.c
 * M2P DMA handling for Cirrus EP93xx chips.
 *
 * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
 * Copyright (C) 2006 Applied Data Systems
 *
 * Copyright (C) 2009 Ryan Mallon <ryan@bluewatersys.com>
 *
 * 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.
 */

/*
 * On the EP93xx chip the following peripherals my be allocated to the 10
 * Memory to Internal Peripheral (M2P) channels (5 transmit + 5 receive).
 *
 *	I2S	contains 3 Tx and 3 Rx DMA Channels
 *	AAC	contains 3 Tx and 3 Rx DMA Channels
 *	UART1	contains 1 Tx and 1 Rx DMA Channels
 *	UART2	contains 1 Tx and 1 Rx DMA Channels
 *	UART3	contains 1 Tx and 1 Rx DMA Channels
 *	IrDA	contains 1 Tx and 1 Rx DMA Channels
 *
 * SSP and IDE use the Memory to Memory (M2M) channels and are not covered
 * with this implementation.
 */

#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/io.h>

#include <mach/dma.h>
#include <mach/hardware.h>

#define M2P_CONTROL			0x00
#define  M2P_CONTROL_STALL_IRQ_EN	(1 << 0)
#define  M2P_CONTROL_NFB_IRQ_EN		(1 << 1)
#define  M2P_CONTROL_ERROR_IRQ_EN	(1 << 3)
#define  M2P_CONTROL_ENABLE		(1 << 4)
#define M2P_INTERRUPT			0x04
#define  M2P_INTERRUPT_STALL		(1 << 0)
#define  M2P_INTERRUPT_NFB		(1 << 1)
#define  M2P_INTERRUPT_ERROR		(1 << 3)
#define M2P_PPALLOC			0x08
#define M2P_STATUS			0x0c
#define M2P_REMAIN			0x14
#define M2P_MAXCNT0			0x20
#define M2P_BASE0			0x24
#define M2P_MAXCNT1			0x30
#define M2P_BASE1			0x34

#define STATE_IDLE	0	/* Channel is inactive.  */
#define STATE_STALL	1	/* Channel is active, no buffers pending.  */
#define STATE_ON	2	/* Channel is active, one buffer pending.  */
#define STATE_NEXT	3	/* Channel is active, two buffers pending.  */

struct m2p_channel {
	char				*name;
	void __iomem			*base;
	int				irq;

	struct clk			*clk;
	spinlock_t			lock;

	void				*client;
	unsigned			next_slot:1;
	struct ep93xx_dma_buffer	*buffer_xfer;
	struct ep93xx_dma_buffer	*buffer_next;
	struct list_head		buffers_pending;
};

static struct m2p_channel m2p_rx[] = {
	{"m2p1", EP93XX_DMA_BASE + 0x0040, IRQ_EP93XX_DMAM2P1},
	{"m2p3", EP93XX_DMA_BASE + 0x00c0, IRQ_EP93XX_DMAM2P3},
	{"m2p5", EP93XX_DMA_BASE + 0x0200, IRQ_EP93XX_DMAM2P5},
	{"m2p7", EP93XX_DMA_BASE + 0x0280, IRQ_EP93XX_DMAM2P7},
	{"m2p9", EP93XX_DMA_BASE + 0x0300, IRQ_EP93XX_DMAM2P9},
	{NULL},
};

static struct m2p_channel m2p_tx[] = {
	{"m2p0", EP93XX_DMA_BASE + 0x0000, IRQ_EP93XX_DMAM2P0},
	{"m2p2", EP93XX_DMA_BASE + 0x0080, IRQ_EP93XX_DMAM2P2},
	{"m2p4", EP93XX_DMA_BASE + 0x0240, IRQ_EP93XX_DMAM2P4},
	{"m2p6", EP93XX_DMA_BASE + 0x02c0, IRQ_EP93XX_DMAM2P6},
	{"m2p8", EP93XX_DMA_BASE + 0x0340, IRQ_EP93XX_DMAM2P8},
	{NULL},
};

static void feed_buf(struct m2p_channel *ch, struct ep93xx_dma_buffer *buf)
{
	if (ch->next_slot == 0) {
		writel(buf->size, ch->base + M2P_MAXCNT0);
		writel(buf->bus_addr, ch->base + M2P_BASE0);
	} else {
		writel(buf->size, ch->base + M2P_MAXCNT1);
		writel(buf->bus_addr, ch->base + M2P_BASE1);
	}
	ch->next_slot ^= 1;
}

static void choose_buffer_xfer(struct m2p_channel *ch)
{
	struct ep93xx_dma_buffer *buf;

	ch->buffer_xfer = NULL;
	if (!list_empty(&ch->buffers_pending)) {
		buf = list_entry(ch->buffers_pending.next,
				 struct ep93xx_dma_buffer, list);
		list_del(&buf->list);
		feed_buf(ch, buf);
		ch->buffer_xfer = buf;
	}
}

static void choose_buffer_next(struct m2p_channel *ch)
{
	struct ep93xx_dma_buffer *buf;

	ch->buffer_next = NULL;
	if (!list_empty(&ch->buffers_pending)) {
		buf = list_entry(ch->buffers_pending.next,
				 struct ep93xx_dma_buffer, list);
		list_del(&buf->list);
		feed_buf(ch, buf);
		ch->buffer_next = buf;
	}
}

static inline void m2p_set_control(struct m2p_channel *ch, u32 v)
{
	/*
	 * The control register must be read immediately after being written so
	 * that the internal state machine is correctly updated. See the ep93xx
	 * users' guide for details.
	 */
	writel(v, ch->base + M2P_CONTROL);
	readl(ch->base + M2P_CONTROL);
}

static inline int m2p_channel_state(struct m2p_channel *ch)
{
	return (readl(ch->base + M2P_STATUS) >> 4) & 0x3;
}

static irqreturn_t m2p_irq(int irq, void *dev_id)
{
	struct m2p_channel *ch = dev_id;
	struct ep93xx_dma_m2p_client *cl;
	u32 irq_status, v;
	int error = 0;

	cl = ch->client;

	spin_lock(&ch->lock);
	irq_status = readl(ch->base + M2P_INTERRUPT);

	if (irq_status & M2P_INTERRUPT_ERROR) {
		writel(M2P_INTERRUPT_ERROR, ch->base + M2P_INTERRUPT);
		error = 1;
	}

	if ((irq_status & (M2P_INTERRUPT_STALL | M2P_INTERRUPT_NFB)) == 0) {
		spin_unlock(&ch->lock);
		return IRQ_NONE;
	}

	switch (m2p_channel_state(ch)) {
	case STATE_IDLE:
		pr_crit("m2p_irq: dma interrupt without a dma buffer\n");
		BUG();
		break;

	case STATE_STALL:
		cl->buffer_finished(cl->cookie, ch->buffer_xfer, 0, error);
		if (ch->buffer_next != NULL) {
			cl->buffer_finished(cl->cookie, ch->buffer_next,
					    0, error);
		}
		choose_buffer_xfer(ch);
		choose_buffer_next(ch);
		if (ch->buffer_xfer != NULL)
			cl->buffer_started(cl->cookie, ch->buffer_xfer);
		break;

	case STATE_ON:
		cl->buffer_finished(cl->cookie, ch->buffer_xfer, 0, error);
		ch->buffer_xfer = ch->buffer_next;
		choose_buffer_next(ch);
		cl->buffer_started(cl->cookie, ch->buffer_xfer);
		break;

	case STATE_NEXT:
		pr_crit("m2p_irq: dma interrupt while next\n");
		BUG();
		break;
	}

	v = readl(ch->base + M2P_CONTROL) & ~(M2P_CONTROL_STALL_IRQ_EN |
					      M2P_CONTROL_NFB_IRQ_EN);
	if (ch->buffer_xfer != NULL)
		v |= M2P_CONTROL_STALL_IRQ_EN;
	if (ch->buffer_next != NULL)
		v |= M2P_CONTROL_NFB_IRQ_EN;
	m2p_set_control(ch, v);

	spin_unlock(&ch->lock);
	return IRQ_HANDLED;
}

static struct m2p_channel *find_free_channel(struct ep93xx_dma_m2p_client *cl)
{
	struct m2p_channel *ch;
	int i;

	if (cl->flags & EP93XX_DMA_M2P_RX)
		ch = m2p_rx;
	else
		ch = m2p_tx;

	for (i = 0; ch[i].base; i++) {
		struct ep93xx_dma_m2p_client *client;

		client = ch[i].client;
		if (client != NULL) {
			int port;

			port = cl->flags & EP93XX_DMA_M2P_PORT_MASK;
			if (port == (client->flags &
				     EP93XX_DMA_M2P_PORT_MASK)) {
				pr_warning("DMA channel already used by %s\n",
					   cl->name ? : "unknown client");
				return ERR_PTR(-EBUSY);
			}
		}
	}

	for (i = 0; ch[i].base; i++) {
		if (ch[i].client == NULL)
			return ch + i;
	}

	pr_warning("No free DMA channel for %s\n",
		   cl->name ? : "unknown client");
	return ERR_PTR(-ENODEV);
}

static void channel_enable(struct m2p_channel *ch)
{
	struct ep93xx_dma_m2p_client *cl = ch->client;
	u32 v;

	clk_enable(ch->clk);

	v = cl->flags & EP93XX_DMA_M2P_PORT_MASK;
	writel(v, ch->base + M2P_PPALLOC);

	v = cl->flags & EP93XX_DMA_M2P_ERROR_MASK;
	v |= M2P_CONTROL_ENABLE | M2P_CONTROL_ERROR_IRQ_EN;
	m2p_set_control(ch, v);
}

static void channel_disable(struct m2p_channel *ch)
{
	u32 v;

	v = readl(ch->base + M2P_CONTROL);
	v &= ~(M2P_CONTROL_STALL_IRQ_EN | M2P_CONTROL_NFB_IRQ_EN);
	m2p_set_control(ch, v);

	while (m2p_channel_state(ch) == STATE_ON)
		cpu_relax();

	m2p_set_control(ch, 0x0);

	while (m2p_channel_state(ch) == STATE_STALL)
		cpu_relax();

	clk_disable(ch->clk);
}

int ep93xx_dma_m2p_client_register(struct ep93xx_dma_m2p_client *cl)
{
	struct m2p_channel *ch;
	int err;

	ch = find_free_channel(cl);
	if (IS_ERR(ch))
		return PTR_ERR(ch);

	err = request_irq(ch->irq, m2p_irq, 0, cl->name ? : "dma-m2p", ch);
	if (err)
		return err;

	ch->client = cl;
	ch->next_slot = 0;
	ch->buffer_xfer = NULL;
	ch->buffer_next = NULL;
	INIT_LIST_HEAD(&ch->buffers_pending);

	cl->channel = ch;

	channel_enable(ch);

	return 0;
}
EXPORT_SYMBOL_GPL(ep93xx_dma_m2p_client_register);

void ep93xx_dma_m2p_client_unregister(struct ep93xx_dma_m2p_client *cl)
{
	struct m2p_channel *ch = cl->channel;

	channel_disable(ch);
	free_irq(ch->irq, ch);
	ch->client = NULL;
}
EXPORT_SYMBOL_GPL(ep93xx_dma_m2p_client_unregister);

void ep93xx_dma_m2p_submit(struct ep93xx_dma_m2p_client *cl,
			   struct ep93xx_dma_buffer *buf)
{
	struct m2p_channel *ch = cl->channel;
	unsigned long flags;
	u32 v;

	spin_lock_irqsave(&ch->lock, flags);
	v = readl(ch->base + M2P_CONTROL);
	if (ch->buffer_xfer == NULL) {
		ch->buffer_xfer = buf;
		feed_buf(ch, buf);
		cl->buffer_started(cl->cookie, buf);

		v |= M2P_CONTROL_STALL_IRQ_EN;
		m2p_set_control(ch, v);

	} else if (ch->buffer_next == NULL) {
		ch->buffer_next = buf;
		feed_buf(ch, buf);

		v |= M2P_CONTROL_NFB_IRQ_EN;
		m2p_set_control(ch, v);
	} else {
		list_add_tail(&buf->list, &ch->buffers_pending);
	}
	spin_unlock_irqrestore(&ch->lock, flags);
}
EXPORT_SYMBOL_GPL(ep93xx_dma_m2p_submit);

void ep93xx_dma_m2p_submit_recursive(struct ep93xx_dma_m2p_client *cl,
				     struct ep93xx_dma_buffer *buf)
{
	struct m2p_channel *ch = cl->channel;

	list_add_tail(&buf->list, &ch->buffers_pending);
}
EXPORT_SYMBOL_GPL(ep93xx_dma_m2p_submit_recursive);

void ep93xx_dma_m2p_flush(struct ep93xx_dma_m2p_client *cl)
{
	struct m2p_channel *ch = cl->channel;

	channel_disable(ch);
	ch->next_slot = 0;
	ch->buffer_xfer = NULL;
	ch->buffer_next = NULL;
	INIT_LIST_HEAD(&ch->buffers_pending);
	channel_enable(ch);
}
EXPORT_SYMBOL_GPL(ep93xx_dma_m2p_flush);

static int init_channel(struct m2p_channel *ch)
{
	ch->clk = clk_get(NULL, ch->name);
	if (IS_ERR(ch->clk))
		return PTR_ERR(ch->clk);

	spin_lock_init(&ch->lock);
	ch->client = NULL;

	return 0;
}

static int __init ep93xx_dma_m2p_init(void)
{
	int i;
	int ret;

	for (i = 0; m2p_rx[i].base; i++) {
		ret = init_channel(m2p_rx + i);
		if (ret)
			return ret;
	}

	for (i = 0; m2p_tx[i].base; i++) {
		ret = init_channel(m2p_tx + i);
		if (ret)
			return ret;
	}

	pr_info("M2P DMA subsystem initialized\n");
	return 0;
}
arch_initcall(ep93xx_dma_m2p_init);