[PATCH] PPC440EP (405GPr) 2.4.20 (2.6) scatter/gather DMA
Roger Larsson
roger.larsson at norran.net
Sun Jul 31 02:46:42 EST 2005
This patch has been tested on PPC440EP,
an earlier, almost identical, version was used on 405GPr
(on a later Linux release).
Both should work.
I now see that some of these changes maybe should not be
done... (removal of inline, export of ppc4xx_set_sg_addr)
/RogerL
--- linux-2.4.20_mvl31_AR2/arch/ppc/kernel/ppc4xx_sgdma.c.org 2005-07-01
13:10:27.000000000 +0200
+++ linux-2.4.20_mvl31_AR2/arch/ppc/kernel/ppc4xx_sgdma.c 2005-07-12
11:11:16.000000000 +0200
@@ -4,11 +4,17 @@
* IBM PPC4xx DMA engine scatter/gather library
*
* Copyright 2002-2003 MontaVista Software Inc.
+ * Copyright 2005 Optronic dp AB
*
* Cleaned by Matt Porter <mporter at mvista.com>
*
* Original code by Armin Kuster <akuster at mvista.com>
* and Pete Popov <ppopov at mvista.com>
+ *
+ * Use of kmalloc, good for short and very long lists
+ * End of Transfer termination and residue
+ * Roger Larsson <roger.larsson at optronic.se> and
+ * Ronnie Hedlund, DataDuctus AB
*
* 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
@@ -19,7 +25,7 @@
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
-
+
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/mm.h>
@@ -31,7 +37,7 @@
#include <asm/io.h>
#include <asm/ppc4xx_dma.h>
-static __inline__ void
+void
ppc4xx_set_sg_addr(int dmanr, phys_addr_t sg_addr)
{
switch (dmanr) {
@@ -73,7 +79,7 @@
* memory to peripheral: set dst_addr to NULL,
* peripheral to memory: set src_addr to NULL.
*/
-static __inline__ int
+int
ppc4xx_add_dma_sgl(sgl_handle_t handle, phys_addr_t src_addr, phys_addr_t
dst_addr,
unsigned int count)
{
@@ -125,18 +131,19 @@
}
#endif
- if ((unsigned) (psgl->ptail + 1) >= ((unsigned) psgl + SGL_LIST_SIZE)) {
- printk("sgl handle out of memory \n");
- return DMA_STATUS_OUT_OF_MEMORY;
- }
-
- if (!psgl->ptail) {
- psgl->phead = (ppc_sgl_t *)
- ((unsigned) psgl + sizeof (sgl_list_info_t));
- psgl->ptail = psgl->phead;
- } else {
- psgl->ptail->next = virt_to_bus(psgl->ptail + 1);
- psgl->ptail++;
+ /* dynamic alloc each list element */
+ {
+ ppc_sgl_t *sgl_el = kmalloc(sizeof(ppc_sgl_t), GFP_KERNEL|GFP_DMA);
+ if (!sgl_el)
+ return DMA_STATUS_OUT_OF_MEMORY;
+
+ if (!psgl->phead) { /* list was empty */
+ psgl->phead = sgl_el;
+ } else { /* not empty, tail exists */
+ psgl->ptail->next = virt_to_phys(sgl_el);
+ dma_cache_wback((unsigned long)psgl->ptail, sizeof(ppc_sgl_t));
+ }
+ psgl->ptail = sgl_el;
}
psgl->ptail->control = psgl->control;
@@ -144,7 +151,8 @@
psgl->ptail->dst_addr = dst_addr;
psgl->ptail->control_count = (count >> p_dma_ch->shift) |
psgl->sgl_control;
- psgl->ptail->next = (uint32_t) NULL;
+ psgl->ptail->next = virt_to_phys(NULL);
+ dma_cache_wback((unsigned long)psgl->ptail, sizeof(ppc_sgl_t)); /* handled
later, skip this one? */
return DMA_STATUS_GOOD;
}
@@ -152,7 +160,8 @@
/*
* Enable (start) the DMA described by the sgl handle.
*/
-static __inline__ void
+
+void
ppc4xx_enable_dma_sgl(sgl_handle_t handle)
{
sgl_list_info_t *psgl = (sgl_list_info_t *) handle;
@@ -173,9 +182,18 @@
p_dma_ch = &dma_channels[psgl->dmanr];
psgl->ptail->control_count &= ~SG_LINK; /* make this the last dscrptr */
+ if (p_dma_ch->int_enable)
+ {
+ /* Require Terminal Count interrupt on last */
+ psgl->ptail->control_count |= SG_TCI_ENABLE;
+ }
+
+ /* No more changes to tail object allowed */
+ dma_cache_wback((unsigned long)psgl->ptail, sizeof(ppc_sgl_t));
+
sg_command = mfdcr(DCRN_ASGC);
- ppc4xx_set_sg_addr(psgl->dmanr, virt_to_bus(psgl->phead));
+ ppc4xx_set_sg_addr(psgl->dmanr, virt_to_phys(psgl->phead));
switch (psgl->dmanr) {
case 0:
@@ -193,37 +211,14 @@
default:
printk("ppc4xx_enable_dma_sgl: bad channel: %d\n", psgl->dmanr);
}
-
-#if 0 /* debug */
- printk("\n\nppc4xx_enable_dma_sgl at dma_addr 0x%x\n",
- virt_to_bus(psgl->phead));
- {
- ppc_sgl_t *pnext, *sgl_addr;
-
- pnext = psgl->phead;
- while (pnext) {
- printk("dma descriptor at 0x%x, dma addr 0x%x\n",
- (unsigned) pnext, (unsigned) virt_to_bus(pnext));
- printk
- ("control 0x%x src 0x%x dst 0x%x c_count 0x%x, next 0x%x\n",
- (unsigned) pnext->control,
- (unsigned) pnext->src_addr,
- (unsigned) pnext->dst_addr,
- (unsigned) pnext->control_count,
- (unsigned) pnext->next);
-
- (unsigned) pnext = bus_to_virt(pnext->next);
- }
- printk("sg_command 0x%x\n", sg_command);
- }
-#endif
+
mtdcr(DCRN_ASGC, sg_command); /* start transfer */
}
/*
* Halt an active scatter/gather DMA operation.
*/
-static __inline__ void
+void
ppc4xx_disable_dma_sgl(sgl_handle_t handle)
{
sgl_list_info_t *psgl = (sgl_list_info_t *) handle;
@@ -265,8 +260,10 @@
* the sgl descriptor where the DMA stopped.
*
* An sgl transfer must NOT be active when this function is called.
+ * Note: Make sure ppc4xx_disable_dma_sgl was called before returning from
+ * interrupt handler (TSn, CSn will not disable the sgl)!
*/
-static __inline__ int
+int
ppc4xx_get_dma_sgl_residue(sgl_handle_t handle, phys_addr_t * src_addr,
phys_addr_t * dst_addr)
{
@@ -286,19 +283,19 @@
switch (psgl->dmanr) {
case 0:
- sgl_addr = (ppc_sgl_t *) bus_to_virt(mfdcr(DCRN_ASG0));
+ sgl_addr = (ppc_sgl_t *) phys_to_virt(mfdcr(DCRN_ASG0));
count_left = mfdcr(DCRN_DMACT0);
break;
case 1:
- sgl_addr = (ppc_sgl_t *) bus_to_virt(mfdcr(DCRN_ASG1));
+ sgl_addr = (ppc_sgl_t *) phys_to_virt(mfdcr(DCRN_ASG1));
count_left = mfdcr(DCRN_DMACT1);
break;
case 2:
- sgl_addr = (ppc_sgl_t *) bus_to_virt(mfdcr(DCRN_ASG2));
+ sgl_addr = (ppc_sgl_t *) phys_to_virt(mfdcr(DCRN_ASG2));
count_left = mfdcr(DCRN_DMACT2);
break;
case 3:
- sgl_addr = (ppc_sgl_t *) bus_to_virt(mfdcr(DCRN_ASG3));
+ sgl_addr = (ppc_sgl_t *) phys_to_virt(mfdcr(DCRN_ASG3));
count_left = mfdcr(DCRN_DMACT3);
break;
default:
@@ -307,54 +304,34 @@
}
if (!sgl_addr) {
- printk("ppc4xx_get_dma_sgl_residue: sgl addr register is null\n");
- goto error;
+ /* Last in list */
+ return count_left;
}
- pnext = psgl->phead;
- while (pnext &&
- ((unsigned) pnext < ((unsigned) psgl + SGL_LIST_SIZE) &&
- (pnext != sgl_addr))
- ) {
- pnext++;
- }
-
- if (pnext == sgl_addr) { /* found the sgl descriptor */
-
- *src_addr = pnext->src_addr;
- *dst_addr = pnext->dst_addr;
-
- /*
- * Now search the remaining descriptors and add their count.
- * We already have the remaining count from this descriptor in
- * count_left.
- */
- pnext++;
-
- while ((pnext != psgl->ptail) &&
- ((unsigned) pnext < ((unsigned) psgl + SGL_LIST_SIZE))
- ) {
- count_left += pnext->control_count & SG_COUNT_MASK;
- }
+ pnext = sgl_addr; /* sgl_addr is next to be loaded */
- if (pnext != psgl->ptail) { /* should never happen */
- printk
- ("ppc4xx_get_dma_sgl_residue error (1) psgl->ptail 0x%x handle
0x%x\n",
- (unsigned int) psgl->ptail, (unsigned int) handle);
- goto error;
- }
+ /*
+ * Why this strange interface? return nothing or sgl_addr instead...
+ * (please check for null pointers)
+ */
+ *src_addr = pnext->src_addr;
+ *dst_addr = pnext->dst_addr;
- /* success */
- p_dma_ch = &dma_channels[psgl->dmanr];
- return (count_left << p_dma_ch->shift); /* count in bytes */
+ /*
+ * Now search the remaining descriptors and add their count.
+ * We already have the remaining count from this descriptor in
+ * count_left.
+ */
+
+ while (pnext) {
+ count_left += pnext->control_count & SG_COUNT_MASK;
+ pnext = phys_to_virt(pnext->next);
+ }
- } else {
- /* this shouldn't happen */
- printk
- ("get_dma_sgl_residue, unable to match current address 0x%x, handle
0x%x\n",
- (unsigned int) sgl_addr, (unsigned int) handle);
- }
+ /* success */
+ p_dma_ch = &dma_channels[psgl->dmanr];
+ return (count_left << p_dma_ch->shift); /* count in bytes */
error:
*src_addr = (phys_addr_t) NULL;
@@ -369,7 +346,7 @@
*
* This function should only be called when the DMA is not active.
*/
-static __inline__ int
+int
ppc4xx_delete_dma_sgl_element(sgl_handle_t handle, phys_addr_t *
src_dma_addr,
phys_addr_t * dst_dma_addr)
{
@@ -385,7 +362,7 @@
}
if (!psgl->phead) {
- printk("ppc4xx_delete_sgl_element: sgl list empty\n");
+ /* printk("ppc4xx_delete_sgl_element: sgl list empty\n"); - not an error */
*src_dma_addr = (phys_addr_t) NULL;
*dst_dma_addr = (phys_addr_t) NULL;
return DMA_STATUS_SGL_LIST_EMPTY;
@@ -396,10 +373,13 @@
if (psgl->phead == psgl->ptail) {
/* last descriptor on the list */
+ kfree(psgl->phead);
psgl->phead = NULL;
psgl->ptail = NULL;
} else {
- psgl->phead++;
+ ppc_sgl_t *next = phys_to_virt(psgl->phead->next);
+ kfree(psgl->phead);
+ psgl->phead = next;
}
return DMA_STATUS_GOOD;
@@ -411,12 +391,7 @@
* describes a scatter/gather list.
*
* A handle is returned in "handle" which the driver should save in order
to
- * be able to access this list later. A chunk of memory will be allocated
- * to be used by the API for internal management purposes, including
managing
- * the sg list and allocating memory for the sgl descriptors. One page
should
- * be more than enough for that purpose. Perhaps it's a bit wasteful to
use
- * a whole page for a single sg list, but most likely there will be only
one
- * sg list per channel.
+ * be able to access this list later.
*
* Interrupt notes:
* Each sgl descriptor has a copy of the DMA control word which the DMA
engine
@@ -433,15 +408,15 @@
* however, only the last descriptor will be setup to interrupt. Thus, an
* interrupt will occur (if interrupts are enabled) only after the complete
* sgl transfer is done.
+ * End of Transfer Interrupt needs to be enabled in all descriptors, since
it
+ * is impossible to know which one will be the last...
*/
int
ppc4xx_alloc_dma_handle(sgl_handle_t * phandle, unsigned int mode, unsigned
int dmanr)
{
- sgl_list_info_t *psgl;
- dma_addr_t dma_addr;
+ sgl_list_info_t *psgl = NULL;
ppc_dma_ch_t *p_dma_ch = &dma_channels[dmanr];
uint32_t sg_command;
- void *ret;
if (dmanr >= MAX_PPC4xx_DMA_CHANNELS) {
printk("ppc4xx_alloc_dma_handle: invalid channel 0x%x\n", dmanr);
@@ -453,19 +428,15 @@
return DMA_STATUS_NULL_POINTER;
}
- /* Get a page of memory, which is zeroed out by consistent_alloc() */
- ret = consistent_alloc(GFP_KERNEL, DMA_PPC4xx_SIZE, &dma_addr);
- if (ret != NULL) {
- memset(ret, 0, DMA_PPC4xx_SIZE);
- psgl = (sgl_list_info_t *) ret;
- }
-
+ /* Get memory for the listinfo struct */
+ psgl = kmalloc(sizeof(sgl_list_info_t), GFP_KERNEL);
if (psgl == NULL) {
*phandle = (sgl_handle_t) NULL;
return DMA_STATUS_OUT_OF_MEMORY;
}
-
- psgl->dma_addr = dma_addr;
+ memset(psgl, 0, sizeof(sgl_list_info_t));
+
+ /* dma_addr is unused now */
psgl->dmanr = dmanr;
/*
@@ -479,7 +450,9 @@
psgl->control &= ~(DMA_TM_MASK | DMA_TD);
/* Save control word and mode */
psgl->control |= (mode | DMA_CE_ENABLE);
-
+ /* PPC Errata? DMA else ignore count on first in list */
+ psgl->control |= SET_DMA_TCE(1);
+
/* In MM mode, we must set ETD/TCE */
if (mode == DMA_MODE_MM)
psgl->control |= DMA_ETD_OUTPUT | DMA_TCE_ENABLE;
@@ -514,13 +487,15 @@
/* Enable SGL control access */
mtdcr(DCRN_ASGC, sg_command);
- psgl->sgl_control = SG_ERI_ENABLE | SG_LINK;
+ psgl->sgl_control = SG_LINK;
if (p_dma_ch->int_enable) {
if (p_dma_ch->tce_enable)
+ {
+ /* reuse as Terminal Count Interrupt Enable on all descr. */
psgl->sgl_control |= SG_TCI_ENABLE;
- else
- psgl->sgl_control |= SG_ETI_ENABLE;
+ }
+ psgl->sgl_control |= SG_ERI_ENABLE | SG_ETI_ENABLE;
}
*phandle = (sgl_handle_t) psgl;
@@ -539,19 +514,19 @@
if (!handle) {
printk("ppc4xx_free_dma_handle: got NULL\n");
return;
- } else if (psgl->phead) {
- printk("ppc4xx_free_dma_handle: list not empty\n");
- return;
- } else if (!psgl->dma_addr) { /* should never happen */
- printk("ppc4xx_free_dma_handle: no dma address\n");
- return;
+ } else if (psgl->phead) { /* free list here, why do it externaly? */
+ phys_addr_t dummy;
+ while (ppc4xx_delete_dma_sgl_element(handle, &dummy, &dummy) ==
DMA_STATUS_GOOD)
+ /* NOOP */;
+ /* printk("ppc4xx_free_dma_handle: list not empty\n"); */
}
- consistent_free((void *) psgl);
+ kfree((void *) psgl);
}
EXPORT_SYMBOL(ppc4xx_alloc_dma_handle);
EXPORT_SYMBOL(ppc4xx_free_dma_handle);
+EXPORT_SYMBOL(ppc4xx_set_sg_addr);
EXPORT_SYMBOL(ppc4xx_add_dma_sgl);
EXPORT_SYMBOL(ppc4xx_delete_dma_sgl_element);
EXPORT_SYMBOL(ppc4xx_enable_dma_sgl);
--- linux-2.4.20_mvl31_AR2/include/asm-ppc/ppc4xx_dma.h.org 2005-07-12
14:08:18.000000000 +0200
+++ linux-2.4.20_mvl31_AR2/include/asm-ppc/ppc4xx_dma.h 2005-07-27
13:09:39.000000000 +0200
@@ -67,7 +67,7 @@
* DMA Channel Control Registers
*/
-#ifdef CONFIG_44x
+#if defined(CONFIG_44x) && !defined(CONFIG_440EP)
#define PPC4xx_DMA_64BIT
#define DMA_CR_OFFSET 1
#else
@@ -183,9 +183,6 @@
#ifdef CONFIG_PPC4xx_EDMA
-#define SGL_LIST_SIZE 4096
-#define DMA_PPC4xx_SIZE SGL_LIST_SIZE
-
#define SET_DMA_PRIORITY(x) (((x)&0x3)<<(6-DMA_CR_OFFSET)) /* DMA Channel
Priority */
#define DMA_PRIORITY_MASK SET_DMA_PRIORITY(3)
#define PRIORITY_LOW 0
@@ -531,8 +528,8 @@
#else
typedef struct {
uint32_t control;
- phys_addr_t src_addr;
- phys_addr_t dst_addr;
+ uint32_t src_addr;
+ uint32_t dst_addr;
uint32_t control_count;
uint32_t next;
} ppc_sgl_t;
@@ -567,6 +564,12 @@
extern unsigned int ppc4xx_get_peripheral_width(unsigned int);
extern int ppc4xx_alloc_dma_handle(sgl_handle_t *, unsigned int, unsigned
int);
extern void ppc4xx_free_dma_handle(sgl_handle_t);
+extern void ppc4xx_set_sg_addr(int dmanr, phys_addr_t sg_addr);
+extern int ppc4xx_add_dma_sgl(sgl_handle_t handle, phys_addr_t src_addr,
phys_addr_t dst_addr, unsigned int count);
+extern void ppc4xx_enable_dma_sgl(sgl_handle_t handle);
+extern void ppc4xx_disable_dma_sgl(sgl_handle_t handle);
+extern int ppc4xx_get_dma_sgl_residue(sgl_handle_t handle, phys_addr_t *
src_addr, phys_addr_t * dst_addr);
+extern int ppc4xx_delete_dma_sgl_element(sgl_handle_t handle, phys_addr_t *
src_dma_addr, phys_addr_t * dst_dma_addr);
extern int ppc4xx_get_dma_status(void);
extern void ppc4xx_set_src_addr(int dmanr, phys_addr_t src_addr);
extern void ppc4xx_set_dst_addr(int dmanr, phys_addr_t dst_addr);
More information about the Linuxppc-embedded
mailing list