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);