2005-04-17 02:20:36 +04:00
/*
* Driver for Tascam US - X2Y USB soundcards
*
* FPGA Loader + ALSA Startup
*
* Copyright ( c ) 2003 by Karsten Wiese < annabellesgarden @ yahoo . de >
*
* 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 ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/interrupt.h>
# include <linux/usb.h>
# include <sound/core.h>
# include <sound/memalloc.h>
# include <sound/pcm.h>
# include <sound/hwdep.h>
# include "usx2y.h"
# include "usbusx2y.h"
# include "usX2Yhwdep.h"
2007-12-13 18:16:40 +03:00
static int snd_us428ctls_vm_fault ( struct vm_area_struct * area ,
struct vm_fault * vmf )
2005-04-17 02:20:36 +04:00
{
unsigned long offset ;
struct page * page ;
void * vaddr ;
2007-12-13 18:16:40 +03:00
snd_printdd ( " ENTER, start %lXh, pgoff %ld \n " ,
2005-04-17 02:20:36 +04:00
area - > vm_start ,
2007-12-13 18:16:40 +03:00
vmf - > pgoff ) ;
2005-04-17 02:20:36 +04:00
2007-12-13 18:16:40 +03:00
offset = vmf - > pgoff < < PAGE_SHIFT ;
2005-11-17 17:08:26 +03:00
vaddr = ( char * ) ( ( struct usX2Ydev * ) area - > vm_private_data ) - > us428ctls_sharedmem + offset ;
2005-04-17 02:20:36 +04:00
page = virt_to_page ( vaddr ) ;
get_page ( page ) ;
2007-12-13 18:16:40 +03:00
vmf - > page = page ;
2005-04-17 02:20:36 +04:00
2007-12-13 18:16:40 +03:00
snd_printdd ( " vaddr=%p made us428ctls_vm_fault() page %p \n " ,
vaddr , page ) ;
2005-04-17 02:20:36 +04:00
2007-12-13 18:16:40 +03:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
static struct vm_operations_struct us428ctls_vm_ops = {
2007-12-13 18:16:40 +03:00
. fault = snd_us428ctls_vm_fault ,
2005-04-17 02:20:36 +04:00
} ;
2005-11-17 17:08:26 +03:00
static int snd_us428ctls_mmap ( struct snd_hwdep * hw , struct file * filp , struct vm_area_struct * area )
2005-04-17 02:20:36 +04:00
{
unsigned long size = ( unsigned long ) ( area - > vm_end - area - > vm_start ) ;
2005-11-17 17:08:26 +03:00
struct usX2Ydev * us428 = hw - > private_data ;
2005-04-17 02:20:36 +04:00
// FIXME this hwdep interface is used twice: fpga download and mmap for controlling Lights etc. Maybe better using 2 hwdep devs?
// so as long as the device isn't fully initialised yet we return -EBUSY here.
2005-11-17 12:48:52 +03:00
if ( ! ( us428 - > chip_status & USX2Y_STAT_CHIP_INIT ) )
2005-04-17 02:20:36 +04:00
return - EBUSY ;
/* if userspace tries to mmap beyond end of our buffer, fail */
2005-11-17 17:08:26 +03:00
if ( size > PAGE_ALIGN ( sizeof ( struct us428ctls_sharedmem ) ) ) {
snd_printd ( " %lu > %lu \n " , size , ( unsigned long ) sizeof ( struct us428ctls_sharedmem ) ) ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
if ( ! us428 - > us428ctls_sharedmem ) {
init_waitqueue_head ( & us428 - > us428ctls_wait_queue_head ) ;
2005-11-17 17:08:26 +03:00
if ( ! ( us428 - > us428ctls_sharedmem = snd_malloc_pages ( sizeof ( struct us428ctls_sharedmem ) , GFP_KERNEL ) ) )
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
2005-11-17 17:08:26 +03:00
memset ( us428 - > us428ctls_sharedmem , - 1 , sizeof ( struct us428ctls_sharedmem ) ) ;
2005-04-17 02:20:36 +04:00
us428 - > us428ctls_sharedmem - > CtlSnapShotLast = - 2 ;
}
area - > vm_ops = & us428ctls_vm_ops ;
2008-02-02 05:08:53 +03:00
area - > vm_flags | = VM_RESERVED | VM_DONTEXPAND ;
2005-04-17 02:20:36 +04:00
area - > vm_private_data = hw - > private_data ;
return 0 ;
}
2005-11-17 17:08:26 +03:00
static unsigned int snd_us428ctls_poll ( struct snd_hwdep * hw , struct file * file , poll_table * wait )
2005-04-17 02:20:36 +04:00
{
unsigned int mask = 0 ;
2005-11-17 17:08:26 +03:00
struct usX2Ydev * us428 = hw - > private_data ;
struct us428ctls_sharedmem * shm = us428 - > us428ctls_sharedmem ;
2005-04-17 02:20:36 +04:00
if ( us428 - > chip_status & USX2Y_STAT_CHIP_HUP )
return POLLHUP ;
poll_wait ( file , & us428 - > us428ctls_wait_queue_head , wait ) ;
if ( shm ! = NULL & & shm - > CtlSnapShotLast ! = shm - > CtlSnapShotRed )
mask | = POLLIN ;
return mask ;
}
2005-11-17 17:08:26 +03:00
static int snd_usX2Y_hwdep_dsp_status ( struct snd_hwdep * hw ,
struct snd_hwdep_dsp_status * info )
2005-04-17 02:20:36 +04:00
{
static char * type_ids [ USX2Y_TYPE_NUMS ] = {
[ USX2Y_TYPE_122 ] = " us122 " ,
[ USX2Y_TYPE_224 ] = " us224 " ,
[ USX2Y_TYPE_428 ] = " us428 " ,
} ;
2005-11-17 17:08:26 +03:00
struct usX2Ydev * us428 = hw - > private_data ;
2005-04-17 02:20:36 +04:00
int id = - 1 ;
2005-11-17 12:48:52 +03:00
switch ( le16_to_cpu ( us428 - > chip . dev - > descriptor . idProduct ) ) {
2005-04-17 02:20:36 +04:00
case USB_ID_US122 :
id = USX2Y_TYPE_122 ;
break ;
case USB_ID_US224 :
id = USX2Y_TYPE_224 ;
break ;
case USB_ID_US428 :
id = USX2Y_TYPE_428 ;
break ;
}
if ( 0 > id )
return - ENODEV ;
strcpy ( info - > id , type_ids [ id ] ) ;
info - > num_dsps = 2 ; // 0: Prepad Data, 1: FPGA Code
2005-11-17 17:08:26 +03:00
if ( us428 - > chip_status & USX2Y_STAT_CHIP_INIT )
2005-04-17 02:20:36 +04:00
info - > chip_ready = 1 ;
info - > version = USX2Y_DRIVER_VERSION ;
return 0 ;
}
2005-11-17 17:08:26 +03:00
static int usX2Y_create_usbmidi ( struct snd_card * card )
2005-04-17 02:20:36 +04:00
{
2005-11-17 17:08:26 +03:00
static struct snd_usb_midi_endpoint_info quirk_data_1 = {
. out_ep = 0x06 ,
2005-04-17 02:20:36 +04:00
. in_ep = 0x06 ,
. out_cables = 0x001 ,
. in_cables = 0x001
} ;
2005-11-17 17:08:26 +03:00
static struct snd_usb_audio_quirk quirk_1 = {
2005-04-17 02:20:36 +04:00
. vendor_name = " TASCAM " ,
. product_name = NAME_ALLCAPS ,
. ifnum = 0 ,
. type = QUIRK_MIDI_FIXED_ENDPOINT ,
. data = & quirk_data_1
} ;
2005-11-17 17:08:26 +03:00
static struct snd_usb_midi_endpoint_info quirk_data_2 = {
. out_ep = 0x06 ,
2005-04-17 02:20:36 +04:00
. in_ep = 0x06 ,
. out_cables = 0x003 ,
. in_cables = 0x003
} ;
2005-11-17 17:08:26 +03:00
static struct snd_usb_audio_quirk quirk_2 = {
2005-04-17 02:20:36 +04:00
. vendor_name = " TASCAM " ,
. product_name = " US428 " ,
. ifnum = 0 ,
. type = QUIRK_MIDI_FIXED_ENDPOINT ,
. data = & quirk_data_2
} ;
struct usb_device * dev = usX2Y ( card ) - > chip . dev ;
struct usb_interface * iface = usb_ifnum_to_if ( dev , 0 ) ;
2005-11-17 17:08:26 +03:00
struct snd_usb_audio_quirk * quirk =
le16_to_cpu ( dev - > descriptor . idProduct ) = = USB_ID_US428 ?
& quirk_2 : & quirk_1 ;
2005-04-17 02:20:36 +04:00
snd_printdd ( " usX2Y_create_usbmidi \n " ) ;
return snd_usb_create_midi_interface ( & usX2Y ( card ) - > chip , iface , quirk ) ;
}
2005-11-17 17:08:26 +03:00
static int usX2Y_create_alsa_devices ( struct snd_card * card )
2005-04-17 02:20:36 +04:00
{
int err ;
do {
if ( ( err = usX2Y_create_usbmidi ( card ) ) < 0 ) {
2005-10-21 18:20:11 +04:00
snd_printk ( KERN_ERR " usX2Y_create_alsa_devices: usX2Y_create_usbmidi error %i \n " , err ) ;
2005-04-17 02:20:36 +04:00
break ;
}
if ( ( err = usX2Y_audio_create ( card ) ) < 0 )
break ;
if ( ( err = usX2Y_hwdep_pcm_new ( card ) ) < 0 )
break ;
if ( ( err = snd_card_register ( card ) ) < 0 )
break ;
} while ( 0 ) ;
return err ;
}
2005-11-17 17:08:26 +03:00
static int snd_usX2Y_hwdep_dsp_load ( struct snd_hwdep * hw ,
struct snd_hwdep_dsp_image * dsp )
2005-04-17 02:20:36 +04:00
{
2005-11-17 17:08:26 +03:00
struct usX2Ydev * priv = hw - > private_data ;
2005-04-17 02:20:36 +04:00
int lret , err = - EINVAL ;
snd_printdd ( " dsp_load %s \n " , dsp - > name ) ;
if ( access_ok ( VERIFY_READ , dsp - > image , dsp - > length ) ) {
struct usb_device * dev = priv - > chip . dev ;
2009-04-10 05:43:59 +04:00
char * buf ;
buf = memdup_user ( dsp - > image , dsp - > length ) ;
if ( IS_ERR ( buf ) )
return PTR_ERR ( buf ) ;
2005-04-17 02:20:36 +04:00
err = usb_set_interface ( dev , 0 , 1 ) ;
if ( err )
2005-10-21 18:20:11 +04:00
snd_printk ( KERN_ERR " usb_set_interface error \n " ) ;
2005-04-17 02:20:36 +04:00
else
err = usb_bulk_msg ( dev , usb_sndbulkpipe ( dev , 2 ) , buf , dsp - > length , & lret , 6000 ) ;
kfree ( buf ) ;
}
if ( err )
return err ;
if ( dsp - > index = = 1 ) {
2005-07-09 12:54:37 +04:00
msleep ( 250 ) ; // give the device some time
2005-04-17 02:20:36 +04:00
err = usX2Y_AsyncSeq04_init ( priv ) ;
if ( err ) {
2005-10-21 18:20:11 +04:00
snd_printk ( KERN_ERR " usX2Y_AsyncSeq04_init error \n " ) ;
2005-04-17 02:20:36 +04:00
return err ;
}
err = usX2Y_In04_init ( priv ) ;
if ( err ) {
2005-10-21 18:20:11 +04:00
snd_printk ( KERN_ERR " usX2Y_In04_init error \n " ) ;
2005-04-17 02:20:36 +04:00
return err ;
}
err = usX2Y_create_alsa_devices ( hw - > card ) ;
if ( err ) {
2005-10-21 18:20:11 +04:00
snd_printk ( KERN_ERR " usX2Y_create_alsa_devices error %i \n " , err ) ;
2005-04-17 02:20:36 +04:00
snd_card_free ( hw - > card ) ;
return err ;
}
priv - > chip_status | = USX2Y_STAT_CHIP_INIT ;
snd_printdd ( " %s: alsa all started \n " , hw - > name ) ;
}
return err ;
}
2005-11-17 17:08:26 +03:00
int usX2Y_hwdep_new ( struct snd_card * card , struct usb_device * device )
2005-04-17 02:20:36 +04:00
{
int err ;
2005-11-17 17:08:26 +03:00
struct snd_hwdep * hw ;
2005-04-17 02:20:36 +04:00
if ( ( err = snd_hwdep_new ( card , SND_USX2Y_LOADER_ID , 0 , & hw ) ) < 0 )
return err ;
hw - > iface = SNDRV_HWDEP_IFACE_USX2Y ;
hw - > private_data = usX2Y ( card ) ;
hw - > ops . dsp_status = snd_usX2Y_hwdep_dsp_status ;
hw - > ops . dsp_load = snd_usX2Y_hwdep_dsp_load ;
hw - > ops . mmap = snd_us428ctls_mmap ;
hw - > ops . poll = snd_us428ctls_poll ;
hw - > exclusive = 1 ;
sprintf ( hw - > name , " /proc/bus/usb/%03d/%03d " , device - > bus - > busnum , device - > devnum ) ;
return 0 ;
}