Skip to content

Commit

Permalink
C6X: memory management and DMA support
Browse files Browse the repository at this point in the history
Original port to early 2.6 kernel using TI COFF toolchain.
Brought up to date by Mark Salter <[email protected]>

The C6X architecture currently lacks an MMU so memory management is relatively
simple. There is no bus snooping between L2 and main memory but coherent DMA
memory is supported by making regions of main memory uncached. If such a region
is desired, it can be specified on the commandline with a "memdma=" argument.

Signed-off-by: Aurelien Jacquiot <[email protected]>
Signed-off-by: Mark Salter <[email protected]>
Acked-by: Arnd Bergmann <[email protected]>
  • Loading branch information
Aurelien Jacquiot authored and mosalter committed Oct 6, 2011
1 parent 041cadc commit 14aa7e8
Show file tree
Hide file tree
Showing 4 changed files with 500 additions and 0 deletions.
91 changes: 91 additions & 0 deletions arch/c6x/include/asm/dma-mapping.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Port on Texas Instruments TMS320C6x architecture
*
* Copyright (C) 2004, 2009, 2010, 2011 Texas Instruments Incorporated
* Author: Aurelien Jacquiot <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#ifndef _ASM_C6X_DMA_MAPPING_H
#define _ASM_C6X_DMA_MAPPING_H

#include <linux/dma-debug.h>
#include <asm-generic/dma-coherent.h>

#define dma_supported(d, m) 1

static inline int dma_set_mask(struct device *dev, u64 dma_mask)
{
if (!dev->dma_mask || !dma_supported(dev, dma_mask))
return -EIO;

*dev->dma_mask = dma_mask;

return 0;
}

/*
* DMA errors are defined by all-bits-set in the DMA address.
*/
static inline int dma_mapping_error(struct device *dev, dma_addr_t dma_addr)
{
return dma_addr == ~0;
}

extern dma_addr_t dma_map_single(struct device *dev, void *cpu_addr,
size_t size, enum dma_data_direction dir);

extern void dma_unmap_single(struct device *dev, dma_addr_t handle,
size_t size, enum dma_data_direction dir);

extern int dma_map_sg(struct device *dev, struct scatterlist *sglist,
int nents, enum dma_data_direction direction);

extern void dma_unmap_sg(struct device *dev, struct scatterlist *sglist,
int nents, enum dma_data_direction direction);

static inline dma_addr_t dma_map_page(struct device *dev, struct page *page,
unsigned long offset, size_t size,
enum dma_data_direction dir)
{
dma_addr_t handle;

handle = dma_map_single(dev, page_address(page) + offset, size, dir);

debug_dma_map_page(dev, page, offset, size, dir, handle, false);

return handle;
}

static inline void dma_unmap_page(struct device *dev, dma_addr_t handle,
size_t size, enum dma_data_direction dir)
{
dma_unmap_single(dev, handle, size, dir);

debug_dma_unmap_page(dev, handle, size, dir, false);
}

extern void dma_sync_single_for_cpu(struct device *dev, dma_addr_t handle,
size_t size, enum dma_data_direction dir);

extern void dma_sync_single_for_device(struct device *dev, dma_addr_t handle,
size_t size,
enum dma_data_direction dir);

extern void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg,
int nents, enum dma_data_direction dir);

extern void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
int nents, enum dma_data_direction dir);

extern void coherent_mem_init(u32 start, u32 size);
extern void *dma_alloc_coherent(struct device *, size_t, dma_addr_t *, gfp_t);
extern void dma_free_coherent(struct device *, size_t, void *, dma_addr_t);

#define dma_alloc_noncoherent(d, s, h, f) dma_alloc_coherent((d), (s), (h), (f))
#define dma_free_noncoherent(d, s, v, h) dma_free_coherent((d), (s), (v), (h))

