2005-04-16 15:20:36 -07:00
/*
* pcm emulation on emu8000 wavetable
*
* Copyright ( C ) 2002 Takashi Iwai < tiwai @ suse . 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 "emu8000_local.h"
# include <linux/init.h>
# include <sound/initval.h>
# include <sound/pcm.h>
/*
* define the following if you want to use this pcm with non - interleaved mode
*/
/* #define USE_NONINTERLEAVE */
/* NOTE: for using the non-interleaved mode with alsa-lib, you have to set
* mmap_emulation flag to 1 in your . asoundrc , such like
*
* pcm . emu8k {
* type plug
* slave . pcm {
* type hw
* card 0
* device 1
* mmap_emulation 1
* }
* }
*
* besides , for the time being , the non - interleaved mode doesn ' t work well on
* alsa - lib . . .
*/
struct snd_emu8k_pcm {
2005-11-17 14:34:36 +01:00
struct snd_emu8000 * emu ;
struct snd_pcm_substream * substream ;
2005-04-16 15:20:36 -07:00
unsigned int allocated_bytes ;
2005-11-17 14:34:36 +01:00
struct snd_util_memblk * block ;
2005-04-16 15:20:36 -07:00
unsigned int offset ;
unsigned int buf_size ;
unsigned int period_size ;
unsigned int loop_start [ 2 ] ;
unsigned int pitch ;
int panning [ 2 ] ;
int last_ptr ;
int period_pos ;
int voices ;
unsigned int dram_opened : 1 ;
unsigned int running : 1 ;
unsigned int timer_running : 1 ;
struct timer_list timer ;
spinlock_t timer_lock ;
} ;
# define LOOP_BLANK_SIZE 8
/*
* open up channels for the simultaneous data transfer and playback
*/
static int
2005-11-17 14:34:36 +01:00
emu8k_open_dram_for_pcm ( struct snd_emu8000 * emu , int channels )
2005-04-16 15:20:36 -07:00
{
int i ;
/* reserve up to 2 voices for playback */
snd_emux_lock_voice ( emu - > emu , 0 ) ;
if ( channels > 1 )
snd_emux_lock_voice ( emu - > emu , 1 ) ;
/* reserve 28 voices for loading */
for ( i = channels + 1 ; i < EMU8000_DRAM_VOICES ; i + + ) {
unsigned int mode = EMU8000_RAM_WRITE ;
snd_emux_lock_voice ( emu - > emu , i ) ;
# ifndef USE_NONINTERLEAVE
if ( channels > 1 & & ( i & 1 ) ! = 0 )
mode | = EMU8000_RAM_RIGHT ;
# endif
snd_emu8000_dma_chan ( emu , i , mode ) ;
}
/* assign voice 31 and 32 to ROM */
EMU8000_VTFT_WRITE ( emu , 30 , 0 ) ;
EMU8000_PSST_WRITE ( emu , 30 , 0x1d8 ) ;
EMU8000_CSL_WRITE ( emu , 30 , 0x1e0 ) ;
EMU8000_CCCA_WRITE ( emu , 30 , 0x1d8 ) ;
EMU8000_VTFT_WRITE ( emu , 31 , 0 ) ;
EMU8000_PSST_WRITE ( emu , 31 , 0x1d8 ) ;
EMU8000_CSL_WRITE ( emu , 31 , 0x1e0 ) ;
EMU8000_CCCA_WRITE ( emu , 31 , 0x1d8 ) ;
return 0 ;
}
/*
*/
static void
2005-11-17 14:34:36 +01:00
snd_emu8000_write_wait ( struct snd_emu8000 * emu , int can_schedule )
2005-04-16 15:20:36 -07:00
{
while ( ( EMU8000_SMALW_READ ( emu ) & 0x80000000 ) ! = 0 ) {
if ( can_schedule ) {
2005-10-24 15:02:37 +02:00
schedule_timeout_interruptible ( 1 ) ;
2005-04-16 15:20:36 -07:00
if ( signal_pending ( current ) )
break ;
}
}
}
/*
* close all channels
*/
static void
2005-11-17 14:34:36 +01:00
emu8k_close_dram ( struct snd_emu8000 * emu )
2005-04-16 15:20:36 -07:00
{
int i ;
for ( i = 0 ; i < 2 ; i + + )
snd_emux_unlock_voice ( emu - > emu , i ) ;
for ( ; i < EMU8000_DRAM_VOICES ; i + + ) {
snd_emu8000_dma_chan ( emu , i , EMU8000_RAM_CLOSE ) ;
snd_emux_unlock_voice ( emu - > emu , i ) ;
}
}
/*
* convert Hz to AWE32 rate offset ( see emux / soundfont . c )
*/
# define OFFSET_SAMPLERATE 1011119 /* base = 44100 */
# define SAMPLERATE_RATIO 4096
static int calc_rate_offset ( int hz )
{
return snd_sf_linear_to_log ( hz , OFFSET_SAMPLERATE , SAMPLERATE_RATIO ) ;
}
/*
*/
2005-11-17 14:34:36 +01:00
static struct snd_pcm_hardware emu8k_pcm_hw = {
2005-04-16 15:20:36 -07:00
# ifdef USE_NONINTERLEAVE
. info = SNDRV_PCM_INFO_NONINTERLEAVED ,
# else
. info = SNDRV_PCM_INFO_INTERLEAVED ,
# endif
. formats = SNDRV_PCM_FMTBIT_S16_LE ,
. rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000 ,
. rate_min = 4000 ,
. rate_max = 48000 ,
. channels_min = 1 ,
. channels_max = 2 ,
. buffer_bytes_max = ( 128 * 1024 ) ,
. period_bytes_min = 1024 ,
. period_bytes_max = ( 128 * 1024 ) ,
. periods_min = 2 ,
. periods_max = 1024 ,
. fifo_size = 0 ,
} ;
/*
* get the current position at the given channel from CCCA register
*/
2005-11-17 14:34:36 +01:00
static inline int emu8k_get_curpos ( struct snd_emu8k_pcm * rec , int ch )
2005-04-16 15:20:36 -07:00
{
int val = EMU8000_CCCA_READ ( rec - > emu , ch ) & 0xfffffff ;
val - = rec - > loop_start [ ch ] - 1 ;
return val ;
}
/*
* timer interrupt handler
* check the current position and update the period if necessary .
*/
static void emu8k_pcm_timer_func ( unsigned long data )
{
2005-11-17 14:34:36 +01:00
struct snd_emu8k_pcm * rec = ( struct snd_emu8k_pcm * ) data ;
2005-04-16 15:20:36 -07:00
int ptr , delta ;
spin_lock ( & rec - > timer_lock ) ;
/* update the current pointer */
ptr = emu8k_get_curpos ( rec , 0 ) ;
if ( ptr < rec - > last_ptr )
delta = ptr + rec - > buf_size - rec - > last_ptr ;
else
delta = ptr - rec - > last_ptr ;
rec - > period_pos + = delta ;
rec - > last_ptr = ptr ;
/* reprogram timer */
rec - > timer . expires = jiffies + 1 ;
add_timer ( & rec - > timer ) ;
/* update period */
if ( rec - > period_pos > = ( int ) rec - > period_size ) {
rec - > period_pos % = rec - > period_size ;
spin_unlock ( & rec - > timer_lock ) ;
snd_pcm_period_elapsed ( rec - > substream ) ;
return ;
}
spin_unlock ( & rec - > timer_lock ) ;
}
/*
* open pcm
* creating an instance here
*/
2005-11-17 14:34:36 +01:00
static int emu8k_pcm_open ( struct snd_pcm_substream * subs )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:34:36 +01:00
struct snd_emu8000 * emu = snd_pcm_substream_chip ( subs ) ;
struct snd_emu8k_pcm * rec ;
struct snd_pcm_runtime * runtime = subs - > runtime ;
2005-04-16 15:20:36 -07:00
2005-09-09 14:21:17 +02:00
rec = kzalloc ( sizeof ( * rec ) , GFP_KERNEL ) ;
2005-04-16 15:20:36 -07:00
if ( ! rec )
return - ENOMEM ;
rec - > emu = emu ;
rec - > substream = subs ;
runtime - > private_data = rec ;
spin_lock_init ( & rec - > timer_lock ) ;
init_timer ( & rec - > timer ) ;
rec - > timer . function = emu8k_pcm_timer_func ;
rec - > timer . data = ( unsigned long ) rec ;
runtime - > hw = emu8k_pcm_hw ;
runtime - > hw . buffer_bytes_max = emu - > mem_size - LOOP_BLANK_SIZE * 3 ;
runtime - > hw . period_bytes_max = runtime - > hw . buffer_bytes_max / 2 ;
/* use timer to update periods.. (specified in msec) */
snd_pcm_hw_constraint_minmax ( runtime , SNDRV_PCM_HW_PARAM_PERIOD_TIME ,
( 1000000 + HZ - 1 ) / HZ , UINT_MAX ) ;
return 0 ;
}
2005-11-17 14:34:36 +01:00
static int emu8k_pcm_close ( struct snd_pcm_substream * subs )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:34:36 +01:00
struct snd_emu8k_pcm * rec = subs - > runtime - > private_data ;
2005-04-16 15:20:36 -07:00
kfree ( rec ) ;
subs - > runtime - > private_data = NULL ;
return 0 ;
}
/*
* calculate pitch target
*/
static int calc_pitch_target ( int pitch )
{
int ptarget = 1 < < ( pitch > > 12 ) ;
if ( pitch & 0x800 ) ptarget + = ( ptarget * 0x102e ) / 0x2710 ;
if ( pitch & 0x400 ) ptarget + = ( ptarget * 0x764 ) / 0x2710 ;
if ( pitch & 0x200 ) ptarget + = ( ptarget * 0x389 ) / 0x2710 ;
ptarget + = ( ptarget > > 1 ) ;
if ( ptarget > 0xffff ) ptarget = 0xffff ;
return ptarget ;
}
/*
* set up the voice
*/
2005-11-17 14:34:36 +01:00
static void setup_voice ( struct snd_emu8k_pcm * rec , int ch )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:34:36 +01:00
struct snd_emu8000 * hw = rec - > emu ;
2005-04-16 15:20:36 -07:00
unsigned int temp ;
/* channel to be silent and idle */
EMU8000_DCYSUSV_WRITE ( hw , ch , 0x0080 ) ;
EMU8000_VTFT_WRITE ( hw , ch , 0x0000FFFF ) ;
EMU8000_CVCF_WRITE ( hw , ch , 0x0000FFFF ) ;
EMU8000_PTRX_WRITE ( hw , ch , 0 ) ;
EMU8000_CPF_WRITE ( hw , ch , 0 ) ;
/* pitch offset */
EMU8000_IP_WRITE ( hw , ch , rec - > pitch ) ;
/* set envelope parameters */
EMU8000_ENVVAL_WRITE ( hw , ch , 0x8000 ) ;
EMU8000_ATKHLD_WRITE ( hw , ch , 0x7f7f ) ;
EMU8000_DCYSUS_WRITE ( hw , ch , 0x7f7f ) ;
EMU8000_ENVVOL_WRITE ( hw , ch , 0x8000 ) ;
EMU8000_ATKHLDV_WRITE ( hw , ch , 0x7f7f ) ;
/* decay/sustain parameter for volume envelope is used
for triggerg the voice */
/* modulation envelope heights */
EMU8000_PEFE_WRITE ( hw , ch , 0x0 ) ;
/* lfo1/2 delay */
EMU8000_LFO1VAL_WRITE ( hw , ch , 0x8000 ) ;
EMU8000_LFO2VAL_WRITE ( hw , ch , 0x8000 ) ;
/* lfo1 pitch & cutoff shift */
EMU8000_FMMOD_WRITE ( hw , ch , 0 ) ;
/* lfo1 volume & freq */
EMU8000_TREMFRQ_WRITE ( hw , ch , 0 ) ;
/* lfo2 pitch & freq */
EMU8000_FM2FRQ2_WRITE ( hw , ch , 0 ) ;
/* pan & loop start */
temp = rec - > panning [ ch ] ;
temp = ( temp < < 24 ) | ( ( unsigned int ) rec - > loop_start [ ch ] - 1 ) ;
EMU8000_PSST_WRITE ( hw , ch , temp ) ;
/* chorus & loop end (chorus 8bit, MSB) */
temp = 0 ; // chorus
temp = ( temp < < 24 ) | ( ( unsigned int ) rec - > loop_start [ ch ] + rec - > buf_size - 1 ) ;
EMU8000_CSL_WRITE ( hw , ch , temp ) ;
/* Q & current address (Q 4bit value, MSB) */
temp = 0 ; // filterQ
temp = ( temp < < 28 ) | ( ( unsigned int ) rec - > loop_start [ ch ] - 1 ) ;
EMU8000_CCCA_WRITE ( hw , ch , temp ) ;
/* clear unknown registers */
EMU8000_00A0_WRITE ( hw , ch , 0 ) ;
EMU8000_0080_WRITE ( hw , ch , 0 ) ;
}
/*
* trigger the voice
*/
2005-11-17 14:34:36 +01:00
static void start_voice ( struct snd_emu8k_pcm * rec , int ch )
2005-04-16 15:20:36 -07:00
{
unsigned long flags ;
2005-11-17 14:34:36 +01:00
struct snd_emu8000 * hw = rec - > emu ;
2005-04-16 15:20:36 -07:00
unsigned int temp , aux ;
int pt = calc_pitch_target ( rec - > pitch ) ;
/* cutoff and volume */
EMU8000_IFATN_WRITE ( hw , ch , 0xff00 ) ;
EMU8000_VTFT_WRITE ( hw , ch , 0xffff ) ;
EMU8000_CVCF_WRITE ( hw , ch , 0xffff ) ;
/* trigger envelope */
EMU8000_DCYSUSV_WRITE ( hw , ch , 0x7f7f ) ;
/* set reverb and pitch target */
temp = 0 ; // reverb
if ( rec - > panning [ ch ] = = 0 )
aux = 0xff ;
else
aux = ( - rec - > panning [ ch ] ) & 0xff ;
temp = ( temp < < 8 ) | ( pt < < 16 ) | aux ;
EMU8000_PTRX_WRITE ( hw , ch , temp ) ;
EMU8000_CPF_WRITE ( hw , ch , pt < < 16 ) ;
/* start timer */
spin_lock_irqsave ( & rec - > timer_lock , flags ) ;
if ( ! rec - > timer_running ) {
rec - > timer . expires = jiffies + 1 ;
add_timer ( & rec - > timer ) ;
rec - > timer_running = 1 ;
}
spin_unlock_irqrestore ( & rec - > timer_lock , flags ) ;
}
/*
* stop the voice immediately
*/
2005-11-17 14:34:36 +01:00
static void stop_voice ( struct snd_emu8k_pcm * rec , int ch )
2005-04-16 15:20:36 -07:00
{
unsigned long flags ;
2005-11-17 14:34:36 +01:00
struct snd_emu8000 * hw = rec - > emu ;
2005-04-16 15:20:36 -07:00
EMU8000_DCYSUSV_WRITE ( hw , ch , 0x807F ) ;
/* stop timer */
spin_lock_irqsave ( & rec - > timer_lock , flags ) ;
if ( rec - > timer_running ) {
del_timer ( & rec - > timer ) ;
rec - > timer_running = 0 ;
}
spin_unlock_irqrestore ( & rec - > timer_lock , flags ) ;
}
2005-11-17 14:34:36 +01:00
static int emu8k_pcm_trigger ( struct snd_pcm_substream * subs , int cmd )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:34:36 +01:00
struct snd_emu8k_pcm * rec = subs - > runtime - > private_data ;
2005-04-16 15:20:36 -07:00
int ch ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
for ( ch = 0 ; ch < rec - > voices ; ch + + )
start_voice ( rec , ch ) ;
rec - > running = 1 ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
rec - > running = 0 ;
for ( ch = 0 ; ch < rec - > voices ; ch + + )
stop_voice ( rec , ch ) ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
/*
* copy / silence ops
*/
/*
* this macro should be inserted in the copy / silence loops
* to reduce the latency . without this , the system will hang up
* during the whole loop .
*/
# define CHECK_SCHEDULER() \
do { \
cond_resched ( ) ; \
if ( signal_pending ( current ) ) \
return - EAGAIN ; \
} while ( 0 )
# ifdef USE_NONINTERLEAVE
/* copy one channel block */
2005-11-17 14:34:36 +01:00
static int emu8k_transfer_block ( struct snd_emu8000 * emu , int offset , unsigned short * buf , int count )
2005-04-16 15:20:36 -07:00
{
EMU8000_SMALW_WRITE ( emu , offset ) ;
while ( count > 0 ) {
unsigned short sval ;
CHECK_SCHEDULER ( ) ;
get_user ( sval , buf ) ;
EMU8000_SMLD_WRITE ( emu , sval ) ;
buf + + ;
count - - ;
}
return 0 ;
}
2005-11-17 14:34:36 +01:00
static int emu8k_pcm_copy ( struct snd_pcm_substream * subs ,
2005-04-16 15:20:36 -07:00
int voice ,
snd_pcm_uframes_t pos ,
void * src ,
snd_pcm_uframes_t count )
{
2005-11-17 14:34:36 +01:00
struct snd_emu8k_pcm * rec = subs - > runtime - > private_data ;
struct snd_emu8000 * emu = rec - > emu ;
2005-04-16 15:20:36 -07:00
snd_emu8000_write_wait ( emu , 1 ) ;
if ( voice = = - 1 ) {
unsigned short * buf = src ;
int i , err ;
count / = rec - > voices ;
for ( i = 0 ; i < rec - > voices ; i + + ) {
err = emu8k_transfer_block ( emu , pos + rec - > loop_start [ i ] , buf , count ) ;
if ( err < 0 )
return err ;
buf + = count ;
}
return 0 ;
} else {
return emu8k_transfer_block ( emu , pos + rec - > loop_start [ voice ] , src , count ) ;
}
}
/* make a channel block silence */
2005-11-17 14:34:36 +01:00
static int emu8k_silence_block ( struct snd_emu8000 * emu , int offset , int count )
2005-04-16 15:20:36 -07:00
{
EMU8000_SMALW_WRITE ( emu , offset ) ;
while ( count > 0 ) {
CHECK_SCHEDULER ( ) ;
EMU8000_SMLD_WRITE ( emu , 0 ) ;
count - - ;
}
return 0 ;
}
2005-11-17 14:34:36 +01:00
static int emu8k_pcm_silence ( struct snd_pcm_substream * subs ,
2005-04-16 15:20:36 -07:00
int voice ,
snd_pcm_uframes_t pos ,
snd_pcm_uframes_t count )
{
2005-11-17 14:34:36 +01:00
struct snd_emu8k_pcm * rec = subs - > runtime - > private_data ;
struct snd_emu8000 * emu = rec - > emu ;
2005-04-16 15:20:36 -07:00
snd_emu8000_write_wait ( emu , 1 ) ;
if ( voice = = - 1 & & rec - > voices = = 1 )
voice = 0 ;
if ( voice = = - 1 ) {
int err ;
err = emu8k_silence_block ( emu , pos + rec - > loop_start [ 0 ] , count / 2 ) ;
if ( err < 0 )
return err ;
return emu8k_silence_block ( emu , pos + rec - > loop_start [ 1 ] , count / 2 ) ;
} else {
return emu8k_silence_block ( emu , pos + rec - > loop_start [ voice ] , count ) ;
}
}
# else /* interleave */
/*
* copy the interleaved data can be done easily by using
* DMA " left " and " right " channels on emu8k engine .
*/
2005-11-17 14:34:36 +01:00
static int emu8k_pcm_copy ( struct snd_pcm_substream * subs ,
2005-04-16 15:20:36 -07:00
int voice ,
snd_pcm_uframes_t pos ,
void __user * src ,
snd_pcm_uframes_t count )
{
2005-11-17 14:34:36 +01:00
struct snd_emu8k_pcm * rec = subs - > runtime - > private_data ;
struct snd_emu8000 * emu = rec - > emu ;
2005-04-16 15:20:36 -07:00
unsigned short __user * buf = src ;
snd_emu8000_write_wait ( emu , 1 ) ;
EMU8000_SMALW_WRITE ( emu , pos + rec - > loop_start [ 0 ] ) ;
if ( rec - > voices > 1 )
EMU8000_SMARW_WRITE ( emu , pos + rec - > loop_start [ 1 ] ) ;
while ( count - - > 0 ) {
unsigned short sval ;
CHECK_SCHEDULER ( ) ;
get_user ( sval , buf ) ;
EMU8000_SMLD_WRITE ( emu , sval ) ;
buf + + ;
if ( rec - > voices > 1 ) {
CHECK_SCHEDULER ( ) ;
get_user ( sval , buf ) ;
EMU8000_SMRD_WRITE ( emu , sval ) ;
buf + + ;
}
}
return 0 ;
}
2005-11-17 14:34:36 +01:00
static int emu8k_pcm_silence ( struct snd_pcm_substream * subs ,
2005-04-16 15:20:36 -07:00
int voice ,
snd_pcm_uframes_t pos ,
snd_pcm_uframes_t count )
{
2005-11-17 14:34:36 +01:00
struct snd_emu8k_pcm * rec = subs - > runtime - > private_data ;
struct snd_emu8000 * emu = rec - > emu ;
2005-04-16 15:20:36 -07:00
snd_emu8000_write_wait ( emu , 1 ) ;
EMU8000_SMALW_WRITE ( emu , rec - > loop_start [ 0 ] + pos ) ;
if ( rec - > voices > 1 )
EMU8000_SMARW_WRITE ( emu , rec - > loop_start [ 1 ] + pos ) ;
while ( count - - > 0 ) {
CHECK_SCHEDULER ( ) ;
EMU8000_SMLD_WRITE ( emu , 0 ) ;
if ( rec - > voices > 1 ) {
CHECK_SCHEDULER ( ) ;
EMU8000_SMRD_WRITE ( emu , 0 ) ;
}
}
return 0 ;
}
# endif
/*
* allocate a memory block
*/
2005-11-17 14:34:36 +01:00
static int emu8k_pcm_hw_params ( struct snd_pcm_substream * subs ,
struct snd_pcm_hw_params * hw_params )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:34:36 +01:00
struct snd_emu8k_pcm * rec = subs - > runtime - > private_data ;
2005-04-16 15:20:36 -07:00
if ( rec - > block ) {
/* reallocation - release the old block */
snd_util_mem_free ( rec - > emu - > memhdr , rec - > block ) ;
rec - > block = NULL ;
}
rec - > allocated_bytes = params_buffer_bytes ( hw_params ) + LOOP_BLANK_SIZE * 4 ;
rec - > block = snd_util_mem_alloc ( rec - > emu - > memhdr , rec - > allocated_bytes ) ;
if ( ! rec - > block )
return - ENOMEM ;
rec - > offset = EMU8000_DRAM_OFFSET + ( rec - > block - > offset > > 1 ) ; /* in word */
/* at least dma_bytes must be set for non-interleaved mode */
subs - > dma_buffer . bytes = params_buffer_bytes ( hw_params ) ;
return 0 ;
}
/*
* free the memory block
*/
2005-11-17 14:34:36 +01:00
static int emu8k_pcm_hw_free ( struct snd_pcm_substream * subs )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:34:36 +01:00
struct snd_emu8k_pcm * rec = subs - > runtime - > private_data ;
2005-04-16 15:20:36 -07:00
if ( rec - > block ) {
int ch ;
for ( ch = 0 ; ch < rec - > voices ; ch + + )
stop_voice ( rec , ch ) ; // to be sure
if ( rec - > dram_opened )
emu8k_close_dram ( rec - > emu ) ;
snd_util_mem_free ( rec - > emu - > memhdr , rec - > block ) ;
rec - > block = NULL ;
}
return 0 ;
}
/*
*/
2005-11-17 14:34:36 +01:00
static int emu8k_pcm_prepare ( struct snd_pcm_substream * subs )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:34:36 +01:00
struct snd_emu8k_pcm * rec = subs - > runtime - > private_data ;
2005-04-16 15:20:36 -07:00
rec - > pitch = 0xe000 + calc_rate_offset ( subs - > runtime - > rate ) ;
rec - > last_ptr = 0 ;
rec - > period_pos = 0 ;
rec - > buf_size = subs - > runtime - > buffer_size ;
rec - > period_size = subs - > runtime - > period_size ;
rec - > voices = subs - > runtime - > channels ;
rec - > loop_start [ 0 ] = rec - > offset + LOOP_BLANK_SIZE ;
if ( rec - > voices > 1 )
rec - > loop_start [ 1 ] = rec - > loop_start [ 0 ] + rec - > buf_size + LOOP_BLANK_SIZE ;
if ( rec - > voices > 1 ) {
rec - > panning [ 0 ] = 0xff ;
rec - > panning [ 1 ] = 0x00 ;
} else
rec - > panning [ 0 ] = 0x80 ;
if ( ! rec - > dram_opened ) {
int err , i , ch ;
snd_emux_terminate_all ( rec - > emu - > emu ) ;
if ( ( err = emu8k_open_dram_for_pcm ( rec - > emu , rec - > voices ) ) ! = 0 )
return err ;
rec - > dram_opened = 1 ;
/* clear loop blanks */
snd_emu8000_write_wait ( rec - > emu , 0 ) ;
EMU8000_SMALW_WRITE ( rec - > emu , rec - > offset ) ;
for ( i = 0 ; i < LOOP_BLANK_SIZE ; i + + )
EMU8000_SMLD_WRITE ( rec - > emu , 0 ) ;
for ( ch = 0 ; ch < rec - > voices ; ch + + ) {
EMU8000_SMALW_WRITE ( rec - > emu , rec - > loop_start [ ch ] + rec - > buf_size ) ;
for ( i = 0 ; i < LOOP_BLANK_SIZE ; i + + )
EMU8000_SMLD_WRITE ( rec - > emu , 0 ) ;
}
}
setup_voice ( rec , 0 ) ;
if ( rec - > voices > 1 )
setup_voice ( rec , 1 ) ;
return 0 ;
}
2005-11-17 14:34:36 +01:00
static snd_pcm_uframes_t emu8k_pcm_pointer ( struct snd_pcm_substream * subs )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:34:36 +01:00
struct snd_emu8k_pcm * rec = subs - > runtime - > private_data ;
2005-04-16 15:20:36 -07:00
if ( rec - > running )
return emu8k_get_curpos ( rec , 0 ) ;
return 0 ;
}
2005-11-17 14:34:36 +01:00
static struct snd_pcm_ops emu8k_pcm_ops = {
2005-04-16 15:20:36 -07:00
. open = emu8k_pcm_open ,
. close = emu8k_pcm_close ,
. ioctl = snd_pcm_lib_ioctl ,
. hw_params = emu8k_pcm_hw_params ,
. hw_free = emu8k_pcm_hw_free ,
. prepare = emu8k_pcm_prepare ,
. trigger = emu8k_pcm_trigger ,
. pointer = emu8k_pcm_pointer ,
. copy = emu8k_pcm_copy ,
. silence = emu8k_pcm_silence ,
} ;
2005-11-17 14:34:36 +01:00
static void snd_emu8000_pcm_free ( struct snd_pcm * pcm )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:34:36 +01:00
struct snd_emu8000 * emu = pcm - > private_data ;
2005-04-16 15:20:36 -07:00
emu - > pcm = NULL ;
}
2005-11-17 14:34:36 +01:00
int snd_emu8000_pcm_new ( struct snd_card * card , struct snd_emu8000 * emu , int index )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:34:36 +01:00
struct snd_pcm * pcm ;
2005-04-16 15:20:36 -07:00
int err ;
if ( ( err = snd_pcm_new ( card , " Emu8000 PCM " , index , 1 , 0 , & pcm ) ) < 0 )
return err ;
pcm - > private_data = emu ;
pcm - > private_free = snd_emu8000_pcm_free ;
snd_pcm_set_ops ( pcm , SNDRV_PCM_STREAM_PLAYBACK , & emu8k_pcm_ops ) ;
emu - > pcm = pcm ;
snd_device_register ( card , pcm ) ;
return 0 ;
}