2007-05-18 16:26:59 +04:00
/*
* This code is licenced under
* the General Public Licence
* version 2
*
* Copyright Adrian McMenamin 2005 , 2006 , 2007
* < adrian @ mcmen . demon . co . uk >
* Requires firmware ( BSD licenced ) available from :
* http : //linuxdc.cvs.sourceforge.net/linuxdc/linux-sh-dc/sound/oss/aica/firmware/
* or the maintainer
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of version 2 of the GNU General Public License as published by
* the Free Software Foundation .
*
* 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/init.h>
# include <linux/jiffies.h>
# include <linux/slab.h>
# include <linux/time.h>
# include <linux/wait.h>
2011-07-15 21:13:37 +04:00
# include <linux/module.h>
2007-05-18 16:26:59 +04:00
# include <linux/platform_device.h>
# include <linux/firmware.h>
# include <linux/timer.h>
# include <linux/delay.h>
# include <linux/workqueue.h>
2015-01-28 18:49:33 +03:00
# include <linux/io.h>
2007-05-18 16:26:59 +04:00
# include <sound/core.h>
# include <sound/control.h>
# include <sound/pcm.h>
# include <sound/initval.h>
# include <sound/info.h>
# include <asm/dma.h>
2008-07-29 03:09:44 +04:00
# include <mach/sysasic.h>
2007-05-18 16:26:59 +04:00
# include "aica.h"
MODULE_AUTHOR ( " Adrian McMenamin <adrian@mcmen.demon.co.uk> " ) ;
MODULE_DESCRIPTION ( " Dreamcast AICA sound (pcm) driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_SUPPORTED_DEVICE ( " {{Yamaha/SEGA, AICA}} " ) ;
2009-11-08 01:13:39 +03:00
MODULE_FIRMWARE ( " aica_firmware.bin " ) ;
2007-05-18 16:26:59 +04:00
/* module parameters */
# define CARD_NAME "AICA"
static int index = - 1 ;
static char * id ;
2011-12-15 07:19:36 +04:00
static bool enable = 1 ;
2007-05-18 16:26:59 +04:00
module_param ( index , int , 0444 ) ;
MODULE_PARM_DESC ( index , " Index value for " CARD_NAME " soundcard. " ) ;
module_param ( id , charp , 0444 ) ;
MODULE_PARM_DESC ( id , " ID string for " CARD_NAME " soundcard. " ) ;
module_param ( enable , bool , 0644 ) ;
MODULE_PARM_DESC ( enable , " Enable " CARD_NAME " soundcard. " ) ;
/* Use workqueue */
static struct workqueue_struct * aica_queue ;
/* Simple platform device */
static struct platform_device * pd ;
static struct resource aica_memory_space [ 2 ] = {
{
. name = " AICA ARM CONTROL " ,
. start = ARM_RESET_REGISTER ,
. flags = IORESOURCE_MEM ,
. end = ARM_RESET_REGISTER + 3 ,
} ,
{
. name = " AICA Sound RAM " ,
. start = SPU_MEMORY_BASE ,
. flags = IORESOURCE_MEM ,
. end = SPU_MEMORY_BASE + 0x200000 - 1 ,
} ,
} ;
/* SPU specific functions */
/* spu_write_wait - wait for G2-SH FIFO to clear */
static void spu_write_wait ( void )
{
int time_count ;
time_count = 0 ;
while ( 1 ) {
if ( ! ( readl ( G2_FIFO ) & 0x11 ) )
break ;
/* To ensure hardware failure doesn't wedge kernel */
time_count + + ;
2007-07-09 12:36:22 +04:00
if ( time_count > 0x10000 ) {
snd_printk
( " WARNING: G2 FIFO appears to be blocked. \n " ) ;
2007-05-18 16:26:59 +04:00
break ;
}
}
}
/* spu_memset - write to memory in SPU address space */
static void spu_memset ( u32 toi , u32 what , int length )
{
int i ;
2007-10-18 12:46:59 +04:00
unsigned long flags ;
2008-08-08 19:12:47 +04:00
if ( snd_BUG_ON ( length % 4 ) )
return ;
2007-05-18 16:26:59 +04:00
for ( i = 0 ; i < length ; i + + ) {
if ( ! ( i % 8 ) )
spu_write_wait ( ) ;
2007-10-18 12:46:59 +04:00
local_irq_save ( flags ) ;
2007-05-18 16:26:59 +04:00
writel ( what , toi + SPU_MEMORY_BASE ) ;
2007-10-18 12:46:59 +04:00
local_irq_restore ( flags ) ;
2007-05-18 16:26:59 +04:00
toi + + ;
}
}
/* spu_memload - write to SPU address space */
static void spu_memload ( u32 toi , void * from , int length )
{
2007-10-18 12:46:59 +04:00
unsigned long flags ;
2007-05-18 16:26:59 +04:00
u32 * froml = from ;
u32 __iomem * to = ( u32 __iomem * ) ( SPU_MEMORY_BASE + toi ) ;
int i ;
u32 val ;
length = DIV_ROUND_UP ( length , 4 ) ;
spu_write_wait ( ) ;
for ( i = 0 ; i < length ; i + + ) {
if ( ! ( i % 8 ) )
spu_write_wait ( ) ;
val = * froml ;
2007-10-18 12:46:59 +04:00
local_irq_save ( flags ) ;
2007-05-18 16:26:59 +04:00
writel ( val , to ) ;
2007-10-18 12:46:59 +04:00
local_irq_restore ( flags ) ;
2007-05-18 16:26:59 +04:00
froml + + ;
to + + ;
}
}
/* spu_disable - set spu registers to stop sound output */
static void spu_disable ( void )
{
int i ;
2007-10-18 12:46:59 +04:00
unsigned long flags ;
2007-05-18 16:26:59 +04:00
u32 regval ;
spu_write_wait ( ) ;
regval = readl ( ARM_RESET_REGISTER ) ;
regval | = 1 ;
spu_write_wait ( ) ;
2007-10-18 12:46:59 +04:00
local_irq_save ( flags ) ;
2007-05-18 16:26:59 +04:00
writel ( regval , ARM_RESET_REGISTER ) ;
2007-10-18 12:46:59 +04:00
local_irq_restore ( flags ) ;
2007-05-18 16:26:59 +04:00
for ( i = 0 ; i < 64 ; i + + ) {
spu_write_wait ( ) ;
regval = readl ( SPU_REGISTER_BASE + ( i * 0x80 ) ) ;
regval = ( regval & ~ 0x4000 ) | 0x8000 ;
spu_write_wait ( ) ;
2007-10-18 12:46:59 +04:00
local_irq_save ( flags ) ;
2007-05-18 16:26:59 +04:00
writel ( regval , SPU_REGISTER_BASE + ( i * 0x80 ) ) ;
2007-10-18 12:46:59 +04:00
local_irq_restore ( flags ) ;
2007-05-18 16:26:59 +04:00
}
}
/* spu_enable - set spu registers to enable sound output */
static void spu_enable ( void )
{
2007-10-18 12:46:59 +04:00
unsigned long flags ;
2007-05-18 16:26:59 +04:00
u32 regval = readl ( ARM_RESET_REGISTER ) ;
regval & = ~ 1 ;
spu_write_wait ( ) ;
2007-10-18 12:46:59 +04:00
local_irq_save ( flags ) ;
2007-05-18 16:26:59 +04:00
writel ( regval , ARM_RESET_REGISTER ) ;
2007-10-18 12:46:59 +04:00
local_irq_restore ( flags ) ;
2007-05-18 16:26:59 +04:00
}
/*
* Halt the sound processor , clear the memory ,
* load some default ARM7 code , and then restart ARM7
*/
static void spu_reset ( void )
{
2007-10-18 12:46:59 +04:00
unsigned long flags ;
2007-05-18 16:26:59 +04:00
spu_disable ( ) ;
spu_memset ( 0 , 0 , 0x200000 / 4 ) ;
/* Put ARM7 in endless loop */
2007-10-18 12:46:59 +04:00
local_irq_save ( flags ) ;
2010-10-27 10:53:50 +04:00
__raw_writel ( 0xea000002 , SPU_MEMORY_BASE ) ;
2007-10-18 12:46:59 +04:00
local_irq_restore ( flags ) ;
2007-05-18 16:26:59 +04:00
spu_enable ( ) ;
}
/* aica_chn_start - write to spu to start playback */
static void aica_chn_start ( void )
{
2007-10-18 12:46:59 +04:00
unsigned long flags ;
2007-05-18 16:26:59 +04:00
spu_write_wait ( ) ;
2007-10-18 12:46:59 +04:00
local_irq_save ( flags ) ;
2007-05-18 16:26:59 +04:00
writel ( AICA_CMD_KICK | AICA_CMD_START , ( u32 * ) AICA_CONTROL_POINT ) ;
2007-10-18 12:46:59 +04:00
local_irq_restore ( flags ) ;
2007-05-18 16:26:59 +04:00
}
/* aica_chn_halt - write to spu to halt playback */
static void aica_chn_halt ( void )
{
2007-10-18 12:46:59 +04:00
unsigned long flags ;
2007-05-18 16:26:59 +04:00
spu_write_wait ( ) ;
2007-10-18 12:46:59 +04:00
local_irq_save ( flags ) ;
2007-05-18 16:26:59 +04:00
writel ( AICA_CMD_KICK | AICA_CMD_STOP , ( u32 * ) AICA_CONTROL_POINT ) ;
2007-10-18 12:46:59 +04:00
local_irq_restore ( flags ) ;
2007-05-18 16:26:59 +04:00
}
/* ALSA code below */
static struct snd_pcm_hardware snd_pcm_aica_playback_hw = {
. info = ( SNDRV_PCM_INFO_NONINTERLEAVED ) ,
. formats =
( SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_IMA_ADPCM ) ,
. rates = SNDRV_PCM_RATE_8000_48000 ,
. rate_min = 8000 ,
. rate_max = 48000 ,
. channels_min = 1 ,
. channels_max = 2 ,
. buffer_bytes_max = AICA_BUFFER_SIZE ,
. period_bytes_min = AICA_PERIOD_SIZE ,
. period_bytes_max = AICA_PERIOD_SIZE ,
. periods_min = AICA_PERIOD_NUMBER ,
. periods_max = AICA_PERIOD_NUMBER ,
} ;
static int aica_dma_transfer ( int channels , int buffer_size ,
struct snd_pcm_substream * substream )
{
int q , err , period_offset ;
struct snd_card_aica * dreamcastcard ;
struct snd_pcm_runtime * runtime ;
2007-10-18 12:46:59 +04:00
unsigned long flags ;
2007-10-29 12:49:43 +03:00
err = 0 ;
2007-05-18 16:26:59 +04:00
dreamcastcard = substream - > pcm - > private_data ;
period_offset = dreamcastcard - > clicks ;
period_offset % = ( AICA_PERIOD_NUMBER / channels ) ;
runtime = substream - > runtime ;
for ( q = 0 ; q < channels ; q + + ) {
2007-10-18 12:46:59 +04:00
local_irq_save ( flags ) ;
2007-05-18 16:26:59 +04:00
err = dma_xfer ( AICA_DMA_CHANNEL ,
2007-07-09 12:36:22 +04:00
( unsigned long ) ( runtime - > dma_area +
( AICA_BUFFER_SIZE * q ) /
channels +
AICA_PERIOD_SIZE *
period_offset ) ,
2007-05-18 16:26:59 +04:00
AICA_CHANNEL0_OFFSET + q * CHANNEL_OFFSET +
AICA_PERIOD_SIZE * period_offset ,
buffer_size / channels , AICA_DMA_MODE ) ;
2007-10-18 12:46:59 +04:00
if ( unlikely ( err < 0 ) ) {
local_irq_restore ( flags ) ;
2007-05-18 16:26:59 +04:00
break ;
2007-10-18 12:46:59 +04:00
}
2007-05-18 16:26:59 +04:00
dma_wait_for_completion ( AICA_DMA_CHANNEL ) ;
2007-10-18 12:46:59 +04:00
local_irq_restore ( flags ) ;
2007-05-18 16:26:59 +04:00
}
return err ;
}
static void startup_aica ( struct snd_card_aica * dreamcastcard )
{
spu_memload ( AICA_CHANNEL0_CONTROL_OFFSET ,
2007-07-09 12:36:22 +04:00
dreamcastcard - > channel , sizeof ( struct aica_channel ) ) ;
2007-05-18 16:26:59 +04:00
aica_chn_start ( ) ;
}
static void run_spu_dma ( struct work_struct * work )
{
int buffer_size ;
struct snd_pcm_runtime * runtime ;
struct snd_card_aica * dreamcastcard ;
2007-07-09 12:36:22 +04:00
dreamcastcard =
container_of ( work , struct snd_card_aica , spu_dma_work ) ;
runtime = dreamcastcard - > substream - > runtime ;
2007-05-18 16:26:59 +04:00
if ( unlikely ( dreamcastcard - > dma_check = = 0 ) ) {
2007-07-09 12:36:22 +04:00
buffer_size =
frames_to_bytes ( runtime , runtime - > buffer_size ) ;
2007-05-18 16:26:59 +04:00
if ( runtime - > channels > 1 )
dreamcastcard - > channel - > flags | = 0x01 ;
2007-07-09 12:36:22 +04:00
aica_dma_transfer ( runtime - > channels , buffer_size ,
dreamcastcard - > substream ) ;
2007-05-18 16:26:59 +04:00
startup_aica ( dreamcastcard ) ;
dreamcastcard - > clicks =
buffer_size / ( AICA_PERIOD_SIZE * runtime - > channels ) ;
return ;
} else {
aica_dma_transfer ( runtime - > channels ,
AICA_PERIOD_SIZE * runtime - > channels ,
2007-07-09 12:36:22 +04:00
dreamcastcard - > substream ) ;
2007-05-18 16:26:59 +04:00
snd_pcm_period_elapsed ( dreamcastcard - > substream ) ;
dreamcastcard - > clicks + + ;
if ( unlikely ( dreamcastcard - > clicks > = AICA_PERIOD_NUMBER ) )
dreamcastcard - > clicks % = AICA_PERIOD_NUMBER ;
mod_timer ( & dreamcastcard - > timer , jiffies + 1 ) ;
}
}
static void aica_period_elapsed ( unsigned long timer_var )
{
/*timer function - so cannot sleep */
int play_period ;
struct snd_pcm_runtime * runtime ;
struct snd_pcm_substream * substream ;
struct snd_card_aica * dreamcastcard ;
2007-07-09 12:36:22 +04:00
substream = ( struct snd_pcm_substream * ) timer_var ;
2007-05-18 16:26:59 +04:00
runtime = substream - > runtime ;
dreamcastcard = substream - > pcm - > private_data ;
/* Have we played out an additional period? */
play_period =
frames_to_bytes ( runtime ,
readl
( AICA_CONTROL_CHANNEL_SAMPLE_NUMBER ) ) /
AICA_PERIOD_SIZE ;
if ( play_period = = dreamcastcard - > current_period ) {
/* reschedule the timer */
mod_timer ( & ( dreamcastcard - > timer ) , jiffies + 1 ) ;
return ;
}
if ( runtime - > channels > 1 )
dreamcastcard - > current_period = play_period ;
if ( unlikely ( dreamcastcard - > dma_check = = 0 ) )
dreamcastcard - > dma_check = 1 ;
2007-07-09 12:36:22 +04:00
queue_work ( aica_queue , & ( dreamcastcard - > spu_dma_work ) ) ;
2007-05-18 16:26:59 +04:00
}
static void spu_begin_dma ( struct snd_pcm_substream * substream )
{
struct snd_card_aica * dreamcastcard ;
struct snd_pcm_runtime * runtime ;
runtime = substream - > runtime ;
dreamcastcard = substream - > pcm - > private_data ;
2007-07-09 12:36:22 +04:00
/*get the queue to do the work */
queue_work ( aica_queue , & ( dreamcastcard - > spu_dma_work ) ) ;
2007-05-18 16:26:59 +04:00
/* Timer may already be running */
if ( unlikely ( dreamcastcard - > timer . data ) ) {
mod_timer ( & dreamcastcard - > timer , jiffies + 4 ) ;
return ;
}
2015-01-19 13:35:01 +03:00
setup_timer ( & dreamcastcard - > timer , aica_period_elapsed ,
( unsigned long ) substream ) ;
mod_timer ( & dreamcastcard - > timer , jiffies + 4 ) ;
2007-05-18 16:26:59 +04:00
}
static int snd_aicapcm_pcm_open ( struct snd_pcm_substream
* substream )
{
struct snd_pcm_runtime * runtime ;
struct aica_channel * channel ;
struct snd_card_aica * dreamcastcard ;
if ( ! enable )
return - ENOENT ;
dreamcastcard = substream - > pcm - > private_data ;
channel = kmalloc ( sizeof ( struct aica_channel ) , GFP_KERNEL ) ;
if ( ! channel )
return - ENOMEM ;
/* set defaults for channel */
channel - > sfmt = SM_8BIT ;
channel - > cmd = AICA_CMD_START ;
channel - > vol = dreamcastcard - > master_volume ;
channel - > pan = 0x80 ;
channel - > pos = 0 ;
channel - > flags = 0 ; /* default to mono */
dreamcastcard - > channel = channel ;
runtime = substream - > runtime ;
runtime - > hw = snd_pcm_aica_playback_hw ;
spu_enable ( ) ;
dreamcastcard - > clicks = 0 ;
dreamcastcard - > current_period = 0 ;
dreamcastcard - > dma_check = 0 ;
return 0 ;
}
static int snd_aicapcm_pcm_close ( struct snd_pcm_substream
* substream )
{
struct snd_card_aica * dreamcastcard = substream - > pcm - > private_data ;
2007-07-09 12:36:22 +04:00
flush_workqueue ( aica_queue ) ;
if ( dreamcastcard - > timer . data )
del_timer ( & dreamcastcard - > timer ) ;
2007-05-18 16:26:59 +04:00
kfree ( dreamcastcard - > channel ) ;
spu_disable ( ) ;
return 0 ;
}
static int snd_aicapcm_pcm_hw_free ( struct snd_pcm_substream
* substream )
{
/* Free the DMA buffer */
return snd_pcm_lib_free_pages ( substream ) ;
}
static int snd_aicapcm_pcm_hw_params ( struct snd_pcm_substream
* substream , struct snd_pcm_hw_params
* hw_params )
{
/* Allocate a DMA buffer using ALSA built-ins */
return
2007-07-09 12:36:22 +04:00
snd_pcm_lib_malloc_pages ( substream ,
params_buffer_bytes ( hw_params ) ) ;
2007-05-18 16:26:59 +04:00
}
static int snd_aicapcm_pcm_prepare ( struct snd_pcm_substream
* substream )
{
struct snd_card_aica * dreamcastcard = substream - > pcm - > private_data ;
if ( ( substream - > runtime ) - > format = = SNDRV_PCM_FORMAT_S16_LE )
dreamcastcard - > channel - > sfmt = SM_16BIT ;
dreamcastcard - > channel - > freq = substream - > runtime - > rate ;
dreamcastcard - > substream = substream ;
return 0 ;
}
static int snd_aicapcm_pcm_trigger ( struct snd_pcm_substream
* substream , int cmd )
{
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
spu_begin_dma ( substream ) ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
aica_chn_halt ( ) ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
static unsigned long snd_aicapcm_pcm_pointer ( struct snd_pcm_substream
* substream )
{
return readl ( AICA_CONTROL_CHANNEL_SAMPLE_NUMBER ) ;
}
static struct snd_pcm_ops snd_aicapcm_playback_ops = {
. open = snd_aicapcm_pcm_open ,
. close = snd_aicapcm_pcm_close ,
. ioctl = snd_pcm_lib_ioctl ,
. hw_params = snd_aicapcm_pcm_hw_params ,
. hw_free = snd_aicapcm_pcm_hw_free ,
. prepare = snd_aicapcm_pcm_prepare ,
. trigger = snd_aicapcm_pcm_trigger ,
. pointer = snd_aicapcm_pcm_pointer ,
} ;
/* TO DO: set up to handle more than one pcm instance */
static int __init snd_aicapcmchip ( struct snd_card_aica
* dreamcastcard , int pcm_index )
{
struct snd_pcm * pcm ;
int err ;
/* AICA has no capture ability */
err =
2007-07-09 12:36:22 +04:00
snd_pcm_new ( dreamcastcard - > card , " AICA PCM " , pcm_index , 1 , 0 ,
& pcm ) ;
2007-05-18 16:26:59 +04:00
if ( unlikely ( err < 0 ) )
return err ;
pcm - > private_data = dreamcastcard ;
strcpy ( pcm - > name , " AICA PCM " ) ;
snd_pcm_set_ops ( pcm , SNDRV_PCM_STREAM_PLAYBACK ,
& snd_aicapcm_playback_ops ) ;
/* Allocate the DMA buffers */
err =
snd_pcm_lib_preallocate_pages_for_all ( pcm ,
SNDRV_DMA_TYPE_CONTINUOUS ,
snd_dma_continuous_data
( GFP_KERNEL ) ,
AICA_BUFFER_SIZE ,
AICA_BUFFER_SIZE ) ;
return err ;
}
/* Mixer controls */
2007-07-23 17:42:26 +04:00
# define aica_pcmswitch_info snd_ctl_boolean_mono_info
2007-05-18 16:26:59 +04:00
static int aica_pcmswitch_get ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
ucontrol - > value . integer . value [ 0 ] = 1 ; /* TO DO: Fix me */
return 0 ;
}
static int aica_pcmswitch_put ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
if ( ucontrol - > value . integer . value [ 0 ] = = 1 )
return 0 ; /* TO DO: Fix me */
else
aica_chn_halt ( ) ;
return 0 ;
}
static int aica_pcmvolume_info ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_info * uinfo )
{
uinfo - > type = SNDRV_CTL_ELEM_TYPE_INTEGER ;
uinfo - > count = 1 ;
uinfo - > value . integer . min = 0 ;
uinfo - > value . integer . max = 0xFF ;
return 0 ;
}
static int aica_pcmvolume_get ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct snd_card_aica * dreamcastcard ;
dreamcastcard = kcontrol - > private_data ;
if ( unlikely ( ! dreamcastcard - > channel ) )
return - ETXTBSY ; /* we've not yet been set up */
ucontrol - > value . integer . value [ 0 ] = dreamcastcard - > channel - > vol ;
return 0 ;
}
static int aica_pcmvolume_put ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct snd_card_aica * dreamcastcard ;
2007-11-15 18:17:24 +03:00
unsigned int vol ;
2007-05-18 16:26:59 +04:00
dreamcastcard = kcontrol - > private_data ;
if ( unlikely ( ! dreamcastcard - > channel ) )
return - ETXTBSY ;
2007-11-15 18:17:24 +03:00
vol = ucontrol - > value . integer . value [ 0 ] ;
if ( vol > 0xff )
return - EINVAL ;
if ( unlikely ( dreamcastcard - > channel - > vol = = vol ) )
2007-05-18 16:26:59 +04:00
return 0 ;
dreamcastcard - > channel - > vol = ucontrol - > value . integer . value [ 0 ] ;
dreamcastcard - > master_volume = ucontrol - > value . integer . value [ 0 ] ;
spu_memload ( AICA_CHANNEL0_CONTROL_OFFSET ,
2007-07-09 12:36:22 +04:00
dreamcastcard - > channel , sizeof ( struct aica_channel ) ) ;
2007-05-18 16:26:59 +04:00
return 1 ;
}
2012-12-06 21:35:24 +04:00
static struct snd_kcontrol_new snd_aica_pcmswitch_control = {
2007-05-18 16:26:59 +04:00
. iface = SNDRV_CTL_ELEM_IFACE_MIXER ,
. name = " PCM Playback Switch " ,
. index = 0 ,
. info = aica_pcmswitch_info ,
. get = aica_pcmswitch_get ,
. put = aica_pcmswitch_put
} ;
2012-12-06 21:35:24 +04:00
static struct snd_kcontrol_new snd_aica_pcmvolume_control = {
2007-05-18 16:26:59 +04:00
. iface = SNDRV_CTL_ELEM_IFACE_MIXER ,
. name = " PCM Playback Volume " ,
. index = 0 ,
. info = aica_pcmvolume_info ,
. get = aica_pcmvolume_get ,
. put = aica_pcmvolume_put
} ;
static int load_aica_firmware ( void )
{
int err ;
const struct firmware * fw_entry ;
spu_reset ( ) ;
err = request_firmware ( & fw_entry , " aica_firmware.bin " , & pd - > dev ) ;
if ( unlikely ( err ) )
return err ;
2009-01-26 13:06:57 +03:00
/* write firmware into memory */
2007-05-18 16:26:59 +04:00
spu_disable ( ) ;
spu_memload ( 0 , fw_entry - > data , fw_entry - > size ) ;
spu_enable ( ) ;
release_firmware ( fw_entry ) ;
return err ;
}
2012-12-06 21:35:24 +04:00
static int add_aicamixer_controls ( struct snd_card_aica * dreamcastcard )
2007-05-18 16:26:59 +04:00
{
int err ;
err = snd_ctl_add
( dreamcastcard - > card ,
snd_ctl_new1 ( & snd_aica_pcmvolume_control , dreamcastcard ) ) ;
if ( unlikely ( err < 0 ) )
return err ;
err = snd_ctl_add
( dreamcastcard - > card ,
snd_ctl_new1 ( & snd_aica_pcmswitch_control , dreamcastcard ) ) ;
if ( unlikely ( err < 0 ) )
return err ;
return 0 ;
}
2012-12-06 21:35:24 +04:00
static int snd_aica_remove ( struct platform_device * devptr )
2007-05-18 16:26:59 +04:00
{
struct snd_card_aica * dreamcastcard ;
dreamcastcard = platform_get_drvdata ( devptr ) ;
if ( unlikely ( ! dreamcastcard ) )
return - ENODEV ;
snd_card_free ( dreamcastcard - > card ) ;
kfree ( dreamcastcard ) ;
return 0 ;
}
2012-12-06 21:35:24 +04:00
static int snd_aica_probe ( struct platform_device * devptr )
2007-05-18 16:26:59 +04:00
{
int err ;
struct snd_card_aica * dreamcastcard ;
dreamcastcard = kmalloc ( sizeof ( struct snd_card_aica ) , GFP_KERNEL ) ;
if ( unlikely ( ! dreamcastcard ) )
return - ENOMEM ;
2014-01-29 17:40:03 +04:00
err = snd_card_new ( & devptr - > dev , index , SND_AICA_DRIVER ,
THIS_MODULE , 0 , & dreamcastcard - > card ) ;
2008-12-28 18:45:02 +03:00
if ( unlikely ( err < 0 ) ) {
2007-05-18 16:26:59 +04:00
kfree ( dreamcastcard ) ;
2008-12-28 18:45:02 +03:00
return err ;
2007-05-18 16:26:59 +04:00
}
strcpy ( dreamcastcard - > card - > driver , " snd_aica " ) ;
strcpy ( dreamcastcard - > card - > shortname , SND_AICA_DRIVER ) ;
strcpy ( dreamcastcard - > card - > longname ,
" Yamaha AICA Super Intelligent Sound Processor for SEGA Dreamcast " ) ;
2007-07-09 12:36:22 +04:00
/* Prepare to use the queue */
INIT_WORK ( & ( dreamcastcard - > spu_dma_work ) , run_spu_dma ) ;
2007-05-18 16:26:59 +04:00
/* Load the PCM 'chip' */
err = snd_aicapcmchip ( dreamcastcard , 0 ) ;
if ( unlikely ( err < 0 ) )
goto freedreamcast ;
dreamcastcard - > timer . data = 0 ;
dreamcastcard - > channel = NULL ;
/* Add basic controls */
err = add_aicamixer_controls ( dreamcastcard ) ;
if ( unlikely ( err < 0 ) )
goto freedreamcast ;
/* Register the card with ALSA subsystem */
err = snd_card_register ( dreamcastcard - > card ) ;
if ( unlikely ( err < 0 ) )
goto freedreamcast ;
platform_set_drvdata ( devptr , dreamcastcard ) ;
aica_queue = create_workqueue ( CARD_NAME ) ;
if ( unlikely ( ! aica_queue ) )
goto freedreamcast ;
snd_printk
( " ALSA Driver for Yamaha AICA Super Intelligent Sound Processor \n " ) ;
return 0 ;
freedreamcast :
snd_card_free ( dreamcastcard - > card ) ;
kfree ( dreamcastcard ) ;
return err ;
}
static struct platform_driver snd_aica_driver = {
. probe = snd_aica_probe ,
2012-12-06 21:35:24 +04:00
. remove = snd_aica_remove ,
2007-05-18 16:26:59 +04:00
. driver = {
2012-07-02 12:50:24 +04:00
. name = SND_AICA_DRIVER ,
} ,
2007-05-18 16:26:59 +04:00
} ;
static int __init aica_init ( void )
{
int err ;
err = platform_driver_register ( & snd_aica_driver ) ;
if ( unlikely ( err < 0 ) )
return err ;
pd = platform_device_register_simple ( SND_AICA_DRIVER , - 1 ,
aica_memory_space , 2 ) ;
2008-04-29 12:03:09 +04:00
if ( IS_ERR ( pd ) ) {
2007-05-18 16:26:59 +04:00
platform_driver_unregister ( & snd_aica_driver ) ;
return PTR_ERR ( pd ) ;
}
/* Load the firmware */
return load_aica_firmware ( ) ;
}
static void __exit aica_exit ( void )
{
2007-07-09 12:36:22 +04:00
/* Destroy the aica kernel thread *
* being extra cautious to check if it exists */
if ( likely ( aica_queue ) )
destroy_workqueue ( aica_queue ) ;
2007-05-18 16:26:59 +04:00
platform_device_unregister ( pd ) ;
platform_driver_unregister ( & snd_aica_driver ) ;
/* Kill any sound still playing and reset ARM7 to safe state */
spu_reset ( ) ;
}
module_init ( aica_init ) ;
module_exit ( aica_exit ) ;