#endif /* _ASM_C6X_DMA_MAPPING_H */
153 changes: 153 additions & 0 deletions arch/c6x/kernel/dma.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright (C) 2011 Texas Instruments Incorporated
* Author: Mark Salter <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/mm.h>
#include <linux/mm_types.h>
#include <linux/scatterlist.h>

#include <asm/cacheflush.h>

static void c6x_dma_sync(dma_addr_t handle, size_t size,
enum dma_data_direction dir)
{
unsigned long paddr = handle;

BUG_ON(!valid_dma_direction(dir));

switch (dir) {
case DMA_FROM_DEVICE:
L2_cache_block_invalidate(paddr, paddr + size);
break;
case DMA_TO_DEVICE:
L2_cache_block_writeback(paddr, paddr + size);
break;
case DMA_BIDIRECTIONAL:
L2_cache_block_writeback_invalidate(paddr, paddr + size);
break;
default:
break;
}
}

dma_addr_t dma_map_single(struct device *dev, void *ptr, size_t size,
enum dma_data_direction dir)
{
dma_addr_t addr = virt_to_phys(ptr);

c6x_dma_sync(addr, size, dir);

debug_dma_map_page(dev, virt_to_page(ptr),
(unsigned long)ptr & ~PAGE_MASK, size,
dir, addr, true);
return addr;
}
EXPORT_SYMBOL(dma_map_single);


void dma_unmap_single(struct device *dev, dma_addr_t handle,
size_t size, enum dma_data_direction dir)
{
c6x_dma_sync(handle, size, dir);

debug_dma_unmap_page(dev, handle, size, dir, true);
}
EXPORT_SYMBOL(dma_unmap_single);


int dma_map_sg(struct device *dev, struct scatterlist *sglist,
int nents, enum dma_data_direction dir)
{
struct scatterlist *sg;
int i;

for_each_sg(sglist, sg, nents, i)
sg->dma_address = dma_map_single(dev, sg_virt(sg), sg->length,
dir);

debug_dma_map_sg(dev, sglist, nents, nents, dir);

return nents;
}
EXPORT_SYMBOL(dma_map_sg);


void dma_unmap_sg(struct device *dev, struct scatterlist *sglist,
int nents, enum dma_data_direction dir)
{
struct scatterlist *sg;
int i;

for_each_sg(sglist, sg, nents, i)
dma_unmap_single(dev, sg_dma_address(sg), sg->length, dir);

debug_dma_unmap_sg(dev, sglist, nents, dir);
}
EXPORT_SYMBOL(dma_unmap_sg);

void dma_sync_single_for_cpu(struct device *dev, dma_addr_t handle,
size_t size, enum dma_data_direction dir)
{
c6x_dma_sync(handle, size, dir);

debug_dma_sync_single_for_cpu(dev, handle, size, dir);
}
EXPORT_SYMBOL(dma_sync_single_for_cpu);


void dma_sync_single_for_device(struct device *dev, dma_addr_t handle,
size_t size, enum dma_data_direction dir)
{
c6x_dma_sync(handle, size, dir);

debug_dma_sync_single_for_device(dev, handle, size, dir);
}
EXPORT_SYMBOL(dma_sync_single_for_device);


void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sglist,
int nents, enum dma_data_direction dir)
{
struct scatterlist *sg;
int i;

for_each_sg(sglist, sg, nents, i)
dma_sync_single_for_cpu(dev, sg_dma_address(sg),
sg->length, dir);

debug_dma_sync_sg_for_cpu(dev, sglist, nents, dir);
}
EXPORT_SYMBOL(dma_sync_sg_for_cpu);


void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sglist,
int nents, enum dma_data_direction dir)
{
struct scatterlist *sg;
int i;

for_each_sg(sglist, sg, nents, i)
dma_sync_single_for_device(dev, sg_dma_address(sg),
sg->length, dir);

debug_dma_sync_sg_for_device(dev, sglist, nents, dir);
}
EXPORT_SYMBOL(dma_sync_sg_for_device);


