2011-11-23 12:20:42 +04:00
/*
2015-01-20 11:20:49 +03:00
* Line 6 Pod HD
2011-11-23 12:20:42 +04:00
*
* Copyright ( C ) 2011 Stefan Hajnoczi < stefanha @ gmail . com >
2016-09-18 21:59:28 +03:00
* Copyright ( C ) 2015 Andrej Krutak < dev @ andree . sk >
2011-11-23 12:20:42 +04:00
*
* 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.
*
*/
2015-01-15 10:22:31 +03:00
# include <linux/usb.h>
# include <linux/slab.h>
# include <linux/module.h>
2011-11-23 12:20:42 +04:00
# include <sound/core.h>
# include <sound/pcm.h>
# include "driver.h"
# include "pcm.h"
2015-01-15 10:22:31 +03:00
2016-09-18 21:59:28 +03:00
# define PODHD_STARTUP_DELAY 500
/*
* Stages of POD startup procedure
*/
enum {
PODHD_STARTUP_INIT = 1 ,
PODHD_STARTUP_SCHEDULE_WORKQUEUE ,
PODHD_STARTUP_SETUP ,
PODHD_STARTUP_LAST = PODHD_STARTUP_SETUP - 1
} ;
2015-01-15 10:22:31 +03:00
enum {
LINE6_PODHD300 ,
LINE6_PODHD400 ,
LINE6_PODHD500_0 ,
LINE6_PODHD500_1 ,
2016-09-18 21:59:28 +03:00
LINE6_PODX3 ,
2016-09-18 21:59:29 +03:00
LINE6_PODX3LIVE
2016-09-18 21:59:28 +03:00
} ;
struct usb_line6_podhd {
/* Generic Line 6 USB data */
struct usb_line6 line6 ;
/* Timer for device initialization */
struct timer_list startup_timer ;
/* Work handler for device initialization */
struct work_struct startup_work ;
/* Current progress in startup procedure */
int startup_progress ;
/* Serial number of device */
u32 serial_number ;
/* Firmware version */
int firmware_version ;
2015-01-15 10:22:31 +03:00
} ;
2011-11-23 12:20:42 +04:00
static struct snd_ratden podhd_ratden = {
. num_min = 48000 ,
. num_max = 48000 ,
. num_step = 1 ,
. den = 1 ,
} ;
static struct line6_pcm_properties podhd_pcm_properties = {
2015-01-28 17:08:59 +03:00
. playback_hw = {
2011-11-23 12:20:42 +04:00
. info = ( SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_SYNC_START ) ,
. formats = SNDRV_PCM_FMTBIT_S24_3LE ,
. rates = SNDRV_PCM_RATE_48000 ,
. rate_min = 48000 ,
. rate_max = 48000 ,
. channels_min = 2 ,
. channels_max = 2 ,
. buffer_bytes_max = 60000 ,
. period_bytes_min = 64 ,
. period_bytes_max = 8192 ,
. periods_min = 1 ,
. periods_max = 1024 } ,
2015-01-28 17:08:59 +03:00
. capture_hw = {
2011-11-23 12:20:42 +04:00
. info = ( SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_SYNC_START ) ,
. formats = SNDRV_PCM_FMTBIT_S24_3LE ,
. rates = SNDRV_PCM_RATE_48000 ,
. rate_min = 48000 ,
. rate_max = 48000 ,
. channels_min = 2 ,
. channels_max = 2 ,
. buffer_bytes_max = 60000 ,
. period_bytes_min = 64 ,
. period_bytes_max = 8192 ,
. periods_min = 1 ,
. periods_max = 1024 } ,
2015-01-28 17:08:59 +03:00
. rates = {
2011-11-23 12:20:42 +04:00
. nrats = 1 ,
. rats = & podhd_ratden } ,
2016-09-18 21:59:28 +03:00
. bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */
2011-11-23 12:20:42 +04:00
} ;
2016-09-18 21:59:28 +03:00
static struct line6_pcm_properties podx3_pcm_properties = {
. playback_hw = {
. info = ( SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_SYNC_START ) ,
. formats = SNDRV_PCM_FMTBIT_S24_3LE ,
. rates = SNDRV_PCM_RATE_48000 ,
. rate_min = 48000 ,
. rate_max = 48000 ,
. channels_min = 2 ,
. channels_max = 2 ,
. buffer_bytes_max = 60000 ,
. period_bytes_min = 64 ,
. period_bytes_max = 8192 ,
. periods_min = 1 ,
. periods_max = 1024 } ,
. capture_hw = {
. info = ( SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_SYNC_START ) ,
. formats = SNDRV_PCM_FMTBIT_S24_3LE ,
. rates = SNDRV_PCM_RATE_48000 ,
. rate_min = 48000 ,
. rate_max = 48000 ,
/* 1+2: Main signal (out), 3+4: Tone 1,
* 5 + 6 : Tone 2 , 7 + 8 : raw
*/
. channels_min = 8 ,
. channels_max = 8 ,
. buffer_bytes_max = 60000 ,
. period_bytes_min = 64 ,
. period_bytes_max = 8192 ,
. periods_min = 1 ,
. periods_max = 1024 } ,
. rates = {
. nrats = 1 ,
. rats = & podhd_ratden } ,
. bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */
} ;
static void podhd_startup_start_workqueue ( unsigned long data ) ;
static void podhd_startup_workqueue ( struct work_struct * work ) ;
static int podhd_startup_finalize ( struct usb_line6_podhd * pod ) ;
static ssize_t serial_number_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct snd_card * card = dev_to_snd_card ( dev ) ;
struct usb_line6_podhd * pod = card - > private_data ;
return sprintf ( buf , " %u \n " , pod - > serial_number ) ;
}
static ssize_t firmware_version_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct snd_card * card = dev_to_snd_card ( dev ) ;
struct usb_line6_podhd * pod = card - > private_data ;
return sprintf ( buf , " %06x \n " , pod - > firmware_version ) ;
}
static DEVICE_ATTR_RO ( firmware_version ) ;
static DEVICE_ATTR_RO ( serial_number ) ;
static struct attribute * podhd_dev_attrs [ ] = {
& dev_attr_firmware_version . attr ,
& dev_attr_serial_number . attr ,
NULL
} ;
static const struct attribute_group podhd_dev_attr_group = {
. name = " podhd " ,
. attrs = podhd_dev_attrs ,
} ;
/*
* POD X3 startup procedure .
*
* May be compatible with other POD HD ' s , since it ' s also similar to the
* previous POD setup . In any case , it doesn ' t seem to be required for the
* audio nor bulk interfaces to work .
*/
static void podhd_startup ( struct usb_line6_podhd * pod )
{
CHECK_STARTUP_PROGRESS ( pod - > startup_progress , PODHD_STARTUP_INIT ) ;
/* delay startup procedure: */
line6_start_timer ( & pod - > startup_timer , PODHD_STARTUP_DELAY ,
podhd_startup_start_workqueue , ( unsigned long ) pod ) ;
}
static void podhd_startup_start_workqueue ( unsigned long data )
{
struct usb_line6_podhd * pod = ( struct usb_line6_podhd * ) data ;
CHECK_STARTUP_PROGRESS ( pod - > startup_progress ,
PODHD_STARTUP_SCHEDULE_WORKQUEUE ) ;
/* schedule work for global work queue: */
schedule_work ( & pod - > startup_work ) ;
}
static int podhd_dev_start ( struct usb_line6_podhd * pod )
{
int ret ;
u8 init_bytes [ 8 ] ;
int i ;
struct usb_device * usbdev = pod - > line6 . usbdev ;
ret = usb_control_msg ( usbdev , usb_sndctrlpipe ( usbdev , 0 ) ,
0x67 , USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT ,
0x11 , 0 ,
NULL , 0 , LINE6_TIMEOUT * HZ ) ;
if ( ret < 0 ) {
dev_err ( pod - > line6 . ifcdev , " read request failed (error %d) \n " , ret ) ;
return ret ;
}
/* NOTE: looks like some kind of ping message */
ret = usb_control_msg ( usbdev , usb_rcvctrlpipe ( usbdev , 0 ) , 0x67 ,
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN ,
0x11 , 0x0 ,
& init_bytes , 3 , LINE6_TIMEOUT * HZ ) ;
if ( ret < 0 ) {
dev_err ( pod - > line6 . ifcdev ,
" receive length failed (error %d) \n " , ret ) ;
return ret ;
}
pod - > firmware_version =
( init_bytes [ 0 ] < < 16 ) | ( init_bytes [ 1 ] < < 8 ) | ( init_bytes [ 2 ] < < 0 ) ;
for ( i = 0 ; i < = 16 ; i + + ) {
ret = line6_read_data ( & pod - > line6 , 0xf000 + 0x08 * i , init_bytes , 8 ) ;
if ( ret < 0 )
return ret ;
}
ret = usb_control_msg ( usbdev , usb_sndctrlpipe ( usbdev , 0 ) ,
USB_REQ_SET_FEATURE ,
USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_DIR_OUT ,
1 , 0 ,
NULL , 0 , LINE6_TIMEOUT * HZ ) ;
if ( ret < 0 )
return ret ;
return 0 ;
}
static void podhd_startup_workqueue ( struct work_struct * work )
{
struct usb_line6_podhd * pod =
container_of ( work , struct usb_line6_podhd , startup_work ) ;
CHECK_STARTUP_PROGRESS ( pod - > startup_progress , PODHD_STARTUP_SETUP ) ;
podhd_dev_start ( pod ) ;
line6_read_serial_number ( & pod - > line6 , & pod - > serial_number ) ;
podhd_startup_finalize ( pod ) ;
}
static int podhd_startup_finalize ( struct usb_line6_podhd * pod )
{
struct usb_line6 * line6 = & pod - > line6 ;
/* ALSA audio interface: */
return snd_card_register ( line6 - > card ) ;
}
static void podhd_disconnect ( struct usb_line6 * line6 )
{
struct usb_line6_podhd * pod = ( struct usb_line6_podhd * ) line6 ;
if ( pod - > line6 . properties - > capabilities & LINE6_CAP_CONTROL ) {
del_timer_sync ( & pod - > startup_timer ) ;
cancel_work_sync ( & pod - > startup_work ) ;
}
}
2011-11-23 12:20:42 +04:00
/*
Try to init POD HD device .
*/
2015-01-25 20:22:58 +03:00
static int podhd_init ( struct usb_line6 * line6 ,
const struct usb_device_id * id )
2011-11-23 12:20:42 +04:00
{
int err ;
2016-09-18 21:59:28 +03:00
struct usb_line6_podhd * pod = ( struct usb_line6_podhd * ) line6 ;
line6 - > disconnect = podhd_disconnect ;
if ( pod - > line6 . properties - > capabilities & LINE6_CAP_CONTROL ) {
/* create sysfs entries: */
err = snd_card_add_dev_attr ( line6 - > card , & podhd_dev_attr_group ) ;
if ( err < 0 )
return err ;
}
2011-11-23 12:20:42 +04:00
2016-09-18 21:59:31 +03:00
if ( pod - > line6 . properties - > capabilities & LINE6_CAP_PCM ) {
/* initialize PCM subsystem: */
err = line6_init_pcm ( line6 ,
( id - > driver_info = = LINE6_PODX3 ) ? & podx3_pcm_properties :
& podhd_pcm_properties ) ;
if ( err < 0 )
return err ;
}
2011-11-23 12:20:42 +04:00
2016-09-18 21:59:28 +03:00
if ( ! ( pod - > line6 . properties - > capabilities & LINE6_CAP_CONTROL ) ) {
/* register USB audio system directly */
return podhd_startup_finalize ( pod ) ;
}
/* init device and delay registering */
init_timer ( & pod - > startup_timer ) ;
INIT_WORK ( & pod - > startup_work , podhd_startup_workqueue ) ;
podhd_startup ( pod ) ;
return 0 ;
2011-11-23 12:20:42 +04:00
}
2015-01-15 10:22:31 +03:00
# 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 podhd_id_table [ ] = {
2016-09-18 21:59:28 +03:00
/* TODO: no need to alloc data interfaces when only audio is used */
2015-01-15 10:22:31 +03:00
{ LINE6_DEVICE ( 0x5057 ) , . driver_info = LINE6_PODHD300 } ,
{ LINE6_DEVICE ( 0x5058 ) , . driver_info = LINE6_PODHD400 } ,
{ LINE6_IF_NUM ( 0x414D , 0 ) , . driver_info = LINE6_PODHD500_0 } ,
{ LINE6_IF_NUM ( 0x414D , 1 ) , . driver_info = LINE6_PODHD500_1 } ,
2016-09-18 21:59:28 +03:00
{ LINE6_IF_NUM ( 0x414A , 0 ) , . driver_info = LINE6_PODX3 } ,
2016-09-18 21:59:29 +03:00
{ LINE6_IF_NUM ( 0x414B , 0 ) , . driver_info = LINE6_PODX3LIVE } ,
2015-01-15 10:22:31 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( usb , podhd_id_table ) ;
static const struct line6_properties podhd_properties_table [ ] = {
[ LINE6_PODHD300 ] = {
. id = " PODHD300 " ,
. name = " POD HD300 " ,
2016-09-18 21:59:28 +03:00
. capabilities = LINE6_CAP_PCM
2015-01-15 10:22:31 +03:00
| LINE6_CAP_HWMON ,
. altsetting = 5 ,
. ep_ctrl_r = 0x84 ,
. ep_ctrl_w = 0x03 ,
. ep_audio_r = 0x82 ,
. ep_audio_w = 0x01 ,
} ,
[ LINE6_PODHD400 ] = {
. id = " PODHD400 " ,
. name = " POD HD400 " ,
2016-09-18 21:59:28 +03:00
. capabilities = LINE6_CAP_PCM
2015-01-15 10:22:31 +03:00
| LINE6_CAP_HWMON ,
. altsetting = 5 ,
. ep_ctrl_r = 0x84 ,
. ep_ctrl_w = 0x03 ,
. ep_audio_r = 0x82 ,
. ep_audio_w = 0x01 ,
} ,
[ LINE6_PODHD500_0 ] = {
. id = " PODHD500 " ,
. name = " POD HD500 " ,
2016-09-18 21:59:28 +03:00
. capabilities = LINE6_CAP_PCM
2015-01-15 10:22:31 +03:00
| LINE6_CAP_HWMON ,
. altsetting = 1 ,
. ep_ctrl_r = 0x81 ,
. ep_ctrl_w = 0x01 ,
. ep_audio_r = 0x86 ,
. ep_audio_w = 0x02 ,
} ,
[ LINE6_PODHD500_1 ] = {
. id = " PODHD500 " ,
. name = " POD HD500 " ,
2016-09-18 21:59:28 +03:00
. capabilities = LINE6_CAP_PCM
2015-01-15 10:22:31 +03:00
| LINE6_CAP_HWMON ,
. altsetting = 1 ,
. ep_ctrl_r = 0x81 ,
. ep_ctrl_w = 0x01 ,
. ep_audio_r = 0x86 ,
. ep_audio_w = 0x02 ,
} ,
2016-09-18 21:59:28 +03:00
[ LINE6_PODX3 ] = {
. id = " PODX3 " ,
. name = " POD X3 " ,
. capabilities = LINE6_CAP_CONTROL
| LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT ,
. altsetting = 1 ,
. ep_ctrl_r = 0x81 ,
. ep_ctrl_w = 0x01 ,
. ep_audio_r = 0x86 ,
. ep_audio_w = 0x02 ,
} ,
2016-09-18 21:59:29 +03:00
[ LINE6_PODX3LIVE ] = {
. id = " PODX3LIVE " ,
. name = " POD X3 LIVE " ,
. capabilities = LINE6_CAP_CONTROL
| LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT ,
. altsetting = 1 ,
. ep_ctrl_r = 0x81 ,
. ep_ctrl_w = 0x01 ,
. ep_audio_r = 0x86 ,
. ep_audio_w = 0x02 ,
} ,
2015-01-15 10:22:31 +03:00
} ;
/*
Probe USB device .
*/
static int podhd_probe ( struct usb_interface * interface ,
const struct usb_device_id * id )
{
2015-02-07 19:43:19 +03:00
return line6_probe ( interface , id , " Line6-PODHD " ,
2015-01-19 17:54:00 +03:00
& podhd_properties_table [ id - > driver_info ] ,
2016-09-18 21:59:28 +03:00
podhd_init , sizeof ( struct usb_line6_podhd ) ) ;
2015-01-15 10:22:31 +03:00
}
static struct usb_driver podhd_driver = {
. name = KBUILD_MODNAME ,
. probe = podhd_probe ,
. disconnect = line6_disconnect ,
# ifdef CONFIG_PM
. suspend = line6_suspend ,
. resume = line6_resume ,
. reset_resume = line6_resume ,
# endif
. id_table = podhd_id_table ,
} ;
module_usb_driver ( podhd_driver ) ;
2015-01-20 11:20:49 +03:00
MODULE_DESCRIPTION ( " Line 6 PODHD USB driver " ) ;
2015-01-15 10:22:31 +03:00
MODULE_LICENSE ( " GPL " ) ;