Commit | Line | Data |
---|---|---|
1394f032 BW |
1 | /* |
2 | * File: arch/blackfin/kernel/dma-mapping.c | |
3 | * Based on: | |
4 | * Author: | |
5 | * | |
6 | * Created: | |
7 | * Description: Dynamic DMA mapping support. | |
8 | * | |
9 | * Modified: | |
10 | * Copyright 2004-2006 Analog Devices Inc. | |
11 | * | |
12 | * Bugs: Enter bugs at http://blackfin.uclinux.org/ | |
13 | * | |
14 | * This program is free software; you can redistribute it and/or modify | |
15 | * it under the terms of the GNU General Public License as published by | |
16 | * the Free Software Foundation; either version 2 of the License, or | |
17 | * (at your option) any later version. | |
18 | * | |
19 | * This program is distributed in the hope that it will be useful, | |
20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
22 | * GNU General Public License for more details. | |
23 | * | |
24 | * You should have received a copy of the GNU General Public License | |
25 | * along with this program; if not, see the file COPYING, or write | |
26 | * to the Free Software Foundation, Inc., | |
27 | * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
28 | */ | |
29 | ||
30 | #include <linux/types.h> | |
31 | #include <linux/mm.h> | |
32 | #include <linux/string.h> | |
33 | #include <linux/bootmem.h> | |
34 | #include <linux/spinlock.h> | |
35 | #include <linux/device.h> | |
36 | #include <linux/dma-mapping.h> | |
37 | #include <asm/cacheflush.h> | |
38 | #include <asm/io.h> | |
39 | #include <asm/bfin-global.h> | |
40 | ||
41 | static spinlock_t dma_page_lock; | |
42 | static unsigned int *dma_page; | |
43 | static unsigned int dma_pages; | |
44 | static unsigned long dma_base; | |
45 | static unsigned long dma_size; | |
46 | static unsigned int dma_initialized; | |
47 | ||
48 | void dma_alloc_init(unsigned long start, unsigned long end) | |
49 | { | |
50 | spin_lock_init(&dma_page_lock); | |
51 | dma_initialized = 0; | |
52 | ||
53 | dma_page = (unsigned int *)__get_free_page(GFP_KERNEL); | |
54 | memset(dma_page, 0, PAGE_SIZE); | |
55 | dma_base = PAGE_ALIGN(start); | |
56 | dma_size = PAGE_ALIGN(end) - PAGE_ALIGN(start); | |
57 | dma_pages = dma_size >> PAGE_SHIFT; | |
58 | memset((void *)dma_base, 0, DMA_UNCACHED_REGION); | |
59 | dma_initialized = 1; | |
60 | ||
61 | printk(KERN_INFO "%s: dma_page @ 0x%p - %d pages at 0x%08lx\n", __FUNCTION__, | |
62 | dma_page, dma_pages, dma_base); | |
63 | } | |
64 | ||
65 | static inline unsigned int get_pages(size_t size) | |
66 | { | |
67 | return ((size - 1) >> PAGE_SHIFT) + 1; | |
68 | } | |
69 | ||
70 | static unsigned long __alloc_dma_pages(unsigned int pages) | |
71 | { | |
72 | unsigned long ret = 0, flags; | |
73 | int i, count = 0; | |
74 | ||
75 | if (dma_initialized == 0) | |
76 | dma_alloc_init(_ramend - DMA_UNCACHED_REGION, _ramend); | |
77 | ||
78 | spin_lock_irqsave(&dma_page_lock, flags); | |
79 | ||
80 | for (i = 0; i < dma_pages;) { | |
81 | if (dma_page[i++] == 0) { | |
82 | if (++count == pages) { | |
83 | while (count--) | |
84 | dma_page[--i] = 1; | |
85 | ret = dma_base + (i << PAGE_SHIFT); | |
86 | break; | |
87 | } | |
88 | } else | |
89 | count = 0; | |
90 | } | |
91 | spin_unlock_irqrestore(&dma_page_lock, flags); | |
92 | return ret; | |
93 | } | |
94 | ||
95 | static void __free_dma_pages(unsigned long addr, unsigned int pages) | |
96 | { | |
97 | unsigned long page = (addr - dma_base) >> PAGE_SHIFT; | |
98 | unsigned long flags; | |
99 | int i; | |
100 | ||
101 | if ((page + pages) > dma_pages) { | |
102 | printk(KERN_ERR "%s: freeing outside range.\n", __FUNCTION__); | |
103 | BUG(); | |
104 | } | |
105 | ||
106 | spin_lock_irqsave(&dma_page_lock, flags); | |
107 | for (i = page; i < page + pages; i++) { | |
108 | dma_page[i] = 0; | |
109 | } | |
110 | spin_unlock_irqrestore(&dma_page_lock, flags); | |
111 | } | |
112 | ||
113 | void *dma_alloc_coherent(struct device *dev, size_t size, | |
114 | dma_addr_t * dma_handle, gfp_t gfp) | |
115 | { | |
116 | void *ret; | |
117 | ||
118 | ret = (void *)__alloc_dma_pages(get_pages(size)); | |
119 | ||
120 | if (ret) { | |
121 | memset(ret, 0, size); | |
122 | *dma_handle = virt_to_phys(ret); | |
123 | } | |
124 | ||
125 | return ret; | |
126 | } | |
127 | EXPORT_SYMBOL(dma_alloc_coherent); | |
128 | ||
129 | void | |
130 | dma_free_coherent(struct device *dev, size_t size, void *vaddr, | |
131 | dma_addr_t dma_handle) | |
132 | { | |
133 | __free_dma_pages((unsigned long)vaddr, get_pages(size)); | |
134 | } | |
135 | EXPORT_SYMBOL(dma_free_coherent); | |
136 | ||
137 | /* | |
138 | * Dummy functions defined for some existing drivers | |
139 | */ | |
140 | ||
141 | dma_addr_t | |
142 | dma_map_single(struct device *dev, void *ptr, size_t size, | |
143 | enum dma_data_direction direction) | |
144 | { | |
145 | BUG_ON(direction == DMA_NONE); | |
146 | ||
147 | invalidate_dcache_range((unsigned long)ptr, | |
148 | (unsigned long)ptr + size); | |
149 | ||
150 | return (dma_addr_t) ptr; | |
151 | } | |
152 | EXPORT_SYMBOL(dma_map_single); | |
153 | ||
154 | int | |
155 | dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, | |
156 | enum dma_data_direction direction) | |
157 | { | |
158 | int i; | |
159 | ||
160 | BUG_ON(direction == DMA_NONE); | |
161 | ||
162 | for (i = 0; i < nents; i++) | |
163 | invalidate_dcache_range(sg_dma_address(&sg[i]), | |
164 | sg_dma_address(&sg[i]) + | |
165 | sg_dma_len(&sg[i])); | |
166 | ||
167 | return nents; | |
168 | } | |
169 | EXPORT_SYMBOL(dma_map_sg); | |
170 | ||
171 | void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, | |
172 | enum dma_data_direction direction) | |
173 | { | |
174 | BUG_ON(direction == DMA_NONE); | |
175 | } | |
176 | EXPORT_SYMBOL(dma_unmap_single); | |
177 | ||
178 | void dma_unmap_sg(struct device *dev, struct scatterlist *sg, | |
179 | int nhwentries, enum dma_data_direction direction) | |
180 | { | |
181 | BUG_ON(direction == DMA_NONE); | |
182 | } | |
183 | EXPORT_SYMBOL(dma_unmap_sg); |