|  | /* | 
|  | * Line 6 Linux USB driver | 
|  | * | 
|  | * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) | 
|  | * | 
|  | *	This program is free software; you can redistribute it and/or | 
|  | *	modify it under the terms of the GNU General Public License as | 
|  | *	published by the Free Software Foundation, version 2. | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include <linux/slab.h> | 
|  | #include <linux/spinlock.h> | 
|  | #include <linux/usb.h> | 
|  | #include <linux/wait.h> | 
|  | #include <linux/module.h> | 
|  | #include <sound/core.h> | 
|  |  | 
|  | #include "driver.h" | 
|  |  | 
|  | #define VARIAX_STARTUP_DELAY1 1000 | 
|  | #define VARIAX_STARTUP_DELAY3 100 | 
|  | #define VARIAX_STARTUP_DELAY4 100 | 
|  |  | 
|  | /* | 
|  | Stages of Variax startup procedure | 
|  | */ | 
|  | enum { | 
|  | VARIAX_STARTUP_INIT = 1, | 
|  | VARIAX_STARTUP_VERSIONREQ, | 
|  | VARIAX_STARTUP_WAIT, | 
|  | VARIAX_STARTUP_ACTIVATE, | 
|  | VARIAX_STARTUP_WORKQUEUE, | 
|  | VARIAX_STARTUP_SETUP, | 
|  | VARIAX_STARTUP_LAST = VARIAX_STARTUP_SETUP - 1 | 
|  | }; | 
|  |  | 
|  | enum { | 
|  | LINE6_PODXTLIVE_VARIAX, | 
|  | LINE6_VARIAX | 
|  | }; | 
|  |  | 
|  | struct usb_line6_variax { | 
|  | /* Generic Line 6 USB data */ | 
|  | struct usb_line6 line6; | 
|  |  | 
|  | /* Buffer for activation code */ | 
|  | unsigned char *buffer_activate; | 
|  |  | 
|  | /* Handler for device initialization */ | 
|  | struct work_struct startup_work; | 
|  |  | 
|  | /* Timers for device initialization */ | 
|  | struct timer_list startup_timer1; | 
|  | struct timer_list startup_timer2; | 
|  |  | 
|  | /* Current progress in startup procedure */ | 
|  | int startup_progress; | 
|  | }; | 
|  |  | 
|  | #define VARIAX_OFFSET_ACTIVATE 7 | 
|  |  | 
|  | /* | 
|  | This message is sent by the device during initialization and identifies | 
|  | the connected guitar version. | 
|  | */ | 
|  | static const char variax_init_version[] = { | 
|  | 0xf0, 0x7e, 0x7f, 0x06, 0x02, 0x00, 0x01, 0x0c, | 
|  | 0x07, 0x00, 0x00, 0x00 | 
|  | }; | 
|  |  | 
|  | /* | 
|  | This message is the last one sent by the device during initialization. | 
|  | */ | 
|  | static const char variax_init_done[] = { | 
|  | 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x6b | 
|  | }; | 
|  |  | 
|  | static const char variax_activate[] = { | 
|  | 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x2a, 0x01, | 
|  | 0xf7 | 
|  | }; | 
|  |  | 
|  | /* forward declarations: */ | 
|  | static void variax_startup2(struct timer_list *t); | 
|  | static void variax_startup4(struct timer_list *t); | 
|  | static void variax_startup5(struct timer_list *t); | 
|  |  | 
|  | static void variax_activate_async(struct usb_line6_variax *variax, int a) | 
|  | { | 
|  | variax->buffer_activate[VARIAX_OFFSET_ACTIVATE] = a; | 
|  | line6_send_raw_message_async(&variax->line6, variax->buffer_activate, | 
|  | sizeof(variax_activate)); | 
|  | } | 
|  |  | 
|  | /* | 
|  | Variax startup procedure. | 
|  | This is a sequence of functions with special requirements (e.g., must | 
|  | not run immediately after initialization, must not run in interrupt | 
|  | context). After the last one has finished, the device is ready to use. | 
|  | */ | 
|  |  | 
|  | static void variax_startup1(struct usb_line6_variax *variax) | 
|  | { | 
|  | CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_INIT); | 
|  |  | 
|  | /* delay startup procedure: */ | 
|  | line6_start_timer(&variax->startup_timer1, VARIAX_STARTUP_DELAY1, | 
|  | variax_startup2); | 
|  | } | 
|  |  | 
|  | static void variax_startup2(struct timer_list *t) | 
|  | { | 
|  | struct usb_line6_variax *variax = from_timer(variax, t, startup_timer1); | 
|  | struct usb_line6 *line6 = &variax->line6; | 
|  |  | 
|  | /* schedule another startup procedure until startup is complete: */ | 
|  | if (variax->startup_progress >= VARIAX_STARTUP_LAST) | 
|  | return; | 
|  |  | 
|  | variax->startup_progress = VARIAX_STARTUP_VERSIONREQ; | 
|  | line6_start_timer(&variax->startup_timer1, VARIAX_STARTUP_DELAY1, | 
|  | variax_startup2); | 
|  |  | 
|  | /* request firmware version: */ | 
|  | line6_version_request_async(line6); | 
|  | } | 
|  |  | 
|  | static void variax_startup3(struct usb_line6_variax *variax) | 
|  | { | 
|  | CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_WAIT); | 
|  |  | 
|  | /* delay startup procedure: */ | 
|  | line6_start_timer(&variax->startup_timer2, VARIAX_STARTUP_DELAY3, | 
|  | variax_startup4); | 
|  | } | 
|  |  | 
|  | static void variax_startup4(struct timer_list *t) | 
|  | { | 
|  | struct usb_line6_variax *variax = from_timer(variax, t, startup_timer2); | 
|  |  | 
|  | CHECK_STARTUP_PROGRESS(variax->startup_progress, | 
|  | VARIAX_STARTUP_ACTIVATE); | 
|  |  | 
|  | /* activate device: */ | 
|  | variax_activate_async(variax, 1); | 
|  | line6_start_timer(&variax->startup_timer2, VARIAX_STARTUP_DELAY4, | 
|  | variax_startup5); | 
|  | } | 
|  |  | 
|  | static void variax_startup5(struct timer_list *t) | 
|  | { | 
|  | struct usb_line6_variax *variax = from_timer(variax, t, startup_timer2); | 
|  |  | 
|  | CHECK_STARTUP_PROGRESS(variax->startup_progress, | 
|  | VARIAX_STARTUP_WORKQUEUE); | 
|  |  | 
|  | /* schedule work for global work queue: */ | 
|  | schedule_work(&variax->startup_work); | 
|  | } | 
|  |  | 
|  | static void variax_startup6(struct work_struct *work) | 
|  | { | 
|  | struct usb_line6_variax *variax = | 
|  | container_of(work, struct usb_line6_variax, startup_work); | 
|  |  | 
|  | CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_SETUP); | 
|  |  | 
|  | /* ALSA audio interface: */ | 
|  | snd_card_register(variax->line6.card); | 
|  | } | 
|  |  | 
|  | /* | 
|  | Process a completely received message. | 
|  | */ | 
|  | static void line6_variax_process_message(struct usb_line6 *line6) | 
|  | { | 
|  | struct usb_line6_variax *variax = (struct usb_line6_variax *) line6; | 
|  | const unsigned char *buf = variax->line6.buffer_message; | 
|  |  | 
|  | switch (buf[0]) { | 
|  | case LINE6_RESET: | 
|  | dev_info(variax->line6.ifcdev, "VARIAX reset\n"); | 
|  | break; | 
|  |  | 
|  | case LINE6_SYSEX_BEGIN: | 
|  | if (memcmp(buf + 1, variax_init_version + 1, | 
|  | sizeof(variax_init_version) - 1) == 0) { | 
|  | variax_startup3(variax); | 
|  | } else if (memcmp(buf + 1, variax_init_done + 1, | 
|  | sizeof(variax_init_done) - 1) == 0) { | 
|  | /* notify of complete initialization: */ | 
|  | variax_startup4(&variax->startup_timer2); | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | Variax destructor. | 
|  | */ | 
|  | static void line6_variax_disconnect(struct usb_line6 *line6) | 
|  | { | 
|  | struct usb_line6_variax *variax = (struct usb_line6_variax *)line6; | 
|  |  | 
|  | del_timer(&variax->startup_timer1); | 
|  | del_timer(&variax->startup_timer2); | 
|  | cancel_work_sync(&variax->startup_work); | 
|  |  | 
|  | kfree(variax->buffer_activate); | 
|  | } | 
|  |  | 
|  | /* | 
|  | Try to init workbench device. | 
|  | */ | 
|  | static int variax_init(struct usb_line6 *line6, | 
|  | const struct usb_device_id *id) | 
|  | { | 
|  | struct usb_line6_variax *variax = (struct usb_line6_variax *) line6; | 
|  | int err; | 
|  |  | 
|  | line6->process_message = line6_variax_process_message; | 
|  | line6->disconnect = line6_variax_disconnect; | 
|  |  | 
|  | timer_setup(&variax->startup_timer1, NULL, 0); | 
|  | timer_setup(&variax->startup_timer2, NULL, 0); | 
|  | INIT_WORK(&variax->startup_work, variax_startup6); | 
|  |  | 
|  | /* initialize USB buffers: */ | 
|  | variax->buffer_activate = kmemdup(variax_activate, | 
|  | sizeof(variax_activate), GFP_KERNEL); | 
|  |  | 
|  | if (variax->buffer_activate == NULL) | 
|  | return -ENOMEM; | 
|  |  | 
|  | /* initialize MIDI subsystem: */ | 
|  | err = line6_init_midi(&variax->line6); | 
|  | if (err < 0) | 
|  | return err; | 
|  |  | 
|  | /* initiate startup procedure: */ | 
|  | variax_startup1(variax); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod) | 
|  | #define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n) | 
|  |  | 
|  | /* table of devices that work with this driver */ | 
|  | static const struct usb_device_id variax_id_table[] = { | 
|  | { LINE6_IF_NUM(0x4650, 1), .driver_info = LINE6_PODXTLIVE_VARIAX }, | 
|  | { LINE6_DEVICE(0x534d),    .driver_info = LINE6_VARIAX }, | 
|  | {} | 
|  | }; | 
|  |  | 
|  | MODULE_DEVICE_TABLE(usb, variax_id_table); | 
|  |  | 
|  | static const struct line6_properties variax_properties_table[] = { | 
|  | [LINE6_PODXTLIVE_VARIAX] = { | 
|  | .id = "PODxtLive", | 
|  | .name = "PODxt Live", | 
|  | .capabilities	= LINE6_CAP_CONTROL | 
|  | | LINE6_CAP_CONTROL_MIDI, | 
|  | .altsetting = 1, | 
|  | .ep_ctrl_r = 0x86, | 
|  | .ep_ctrl_w = 0x05, | 
|  | .ep_audio_r = 0x82, | 
|  | .ep_audio_w = 0x01, | 
|  | }, | 
|  | [LINE6_VARIAX] = { | 
|  | .id = "Variax", | 
|  | .name = "Variax Workbench", | 
|  | .capabilities	= LINE6_CAP_CONTROL | 
|  | | LINE6_CAP_CONTROL_MIDI, | 
|  | .altsetting = 1, | 
|  | .ep_ctrl_r = 0x82, | 
|  | .ep_ctrl_w = 0x01, | 
|  | /* no audio channel */ | 
|  | } | 
|  | }; | 
|  |  | 
|  | /* | 
|  | Probe USB device. | 
|  | */ | 
|  | static int variax_probe(struct usb_interface *interface, | 
|  | const struct usb_device_id *id) | 
|  | { | 
|  | return line6_probe(interface, id, "Line6-Variax", | 
|  | &variax_properties_table[id->driver_info], | 
|  | variax_init, sizeof(struct usb_line6_variax)); | 
|  | } | 
|  |  | 
|  | static struct usb_driver variax_driver = { | 
|  | .name = KBUILD_MODNAME, | 
|  | .probe = variax_probe, | 
|  | .disconnect = line6_disconnect, | 
|  | #ifdef CONFIG_PM | 
|  | .suspend = line6_suspend, | 
|  | .resume = line6_resume, | 
|  | .reset_resume = line6_resume, | 
|  | #endif | 
|  | .id_table = variax_id_table, | 
|  | }; | 
|  |  | 
|  | module_usb_driver(variax_driver); | 
|  |  | 
|  | MODULE_DESCRIPTION("Vairax Workbench USB driver"); | 
|  | MODULE_LICENSE("GPL"); |