1a10e763bSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2705ececdSMarkus Grabner /*
3c078a4aaSChris Rorvick * Line 6 Linux USB driver
4705ececdSMarkus Grabner *
51027f476SMarkus Grabner * Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.at)
6705ececdSMarkus Grabner */
7705ececdSMarkus Grabner
85a0e3ad6STejun Heo #include <linux/slab.h>
9ccddbe4aSTakashi Iwai #include <linux/spinlock.h>
10ccddbe4aSTakashi Iwai #include <linux/usb.h>
11ccddbe4aSTakashi Iwai #include <linux/wait.h>
12ccddbe4aSTakashi Iwai #include <linux/module.h>
13ccddbe4aSTakashi Iwai #include <sound/core.h>
145a0e3ad6STejun Heo
151027f476SMarkus Grabner #include "driver.h"
16ccddbe4aSTakashi Iwai
17ccddbe4aSTakashi Iwai #define VARIAX_STARTUP_DELAY1 1000
18ccddbe4aSTakashi Iwai #define VARIAX_STARTUP_DELAY3 100
19ccddbe4aSTakashi Iwai #define VARIAX_STARTUP_DELAY4 100
20ccddbe4aSTakashi Iwai
21ccddbe4aSTakashi Iwai /*
22ccddbe4aSTakashi Iwai Stages of Variax startup procedure
23ccddbe4aSTakashi Iwai */
24ccddbe4aSTakashi Iwai enum {
25ccddbe4aSTakashi Iwai VARIAX_STARTUP_VERSIONREQ,
26ccddbe4aSTakashi Iwai VARIAX_STARTUP_ACTIVATE,
27ccddbe4aSTakashi Iwai VARIAX_STARTUP_SETUP,
28ccddbe4aSTakashi Iwai };
29ccddbe4aSTakashi Iwai
30ccddbe4aSTakashi Iwai enum {
31ccddbe4aSTakashi Iwai LINE6_PODXTLIVE_VARIAX,
32ccddbe4aSTakashi Iwai LINE6_VARIAX
33ccddbe4aSTakashi Iwai };
34ccddbe4aSTakashi Iwai
35ccddbe4aSTakashi Iwai struct usb_line6_variax {
36cddbd4f1STakashi Iwai /* Generic Line 6 USB data */
37ccddbe4aSTakashi Iwai struct usb_line6 line6;
38ccddbe4aSTakashi Iwai
39cddbd4f1STakashi Iwai /* Buffer for activation code */
40ccddbe4aSTakashi Iwai unsigned char *buffer_activate;
41ccddbe4aSTakashi Iwai
42cddbd4f1STakashi Iwai /* Current progress in startup procedure */
43ccddbe4aSTakashi Iwai int startup_progress;
44ccddbe4aSTakashi Iwai };
45705ececdSMarkus Grabner
46f23a09eeSTakashi Iwai #define line6_to_variax(x) container_of(x, struct usb_line6_variax, line6)
47f23a09eeSTakashi Iwai
48705ececdSMarkus Grabner #define VARIAX_OFFSET_ACTIVATE 7
49705ececdSMarkus Grabner
501027f476SMarkus Grabner /*
511027f476SMarkus Grabner This message is sent by the device during initialization and identifies
521027f476SMarkus Grabner the connected guitar version.
531027f476SMarkus Grabner */
541027f476SMarkus Grabner static const char variax_init_version[] = {
551027f476SMarkus Grabner 0xf0, 0x7e, 0x7f, 0x06, 0x02, 0x00, 0x01, 0x0c,
561027f476SMarkus Grabner 0x07, 0x00, 0x00, 0x00
571027f476SMarkus Grabner };
581027f476SMarkus Grabner
591027f476SMarkus Grabner /*
601027f476SMarkus Grabner This message is the last one sent by the device during initialization.
611027f476SMarkus Grabner */
621027f476SMarkus Grabner static const char variax_init_done[] = {
631027f476SMarkus Grabner 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x6b
641027f476SMarkus Grabner };
651027f476SMarkus Grabner
66705ececdSMarkus Grabner static const char variax_activate[] = {
67705ececdSMarkus Grabner 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x2a, 0x01,
68705ececdSMarkus Grabner 0xf7
69705ececdSMarkus Grabner };
701027f476SMarkus Grabner
variax_activate_async(struct usb_line6_variax * variax,int a)711027f476SMarkus Grabner static void variax_activate_async(struct usb_line6_variax *variax, int a)
72705ececdSMarkus Grabner {
731027f476SMarkus Grabner variax->buffer_activate[VARIAX_OFFSET_ACTIVATE] = a;
749cd57f77SGreg Kroah-Hartman line6_send_raw_message_async(&variax->line6, variax->buffer_activate,
759cd57f77SGreg Kroah-Hartman sizeof(variax_activate));
76705ececdSMarkus Grabner }
77705ececdSMarkus Grabner
78705ececdSMarkus Grabner /*
791027f476SMarkus Grabner Variax startup procedure.
801027f476SMarkus Grabner This is a sequence of functions with special requirements (e.g., must
811027f476SMarkus Grabner not run immediately after initialization, must not run in interrupt
821027f476SMarkus Grabner context). After the last one has finished, the device is ready to use.
83705ececdSMarkus Grabner */
841027f476SMarkus Grabner
variax_startup(struct usb_line6 * line6)856ea53391STakashi Iwai static void variax_startup(struct usb_line6 *line6)
86705ececdSMarkus Grabner {
87f23a09eeSTakashi Iwai struct usb_line6_variax *variax = line6_to_variax(line6);
881027f476SMarkus Grabner
896ea53391STakashi Iwai switch (variax->startup_progress) {
906ea53391STakashi Iwai case VARIAX_STARTUP_VERSIONREQ:
916ea53391STakashi Iwai /* repeat request until getting the response */
926ea53391STakashi Iwai schedule_delayed_work(&line6->startup_work,
936ea53391STakashi Iwai msecs_to_jiffies(VARIAX_STARTUP_DELAY1));
941027f476SMarkus Grabner /* request firmware version: */
951027f476SMarkus Grabner line6_version_request_async(line6);
966ea53391STakashi Iwai break;
976ea53391STakashi Iwai case VARIAX_STARTUP_ACTIVATE:
981027f476SMarkus Grabner /* activate device: */
991027f476SMarkus Grabner variax_activate_async(variax, 1);
1006ea53391STakashi Iwai variax->startup_progress = VARIAX_STARTUP_SETUP;
1016ea53391STakashi Iwai schedule_delayed_work(&line6->startup_work,
1026ea53391STakashi Iwai msecs_to_jiffies(VARIAX_STARTUP_DELAY4));
1036ea53391STakashi Iwai break;
1046ea53391STakashi Iwai case VARIAX_STARTUP_SETUP:
1051027f476SMarkus Grabner /* ALSA audio interface: */
10685a9339bSTakashi Iwai snd_card_register(variax->line6.card);
1076ea53391STakashi Iwai break;
1086ea53391STakashi Iwai }
109705ececdSMarkus Grabner }
110705ececdSMarkus Grabner
111705ececdSMarkus Grabner /*
112705ececdSMarkus Grabner Process a completely received message.
113705ececdSMarkus Grabner */
line6_variax_process_message(struct usb_line6 * line6)11401f6b2bcSChris Rorvick static void line6_variax_process_message(struct usb_line6 *line6)
115705ececdSMarkus Grabner {
116f23a09eeSTakashi Iwai struct usb_line6_variax *variax = line6_to_variax(line6);
117705ececdSMarkus Grabner const unsigned char *buf = variax->line6.buffer_message;
118705ececdSMarkus Grabner
119705ececdSMarkus Grabner switch (buf[0]) {
120705ececdSMarkus Grabner case LINE6_RESET:
121705ececdSMarkus Grabner dev_info(variax->line6.ifcdev, "VARIAX reset\n");
122705ececdSMarkus Grabner break;
123705ececdSMarkus Grabner
124705ececdSMarkus Grabner case LINE6_SYSEX_BEGIN:
125323246b2SStefan Hajnoczi if (memcmp(buf + 1, variax_init_version + 1,
1261027f476SMarkus Grabner sizeof(variax_init_version) - 1) == 0) {
1276ea53391STakashi Iwai if (variax->startup_progress >= VARIAX_STARTUP_ACTIVATE)
1286ea53391STakashi Iwai break;
1296ea53391STakashi Iwai variax->startup_progress = VARIAX_STARTUP_ACTIVATE;
1306ea53391STakashi Iwai cancel_delayed_work(&line6->startup_work);
1316ea53391STakashi Iwai schedule_delayed_work(&line6->startup_work,
1326ea53391STakashi Iwai msecs_to_jiffies(VARIAX_STARTUP_DELAY3));
1331027f476SMarkus Grabner } else if (memcmp(buf + 1, variax_init_done + 1,
1341027f476SMarkus Grabner sizeof(variax_init_done) - 1) == 0) {
1351027f476SMarkus Grabner /* notify of complete initialization: */
1366ea53391STakashi Iwai if (variax->startup_progress >= VARIAX_STARTUP_SETUP)
1376ea53391STakashi Iwai break;
1386ea53391STakashi Iwai cancel_delayed_work(&line6->startup_work);
1396ea53391STakashi Iwai schedule_delayed_work(&line6->startup_work, 0);
140705ececdSMarkus Grabner }
141705ececdSMarkus Grabner break;
142705ececdSMarkus Grabner }
143705ececdSMarkus Grabner }
144705ececdSMarkus Grabner
145705ececdSMarkus Grabner /*
146705ececdSMarkus Grabner Variax destructor.
147705ececdSMarkus Grabner */
line6_variax_disconnect(struct usb_line6 * line6)148f66fd990STakashi Iwai static void line6_variax_disconnect(struct usb_line6 *line6)
149705ececdSMarkus Grabner {
150f23a09eeSTakashi Iwai struct usb_line6_variax *variax = line6_to_variax(line6);
151705ececdSMarkus Grabner
1529cd57f77SGreg Kroah-Hartman kfree(variax->buffer_activate);
153705ececdSMarkus Grabner }
154705ececdSMarkus Grabner
155705ececdSMarkus Grabner /*
1561027f476SMarkus Grabner Try to init workbench device.
157705ececdSMarkus Grabner */
variax_init(struct usb_line6 * line6,const struct usb_device_id * id)158f66fd990STakashi Iwai static int variax_init(struct usb_line6 *line6,
159f66fd990STakashi Iwai const struct usb_device_id *id)
160705ececdSMarkus Grabner {
161f23a09eeSTakashi Iwai struct usb_line6_variax *variax = line6_to_variax(line6);
162705ececdSMarkus Grabner
16301f6b2bcSChris Rorvick line6->process_message = line6_variax_process_message;
164a46c4672SChris Rorvick line6->disconnect = line6_variax_disconnect;
1656ea53391STakashi Iwai line6->startup = variax_startup;
166e1a164d7SMarkus Grabner
167705ececdSMarkus Grabner /* initialize USB buffers: */
16894002c07SJulia Lawall variax->buffer_activate = kmemdup(variax_activate,
16994002c07SJulia Lawall sizeof(variax_activate), GFP_KERNEL);
170705ececdSMarkus Grabner
171a019f5e8STakashi Iwai if (variax->buffer_activate == NULL)
172705ececdSMarkus Grabner return -ENOMEM;
173705ececdSMarkus Grabner
1741027f476SMarkus Grabner /* initiate startup procedure: */
1756ea53391STakashi Iwai schedule_delayed_work(&line6->startup_work,
1766ea53391STakashi Iwai msecs_to_jiffies(VARIAX_STARTUP_DELAY1));
1771027f476SMarkus Grabner return 0;
1781027f476SMarkus Grabner }
1791027f476SMarkus Grabner
180ccddbe4aSTakashi Iwai #define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod)
181ccddbe4aSTakashi Iwai #define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n)
182ccddbe4aSTakashi Iwai
183ccddbe4aSTakashi Iwai /* table of devices that work with this driver */
184ccddbe4aSTakashi Iwai static const struct usb_device_id variax_id_table[] = {
185ccddbe4aSTakashi Iwai { LINE6_IF_NUM(0x4650, 1), .driver_info = LINE6_PODXTLIVE_VARIAX },
186ccddbe4aSTakashi Iwai { LINE6_DEVICE(0x534d), .driver_info = LINE6_VARIAX },
187ccddbe4aSTakashi Iwai {}
188ccddbe4aSTakashi Iwai };
189ccddbe4aSTakashi Iwai
190ccddbe4aSTakashi Iwai MODULE_DEVICE_TABLE(usb, variax_id_table);
191ccddbe4aSTakashi Iwai
192ccddbe4aSTakashi Iwai static const struct line6_properties variax_properties_table[] = {
193ccddbe4aSTakashi Iwai [LINE6_PODXTLIVE_VARIAX] = {
194ccddbe4aSTakashi Iwai .id = "PODxtLive",
195ccddbe4aSTakashi Iwai .name = "PODxt Live",
196174e1fc0SAndrej Krutak .capabilities = LINE6_CAP_CONTROL
197174e1fc0SAndrej Krutak | LINE6_CAP_CONTROL_MIDI,
198ccddbe4aSTakashi Iwai .altsetting = 1,
199ccddbe4aSTakashi Iwai .ep_ctrl_r = 0x86,
200ccddbe4aSTakashi Iwai .ep_ctrl_w = 0x05,
201ccddbe4aSTakashi Iwai .ep_audio_r = 0x82,
202ccddbe4aSTakashi Iwai .ep_audio_w = 0x01,
203ccddbe4aSTakashi Iwai },
204ccddbe4aSTakashi Iwai [LINE6_VARIAX] = {
205ccddbe4aSTakashi Iwai .id = "Variax",
206ccddbe4aSTakashi Iwai .name = "Variax Workbench",
207174e1fc0SAndrej Krutak .capabilities = LINE6_CAP_CONTROL
208174e1fc0SAndrej Krutak | LINE6_CAP_CONTROL_MIDI,
209ccddbe4aSTakashi Iwai .altsetting = 1,
210ccddbe4aSTakashi Iwai .ep_ctrl_r = 0x82,
211ccddbe4aSTakashi Iwai .ep_ctrl_w = 0x01,
212ccddbe4aSTakashi Iwai /* no audio channel */
213ccddbe4aSTakashi Iwai }
214ccddbe4aSTakashi Iwai };
215ccddbe4aSTakashi Iwai
216ccddbe4aSTakashi Iwai /*
217ccddbe4aSTakashi Iwai Probe USB device.
218ccddbe4aSTakashi Iwai */
variax_probe(struct usb_interface * interface,const struct usb_device_id * id)219ccddbe4aSTakashi Iwai static int variax_probe(struct usb_interface *interface,
220ccddbe4aSTakashi Iwai const struct usb_device_id *id)
221ccddbe4aSTakashi Iwai {
22212865cacSChris Rorvick return line6_probe(interface, id, "Line6-Variax",
223ccddbe4aSTakashi Iwai &variax_properties_table[id->driver_info],
224aca514b8STakashi Iwai variax_init, sizeof(struct usb_line6_variax));
225ccddbe4aSTakashi Iwai }
226ccddbe4aSTakashi Iwai
227ccddbe4aSTakashi Iwai static struct usb_driver variax_driver = {
228ccddbe4aSTakashi Iwai .name = KBUILD_MODNAME,
229ccddbe4aSTakashi Iwai .probe = variax_probe,
230ccddbe4aSTakashi Iwai .disconnect = line6_disconnect,
231ccddbe4aSTakashi Iwai #ifdef CONFIG_PM
232ccddbe4aSTakashi Iwai .suspend = line6_suspend,
233ccddbe4aSTakashi Iwai .resume = line6_resume,
234ccddbe4aSTakashi Iwai .reset_resume = line6_resume,
235ccddbe4aSTakashi Iwai #endif
236ccddbe4aSTakashi Iwai .id_table = variax_id_table,
237ccddbe4aSTakashi Iwai };
238ccddbe4aSTakashi Iwai
239ccddbe4aSTakashi Iwai module_usb_driver(variax_driver);
240ccddbe4aSTakashi Iwai
241*e4091bddSChristophe JAILLET MODULE_DESCRIPTION("Variax Workbench USB driver");
242ccddbe4aSTakashi Iwai MODULE_LICENSE("GPL");
243