18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Line 6 Linux USB driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/slab.h> 98c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 108c2ecf20Sopenharmony_ci#include <linux/usb.h> 118c2ecf20Sopenharmony_ci#include <linux/wait.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <sound/core.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include "driver.h" 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#define VARIAX_STARTUP_DELAY1 1000 188c2ecf20Sopenharmony_ci#define VARIAX_STARTUP_DELAY3 100 198c2ecf20Sopenharmony_ci#define VARIAX_STARTUP_DELAY4 100 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci/* 228c2ecf20Sopenharmony_ci Stages of Variax startup procedure 238c2ecf20Sopenharmony_ci*/ 248c2ecf20Sopenharmony_cienum { 258c2ecf20Sopenharmony_ci VARIAX_STARTUP_VERSIONREQ, 268c2ecf20Sopenharmony_ci VARIAX_STARTUP_ACTIVATE, 278c2ecf20Sopenharmony_ci VARIAX_STARTUP_SETUP, 288c2ecf20Sopenharmony_ci}; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cienum { 318c2ecf20Sopenharmony_ci LINE6_PODXTLIVE_VARIAX, 328c2ecf20Sopenharmony_ci LINE6_VARIAX 338c2ecf20Sopenharmony_ci}; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_cistruct usb_line6_variax { 368c2ecf20Sopenharmony_ci /* Generic Line 6 USB data */ 378c2ecf20Sopenharmony_ci struct usb_line6 line6; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci /* Buffer for activation code */ 408c2ecf20Sopenharmony_ci unsigned char *buffer_activate; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci /* Current progress in startup procedure */ 438c2ecf20Sopenharmony_ci int startup_progress; 448c2ecf20Sopenharmony_ci}; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci#define line6_to_variax(x) container_of(x, struct usb_line6_variax, line6) 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci#define VARIAX_OFFSET_ACTIVATE 7 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci/* 518c2ecf20Sopenharmony_ci This message is sent by the device during initialization and identifies 528c2ecf20Sopenharmony_ci the connected guitar version. 538c2ecf20Sopenharmony_ci*/ 548c2ecf20Sopenharmony_cistatic const char variax_init_version[] = { 558c2ecf20Sopenharmony_ci 0xf0, 0x7e, 0x7f, 0x06, 0x02, 0x00, 0x01, 0x0c, 568c2ecf20Sopenharmony_ci 0x07, 0x00, 0x00, 0x00 578c2ecf20Sopenharmony_ci}; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci/* 608c2ecf20Sopenharmony_ci This message is the last one sent by the device during initialization. 618c2ecf20Sopenharmony_ci*/ 628c2ecf20Sopenharmony_cistatic const char variax_init_done[] = { 638c2ecf20Sopenharmony_ci 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x6b 648c2ecf20Sopenharmony_ci}; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_cistatic const char variax_activate[] = { 678c2ecf20Sopenharmony_ci 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x2a, 0x01, 688c2ecf20Sopenharmony_ci 0xf7 698c2ecf20Sopenharmony_ci}; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistatic void variax_activate_async(struct usb_line6_variax *variax, int a) 728c2ecf20Sopenharmony_ci{ 738c2ecf20Sopenharmony_ci variax->buffer_activate[VARIAX_OFFSET_ACTIVATE] = a; 748c2ecf20Sopenharmony_ci line6_send_raw_message_async(&variax->line6, variax->buffer_activate, 758c2ecf20Sopenharmony_ci sizeof(variax_activate)); 768c2ecf20Sopenharmony_ci} 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci/* 798c2ecf20Sopenharmony_ci Variax startup procedure. 808c2ecf20Sopenharmony_ci This is a sequence of functions with special requirements (e.g., must 818c2ecf20Sopenharmony_ci not run immediately after initialization, must not run in interrupt 828c2ecf20Sopenharmony_ci context). After the last one has finished, the device is ready to use. 838c2ecf20Sopenharmony_ci*/ 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic void variax_startup(struct usb_line6 *line6) 868c2ecf20Sopenharmony_ci{ 878c2ecf20Sopenharmony_ci struct usb_line6_variax *variax = line6_to_variax(line6); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci switch (variax->startup_progress) { 908c2ecf20Sopenharmony_ci case VARIAX_STARTUP_VERSIONREQ: 918c2ecf20Sopenharmony_ci /* repeat request until getting the response */ 928c2ecf20Sopenharmony_ci schedule_delayed_work(&line6->startup_work, 938c2ecf20Sopenharmony_ci msecs_to_jiffies(VARIAX_STARTUP_DELAY1)); 948c2ecf20Sopenharmony_ci /* request firmware version: */ 958c2ecf20Sopenharmony_ci line6_version_request_async(line6); 968c2ecf20Sopenharmony_ci break; 978c2ecf20Sopenharmony_ci case VARIAX_STARTUP_ACTIVATE: 988c2ecf20Sopenharmony_ci /* activate device: */ 998c2ecf20Sopenharmony_ci variax_activate_async(variax, 1); 1008c2ecf20Sopenharmony_ci variax->startup_progress = VARIAX_STARTUP_SETUP; 1018c2ecf20Sopenharmony_ci schedule_delayed_work(&line6->startup_work, 1028c2ecf20Sopenharmony_ci msecs_to_jiffies(VARIAX_STARTUP_DELAY4)); 1038c2ecf20Sopenharmony_ci break; 1048c2ecf20Sopenharmony_ci case VARIAX_STARTUP_SETUP: 1058c2ecf20Sopenharmony_ci /* ALSA audio interface: */ 1068c2ecf20Sopenharmony_ci snd_card_register(variax->line6.card); 1078c2ecf20Sopenharmony_ci break; 1088c2ecf20Sopenharmony_ci } 1098c2ecf20Sopenharmony_ci} 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci/* 1128c2ecf20Sopenharmony_ci Process a completely received message. 1138c2ecf20Sopenharmony_ci*/ 1148c2ecf20Sopenharmony_cistatic void line6_variax_process_message(struct usb_line6 *line6) 1158c2ecf20Sopenharmony_ci{ 1168c2ecf20Sopenharmony_ci struct usb_line6_variax *variax = line6_to_variax(line6); 1178c2ecf20Sopenharmony_ci const unsigned char *buf = variax->line6.buffer_message; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci switch (buf[0]) { 1208c2ecf20Sopenharmony_ci case LINE6_RESET: 1218c2ecf20Sopenharmony_ci dev_info(variax->line6.ifcdev, "VARIAX reset\n"); 1228c2ecf20Sopenharmony_ci break; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci case LINE6_SYSEX_BEGIN: 1258c2ecf20Sopenharmony_ci if (memcmp(buf + 1, variax_init_version + 1, 1268c2ecf20Sopenharmony_ci sizeof(variax_init_version) - 1) == 0) { 1278c2ecf20Sopenharmony_ci if (variax->startup_progress >= VARIAX_STARTUP_ACTIVATE) 1288c2ecf20Sopenharmony_ci break; 1298c2ecf20Sopenharmony_ci variax->startup_progress = VARIAX_STARTUP_ACTIVATE; 1308c2ecf20Sopenharmony_ci cancel_delayed_work(&line6->startup_work); 1318c2ecf20Sopenharmony_ci schedule_delayed_work(&line6->startup_work, 1328c2ecf20Sopenharmony_ci msecs_to_jiffies(VARIAX_STARTUP_DELAY3)); 1338c2ecf20Sopenharmony_ci } else if (memcmp(buf + 1, variax_init_done + 1, 1348c2ecf20Sopenharmony_ci sizeof(variax_init_done) - 1) == 0) { 1358c2ecf20Sopenharmony_ci /* notify of complete initialization: */ 1368c2ecf20Sopenharmony_ci if (variax->startup_progress >= VARIAX_STARTUP_SETUP) 1378c2ecf20Sopenharmony_ci break; 1388c2ecf20Sopenharmony_ci cancel_delayed_work(&line6->startup_work); 1398c2ecf20Sopenharmony_ci schedule_delayed_work(&line6->startup_work, 0); 1408c2ecf20Sopenharmony_ci } 1418c2ecf20Sopenharmony_ci break; 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci} 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci/* 1468c2ecf20Sopenharmony_ci Variax destructor. 1478c2ecf20Sopenharmony_ci*/ 1488c2ecf20Sopenharmony_cistatic void line6_variax_disconnect(struct usb_line6 *line6) 1498c2ecf20Sopenharmony_ci{ 1508c2ecf20Sopenharmony_ci struct usb_line6_variax *variax = line6_to_variax(line6); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci kfree(variax->buffer_activate); 1538c2ecf20Sopenharmony_ci} 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci/* 1568c2ecf20Sopenharmony_ci Try to init workbench device. 1578c2ecf20Sopenharmony_ci*/ 1588c2ecf20Sopenharmony_cistatic int variax_init(struct usb_line6 *line6, 1598c2ecf20Sopenharmony_ci const struct usb_device_id *id) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci struct usb_line6_variax *variax = line6_to_variax(line6); 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci line6->process_message = line6_variax_process_message; 1648c2ecf20Sopenharmony_ci line6->disconnect = line6_variax_disconnect; 1658c2ecf20Sopenharmony_ci line6->startup = variax_startup; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci /* initialize USB buffers: */ 1688c2ecf20Sopenharmony_ci variax->buffer_activate = kmemdup(variax_activate, 1698c2ecf20Sopenharmony_ci sizeof(variax_activate), GFP_KERNEL); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci if (variax->buffer_activate == NULL) 1728c2ecf20Sopenharmony_ci return -ENOMEM; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci /* initiate startup procedure: */ 1758c2ecf20Sopenharmony_ci schedule_delayed_work(&line6->startup_work, 1768c2ecf20Sopenharmony_ci msecs_to_jiffies(VARIAX_STARTUP_DELAY1)); 1778c2ecf20Sopenharmony_ci return 0; 1788c2ecf20Sopenharmony_ci} 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod) 1818c2ecf20Sopenharmony_ci#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n) 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci/* table of devices that work with this driver */ 1848c2ecf20Sopenharmony_cistatic const struct usb_device_id variax_id_table[] = { 1858c2ecf20Sopenharmony_ci { LINE6_IF_NUM(0x4650, 1), .driver_info = LINE6_PODXTLIVE_VARIAX }, 1868c2ecf20Sopenharmony_ci { LINE6_DEVICE(0x534d), .driver_info = LINE6_VARIAX }, 1878c2ecf20Sopenharmony_ci {} 1888c2ecf20Sopenharmony_ci}; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, variax_id_table); 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_cistatic const struct line6_properties variax_properties_table[] = { 1938c2ecf20Sopenharmony_ci [LINE6_PODXTLIVE_VARIAX] = { 1948c2ecf20Sopenharmony_ci .id = "PODxtLive", 1958c2ecf20Sopenharmony_ci .name = "PODxt Live", 1968c2ecf20Sopenharmony_ci .capabilities = LINE6_CAP_CONTROL 1978c2ecf20Sopenharmony_ci | LINE6_CAP_CONTROL_MIDI, 1988c2ecf20Sopenharmony_ci .altsetting = 1, 1998c2ecf20Sopenharmony_ci .ep_ctrl_r = 0x86, 2008c2ecf20Sopenharmony_ci .ep_ctrl_w = 0x05, 2018c2ecf20Sopenharmony_ci .ep_audio_r = 0x82, 2028c2ecf20Sopenharmony_ci .ep_audio_w = 0x01, 2038c2ecf20Sopenharmony_ci }, 2048c2ecf20Sopenharmony_ci [LINE6_VARIAX] = { 2058c2ecf20Sopenharmony_ci .id = "Variax", 2068c2ecf20Sopenharmony_ci .name = "Variax Workbench", 2078c2ecf20Sopenharmony_ci .capabilities = LINE6_CAP_CONTROL 2088c2ecf20Sopenharmony_ci | LINE6_CAP_CONTROL_MIDI, 2098c2ecf20Sopenharmony_ci .altsetting = 1, 2108c2ecf20Sopenharmony_ci .ep_ctrl_r = 0x82, 2118c2ecf20Sopenharmony_ci .ep_ctrl_w = 0x01, 2128c2ecf20Sopenharmony_ci /* no audio channel */ 2138c2ecf20Sopenharmony_ci } 2148c2ecf20Sopenharmony_ci}; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci/* 2178c2ecf20Sopenharmony_ci Probe USB device. 2188c2ecf20Sopenharmony_ci*/ 2198c2ecf20Sopenharmony_cistatic int variax_probe(struct usb_interface *interface, 2208c2ecf20Sopenharmony_ci const struct usb_device_id *id) 2218c2ecf20Sopenharmony_ci{ 2228c2ecf20Sopenharmony_ci return line6_probe(interface, id, "Line6-Variax", 2238c2ecf20Sopenharmony_ci &variax_properties_table[id->driver_info], 2248c2ecf20Sopenharmony_ci variax_init, sizeof(struct usb_line6_variax)); 2258c2ecf20Sopenharmony_ci} 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_cistatic struct usb_driver variax_driver = { 2288c2ecf20Sopenharmony_ci .name = KBUILD_MODNAME, 2298c2ecf20Sopenharmony_ci .probe = variax_probe, 2308c2ecf20Sopenharmony_ci .disconnect = line6_disconnect, 2318c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 2328c2ecf20Sopenharmony_ci .suspend = line6_suspend, 2338c2ecf20Sopenharmony_ci .resume = line6_resume, 2348c2ecf20Sopenharmony_ci .reset_resume = line6_resume, 2358c2ecf20Sopenharmony_ci#endif 2368c2ecf20Sopenharmony_ci .id_table = variax_id_table, 2378c2ecf20Sopenharmony_ci}; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_cimodule_usb_driver(variax_driver); 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Variax Workbench USB driver"); 2428c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 243