/* Number of entries preallocated for DMA-API debugging */
#define PREALLOC_DMA_DEBUG_ENTRIES (1 << 16)

static int __init dma_init(void)
{
dma_debug_init(PREALLOC_DMA_DEBUG_ENTRIES);

return 0;
}
fs_initcall(dma_init);
143 changes: 143 additions & 0 deletions arch/c6x/mm/dma-coherent.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* Port on Texas Instruments TMS320C6x architecture
*
* Copyright (C) 2004, 2009, 2010, 2011 Texas Instruments Incorporated
* Author: Aurelien Jacquiot <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* DMA uncached mapping support.
*
* Using code pulled from ARM
* Copyright (C) 2000-2004 Russell King
*
*/
#include <linux/slab.h>
#include <linux/bitmap.h>
#include <linux/bitops.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/memblock.h>

#include <asm/page.h>

/*
* DMA coherent memory management, can be redefined using the memdma=
* kernel command line
*/

/* none by default */
static phys_addr_t dma_base;
static u32 dma_size;
static u32 dma_pages;

static unsigned long *dma_bitmap;

/* bitmap lock */
static DEFINE_SPINLOCK(dma_lock);

/*
* Return a DMA coherent and contiguous memory chunk from the DMA memory
*/
static inline u32 __alloc_dma_pages(int order)
{
unsigned long flags;
u32 pos;

spin_lock_irqsave(&dma_lock, flags);
pos = bitmap_find_free_region(dma_bitmap, dma_pages, order);
spin_unlock_irqrestore(&dma_lock, flags);

return dma_base + (pos << PAGE_SHIFT);
}

static void __free_dma_pages(u32 addr, int order)
{
unsigned long flags;
u32 pos = (addr - dma_base) >> PAGE_SHIFT;

if (addr < dma_base || (pos + (1 << order)) >= dma_pages) {
printk(KERN_ERR "%s: freeing outside range.\n", __func__);
BUG();
}

spin_lock_irqsave(&dma_lock, flags);
bitmap_release_region(dma_bitmap, pos, order);
spin_unlock_irqrestore(&dma_lock, flags);
}

/*
* Allocate DMA coherent memory space and return both the kernel
* virtual and DMA address for that space.
*/
void *dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *handle, gfp_t gfp)
{
u32 paddr;
int order;

if (!dma_size || !size)
return NULL;

order = get_count_order(((size - 1) >> PAGE_SHIFT) + 1);

paddr = __alloc_dma_pages(order);

if (handle)
*handle = paddr;

if (!paddr)
return NULL;

return phys_to_virt(paddr);
}
EXPORT_SYMBOL(dma_alloc_coherent);

/*
* Free DMA coherent memory as defined by the above mapping.
*/
void dma_free_coherent(struct device *dev, size_t size, void *vaddr,
dma_addr_t dma_handle)
{
int order;

if (!dma_size || !size)
return;

order = get_count_order(((size - 1) >> PAGE_SHIFT) + 1);

__free_dma_pages(virt_to_phys(vaddr), order);
}
EXPORT_SYMBOL(dma_free_coherent);

/*
* Initialise the coherent DMA memory allocator using the given uncached region.
*/
void __init coherent_mem_init(phys_addr_t start, u32 size)
{
phys_addr_t bitmap_phys;

if (!size)
return;

printk(KERN_INFO
"Coherent memory (DMA) region start=0x%x size=0x%x\n",
start, size);

dma_base = start;
dma_size = size;

/* allocate bitmap */
dma_pages = dma_size >> PAGE_SHIFT;
if (dma_size & (PAGE_SIZE - 1))
++dma_pages;

bitmap_phys = memblock_alloc(BITS_TO_LONGS(dma_pages) * sizeof(long),
sizeof(long));

dma_bitmap = phys_to_virt(bitmap_phys);
memset(dma_bitmap, 0, dma_pages * PAGE_SIZE);
}
Loading

0 comments on commit 14aa7e8

Please sign in to comment.