18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Line 6 Pod HD 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2011 Stefan Hajnoczi <stefanha@gmail.com> 68c2ecf20Sopenharmony_ci * Copyright (C) 2015 Andrej Krutak <dev@andree.sk> 78c2ecf20Sopenharmony_ci * Copyright (C) 2017 Hans P. Moller <hmoller@uc.cl> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/usb.h> 118c2ecf20Sopenharmony_ci#include <linux/slab.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <sound/core.h> 148c2ecf20Sopenharmony_ci#include <sound/control.h> 158c2ecf20Sopenharmony_ci#include <sound/pcm.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#include "driver.h" 188c2ecf20Sopenharmony_ci#include "pcm.h" 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#define PODHD_STARTUP_DELAY 500 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cienum { 238c2ecf20Sopenharmony_ci LINE6_PODHD300, 248c2ecf20Sopenharmony_ci LINE6_PODHD400, 258c2ecf20Sopenharmony_ci LINE6_PODHD500, 268c2ecf20Sopenharmony_ci LINE6_PODX3, 278c2ecf20Sopenharmony_ci LINE6_PODX3LIVE, 288c2ecf20Sopenharmony_ci LINE6_PODHD500X, 298c2ecf20Sopenharmony_ci LINE6_PODHDDESKTOP 308c2ecf20Sopenharmony_ci}; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistruct usb_line6_podhd { 338c2ecf20Sopenharmony_ci /* Generic Line 6 USB data */ 348c2ecf20Sopenharmony_ci struct usb_line6 line6; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci /* Serial number of device */ 378c2ecf20Sopenharmony_ci u32 serial_number; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci /* Firmware version */ 408c2ecf20Sopenharmony_ci int firmware_version; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci /* Monitor level */ 438c2ecf20Sopenharmony_ci int monitor_level; 448c2ecf20Sopenharmony_ci}; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci#define line6_to_podhd(x) container_of(x, struct usb_line6_podhd, line6) 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic const struct snd_ratden podhd_ratden = { 498c2ecf20Sopenharmony_ci .num_min = 48000, 508c2ecf20Sopenharmony_ci .num_max = 48000, 518c2ecf20Sopenharmony_ci .num_step = 1, 528c2ecf20Sopenharmony_ci .den = 1, 538c2ecf20Sopenharmony_ci}; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistatic struct line6_pcm_properties podhd_pcm_properties = { 568c2ecf20Sopenharmony_ci .playback_hw = { 578c2ecf20Sopenharmony_ci .info = (SNDRV_PCM_INFO_MMAP | 588c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_INTERLEAVED | 598c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_BLOCK_TRANSFER | 608c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_MMAP_VALID | 618c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_PAUSE | 628c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_SYNC_START), 638c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S24_3LE, 648c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_48000, 658c2ecf20Sopenharmony_ci .rate_min = 48000, 668c2ecf20Sopenharmony_ci .rate_max = 48000, 678c2ecf20Sopenharmony_ci .channels_min = 2, 688c2ecf20Sopenharmony_ci .channels_max = 2, 698c2ecf20Sopenharmony_ci .buffer_bytes_max = 60000, 708c2ecf20Sopenharmony_ci .period_bytes_min = 64, 718c2ecf20Sopenharmony_ci .period_bytes_max = 8192, 728c2ecf20Sopenharmony_ci .periods_min = 1, 738c2ecf20Sopenharmony_ci .periods_max = 1024}, 748c2ecf20Sopenharmony_ci .capture_hw = { 758c2ecf20Sopenharmony_ci .info = (SNDRV_PCM_INFO_MMAP | 768c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_INTERLEAVED | 778c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_BLOCK_TRANSFER | 788c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_MMAP_VALID | 798c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_SYNC_START), 808c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S24_3LE, 818c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_48000, 828c2ecf20Sopenharmony_ci .rate_min = 48000, 838c2ecf20Sopenharmony_ci .rate_max = 48000, 848c2ecf20Sopenharmony_ci .channels_min = 2, 858c2ecf20Sopenharmony_ci .channels_max = 2, 868c2ecf20Sopenharmony_ci .buffer_bytes_max = 60000, 878c2ecf20Sopenharmony_ci .period_bytes_min = 64, 888c2ecf20Sopenharmony_ci .period_bytes_max = 8192, 898c2ecf20Sopenharmony_ci .periods_min = 1, 908c2ecf20Sopenharmony_ci .periods_max = 1024}, 918c2ecf20Sopenharmony_ci .rates = { 928c2ecf20Sopenharmony_ci .nrats = 1, 938c2ecf20Sopenharmony_ci .rats = &podhd_ratden}, 948c2ecf20Sopenharmony_ci .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ 958c2ecf20Sopenharmony_ci}; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_cistatic struct line6_pcm_properties podx3_pcm_properties = { 988c2ecf20Sopenharmony_ci .playback_hw = { 998c2ecf20Sopenharmony_ci .info = (SNDRV_PCM_INFO_MMAP | 1008c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_INTERLEAVED | 1018c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_BLOCK_TRANSFER | 1028c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_MMAP_VALID | 1038c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_PAUSE | 1048c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_SYNC_START), 1058c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S24_3LE, 1068c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_48000, 1078c2ecf20Sopenharmony_ci .rate_min = 48000, 1088c2ecf20Sopenharmony_ci .rate_max = 48000, 1098c2ecf20Sopenharmony_ci .channels_min = 2, 1108c2ecf20Sopenharmony_ci .channels_max = 2, 1118c2ecf20Sopenharmony_ci .buffer_bytes_max = 60000, 1128c2ecf20Sopenharmony_ci .period_bytes_min = 64, 1138c2ecf20Sopenharmony_ci .period_bytes_max = 8192, 1148c2ecf20Sopenharmony_ci .periods_min = 1, 1158c2ecf20Sopenharmony_ci .periods_max = 1024}, 1168c2ecf20Sopenharmony_ci .capture_hw = { 1178c2ecf20Sopenharmony_ci .info = (SNDRV_PCM_INFO_MMAP | 1188c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_INTERLEAVED | 1198c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_BLOCK_TRANSFER | 1208c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_MMAP_VALID | 1218c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_SYNC_START), 1228c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S24_3LE, 1238c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_48000, 1248c2ecf20Sopenharmony_ci .rate_min = 48000, 1258c2ecf20Sopenharmony_ci .rate_max = 48000, 1268c2ecf20Sopenharmony_ci /* 1+2: Main signal (out), 3+4: Tone 1, 1278c2ecf20Sopenharmony_ci * 5+6: Tone 2, 7+8: raw 1288c2ecf20Sopenharmony_ci */ 1298c2ecf20Sopenharmony_ci .channels_min = 8, 1308c2ecf20Sopenharmony_ci .channels_max = 8, 1318c2ecf20Sopenharmony_ci .buffer_bytes_max = 60000, 1328c2ecf20Sopenharmony_ci .period_bytes_min = 64, 1338c2ecf20Sopenharmony_ci .period_bytes_max = 8192, 1348c2ecf20Sopenharmony_ci .periods_min = 1, 1358c2ecf20Sopenharmony_ci .periods_max = 1024}, 1368c2ecf20Sopenharmony_ci .rates = { 1378c2ecf20Sopenharmony_ci .nrats = 1, 1388c2ecf20Sopenharmony_ci .rats = &podhd_ratden}, 1398c2ecf20Sopenharmony_ci .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ 1408c2ecf20Sopenharmony_ci}; 1418c2ecf20Sopenharmony_cistatic struct usb_driver podhd_driver; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cistatic ssize_t serial_number_show(struct device *dev, 1448c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci struct snd_card *card = dev_to_snd_card(dev); 1478c2ecf20Sopenharmony_ci struct usb_line6_podhd *pod = card->private_data; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci return sprintf(buf, "%u\n", pod->serial_number); 1508c2ecf20Sopenharmony_ci} 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cistatic ssize_t firmware_version_show(struct device *dev, 1538c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 1548c2ecf20Sopenharmony_ci{ 1558c2ecf20Sopenharmony_ci struct snd_card *card = dev_to_snd_card(dev); 1568c2ecf20Sopenharmony_ci struct usb_line6_podhd *pod = card->private_data; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci return sprintf(buf, "%06x\n", pod->firmware_version); 1598c2ecf20Sopenharmony_ci} 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(firmware_version); 1628c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(serial_number); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cistatic struct attribute *podhd_dev_attrs[] = { 1658c2ecf20Sopenharmony_ci &dev_attr_firmware_version.attr, 1668c2ecf20Sopenharmony_ci &dev_attr_serial_number.attr, 1678c2ecf20Sopenharmony_ci NULL 1688c2ecf20Sopenharmony_ci}; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_cistatic const struct attribute_group podhd_dev_attr_group = { 1718c2ecf20Sopenharmony_ci .name = "podhd", 1728c2ecf20Sopenharmony_ci .attrs = podhd_dev_attrs, 1738c2ecf20Sopenharmony_ci}; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci/* 1768c2ecf20Sopenharmony_ci * POD X3 startup procedure. 1778c2ecf20Sopenharmony_ci * 1788c2ecf20Sopenharmony_ci * May be compatible with other POD HD's, since it's also similar to the 1798c2ecf20Sopenharmony_ci * previous POD setup. In any case, it doesn't seem to be required for the 1808c2ecf20Sopenharmony_ci * audio nor bulk interfaces to work. 1818c2ecf20Sopenharmony_ci */ 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_cistatic int podhd_dev_start(struct usb_line6_podhd *pod) 1848c2ecf20Sopenharmony_ci{ 1858c2ecf20Sopenharmony_ci int ret; 1868c2ecf20Sopenharmony_ci u8 init_bytes[8]; 1878c2ecf20Sopenharmony_ci int i; 1888c2ecf20Sopenharmony_ci struct usb_device *usbdev = pod->line6.usbdev; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci ret = usb_control_msg_send(usbdev, 0, 1918c2ecf20Sopenharmony_ci 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, 1928c2ecf20Sopenharmony_ci 0x11, 0, 1938c2ecf20Sopenharmony_ci NULL, 0, LINE6_TIMEOUT, GFP_KERNEL); 1948c2ecf20Sopenharmony_ci if (ret) { 1958c2ecf20Sopenharmony_ci dev_err(pod->line6.ifcdev, "read request failed (error %d)\n", ret); 1968c2ecf20Sopenharmony_ci goto exit; 1978c2ecf20Sopenharmony_ci } 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci /* NOTE: looks like some kind of ping message */ 2008c2ecf20Sopenharmony_ci ret = usb_control_msg_recv(usbdev, 0, 0x67, 2018c2ecf20Sopenharmony_ci USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, 2028c2ecf20Sopenharmony_ci 0x11, 0x0, 2038c2ecf20Sopenharmony_ci init_bytes, 3, LINE6_TIMEOUT, GFP_KERNEL); 2048c2ecf20Sopenharmony_ci if (ret) { 2058c2ecf20Sopenharmony_ci dev_err(pod->line6.ifcdev, 2068c2ecf20Sopenharmony_ci "receive length failed (error %d)\n", ret); 2078c2ecf20Sopenharmony_ci goto exit; 2088c2ecf20Sopenharmony_ci } 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci pod->firmware_version = 2118c2ecf20Sopenharmony_ci (init_bytes[0] << 16) | (init_bytes[1] << 8) | (init_bytes[2] << 0); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci for (i = 0; i <= 16; i++) { 2148c2ecf20Sopenharmony_ci ret = line6_read_data(&pod->line6, 0xf000 + 0x08 * i, init_bytes, 8); 2158c2ecf20Sopenharmony_ci if (ret < 0) 2168c2ecf20Sopenharmony_ci goto exit; 2178c2ecf20Sopenharmony_ci } 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci ret = usb_control_msg_send(usbdev, 0, 2208c2ecf20Sopenharmony_ci USB_REQ_SET_FEATURE, 2218c2ecf20Sopenharmony_ci USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_DIR_OUT, 2228c2ecf20Sopenharmony_ci 1, 0, 2238c2ecf20Sopenharmony_ci NULL, 0, LINE6_TIMEOUT, GFP_KERNEL); 2248c2ecf20Sopenharmony_ciexit: 2258c2ecf20Sopenharmony_ci return ret; 2268c2ecf20Sopenharmony_ci} 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_cistatic void podhd_startup(struct usb_line6 *line6) 2298c2ecf20Sopenharmony_ci{ 2308c2ecf20Sopenharmony_ci struct usb_line6_podhd *pod = line6_to_podhd(line6); 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci podhd_dev_start(pod); 2338c2ecf20Sopenharmony_ci line6_read_serial_number(&pod->line6, &pod->serial_number); 2348c2ecf20Sopenharmony_ci if (snd_card_register(line6->card)) 2358c2ecf20Sopenharmony_ci dev_err(line6->ifcdev, "Failed to register POD HD card.\n"); 2368c2ecf20Sopenharmony_ci} 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_cistatic void podhd_disconnect(struct usb_line6 *line6) 2398c2ecf20Sopenharmony_ci{ 2408c2ecf20Sopenharmony_ci struct usb_line6_podhd *pod = line6_to_podhd(line6); 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL_INFO) { 2438c2ecf20Sopenharmony_ci struct usb_interface *intf; 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci intf = usb_ifnum_to_if(line6->usbdev, 2468c2ecf20Sopenharmony_ci pod->line6.properties->ctrl_if); 2478c2ecf20Sopenharmony_ci if (intf) 2488c2ecf20Sopenharmony_ci usb_driver_release_interface(&podhd_driver, intf); 2498c2ecf20Sopenharmony_ci } 2508c2ecf20Sopenharmony_ci} 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_cistatic const unsigned int float_zero_to_one_lookup[] = { 2538c2ecf20Sopenharmony_ci0x00000000, 0x3c23d70a, 0x3ca3d70a, 0x3cf5c28f, 0x3d23d70a, 0x3d4ccccd, 2548c2ecf20Sopenharmony_ci0x3d75c28f, 0x3d8f5c29, 0x3da3d70a, 0x3db851ec, 0x3dcccccd, 0x3de147ae, 2558c2ecf20Sopenharmony_ci0x3df5c28f, 0x3e051eb8, 0x3e0f5c29, 0x3e19999a, 0x3e23d70a, 0x3e2e147b, 2568c2ecf20Sopenharmony_ci0x3e3851ec, 0x3e428f5c, 0x3e4ccccd, 0x3e570a3d, 0x3e6147ae, 0x3e6b851f, 2578c2ecf20Sopenharmony_ci0x3e75c28f, 0x3e800000, 0x3e851eb8, 0x3e8a3d71, 0x3e8f5c29, 0x3e947ae1, 2588c2ecf20Sopenharmony_ci0x3e99999a, 0x3e9eb852, 0x3ea3d70a, 0x3ea8f5c3, 0x3eae147b, 0x3eb33333, 2598c2ecf20Sopenharmony_ci0x3eb851ec, 0x3ebd70a4, 0x3ec28f5c, 0x3ec7ae14, 0x3ecccccd, 0x3ed1eb85, 2608c2ecf20Sopenharmony_ci0x3ed70a3d, 0x3edc28f6, 0x3ee147ae, 0x3ee66666, 0x3eeb851f, 0x3ef0a3d7, 2618c2ecf20Sopenharmony_ci0x3ef5c28f, 0x3efae148, 0x3f000000, 0x3f028f5c, 0x3f051eb8, 0x3f07ae14, 2628c2ecf20Sopenharmony_ci0x3f0a3d71, 0x3f0ccccd, 0x3f0f5c29, 0x3f11eb85, 0x3f147ae1, 0x3f170a3d, 2638c2ecf20Sopenharmony_ci0x3f19999a, 0x3f1c28f6, 0x3f1eb852, 0x3f2147ae, 0x3f23d70a, 0x3f266666, 2648c2ecf20Sopenharmony_ci0x3f28f5c3, 0x3f2b851f, 0x3f2e147b, 0x3f30a3d7, 0x3f333333, 0x3f35c28f, 2658c2ecf20Sopenharmony_ci0x3f3851ec, 0x3f3ae148, 0x3f3d70a4, 0x3f400000, 0x3f428f5c, 0x3f451eb8, 2668c2ecf20Sopenharmony_ci0x3f47ae14, 0x3f4a3d71, 0x3f4ccccd, 0x3f4f5c29, 0x3f51eb85, 0x3f547ae1, 2678c2ecf20Sopenharmony_ci0x3f570a3d, 0x3f59999a, 0x3f5c28f6, 0x3f5eb852, 0x3f6147ae, 0x3f63d70a, 2688c2ecf20Sopenharmony_ci0x3f666666, 0x3f68f5c3, 0x3f6b851f, 0x3f6e147b, 0x3f70a3d7, 0x3f733333, 2698c2ecf20Sopenharmony_ci0x3f75c28f, 0x3f7851ec, 0x3f7ae148, 0x3f7d70a4, 0x3f800000 2708c2ecf20Sopenharmony_ci}; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_cistatic void podhd_set_monitor_level(struct usb_line6_podhd *podhd, int value) 2738c2ecf20Sopenharmony_ci{ 2748c2ecf20Sopenharmony_ci unsigned int fl; 2758c2ecf20Sopenharmony_ci static const unsigned char msg[16] = { 2768c2ecf20Sopenharmony_ci /* Chunk is 0xc bytes (without first word) */ 2778c2ecf20Sopenharmony_ci 0x0c, 0x00, 2788c2ecf20Sopenharmony_ci /* First chunk in the message */ 2798c2ecf20Sopenharmony_ci 0x01, 0x00, 2808c2ecf20Sopenharmony_ci /* Message size is 2 4-byte words */ 2818c2ecf20Sopenharmony_ci 0x02, 0x00, 2828c2ecf20Sopenharmony_ci /* Unknown */ 2838c2ecf20Sopenharmony_ci 0x04, 0x41, 2848c2ecf20Sopenharmony_ci /* Unknown */ 2858c2ecf20Sopenharmony_ci 0x04, 0x00, 0x13, 0x00, 2868c2ecf20Sopenharmony_ci /* Volume, LE float32, 0.0 - 1.0 */ 2878c2ecf20Sopenharmony_ci 0x00, 0x00, 0x00, 0x00 2888c2ecf20Sopenharmony_ci }; 2898c2ecf20Sopenharmony_ci unsigned char *buf; 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci buf = kmemdup(msg, sizeof(msg), GFP_KERNEL); 2928c2ecf20Sopenharmony_ci if (!buf) 2938c2ecf20Sopenharmony_ci return; 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci if (value < 0) 2968c2ecf20Sopenharmony_ci value = 0; 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci if (value >= ARRAY_SIZE(float_zero_to_one_lookup)) 2998c2ecf20Sopenharmony_ci value = ARRAY_SIZE(float_zero_to_one_lookup) - 1; 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ci fl = float_zero_to_one_lookup[value]; 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci buf[12] = (fl >> 0) & 0xff; 3048c2ecf20Sopenharmony_ci buf[13] = (fl >> 8) & 0xff; 3058c2ecf20Sopenharmony_ci buf[14] = (fl >> 16) & 0xff; 3068c2ecf20Sopenharmony_ci buf[15] = (fl >> 24) & 0xff; 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ci line6_send_raw_message(&podhd->line6, buf, sizeof(msg)); 3098c2ecf20Sopenharmony_ci kfree(buf); 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci podhd->monitor_level = value; 3128c2ecf20Sopenharmony_ci} 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ci/* control info callback */ 3158c2ecf20Sopenharmony_cistatic int snd_podhd_control_monitor_info(struct snd_kcontrol *kcontrol, 3168c2ecf20Sopenharmony_ci struct snd_ctl_elem_info *uinfo) 3178c2ecf20Sopenharmony_ci{ 3188c2ecf20Sopenharmony_ci uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; 3198c2ecf20Sopenharmony_ci uinfo->count = 1; 3208c2ecf20Sopenharmony_ci uinfo->value.integer.min = 0; 3218c2ecf20Sopenharmony_ci uinfo->value.integer.max = 100; 3228c2ecf20Sopenharmony_ci uinfo->value.integer.step = 1; 3238c2ecf20Sopenharmony_ci return 0; 3248c2ecf20Sopenharmony_ci} 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci/* control get callback */ 3278c2ecf20Sopenharmony_cistatic int snd_podhd_control_monitor_get(struct snd_kcontrol *kcontrol, 3288c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 3298c2ecf20Sopenharmony_ci{ 3308c2ecf20Sopenharmony_ci struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); 3318c2ecf20Sopenharmony_ci struct usb_line6_podhd *podhd = line6_to_podhd(line6pcm->line6); 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci ucontrol->value.integer.value[0] = podhd->monitor_level; 3348c2ecf20Sopenharmony_ci return 0; 3358c2ecf20Sopenharmony_ci} 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci/* control put callback */ 3388c2ecf20Sopenharmony_cistatic int snd_podhd_control_monitor_put(struct snd_kcontrol *kcontrol, 3398c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 3408c2ecf20Sopenharmony_ci{ 3418c2ecf20Sopenharmony_ci struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); 3428c2ecf20Sopenharmony_ci struct usb_line6_podhd *podhd = line6_to_podhd(line6pcm->line6); 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_ci if (ucontrol->value.integer.value[0] == podhd->monitor_level) 3458c2ecf20Sopenharmony_ci return 0; 3468c2ecf20Sopenharmony_ci 3478c2ecf20Sopenharmony_ci podhd_set_monitor_level(podhd, ucontrol->value.integer.value[0]); 3488c2ecf20Sopenharmony_ci return 1; 3498c2ecf20Sopenharmony_ci} 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci/* control definition */ 3528c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new podhd_control_monitor = { 3538c2ecf20Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 3548c2ecf20Sopenharmony_ci .name = "Monitor Playback Volume", 3558c2ecf20Sopenharmony_ci .index = 0, 3568c2ecf20Sopenharmony_ci .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 3578c2ecf20Sopenharmony_ci .info = snd_podhd_control_monitor_info, 3588c2ecf20Sopenharmony_ci .get = snd_podhd_control_monitor_get, 3598c2ecf20Sopenharmony_ci .put = snd_podhd_control_monitor_put 3608c2ecf20Sopenharmony_ci}; 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci/* 3638c2ecf20Sopenharmony_ci Try to init POD HD device. 3648c2ecf20Sopenharmony_ci*/ 3658c2ecf20Sopenharmony_cistatic int podhd_init(struct usb_line6 *line6, 3668c2ecf20Sopenharmony_ci const struct usb_device_id *id) 3678c2ecf20Sopenharmony_ci{ 3688c2ecf20Sopenharmony_ci int err; 3698c2ecf20Sopenharmony_ci struct usb_line6_podhd *pod = line6_to_podhd(line6); 3708c2ecf20Sopenharmony_ci struct usb_interface *intf; 3718c2ecf20Sopenharmony_ci 3728c2ecf20Sopenharmony_ci line6->disconnect = podhd_disconnect; 3738c2ecf20Sopenharmony_ci line6->startup = podhd_startup; 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_ci if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) { 3768c2ecf20Sopenharmony_ci /* claim the data interface */ 3778c2ecf20Sopenharmony_ci intf = usb_ifnum_to_if(line6->usbdev, 3788c2ecf20Sopenharmony_ci pod->line6.properties->ctrl_if); 3798c2ecf20Sopenharmony_ci if (!intf) { 3808c2ecf20Sopenharmony_ci dev_err(pod->line6.ifcdev, "interface %d not found\n", 3818c2ecf20Sopenharmony_ci pod->line6.properties->ctrl_if); 3828c2ecf20Sopenharmony_ci return -ENODEV; 3838c2ecf20Sopenharmony_ci } 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_ci err = usb_driver_claim_interface(&podhd_driver, intf, NULL); 3868c2ecf20Sopenharmony_ci if (err != 0) { 3878c2ecf20Sopenharmony_ci dev_err(pod->line6.ifcdev, "can't claim interface %d, error %d\n", 3888c2ecf20Sopenharmony_ci pod->line6.properties->ctrl_if, err); 3898c2ecf20Sopenharmony_ci return err; 3908c2ecf20Sopenharmony_ci } 3918c2ecf20Sopenharmony_ci } 3928c2ecf20Sopenharmony_ci 3938c2ecf20Sopenharmony_ci if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL_INFO) { 3948c2ecf20Sopenharmony_ci /* create sysfs entries: */ 3958c2ecf20Sopenharmony_ci err = snd_card_add_dev_attr(line6->card, &podhd_dev_attr_group); 3968c2ecf20Sopenharmony_ci if (err < 0) 3978c2ecf20Sopenharmony_ci return err; 3988c2ecf20Sopenharmony_ci } 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_ci if (pod->line6.properties->capabilities & LINE6_CAP_PCM) { 4018c2ecf20Sopenharmony_ci /* initialize PCM subsystem: */ 4028c2ecf20Sopenharmony_ci err = line6_init_pcm(line6, 4038c2ecf20Sopenharmony_ci (id->driver_info == LINE6_PODX3 || 4048c2ecf20Sopenharmony_ci id->driver_info == LINE6_PODX3LIVE) ? &podx3_pcm_properties : 4058c2ecf20Sopenharmony_ci &podhd_pcm_properties); 4068c2ecf20Sopenharmony_ci if (err < 0) 4078c2ecf20Sopenharmony_ci return err; 4088c2ecf20Sopenharmony_ci } 4098c2ecf20Sopenharmony_ci 4108c2ecf20Sopenharmony_ci if (pod->line6.properties->capabilities & LINE6_CAP_HWMON_CTL) { 4118c2ecf20Sopenharmony_ci podhd_set_monitor_level(pod, 100); 4128c2ecf20Sopenharmony_ci err = snd_ctl_add(line6->card, 4138c2ecf20Sopenharmony_ci snd_ctl_new1(&podhd_control_monitor, 4148c2ecf20Sopenharmony_ci line6->line6pcm)); 4158c2ecf20Sopenharmony_ci if (err < 0) 4168c2ecf20Sopenharmony_ci return err; 4178c2ecf20Sopenharmony_ci } 4188c2ecf20Sopenharmony_ci 4198c2ecf20Sopenharmony_ci if (!(pod->line6.properties->capabilities & LINE6_CAP_CONTROL_INFO)) { 4208c2ecf20Sopenharmony_ci /* register USB audio system directly */ 4218c2ecf20Sopenharmony_ci return snd_card_register(line6->card); 4228c2ecf20Sopenharmony_ci } 4238c2ecf20Sopenharmony_ci 4248c2ecf20Sopenharmony_ci /* init device and delay registering */ 4258c2ecf20Sopenharmony_ci schedule_delayed_work(&line6->startup_work, 4268c2ecf20Sopenharmony_ci msecs_to_jiffies(PODHD_STARTUP_DELAY)); 4278c2ecf20Sopenharmony_ci return 0; 4288c2ecf20Sopenharmony_ci} 4298c2ecf20Sopenharmony_ci 4308c2ecf20Sopenharmony_ci#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod) 4318c2ecf20Sopenharmony_ci#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n) 4328c2ecf20Sopenharmony_ci 4338c2ecf20Sopenharmony_ci/* table of devices that work with this driver */ 4348c2ecf20Sopenharmony_cistatic const struct usb_device_id podhd_id_table[] = { 4358c2ecf20Sopenharmony_ci /* TODO: no need to alloc data interfaces when only audio is used */ 4368c2ecf20Sopenharmony_ci { LINE6_DEVICE(0x5057), .driver_info = LINE6_PODHD300 }, 4378c2ecf20Sopenharmony_ci { LINE6_DEVICE(0x5058), .driver_info = LINE6_PODHD400 }, 4388c2ecf20Sopenharmony_ci { LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500 }, 4398c2ecf20Sopenharmony_ci { LINE6_IF_NUM(0x414A, 0), .driver_info = LINE6_PODX3 }, 4408c2ecf20Sopenharmony_ci { LINE6_IF_NUM(0x414B, 0), .driver_info = LINE6_PODX3LIVE }, 4418c2ecf20Sopenharmony_ci { LINE6_IF_NUM(0x4159, 0), .driver_info = LINE6_PODHD500X }, 4428c2ecf20Sopenharmony_ci { LINE6_IF_NUM(0x4156, 0), .driver_info = LINE6_PODHDDESKTOP }, 4438c2ecf20Sopenharmony_ci {} 4448c2ecf20Sopenharmony_ci}; 4458c2ecf20Sopenharmony_ci 4468c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, podhd_id_table); 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_cistatic const struct line6_properties podhd_properties_table[] = { 4498c2ecf20Sopenharmony_ci [LINE6_PODHD300] = { 4508c2ecf20Sopenharmony_ci .id = "PODHD300", 4518c2ecf20Sopenharmony_ci .name = "POD HD300", 4528c2ecf20Sopenharmony_ci .capabilities = LINE6_CAP_PCM 4538c2ecf20Sopenharmony_ci | LINE6_CAP_HWMON, 4548c2ecf20Sopenharmony_ci .altsetting = 5, 4558c2ecf20Sopenharmony_ci .ep_ctrl_r = 0x84, 4568c2ecf20Sopenharmony_ci .ep_ctrl_w = 0x03, 4578c2ecf20Sopenharmony_ci .ep_audio_r = 0x82, 4588c2ecf20Sopenharmony_ci .ep_audio_w = 0x01, 4598c2ecf20Sopenharmony_ci }, 4608c2ecf20Sopenharmony_ci [LINE6_PODHD400] = { 4618c2ecf20Sopenharmony_ci .id = "PODHD400", 4628c2ecf20Sopenharmony_ci .name = "POD HD400", 4638c2ecf20Sopenharmony_ci .capabilities = LINE6_CAP_PCM 4648c2ecf20Sopenharmony_ci | LINE6_CAP_HWMON, 4658c2ecf20Sopenharmony_ci .altsetting = 5, 4668c2ecf20Sopenharmony_ci .ep_ctrl_r = 0x84, 4678c2ecf20Sopenharmony_ci .ep_ctrl_w = 0x03, 4688c2ecf20Sopenharmony_ci .ep_audio_r = 0x82, 4698c2ecf20Sopenharmony_ci .ep_audio_w = 0x01, 4708c2ecf20Sopenharmony_ci }, 4718c2ecf20Sopenharmony_ci [LINE6_PODHD500] = { 4728c2ecf20Sopenharmony_ci .id = "PODHD500", 4738c2ecf20Sopenharmony_ci .name = "POD HD500", 4748c2ecf20Sopenharmony_ci .capabilities = LINE6_CAP_PCM | LINE6_CAP_CONTROL 4758c2ecf20Sopenharmony_ci | LINE6_CAP_HWMON | LINE6_CAP_HWMON_CTL, 4768c2ecf20Sopenharmony_ci .altsetting = 1, 4778c2ecf20Sopenharmony_ci .ctrl_if = 1, 4788c2ecf20Sopenharmony_ci .ep_ctrl_r = 0x81, 4798c2ecf20Sopenharmony_ci .ep_ctrl_w = 0x01, 4808c2ecf20Sopenharmony_ci .ep_audio_r = 0x86, 4818c2ecf20Sopenharmony_ci .ep_audio_w = 0x02, 4828c2ecf20Sopenharmony_ci }, 4838c2ecf20Sopenharmony_ci [LINE6_PODX3] = { 4848c2ecf20Sopenharmony_ci .id = "PODX3", 4858c2ecf20Sopenharmony_ci .name = "POD X3", 4868c2ecf20Sopenharmony_ci .capabilities = LINE6_CAP_CONTROL | LINE6_CAP_CONTROL_INFO 4878c2ecf20Sopenharmony_ci | LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT, 4888c2ecf20Sopenharmony_ci .altsetting = 1, 4898c2ecf20Sopenharmony_ci .ep_ctrl_r = 0x81, 4908c2ecf20Sopenharmony_ci .ep_ctrl_w = 0x01, 4918c2ecf20Sopenharmony_ci .ctrl_if = 1, 4928c2ecf20Sopenharmony_ci .ep_audio_r = 0x86, 4938c2ecf20Sopenharmony_ci .ep_audio_w = 0x02, 4948c2ecf20Sopenharmony_ci }, 4958c2ecf20Sopenharmony_ci [LINE6_PODX3LIVE] = { 4968c2ecf20Sopenharmony_ci .id = "PODX3LIVE", 4978c2ecf20Sopenharmony_ci .name = "POD X3 LIVE", 4988c2ecf20Sopenharmony_ci .capabilities = LINE6_CAP_CONTROL | LINE6_CAP_CONTROL_INFO 4998c2ecf20Sopenharmony_ci | LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT, 5008c2ecf20Sopenharmony_ci .altsetting = 1, 5018c2ecf20Sopenharmony_ci .ep_ctrl_r = 0x81, 5028c2ecf20Sopenharmony_ci .ep_ctrl_w = 0x01, 5038c2ecf20Sopenharmony_ci .ctrl_if = 1, 5048c2ecf20Sopenharmony_ci .ep_audio_r = 0x86, 5058c2ecf20Sopenharmony_ci .ep_audio_w = 0x02, 5068c2ecf20Sopenharmony_ci }, 5078c2ecf20Sopenharmony_ci [LINE6_PODHD500X] = { 5088c2ecf20Sopenharmony_ci .id = "PODHD500X", 5098c2ecf20Sopenharmony_ci .name = "POD HD500X", 5108c2ecf20Sopenharmony_ci .capabilities = LINE6_CAP_CONTROL 5118c2ecf20Sopenharmony_ci | LINE6_CAP_PCM | LINE6_CAP_HWMON, 5128c2ecf20Sopenharmony_ci .altsetting = 1, 5138c2ecf20Sopenharmony_ci .ep_ctrl_r = 0x81, 5148c2ecf20Sopenharmony_ci .ep_ctrl_w = 0x01, 5158c2ecf20Sopenharmony_ci .ctrl_if = 1, 5168c2ecf20Sopenharmony_ci .ep_audio_r = 0x86, 5178c2ecf20Sopenharmony_ci .ep_audio_w = 0x02, 5188c2ecf20Sopenharmony_ci }, 5198c2ecf20Sopenharmony_ci [LINE6_PODHDDESKTOP] = { 5208c2ecf20Sopenharmony_ci .id = "PODHDDESKTOP", 5218c2ecf20Sopenharmony_ci .name = "POD HDDESKTOP", 5228c2ecf20Sopenharmony_ci .capabilities = LINE6_CAP_CONTROL 5238c2ecf20Sopenharmony_ci | LINE6_CAP_PCM | LINE6_CAP_HWMON, 5248c2ecf20Sopenharmony_ci .altsetting = 1, 5258c2ecf20Sopenharmony_ci .ep_ctrl_r = 0x81, 5268c2ecf20Sopenharmony_ci .ep_ctrl_w = 0x01, 5278c2ecf20Sopenharmony_ci .ctrl_if = 1, 5288c2ecf20Sopenharmony_ci .ep_audio_r = 0x86, 5298c2ecf20Sopenharmony_ci .ep_audio_w = 0x02, 5308c2ecf20Sopenharmony_ci }, 5318c2ecf20Sopenharmony_ci}; 5328c2ecf20Sopenharmony_ci 5338c2ecf20Sopenharmony_ci/* 5348c2ecf20Sopenharmony_ci Probe USB device. 5358c2ecf20Sopenharmony_ci*/ 5368c2ecf20Sopenharmony_cistatic int podhd_probe(struct usb_interface *interface, 5378c2ecf20Sopenharmony_ci const struct usb_device_id *id) 5388c2ecf20Sopenharmony_ci{ 5398c2ecf20Sopenharmony_ci return line6_probe(interface, id, "Line6-PODHD", 5408c2ecf20Sopenharmony_ci &podhd_properties_table[id->driver_info], 5418c2ecf20Sopenharmony_ci podhd_init, sizeof(struct usb_line6_podhd)); 5428c2ecf20Sopenharmony_ci} 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_cistatic struct usb_driver podhd_driver = { 5458c2ecf20Sopenharmony_ci .name = KBUILD_MODNAME, 5468c2ecf20Sopenharmony_ci .probe = podhd_probe, 5478c2ecf20Sopenharmony_ci .disconnect = line6_disconnect, 5488c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 5498c2ecf20Sopenharmony_ci .suspend = line6_suspend, 5508c2ecf20Sopenharmony_ci .resume = line6_resume, 5518c2ecf20Sopenharmony_ci .reset_resume = line6_resume, 5528c2ecf20Sopenharmony_ci#endif 5538c2ecf20Sopenharmony_ci .id_table = podhd_id_table, 5548c2ecf20Sopenharmony_ci}; 5558c2ecf20Sopenharmony_ci 5568c2ecf20Sopenharmony_cimodule_usb_driver(podhd_driver); 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Line 6 PODHD USB driver"); 5598c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 560