]>
Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * Driver for Tascam US-X2Y USB soundcards | |
3 | * | |
4 | * FPGA Loader + ALSA Startup | |
5 | * | |
6 | * Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
21 | */ | |
22 | ||
23 | #include <sound/driver.h> | |
24 | #include <linux/interrupt.h> | |
25 | #include <linux/usb.h> | |
26 | #include <sound/core.h> | |
27 | #include <sound/memalloc.h> | |
28 | #include <sound/pcm.h> | |
29 | #include <sound/hwdep.h> | |
30 | #include "usx2y.h" | |
31 | #include "usbusx2y.h" | |
32 | #include "usX2Yhwdep.h" | |
33 | ||
34 | int usX2Y_hwdep_pcm_new(snd_card_t* card); | |
35 | ||
36 | ||
37 | static struct page * snd_us428ctls_vm_nopage(struct vm_area_struct *area, unsigned long address, int *type) | |
38 | { | |
39 | unsigned long offset; | |
40 | struct page * page; | |
41 | void *vaddr; | |
42 | ||
43 | snd_printdd("ENTER, start %lXh, ofs %lXh, pgoff %ld, addr %lXh\n", | |
44 | area->vm_start, | |
45 | address - area->vm_start, | |
46 | (address - area->vm_start) >> PAGE_SHIFT, | |
47 | address); | |
48 | ||
49 | offset = area->vm_pgoff << PAGE_SHIFT; | |
50 | offset += address - area->vm_start; | |
51 | snd_assert((offset % PAGE_SIZE) == 0, return NOPAGE_OOM); | |
52 | vaddr = (char*)((usX2Ydev_t*)area->vm_private_data)->us428ctls_sharedmem + offset; | |
53 | page = virt_to_page(vaddr); | |
54 | get_page(page); | |
55 | snd_printdd( "vaddr=%p made us428ctls_vm_nopage() return %p; offset=%lX\n", vaddr, page, offset); | |
56 | ||
57 | if (type) | |
58 | *type = VM_FAULT_MINOR; | |
59 | ||
60 | return page; | |
61 | } | |
62 | ||
63 | static struct vm_operations_struct us428ctls_vm_ops = { | |
64 | .nopage = snd_us428ctls_vm_nopage, | |
65 | }; | |
66 | ||
67 | static int snd_us428ctls_mmap(snd_hwdep_t * hw, struct file *filp, struct vm_area_struct *area) | |
68 | { | |
69 | unsigned long size = (unsigned long)(area->vm_end - area->vm_start); | |
70 | usX2Ydev_t *us428 = (usX2Ydev_t*)hw->private_data; | |
71 | ||
72 | // FIXME this hwdep interface is used twice: fpga download and mmap for controlling Lights etc. Maybe better using 2 hwdep devs? | |
73 | // so as long as the device isn't fully initialised yet we return -EBUSY here. | |
74 | if (!(((usX2Ydev_t*)hw->private_data)->chip_status & USX2Y_STAT_CHIP_INIT)) | |
75 | return -EBUSY; | |
76 | ||
77 | /* if userspace tries to mmap beyond end of our buffer, fail */ | |
78 | if (size > ((PAGE_SIZE - 1 + sizeof(us428ctls_sharedmem_t)) / PAGE_SIZE) * PAGE_SIZE) { | |
79 | snd_printd( "%lu > %lu\n", size, (unsigned long)sizeof(us428ctls_sharedmem_t)); | |
80 | return -EINVAL; | |
81 | } | |
82 | ||
83 | if (!us428->us428ctls_sharedmem) { | |
84 | init_waitqueue_head(&us428->us428ctls_wait_queue_head); | |
85 | if(!(us428->us428ctls_sharedmem = snd_malloc_pages(sizeof(us428ctls_sharedmem_t), GFP_KERNEL))) | |
86 | return -ENOMEM; | |
87 | memset(us428->us428ctls_sharedmem, -1, sizeof(us428ctls_sharedmem_t)); | |
88 | us428->us428ctls_sharedmem->CtlSnapShotLast = -2; | |
89 | } | |
90 | area->vm_ops = &us428ctls_vm_ops; | |
91 | area->vm_flags |= VM_RESERVED; | |
92 | area->vm_private_data = hw->private_data; | |
93 | return 0; | |
94 | } | |
95 | ||
96 | static unsigned int snd_us428ctls_poll(snd_hwdep_t *hw, struct file *file, poll_table *wait) | |
97 | { | |
98 | unsigned int mask = 0; | |
99 | usX2Ydev_t *us428 = (usX2Ydev_t*)hw->private_data; | |
100 | us428ctls_sharedmem_t *shm = us428->us428ctls_sharedmem; | |
101 | if (us428->chip_status & USX2Y_STAT_CHIP_HUP) | |
102 | return POLLHUP; | |
103 | ||
104 | poll_wait(file, &us428->us428ctls_wait_queue_head, wait); | |
105 | ||
106 | if (shm != NULL && shm->CtlSnapShotLast != shm->CtlSnapShotRed) | |
107 | mask |= POLLIN; | |
108 | ||
109 | return mask; | |
110 | } | |
111 | ||
112 | ||
113 | static int snd_usX2Y_hwdep_open(snd_hwdep_t *hw, struct file *file) | |
114 | { | |
115 | return 0; | |
116 | } | |
117 | ||
118 | static int snd_usX2Y_hwdep_release(snd_hwdep_t *hw, struct file *file) | |
119 | { | |
120 | return 0; | |
121 | } | |
122 | ||
123 | static int snd_usX2Y_hwdep_dsp_status(snd_hwdep_t *hw, snd_hwdep_dsp_status_t *info) | |
124 | { | |
125 | static char *type_ids[USX2Y_TYPE_NUMS] = { | |
126 | [USX2Y_TYPE_122] = "us122", | |
127 | [USX2Y_TYPE_224] = "us224", | |
128 | [USX2Y_TYPE_428] = "us428", | |
129 | }; | |
130 | int id = -1; | |
131 | ||
132 | switch (le16_to_cpu(((usX2Ydev_t*)hw->private_data)->chip.dev->descriptor.idProduct)) { | |
133 | case USB_ID_US122: | |
134 | id = USX2Y_TYPE_122; | |
135 | break; | |
136 | case USB_ID_US224: | |
137 | id = USX2Y_TYPE_224; | |
138 | break; | |
139 | case USB_ID_US428: | |
140 | id = USX2Y_TYPE_428; | |
141 | break; | |
142 | } | |
143 | if (0 > id) | |
144 | return -ENODEV; | |
145 | strcpy(info->id, type_ids[id]); | |
146 | info->num_dsps = 2; // 0: Prepad Data, 1: FPGA Code | |
147 | if (((usX2Ydev_t*)hw->private_data)->chip_status & USX2Y_STAT_CHIP_INIT) | |
148 | info->chip_ready = 1; | |
149 | info->version = USX2Y_DRIVER_VERSION; | |
150 | return 0; | |
151 | } | |
152 | ||
153 | ||
154 | static int usX2Y_create_usbmidi(snd_card_t* card ) | |
155 | { | |
156 | static snd_usb_midi_endpoint_info_t quirk_data_1 = { | |
157 | .out_ep =0x06, | |
158 | .in_ep = 0x06, | |
159 | .out_cables = 0x001, | |
160 | .in_cables = 0x001 | |
161 | }; | |
162 | static snd_usb_audio_quirk_t quirk_1 = { | |
163 | .vendor_name = "TASCAM", | |
164 | .product_name = NAME_ALLCAPS, | |
165 | .ifnum = 0, | |
166 | .type = QUIRK_MIDI_FIXED_ENDPOINT, | |
167 | .data = &quirk_data_1 | |
168 | }; | |
169 | static snd_usb_midi_endpoint_info_t quirk_data_2 = { | |
170 | .out_ep =0x06, | |
171 | .in_ep = 0x06, | |
172 | .out_cables = 0x003, | |
173 | .in_cables = 0x003 | |
174 | }; | |
175 | static snd_usb_audio_quirk_t quirk_2 = { | |
176 | .vendor_name = "TASCAM", | |
177 | .product_name = "US428", | |
178 | .ifnum = 0, | |
179 | .type = QUIRK_MIDI_FIXED_ENDPOINT, | |
180 | .data = &quirk_data_2 | |
181 | }; | |
182 | struct usb_device *dev = usX2Y(card)->chip.dev; | |
183 | struct usb_interface *iface = usb_ifnum_to_if(dev, 0); | |
184 | snd_usb_audio_quirk_t *quirk = le16_to_cpu(dev->descriptor.idProduct) == USB_ID_US428 ? &quirk_2 : &quirk_1; | |
185 | ||
186 | snd_printdd("usX2Y_create_usbmidi \n"); | |
187 | return snd_usb_create_midi_interface(&usX2Y(card)->chip, iface, quirk); | |
188 | } | |
189 | ||
190 | static int usX2Y_create_alsa_devices(snd_card_t* card) | |
191 | { | |
192 | int err; | |
193 | ||
194 | do { | |
195 | if ((err = usX2Y_create_usbmidi(card)) < 0) { | |
196 | snd_printk("usX2Y_create_alsa_devices: usX2Y_create_usbmidi error %i \n", err); | |
197 | break; | |
198 | } | |
199 | if ((err = usX2Y_audio_create(card)) < 0) | |
200 | break; | |
201 | if ((err = usX2Y_hwdep_pcm_new(card)) < 0) | |
202 | break; | |
203 | if ((err = snd_card_register(card)) < 0) | |
204 | break; | |
205 | } while (0); | |
206 | ||
207 | return err; | |
208 | } | |
209 | ||
210 | static int snd_usX2Y_hwdep_dsp_load(snd_hwdep_t *hw, snd_hwdep_dsp_image_t *dsp) | |
211 | { | |
212 | usX2Ydev_t *priv = hw->private_data; | |
213 | int lret, err = -EINVAL; | |
214 | snd_printdd( "dsp_load %s\n", dsp->name); | |
215 | ||
216 | if (access_ok(VERIFY_READ, dsp->image, dsp->length)) { | |
217 | struct usb_device* dev = priv->chip.dev; | |
218 | char *buf = kmalloc(dsp->length, GFP_KERNEL); | |
219 | if (!buf) | |
220 | return -ENOMEM; | |
221 | if (copy_from_user(buf, dsp->image, dsp->length)) { | |
222 | kfree(buf); | |
223 | return -EFAULT; | |
224 | } | |
225 | err = usb_set_interface(dev, 0, 1); | |
226 | if (err) | |
227 | snd_printk("usb_set_interface error \n"); | |
228 | else | |
229 | err = usb_bulk_msg(dev, usb_sndbulkpipe(dev, 2), buf, dsp->length, &lret, 6000); | |
230 | kfree(buf); | |
231 | } | |
232 | if (err) | |
233 | return err; | |
234 | if (dsp->index == 1) { | |
235 | set_current_state(TASK_UNINTERRUPTIBLE); | |
236 | schedule_timeout(HZ/4); // give the device some time | |
237 | err = usX2Y_AsyncSeq04_init(priv); | |
238 | if (err) { | |
239 | snd_printk("usX2Y_AsyncSeq04_init error \n"); | |
240 | return err; | |
241 | } | |
242 | err = usX2Y_In04_init(priv); | |
243 | if (err) { | |
244 | snd_printk("usX2Y_In04_init error \n"); | |
245 | return err; | |
246 | } | |
247 | err = usX2Y_create_alsa_devices(hw->card); | |
248 | if (err) { | |
249 | snd_printk("usX2Y_create_alsa_devices error %i \n", err); | |
250 | snd_card_free(hw->card); | |
251 | return err; | |
252 | } | |
253 | priv->chip_status |= USX2Y_STAT_CHIP_INIT; | |
254 | snd_printdd("%s: alsa all started\n", hw->name); | |
255 | } | |
256 | return err; | |
257 | } | |
258 | ||
259 | ||
260 | int usX2Y_hwdep_new(snd_card_t* card, struct usb_device* device) | |
261 | { | |
262 | int err; | |
263 | snd_hwdep_t *hw; | |
264 | ||
265 | if ((err = snd_hwdep_new(card, SND_USX2Y_LOADER_ID, 0, &hw)) < 0) | |
266 | return err; | |
267 | ||
268 | hw->iface = SNDRV_HWDEP_IFACE_USX2Y; | |
269 | hw->private_data = usX2Y(card); | |
270 | hw->ops.open = snd_usX2Y_hwdep_open; | |
271 | hw->ops.release = snd_usX2Y_hwdep_release; | |
272 | hw->ops.dsp_status = snd_usX2Y_hwdep_dsp_status; | |
273 | hw->ops.dsp_load = snd_usX2Y_hwdep_dsp_load; | |
274 | hw->ops.mmap = snd_us428ctls_mmap; | |
275 | hw->ops.poll = snd_us428ctls_poll; | |
276 | hw->exclusive = 1; | |
277 | sprintf(hw->name, "/proc/bus/usb/%03d/%03d", device->bus->busnum, device->devnum); | |
278 | return 0; | |
279 | } | |
280 |