Commit | Line | Data |
---|---|---|
1a59d1b8 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
1da177e4 LT |
2 | /* |
3 | * Driver for Tascam US-X2Y USB soundcards | |
4 | * | |
5 | * FPGA Loader + ALSA Startup | |
6 | * | |
7 | * Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de> | |
1da177e4 LT |
8 | */ |
9 | ||
1da177e4 | 10 | #include <linux/interrupt.h> |
5a0e3ad6 | 11 | #include <linux/slab.h> |
1da177e4 LT |
12 | #include <linux/usb.h> |
13 | #include <sound/core.h> | |
14 | #include <sound/memalloc.h> | |
15 | #include <sound/pcm.h> | |
16 | #include <sound/hwdep.h> | |
17 | #include "usx2y.h" | |
18 | #include "usbusx2y.h" | |
19 | #include "usX2Yhwdep.h" | |
20 | ||
29581051 | 21 | static vm_fault_t snd_us428ctls_vm_fault(struct vm_fault *vmf) |
1da177e4 LT |
22 | { |
23 | unsigned long offset; | |
4c0a58ef | 24 | struct page *page; |
1da177e4 LT |
25 | void *vaddr; |
26 | ||
eb415b8f | 27 | snd_printdd("ENTER, start %lXh, pgoff %ld\n", |
11bac800 | 28 | vmf->vma->vm_start, |
eb415b8f | 29 | vmf->pgoff); |
4c0a58ef | 30 | |
eb415b8f | 31 | offset = vmf->pgoff << PAGE_SHIFT; |
bae3ce49 | 32 | vaddr = (char *)((struct usx2ydev *)vmf->vma->vm_private_data)->us428ctls_sharedmem + offset; |
1da177e4 LT |
33 | page = virt_to_page(vaddr); |
34 | get_page(page); | |
eb415b8f | 35 | vmf->page = page; |
1da177e4 | 36 | |
eb415b8f NP |
37 | snd_printdd("vaddr=%p made us428ctls_vm_fault() page %p\n", |
38 | vaddr, page); | |
1da177e4 | 39 | |
eb415b8f | 40 | return 0; |
1da177e4 LT |
41 | } |
42 | ||
f0f37e2f | 43 | static const struct vm_operations_struct us428ctls_vm_ops = { |
eb415b8f | 44 | .fault = snd_us428ctls_vm_fault, |
1da177e4 LT |
45 | }; |
46 | ||
4c0a58ef | 47 | static int snd_us428ctls_mmap(struct snd_hwdep *hw, struct file *filp, struct vm_area_struct *area) |
1da177e4 LT |
48 | { |
49 | unsigned long size = (unsigned long)(area->vm_end - area->vm_start); | |
bae3ce49 | 50 | struct usx2ydev *us428 = hw->private_data; |
1da177e4 LT |
51 | |
52 | // FIXME this hwdep interface is used twice: fpga download and mmap for controlling Lights etc. Maybe better using 2 hwdep devs? | |
53 | // so as long as the device isn't fully initialised yet we return -EBUSY here. | |
4c0a58ef | 54 | if (!(us428->chip_status & USX2Y_STAT_CHIP_INIT)) |
1da177e4 LT |
55 | return -EBUSY; |
56 | ||
4c0a58ef | 57 | /* if userspace tries to mmap beyond end of our buffer, fail */ |
4e268db7 TI |
58 | if (size > US428_SHAREDMEM_PAGES) { |
59 | snd_printd("%lu > %lu\n", size, (unsigned long)US428_SHAREDMEM_PAGES); | |
4c0a58ef | 60 | return -EINVAL; |
1da177e4 LT |
61 | } |
62 | ||
1da177e4 | 63 | area->vm_ops = &us428ctls_vm_ops; |
314e51b9 | 64 | area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; |
1da177e4 LT |
65 | area->vm_private_data = hw->private_data; |
66 | return 0; | |
67 | } | |
68 | ||
680ef72a | 69 | static __poll_t snd_us428ctls_poll(struct snd_hwdep *hw, struct file *file, poll_table *wait) |
1da177e4 | 70 | { |
680ef72a | 71 | __poll_t mask = 0; |
bae3ce49 | 72 | struct usx2ydev *us428 = hw->private_data; |
bbe85bbd | 73 | struct us428ctls_sharedmem *shm = us428->us428ctls_sharedmem; |
4c0a58ef | 74 | |
1da177e4 | 75 | if (us428->chip_status & USX2Y_STAT_CHIP_HUP) |
a9a08845 | 76 | return EPOLLHUP; |
1da177e4 LT |
77 | |
78 | poll_wait(file, &us428->us428ctls_wait_queue_head, wait); | |
79 | ||
a829dd5b | 80 | if (shm && shm->ctl_snapshot_last != shm->ctl_snapshot_red) |
a9a08845 | 81 | mask |= EPOLLIN; |
1da177e4 LT |
82 | |
83 | return mask; | |
84 | } | |
85 | ||
86 | ||
bae3ce49 | 87 | static int snd_usx2y_hwdep_dsp_status(struct snd_hwdep *hw, |
bbe85bbd | 88 | struct snd_hwdep_dsp_status *info) |
1da177e4 | 89 | { |
7ec03ff7 | 90 | static const char * const type_ids[USX2Y_TYPE_NUMS] = { |
1da177e4 LT |
91 | [USX2Y_TYPE_122] = "us122", |
92 | [USX2Y_TYPE_224] = "us224", | |
93 | [USX2Y_TYPE_428] = "us428", | |
94 | }; | |
bae3ce49 | 95 | struct usx2ydev *us428 = hw->private_data; |
1da177e4 LT |
96 | int id = -1; |
97 | ||
a014bbad | 98 | switch (le16_to_cpu(us428->dev->descriptor.idProduct)) { |
1da177e4 LT |
99 | case USB_ID_US122: |
100 | id = USX2Y_TYPE_122; | |
101 | break; | |
102 | case USB_ID_US224: | |
103 | id = USX2Y_TYPE_224; | |
104 | break; | |
105 | case USB_ID_US428: | |
106 | id = USX2Y_TYPE_428; | |
107 | break; | |
108 | } | |
a829dd5b | 109 | if (id < 0) |
1da177e4 LT |
110 | return -ENODEV; |
111 | strcpy(info->id, type_ids[id]); | |
112 | info->num_dsps = 2; // 0: Prepad Data, 1: FPGA Code | |
bbe85bbd | 113 | if (us428->chip_status & USX2Y_STAT_CHIP_INIT) |
1da177e4 | 114 | info->chip_ready = 1; |
df4654bd | 115 | info->version = USX2Y_DRIVER_VERSION; |
1da177e4 LT |
116 | return 0; |
117 | } | |
118 | ||
bae3ce49 | 119 | static int usx2y_create_usbmidi(struct snd_card *card) |
1da177e4 | 120 | { |
49624472 | 121 | static const struct snd_usb_midi_endpoint_info quirk_data_1 = { |
bbe85bbd | 122 | .out_ep = 0x06, |
1da177e4 LT |
123 | .in_ep = 0x06, |
124 | .out_cables = 0x001, | |
125 | .in_cables = 0x001 | |
126 | }; | |
49624472 | 127 | static const struct snd_usb_audio_quirk quirk_1 = { |
1da177e4 LT |
128 | .vendor_name = "TASCAM", |
129 | .product_name = NAME_ALLCAPS, | |
4c0a58ef TI |
130 | .ifnum = 0, |
131 | .type = QUIRK_MIDI_FIXED_ENDPOINT, | |
1da177e4 LT |
132 | .data = &quirk_data_1 |
133 | }; | |
49624472 | 134 | static const struct snd_usb_midi_endpoint_info quirk_data_2 = { |
bbe85bbd | 135 | .out_ep = 0x06, |
1da177e4 LT |
136 | .in_ep = 0x06, |
137 | .out_cables = 0x003, | |
138 | .in_cables = 0x003 | |
139 | }; | |
49624472 | 140 | static const struct snd_usb_audio_quirk quirk_2 = { |
1da177e4 LT |
141 | .vendor_name = "TASCAM", |
142 | .product_name = "US428", | |
4c0a58ef TI |
143 | .ifnum = 0, |
144 | .type = QUIRK_MIDI_FIXED_ENDPOINT, | |
1da177e4 LT |
145 | .data = &quirk_data_2 |
146 | }; | |
bae3ce49 | 147 | struct usb_device *dev = usx2y(card)->dev; |
1da177e4 | 148 | struct usb_interface *iface = usb_ifnum_to_if(dev, 0); |
49624472 | 149 | const struct snd_usb_audio_quirk *quirk = |
bbe85bbd TI |
150 | le16_to_cpu(dev->descriptor.idProduct) == USB_ID_US428 ? |
151 | &quirk_2 : &quirk_1; | |
1da177e4 | 152 | |
a829dd5b | 153 | snd_printdd("%s\n", __func__); |
bae3ce49 | 154 | return snd_usbmidi_create(card, iface, &usx2y(card)->midi_list, quirk); |
1da177e4 LT |
155 | } |
156 | ||
bae3ce49 | 157 | static int usx2y_create_alsa_devices(struct snd_card *card) |
1da177e4 LT |
158 | { |
159 | int err; | |
160 | ||
a829dd5b TI |
161 | err = usx2y_create_usbmidi(card); |
162 | if (err < 0) { | |
163 | snd_printk(KERN_ERR "%s: usx2y_create_usbmidi error %i\n", __func__, err); | |
164 | return err; | |
165 | } | |
166 | err = usx2y_audio_create(card); | |
167 | if (err < 0) | |
168 | return err; | |
169 | err = usx2y_hwdep_pcm_new(card); | |
170 | if (err < 0) | |
171 | return err; | |
172 | err = snd_card_register(card); | |
173 | if (err < 0) | |
174 | return err; | |
175 | return 0; | |
4c0a58ef | 176 | } |
1da177e4 | 177 | |
bae3ce49 | 178 | static int snd_usx2y_hwdep_dsp_load(struct snd_hwdep *hw, |
bbe85bbd | 179 | struct snd_hwdep_dsp_image *dsp) |
1da177e4 | 180 | { |
bae3ce49 | 181 | struct usx2ydev *priv = hw->private_data; |
4c0a58ef | 182 | struct usb_device *dev = priv->dev; |
fc1c428e AV |
183 | int lret, err; |
184 | char *buf; | |
1da177e4 | 185 | |
4c0a58ef | 186 | snd_printdd("dsp_load %s\n", dsp->name); |
85385c15 | 187 | |
fc1c428e AV |
188 | buf = memdup_user(dsp->image, dsp->length); |
189 | if (IS_ERR(buf)) | |
190 | return PTR_ERR(buf); | |
85385c15 | 191 | |
fc1c428e AV |
192 | err = usb_set_interface(dev, 0, 1); |
193 | if (err) | |
4c0a58ef | 194 | snd_printk(KERN_ERR "usb_set_interface error\n"); |
fc1c428e AV |
195 | else |
196 | err = usb_bulk_msg(dev, usb_sndbulkpipe(dev, 2), buf, dsp->length, &lret, 6000); | |
197 | kfree(buf); | |
1da177e4 LT |
198 | if (err) |
199 | return err; | |
200 | if (dsp->index == 1) { | |
b27c187f | 201 | msleep(250); // give the device some time |
bae3ce49 | 202 | err = usx2y_async_seq04_init(priv); |
1da177e4 | 203 | if (err) { |
4c0a58ef | 204 | snd_printk(KERN_ERR "usx2y_async_seq04_init error\n"); |
1da177e4 LT |
205 | return err; |
206 | } | |
bae3ce49 | 207 | err = usx2y_in04_init(priv); |
1da177e4 | 208 | if (err) { |
4c0a58ef | 209 | snd_printk(KERN_ERR "usx2y_in04_init error\n"); |
1da177e4 LT |
210 | return err; |
211 | } | |
bae3ce49 | 212 | err = usx2y_create_alsa_devices(hw->card); |
1da177e4 | 213 | if (err) { |
4c0a58ef | 214 | snd_printk(KERN_ERR "usx2y_create_alsa_devices error %i\n", err); |
1da177e4 LT |
215 | return err; |
216 | } | |
4c0a58ef | 217 | priv->chip_status |= USX2Y_STAT_CHIP_INIT; |
1da177e4 LT |
218 | snd_printdd("%s: alsa all started\n", hw->name); |
219 | } | |
220 | return err; | |
221 | } | |
222 | ||
4c0a58ef | 223 | int usx2y_hwdep_new(struct snd_card *card, struct usb_device *device) |
1da177e4 LT |
224 | { |
225 | int err; | |
bbe85bbd | 226 | struct snd_hwdep *hw; |
64a06f19 | 227 | struct usx2ydev *us428 = usx2y(card); |
1da177e4 | 228 | |
a829dd5b TI |
229 | err = snd_hwdep_new(card, SND_USX2Y_LOADER_ID, 0, &hw); |
230 | if (err < 0) | |
1da177e4 LT |
231 | return err; |
232 | ||
233 | hw->iface = SNDRV_HWDEP_IFACE_USX2Y; | |
64a06f19 | 234 | hw->private_data = us428; |
bae3ce49 TI |
235 | hw->ops.dsp_status = snd_usx2y_hwdep_dsp_status; |
236 | hw->ops.dsp_load = snd_usx2y_hwdep_dsp_load; | |
1da177e4 LT |
237 | hw->ops.mmap = snd_us428ctls_mmap; |
238 | hw->ops.poll = snd_us428ctls_poll; | |
239 | hw->exclusive = 1; | |
a5f8661d | 240 | sprintf(hw->name, "/dev/bus/usb/%03d/%03d", device->bus->busnum, device->devnum); |
64a06f19 TI |
241 | |
242 | us428->us428ctls_sharedmem = alloc_pages_exact(US428_SHAREDMEM_PAGES, GFP_KERNEL); | |
243 | if (!us428->us428ctls_sharedmem) | |
244 | return -ENOMEM; | |
245 | memset(us428->us428ctls_sharedmem, -1, US428_SHAREDMEM_PAGES); | |
246 | us428->us428ctls_sharedmem->ctl_snapshot_last = -2; | |
247 | ||
1da177e4 LT |
248 | return 0; |
249 | } |