1 | // SPDX-License-Identifier: GPL-2.0-or-later |
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> |
8 | */ |
9 | |
10 | #include <linux/interrupt.h> |
11 | #include <linux/slab.h> |
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 | |
21 | static vm_fault_t snd_us428ctls_vm_fault(struct vm_fault *vmf) |
22 | { |
23 | unsigned long offset; |
24 | struct page *page; |
25 | void *vaddr; |
26 | |
27 | snd_printdd("ENTER, start %lXh, pgoff %ld\n" , |
28 | vmf->vma->vm_start, |
29 | vmf->pgoff); |
30 | |
31 | offset = vmf->pgoff << PAGE_SHIFT; |
32 | vaddr = (char *)((struct usx2ydev *)vmf->vma->vm_private_data)->us428ctls_sharedmem + offset; |
33 | page = virt_to_page(vaddr); |
34 | get_page(page); |
35 | vmf->page = page; |
36 | |
37 | snd_printdd("vaddr=%p made us428ctls_vm_fault() page %p\n" , |
38 | vaddr, page); |
39 | |
40 | return 0; |
41 | } |
42 | |
43 | static const struct vm_operations_struct us428ctls_vm_ops = { |
44 | .fault = snd_us428ctls_vm_fault, |
45 | }; |
46 | |
47 | static int snd_us428ctls_mmap(struct snd_hwdep *hw, struct file *filp, struct vm_area_struct *area) |
48 | { |
49 | unsigned long size = (unsigned long)(area->vm_end - area->vm_start); |
50 | struct usx2ydev *us428 = hw->private_data; |
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. |
54 | if (!(us428->chip_status & USX2Y_STAT_CHIP_INIT)) |
55 | return -EBUSY; |
56 | |
57 | /* if userspace tries to mmap beyond end of our buffer, fail */ |
58 | if (size > US428_SHAREDMEM_PAGES) { |
59 | snd_printd("%lu > %lu\n" , size, (unsigned long)US428_SHAREDMEM_PAGES); |
60 | return -EINVAL; |
61 | } |
62 | |
63 | area->vm_ops = &us428ctls_vm_ops; |
64 | vm_flags_set(vma: area, VM_DONTEXPAND | VM_DONTDUMP); |
65 | area->vm_private_data = hw->private_data; |
66 | return 0; |
67 | } |
68 | |
69 | static __poll_t snd_us428ctls_poll(struct snd_hwdep *hw, struct file *file, poll_table *wait) |
70 | { |
71 | __poll_t mask = 0; |
72 | struct usx2ydev *us428 = hw->private_data; |
73 | struct us428ctls_sharedmem *shm = us428->us428ctls_sharedmem; |
74 | |
75 | if (us428->chip_status & USX2Y_STAT_CHIP_HUP) |
76 | return EPOLLHUP; |
77 | |
78 | poll_wait(filp: file, wait_address: &us428->us428ctls_wait_queue_head, p: wait); |
79 | |
80 | if (shm && shm->ctl_snapshot_last != shm->ctl_snapshot_red) |
81 | mask |= EPOLLIN; |
82 | |
83 | return mask; |
84 | } |
85 | |
86 | |
87 | static int snd_usx2y_hwdep_dsp_status(struct snd_hwdep *hw, |
88 | struct snd_hwdep_dsp_status *info) |
89 | { |
90 | static const char * const type_ids[USX2Y_TYPE_NUMS] = { |
91 | [USX2Y_TYPE_122] = "us122" , |
92 | [USX2Y_TYPE_224] = "us224" , |
93 | [USX2Y_TYPE_428] = "us428" , |
94 | }; |
95 | struct usx2ydev *us428 = hw->private_data; |
96 | int id = -1; |
97 | |
98 | switch (le16_to_cpu(us428->dev->descriptor.idProduct)) { |
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 | } |
109 | if (id < 0) |
110 | return -ENODEV; |
111 | strcpy(p: info->id, q: type_ids[id]); |
112 | info->num_dsps = 2; // 0: Prepad Data, 1: FPGA Code |
113 | if (us428->chip_status & USX2Y_STAT_CHIP_INIT) |
114 | info->chip_ready = 1; |
115 | info->version = USX2Y_DRIVER_VERSION; |
116 | return 0; |
117 | } |
118 | |
119 | static int usx2y_create_usbmidi(struct snd_card *card) |
120 | { |
121 | static const struct snd_usb_midi_endpoint_info quirk_data_1 = { |
122 | .out_ep = 0x06, |
123 | .in_ep = 0x06, |
124 | .out_cables = 0x001, |
125 | .in_cables = 0x001 |
126 | }; |
127 | static const struct snd_usb_audio_quirk quirk_1 = { |
128 | .vendor_name = "TASCAM" , |
129 | .product_name = NAME_ALLCAPS, |
130 | .ifnum = 0, |
131 | .type = QUIRK_MIDI_FIXED_ENDPOINT, |
132 | .data = &quirk_data_1 |
133 | }; |
134 | static const struct snd_usb_midi_endpoint_info quirk_data_2 = { |
135 | .out_ep = 0x06, |
136 | .in_ep = 0x06, |
137 | .out_cables = 0x003, |
138 | .in_cables = 0x003 |
139 | }; |
140 | static const struct snd_usb_audio_quirk quirk_2 = { |
141 | .vendor_name = "TASCAM" , |
142 | .product_name = "US428" , |
143 | .ifnum = 0, |
144 | .type = QUIRK_MIDI_FIXED_ENDPOINT, |
145 | .data = &quirk_data_2 |
146 | }; |
147 | struct usb_device *dev = usx2y(card)->dev; |
148 | struct usb_interface *iface = usb_ifnum_to_if(dev, ifnum: 0); |
149 | const struct snd_usb_audio_quirk *quirk = |
150 | le16_to_cpu(dev->descriptor.idProduct) == USB_ID_US428 ? |
151 | &quirk_2 : &quirk_1; |
152 | |
153 | snd_printdd("%s\n" , __func__); |
154 | return snd_usbmidi_create(card, iface, midi_list: &usx2y(card)->midi_list, quirk); |
155 | } |
156 | |
157 | static int usx2y_create_alsa_devices(struct snd_card *card) |
158 | { |
159 | int err; |
160 | |
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; |
176 | } |
177 | |
178 | static int snd_usx2y_hwdep_dsp_load(struct snd_hwdep *hw, |
179 | struct snd_hwdep_dsp_image *dsp) |
180 | { |
181 | struct usx2ydev *priv = hw->private_data; |
182 | struct usb_device *dev = priv->dev; |
183 | int lret, err; |
184 | char *buf; |
185 | |
186 | snd_printdd("dsp_load %s\n" , dsp->name); |
187 | |
188 | buf = memdup_user(dsp->image, dsp->length); |
189 | if (IS_ERR(ptr: buf)) |
190 | return PTR_ERR(ptr: buf); |
191 | |
192 | err = usb_set_interface(dev, ifnum: 0, alternate: 1); |
193 | if (err) |
194 | snd_printk(KERN_ERR "usb_set_interface error\n" ); |
195 | else |
196 | err = usb_bulk_msg(usb_dev: dev, usb_sndbulkpipe(dev, 2), data: buf, len: dsp->length, actual_length: &lret, timeout: 6000); |
197 | kfree(objp: buf); |
198 | if (err) |
199 | return err; |
200 | if (dsp->index == 1) { |
201 | msleep(msecs: 250); // give the device some time |
202 | err = usx2y_async_seq04_init(usx2y: priv); |
203 | if (err) { |
204 | snd_printk(KERN_ERR "usx2y_async_seq04_init error\n" ); |
205 | return err; |
206 | } |
207 | err = usx2y_in04_init(usx2y: priv); |
208 | if (err) { |
209 | snd_printk(KERN_ERR "usx2y_in04_init error\n" ); |
210 | return err; |
211 | } |
212 | err = usx2y_create_alsa_devices(card: hw->card); |
213 | if (err) { |
214 | snd_printk(KERN_ERR "usx2y_create_alsa_devices error %i\n" , err); |
215 | return err; |
216 | } |
217 | priv->chip_status |= USX2Y_STAT_CHIP_INIT; |
218 | snd_printdd("%s: alsa all started\n" , hw->name); |
219 | } |
220 | return err; |
221 | } |
222 | |
223 | int usx2y_hwdep_new(struct snd_card *card, struct usb_device *device) |
224 | { |
225 | int err; |
226 | struct snd_hwdep *hw; |
227 | struct usx2ydev *us428 = usx2y(card); |
228 | |
229 | err = snd_hwdep_new(card, SND_USX2Y_LOADER_ID, device: 0, rhwdep: &hw); |
230 | if (err < 0) |
231 | return err; |
232 | |
233 | hw->iface = SNDRV_HWDEP_IFACE_USX2Y; |
234 | hw->private_data = us428; |
235 | hw->ops.dsp_status = snd_usx2y_hwdep_dsp_status; |
236 | hw->ops.dsp_load = snd_usx2y_hwdep_dsp_load; |
237 | hw->ops.mmap = snd_us428ctls_mmap; |
238 | hw->ops.poll = snd_us428ctls_poll; |
239 | hw->exclusive = 1; |
240 | sprintf(buf: hw->name, fmt: "/dev/bus/usb/%03d/%03d" , device->bus->busnum, device->devnum); |
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 | |
248 | return 0; |
249 | } |
250 | |