1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Line 6 Linux USB driver |
4 | * |
5 | * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) |
6 | */ |
7 | |
8 | #include <linux/slab.h> |
9 | #include <linux/spinlock.h> |
10 | #include <linux/usb.h> |
11 | #include <linux/wait.h> |
12 | #include <linux/module.h> |
13 | #include <sound/core.h> |
14 | |
15 | #include "driver.h" |
16 | |
17 | #define VARIAX_STARTUP_DELAY1 1000 |
18 | #define VARIAX_STARTUP_DELAY3 100 |
19 | #define VARIAX_STARTUP_DELAY4 100 |
20 | |
21 | /* |
22 | Stages of Variax startup procedure |
23 | */ |
24 | enum { |
25 | VARIAX_STARTUP_VERSIONREQ, |
26 | VARIAX_STARTUP_ACTIVATE, |
27 | VARIAX_STARTUP_SETUP, |
28 | }; |
29 | |
30 | enum { |
31 | LINE6_PODXTLIVE_VARIAX, |
32 | LINE6_VARIAX |
33 | }; |
34 | |
35 | struct usb_line6_variax { |
36 | /* Generic Line 6 USB data */ |
37 | struct usb_line6 line6; |
38 | |
39 | /* Buffer for activation code */ |
40 | unsigned char *buffer_activate; |
41 | |
42 | /* Current progress in startup procedure */ |
43 | int startup_progress; |
44 | }; |
45 | |
46 | #define line6_to_variax(x) container_of(x, struct usb_line6_variax, line6) |
47 | |
48 | #define VARIAX_OFFSET_ACTIVATE 7 |
49 | |
50 | /* |
51 | This message is sent by the device during initialization and identifies |
52 | the connected guitar version. |
53 | */ |
54 | static const char variax_init_version[] = { |
55 | 0xf0, 0x7e, 0x7f, 0x06, 0x02, 0x00, 0x01, 0x0c, |
56 | 0x07, 0x00, 0x00, 0x00 |
57 | }; |
58 | |
59 | /* |
60 | This message is the last one sent by the device during initialization. |
61 | */ |
62 | static const char variax_init_done[] = { |
63 | 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x6b |
64 | }; |
65 | |
66 | static const char variax_activate[] = { |
67 | 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x2a, 0x01, |
68 | 0xf7 |
69 | }; |
70 | |
71 | static void variax_activate_async(struct usb_line6_variax *variax, int a) |
72 | { |
73 | variax->buffer_activate[VARIAX_OFFSET_ACTIVATE] = a; |
74 | line6_send_raw_message_async(line6: &variax->line6, buffer: variax->buffer_activate, |
75 | size: sizeof(variax_activate)); |
76 | } |
77 | |
78 | /* |
79 | Variax startup procedure. |
80 | This is a sequence of functions with special requirements (e.g., must |
81 | not run immediately after initialization, must not run in interrupt |
82 | context). After the last one has finished, the device is ready to use. |
83 | */ |
84 | |
85 | static void variax_startup(struct usb_line6 *line6) |
86 | { |
87 | struct usb_line6_variax *variax = line6_to_variax(line6); |
88 | |
89 | switch (variax->startup_progress) { |
90 | case VARIAX_STARTUP_VERSIONREQ: |
91 | /* repeat request until getting the response */ |
92 | schedule_delayed_work(dwork: &line6->startup_work, |
93 | delay: msecs_to_jiffies(VARIAX_STARTUP_DELAY1)); |
94 | /* request firmware version: */ |
95 | line6_version_request_async(line6); |
96 | break; |
97 | case VARIAX_STARTUP_ACTIVATE: |
98 | /* activate device: */ |
99 | variax_activate_async(variax, a: 1); |
100 | variax->startup_progress = VARIAX_STARTUP_SETUP; |
101 | schedule_delayed_work(dwork: &line6->startup_work, |
102 | delay: msecs_to_jiffies(VARIAX_STARTUP_DELAY4)); |
103 | break; |
104 | case VARIAX_STARTUP_SETUP: |
105 | /* ALSA audio interface: */ |
106 | snd_card_register(card: variax->line6.card); |
107 | break; |
108 | } |
109 | } |
110 | |
111 | /* |
112 | Process a completely received message. |
113 | */ |
114 | static void line6_variax_process_message(struct usb_line6 *line6) |
115 | { |
116 | struct usb_line6_variax *variax = line6_to_variax(line6); |
117 | const unsigned char *buf = variax->line6.buffer_message; |
118 | |
119 | switch (buf[0]) { |
120 | case LINE6_RESET: |
121 | dev_info(variax->line6.ifcdev, "VARIAX reset\n" ); |
122 | break; |
123 | |
124 | case LINE6_SYSEX_BEGIN: |
125 | if (memcmp(p: buf + 1, q: variax_init_version + 1, |
126 | size: sizeof(variax_init_version) - 1) == 0) { |
127 | if (variax->startup_progress >= VARIAX_STARTUP_ACTIVATE) |
128 | break; |
129 | variax->startup_progress = VARIAX_STARTUP_ACTIVATE; |
130 | cancel_delayed_work(dwork: &line6->startup_work); |
131 | schedule_delayed_work(dwork: &line6->startup_work, |
132 | delay: msecs_to_jiffies(VARIAX_STARTUP_DELAY3)); |
133 | } else if (memcmp(p: buf + 1, q: variax_init_done + 1, |
134 | size: sizeof(variax_init_done) - 1) == 0) { |
135 | /* notify of complete initialization: */ |
136 | if (variax->startup_progress >= VARIAX_STARTUP_SETUP) |
137 | break; |
138 | cancel_delayed_work(dwork: &line6->startup_work); |
139 | schedule_delayed_work(dwork: &line6->startup_work, delay: 0); |
140 | } |
141 | break; |
142 | } |
143 | } |
144 | |
145 | /* |
146 | Variax destructor. |
147 | */ |
148 | static void line6_variax_disconnect(struct usb_line6 *line6) |
149 | { |
150 | struct usb_line6_variax *variax = line6_to_variax(line6); |
151 | |
152 | kfree(objp: variax->buffer_activate); |
153 | } |
154 | |
155 | /* |
156 | Try to init workbench device. |
157 | */ |
158 | static int variax_init(struct usb_line6 *line6, |
159 | const struct usb_device_id *id) |
160 | { |
161 | struct usb_line6_variax *variax = line6_to_variax(line6); |
162 | |
163 | line6->process_message = line6_variax_process_message; |
164 | line6->disconnect = line6_variax_disconnect; |
165 | line6->startup = variax_startup; |
166 | |
167 | /* initialize USB buffers: */ |
168 | variax->buffer_activate = kmemdup(p: variax_activate, |
169 | size: sizeof(variax_activate), GFP_KERNEL); |
170 | |
171 | if (variax->buffer_activate == NULL) |
172 | return -ENOMEM; |
173 | |
174 | /* initiate startup procedure: */ |
175 | schedule_delayed_work(dwork: &line6->startup_work, |
176 | delay: msecs_to_jiffies(VARIAX_STARTUP_DELAY1)); |
177 | return 0; |
178 | } |
179 | |
180 | #define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod) |
181 | #define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n) |
182 | |
183 | /* table of devices that work with this driver */ |
184 | static const struct usb_device_id variax_id_table[] = { |
185 | { LINE6_IF_NUM(0x4650, 1), .driver_info = LINE6_PODXTLIVE_VARIAX }, |
186 | { LINE6_DEVICE(0x534d), .driver_info = LINE6_VARIAX }, |
187 | {} |
188 | }; |
189 | |
190 | MODULE_DEVICE_TABLE(usb, variax_id_table); |
191 | |
192 | static const struct line6_properties variax_properties_table[] = { |
193 | [LINE6_PODXTLIVE_VARIAX] = { |
194 | .id = "PODxtLive" , |
195 | .name = "PODxt Live" , |
196 | .capabilities = LINE6_CAP_CONTROL |
197 | | LINE6_CAP_CONTROL_MIDI, |
198 | .altsetting = 1, |
199 | .ep_ctrl_r = 0x86, |
200 | .ep_ctrl_w = 0x05, |
201 | .ep_audio_r = 0x82, |
202 | .ep_audio_w = 0x01, |
203 | }, |
204 | [LINE6_VARIAX] = { |
205 | .id = "Variax" , |
206 | .name = "Variax Workbench" , |
207 | .capabilities = LINE6_CAP_CONTROL |
208 | | LINE6_CAP_CONTROL_MIDI, |
209 | .altsetting = 1, |
210 | .ep_ctrl_r = 0x82, |
211 | .ep_ctrl_w = 0x01, |
212 | /* no audio channel */ |
213 | } |
214 | }; |
215 | |
216 | /* |
217 | Probe USB device. |
218 | */ |
219 | static int variax_probe(struct usb_interface *interface, |
220 | const struct usb_device_id *id) |
221 | { |
222 | return line6_probe(interface, id, driver_name: "Line6-Variax" , |
223 | properties: &variax_properties_table[id->driver_info], |
224 | private_init: variax_init, data_size: sizeof(struct usb_line6_variax)); |
225 | } |
226 | |
227 | static struct usb_driver variax_driver = { |
228 | .name = KBUILD_MODNAME, |
229 | .probe = variax_probe, |
230 | .disconnect = line6_disconnect, |
231 | #ifdef CONFIG_PM |
232 | .suspend = line6_suspend, |
233 | .resume = line6_resume, |
234 | .reset_resume = line6_resume, |
235 | #endif |
236 | .id_table = variax_id_table, |
237 | }; |
238 | |
239 | module_usb_driver(variax_driver); |
240 | |
241 | MODULE_DESCRIPTION("Variax Workbench USB driver" ); |
242 | MODULE_LICENSE("GPL" ); |
243 | |