diff options
author | Wai Yew CHAY <wychay@ctl.creative.com> | 2009-05-14 02:05:58 -0400 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2009-05-14 02:24:10 -0400 |
commit | 8cc72361481f00253f1e468ade5795427386d593 (patch) | |
tree | ec6f3ea304f90fa9c99abb1bf2354fc5d357db27 /sound/pci/ctxfi/ctvmem.c | |
parent | 091bf7624d1c90cec9e578a18529f615213ff847 (diff) |
ALSA: SB X-Fi driver merge
The Sound Blaster X-Fi driver supports Creative solutions based on
20K1 and 20K2 chipsets.
Supported hardware :
Creative Sound Blaster X-Fi Titanium Fatal1ty® Champion Series
Creative Sound Blaster X-Fi Titanium Fatal1ty Professional Series
Creative Sound Blaster X-Fi Titanium Professional Audio
Creative Sound Blaster X-Fi Titanium
Creative Sound Blaster X-Fi Elite Pro
Creative Sound Blaster X-Fi Platinum
Creative Sound Blaster X-Fi Fatal1ty
Creative Sound Blaster X-Fi XtremeGamer
Creative Sound Blaster X-Fi XtremeMusic
Current release features:
* ALSA PCM Playback
* ALSA Record
* ALSA Mixer
Note:
* External I/O modules detection not included.
Signed-off-by: Wai Yew CHAY <wychay@ctl.creative.com>
Singed-off-by: Ryan RICHARDS <ryan_richards@creativelabs.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/pci/ctxfi/ctvmem.c')
-rw-r--r-- | sound/pci/ctxfi/ctvmem.c | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/sound/pci/ctxfi/ctvmem.c b/sound/pci/ctxfi/ctvmem.c new file mode 100644 index 000000000000..46ca04ce9210 --- /dev/null +++ b/sound/pci/ctxfi/ctvmem.c | |||
@@ -0,0 +1,254 @@ | |||
1 | /** | ||
2 | * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. | ||
3 | * | ||
4 | * This source file is released under GPL v2 license (no other versions). | ||
5 | * See the COPYING file included in the main directory of this source | ||
6 | * distribution for the license terms and conditions. | ||
7 | * | ||
8 | * @File ctvmem.c | ||
9 | * | ||
10 | * @Brief | ||
11 | * This file contains the implementation of virtual memory management object | ||
12 | * for card device. | ||
13 | * | ||
14 | * @Author Liu Chun | ||
15 | * @Date Apr 1 2008 | ||
16 | */ | ||
17 | |||
18 | #include "ctvmem.h" | ||
19 | #include <linux/slab.h> | ||
20 | #include <linux/mm.h> | ||
21 | #include <asm/page.h> /* for PAGE_SIZE macro definition */ | ||
22 | #include <linux/io.h> | ||
23 | #include <asm/pgtable.h> | ||
24 | |||
25 | #define CT_PTES_PER_PAGE (PAGE_SIZE / sizeof(void *)) | ||
26 | #define CT_ADDRS_PER_PAGE (CT_PTES_PER_PAGE * PAGE_SIZE) | ||
27 | |||
28 | /* * | ||
29 | * Find or create vm block based on requested @size. | ||
30 | * @size must be page aligned. | ||
31 | * */ | ||
32 | static struct ct_vm_block * | ||
33 | get_vm_block(struct ct_vm *vm, unsigned int size) | ||
34 | { | ||
35 | struct ct_vm_block *block = NULL, *entry = NULL; | ||
36 | struct list_head *pos = NULL; | ||
37 | |||
38 | list_for_each(pos, &vm->unused) { | ||
39 | entry = list_entry(pos, struct ct_vm_block, list); | ||
40 | if (entry->size >= size) | ||
41 | break; /* found a block that is big enough */ | ||
42 | } | ||
43 | if (pos == &vm->unused) | ||
44 | return NULL; | ||
45 | |||
46 | if (entry->size == size) { | ||
47 | /* Move the vm node from unused list to used list directly */ | ||
48 | list_del(&entry->list); | ||
49 | list_add(&entry->list, &vm->used); | ||
50 | vm->size -= size; | ||
51 | return entry; | ||
52 | } | ||
53 | |||
54 | block = kzalloc(sizeof(*block), GFP_KERNEL); | ||
55 | if (NULL == block) | ||
56 | return NULL; | ||
57 | |||
58 | block->addr = entry->addr; | ||
59 | block->size = size; | ||
60 | list_add(&block->list, &vm->used); | ||
61 | entry->addr += size; | ||
62 | entry->size -= size; | ||
63 | vm->size -= size; | ||
64 | |||
65 | return block; | ||
66 | } | ||
67 | |||
68 | static void put_vm_block(struct ct_vm *vm, struct ct_vm_block *block) | ||
69 | { | ||
70 | struct ct_vm_block *entry = NULL, *pre_ent = NULL; | ||
71 | struct list_head *pos = NULL, *pre = NULL; | ||
72 | |||
73 | list_del(&block->list); | ||
74 | vm->size += block->size; | ||
75 | |||
76 | list_for_each(pos, &vm->unused) { | ||
77 | entry = list_entry(pos, struct ct_vm_block, list); | ||
78 | if (entry->addr >= (block->addr + block->size)) | ||
79 | break; /* found a position */ | ||
80 | } | ||
81 | if (pos == &vm->unused) { | ||
82 | list_add_tail(&block->list, &vm->unused); | ||
83 | entry = block; | ||
84 | } else { | ||
85 | if ((block->addr + block->size) == entry->addr) { | ||
86 | entry->addr = block->addr; | ||
87 | entry->size += block->size; | ||
88 | kfree(block); | ||
89 | } else { | ||
90 | __list_add(&block->list, pos->prev, pos); | ||
91 | entry = block; | ||
92 | } | ||
93 | } | ||
94 | |||
95 | pos = &entry->list; | ||
96 | pre = pos->prev; | ||
97 | while (pre != &vm->unused) { | ||
98 | entry = list_entry(pos, struct ct_vm_block, list); | ||
99 | pre_ent = list_entry(pre, struct ct_vm_block, list); | ||
100 | if ((pre_ent->addr + pre_ent->size) > entry->addr) | ||
101 | break; | ||
102 | |||
103 | pre_ent->size += entry->size; | ||
104 | list_del(pos); | ||
105 | kfree(entry); | ||
106 | pos = pre; | ||
107 | pre = pos->prev; | ||
108 | } | ||
109 | } | ||
110 | |||
111 | /* Map host addr (kmalloced/vmalloced) to device logical addr. */ | ||
112 | static struct ct_vm_block * | ||
113 | ct_vm_map(struct ct_vm *vm, void *host_addr, int size) | ||
114 | { | ||
115 | struct ct_vm_block *block = NULL; | ||
116 | unsigned long pte_start; | ||
117 | unsigned long i; | ||
118 | unsigned long pages; | ||
119 | unsigned long start_phys; | ||
120 | unsigned long *ptp; | ||
121 | |||
122 | /* do mapping */ | ||
123 | if ((unsigned long)host_addr >= VMALLOC_START) { | ||
124 | printk(KERN_ERR "Fail! Not support vmalloced addr now!\n"); | ||
125 | return NULL; | ||
126 | } | ||
127 | |||
128 | if (size > vm->size) { | ||
129 | printk(KERN_ERR "Fail! No sufficient device virtural " | ||
130 | "memory space available!\n"); | ||
131 | return NULL; | ||
132 | } | ||
133 | |||
134 | start_phys = (virt_to_phys(host_addr) & PAGE_MASK); | ||
135 | pages = (PAGE_ALIGN(virt_to_phys(host_addr) + size) | ||
136 | - start_phys) >> PAGE_SHIFT; | ||
137 | |||
138 | ptp = vm->ptp[0]; | ||
139 | |||
140 | block = get_vm_block(vm, (pages << PAGE_SHIFT)); | ||
141 | if (block == NULL) { | ||
142 | printk(KERN_ERR "No virtual memory block that is big " | ||
143 | "enough to allocate!\n"); | ||
144 | return NULL; | ||
145 | } | ||
146 | |||
147 | pte_start = (block->addr >> PAGE_SHIFT); | ||
148 | for (i = 0; i < pages; i++) | ||
149 | ptp[pte_start+i] = start_phys + (i << PAGE_SHIFT); | ||
150 | |||
151 | block->addr += (virt_to_phys(host_addr) & (~PAGE_MASK)); | ||
152 | block->size = size; | ||
153 | |||
154 | return block; | ||
155 | } | ||
156 | |||
157 | static void ct_vm_unmap(struct ct_vm *vm, struct ct_vm_block *block) | ||
158 | { | ||
159 | /* do unmapping */ | ||
160 | block->size = ((block->addr + block->size + PAGE_SIZE - 1) | ||
161 | & PAGE_MASK) - (block->addr & PAGE_MASK); | ||
162 | block->addr &= PAGE_MASK; | ||
163 | put_vm_block(vm, block); | ||
164 | } | ||
165 | |||
166 | /* * | ||
167 | * return the host (kmalloced) addr of the @index-th device | ||
168 | * page talbe page on success, or NULL on failure. | ||
169 | * The first returned NULL indicates the termination. | ||
170 | * */ | ||
171 | static void * | ||
172 | ct_get_ptp_virt(struct ct_vm *vm, int index) | ||
173 | { | ||
174 | void *addr; | ||
175 | |||
176 | addr = (index >= CT_PTP_NUM) ? NULL : vm->ptp[index]; | ||
177 | |||
178 | return addr; | ||
179 | } | ||
180 | |||
181 | int ct_vm_create(struct ct_vm **rvm) | ||
182 | { | ||
183 | struct ct_vm *vm; | ||
184 | struct ct_vm_block *block; | ||
185 | int i; | ||
186 | |||
187 | *rvm = NULL; | ||
188 | |||
189 | vm = kzalloc(sizeof(*vm), GFP_KERNEL); | ||
190 | if (NULL == vm) | ||
191 | return -ENOMEM; | ||
192 | |||
193 | /* Allocate page table pages */ | ||
194 | for (i = 0; i < CT_PTP_NUM; i++) { | ||
195 | vm->ptp[i] = kmalloc(PAGE_SIZE, GFP_KERNEL); | ||
196 | if (NULL == vm->ptp[i]) | ||
197 | break; | ||
198 | } | ||
199 | if (!i) { | ||
200 | /* no page table pages are allocated */ | ||
201 | kfree(vm); | ||
202 | return -ENOMEM; | ||
203 | } | ||
204 | vm->size = CT_ADDRS_PER_PAGE * i; | ||
205 | /* Initialise remaining ptps */ | ||
206 | for (; i < CT_PTP_NUM; i++) | ||
207 | vm->ptp[i] = NULL; | ||
208 | |||
209 | vm->map = ct_vm_map; | ||
210 | vm->unmap = ct_vm_unmap; | ||
211 | vm->get_ptp_virt = ct_get_ptp_virt; | ||
212 | INIT_LIST_HEAD(&vm->unused); | ||
213 | INIT_LIST_HEAD(&vm->used); | ||
214 | block = kzalloc(sizeof(*block), GFP_KERNEL); | ||
215 | if (NULL != block) { | ||
216 | block->addr = 0; | ||
217 | block->size = vm->size; | ||
218 | list_add(&block->list, &vm->unused); | ||
219 | } | ||
220 | |||
221 | *rvm = vm; | ||
222 | return 0; | ||
223 | } | ||
224 | |||
225 | /* The caller must ensure no mapping pages are being used | ||
226 | * by hardware before calling this function */ | ||
227 | void ct_vm_destroy(struct ct_vm *vm) | ||
228 | { | ||
229 | int i; | ||
230 | struct list_head *pos = NULL; | ||
231 | struct ct_vm_block *entry = NULL; | ||
232 | |||
233 | /* free used and unused list nodes */ | ||
234 | while (!list_empty(&vm->used)) { | ||
235 | pos = vm->used.next; | ||
236 | list_del(pos); | ||
237 | entry = list_entry(pos, struct ct_vm_block, list); | ||
238 | kfree(entry); | ||
239 | } | ||
240 | while (!list_empty(&vm->unused)) { | ||
241 | pos = vm->unused.next; | ||
242 | list_del(pos); | ||
243 | entry = list_entry(pos, struct ct_vm_block, list); | ||
244 | kfree(entry); | ||
245 | } | ||
246 | |||
247 | /* free allocated page table pages */ | ||
248 | for (i = 0; i < CT_PTP_NUM; i++) | ||
249 | kfree(vm->ptp[i]); | ||
250 | |||
251 | vm->size = 0; | ||
252 | |||
253 | kfree(vm); | ||
254 | } | ||