2008-03-03 10:53:54 +01:00
/*
* PC - Speaker driver for Linux
*
* Copyright ( C ) 1993 - 1997 Michael Beck
* Copyright ( C ) 1997 - 2001 David Woodhouse
* Copyright ( C ) 2001 - 2008 Stas Sergeev
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
2008-08-11 10:18:39 +02:00
# include <linux/interrupt.h>
2008-03-03 10:53:54 +01:00
# include <sound/pcm.h>
# include <asm/io.h>
# include "pcsp.h"
static int nforce_wa ;
module_param ( nforce_wa , bool , 0444 ) ;
MODULE_PARM_DESC ( nforce_wa , " Apply NForce chipset workaround "
" (expect bad sound) " ) ;
2008-05-17 08:44:41 +02:00
# define DMIX_WANTS_S16 1
2008-08-11 10:18:39 +02:00
/*
* Call snd_pcm_period_elapsed in a tasklet
* This avoids spinlock messes and long - running irq contexts
*/
static void pcsp_call_pcm_elapsed ( unsigned long priv )
{
if ( atomic_read ( & pcsp_chip . timer_active ) ) {
struct snd_pcm_substream * substream ;
substream = pcsp_chip . playback_substream ;
if ( substream )
snd_pcm_period_elapsed ( substream ) ;
}
}
static DECLARE_TASKLET ( pcsp_pcm_tasklet , pcsp_call_pcm_elapsed , 0 ) ;
2008-11-26 14:13:03 +01:00
/* write the port and returns the next expire time in ns;
* called at the trigger - start and in hrtimer callback
*/
static unsigned long pcsp_timer_update ( struct hrtimer * handle )
2008-03-03 10:53:54 +01:00
{
unsigned char timer_cnt , val ;
u64 ns ;
struct snd_pcm_substream * substream ;
struct snd_pcm_runtime * runtime ;
struct snd_pcsp * chip = container_of ( handle , struct snd_pcsp , timer ) ;
2008-08-11 10:18:39 +02:00
unsigned long flags ;
2008-03-03 10:53:54 +01:00
if ( chip - > thalf ) {
outb ( chip - > val61 , 0x61 ) ;
chip - > thalf = 0 ;
if ( ! atomic_read ( & chip - > timer_active ) )
2008-11-26 14:13:03 +01:00
return 0 ;
return chip - > ns_rem ;
2008-03-03 10:53:54 +01:00
}
if ( ! atomic_read ( & chip - > timer_active ) )
2008-11-26 14:13:03 +01:00
return 0 ;
2008-08-11 10:18:39 +02:00
substream = chip - > playback_substream ;
if ( ! substream )
2008-11-26 14:13:03 +01:00
return 0 ;
2008-03-03 10:53:54 +01:00
runtime = substream - > runtime ;
2008-05-17 08:44:41 +02:00
/* assume it is mono! */
2008-11-26 14:13:03 +01:00
val = runtime - > dma_area [ chip - > playback_ptr + chip - > fmt_size - 1 ] ;
if ( chip - > is_signed )
2008-05-17 08:44:41 +02:00
val ^ = 0x80 ;
2008-03-03 10:53:54 +01:00
timer_cnt = val * CUR_DIV ( ) / 256 ;
if ( timer_cnt & & chip - > enable ) {
2008-11-26 14:13:03 +01:00
spin_lock_irqsave ( & i8253_lock , flags ) ;
2008-03-03 10:53:54 +01:00
if ( ! nforce_wa ) {
outb_p ( chip - > val61 , 0x61 ) ;
outb_p ( timer_cnt , 0x42 ) ;
outb ( chip - > val61 ^ 1 , 0x61 ) ;
} else {
outb ( chip - > val61 ^ 2 , 0x61 ) ;
chip - > thalf = 1 ;
}
2008-11-26 14:13:03 +01:00
spin_unlock_irqrestore ( & i8253_lock , flags ) ;
2008-03-03 10:53:54 +01:00
}
2008-11-26 14:13:03 +01:00
chip - > ns_rem = PCSP_PERIOD_NS ( ) ;
ns = ( chip - > thalf ? PCSP_CALC_NS ( timer_cnt ) : chip - > ns_rem ) ;
chip - > ns_rem - = ns ;
return ns ;
}
enum hrtimer_restart pcsp_do_timer ( struct hrtimer * handle )
{
struct snd_pcsp * chip = container_of ( handle , struct snd_pcsp , timer ) ;
struct snd_pcm_substream * substream ;
int periods_elapsed , pointer_update ;
size_t period_bytes , buffer_bytes ;
unsigned long ns ;
unsigned long flags ;
pointer_update = ! chip - > thalf ;
ns = pcsp_timer_update ( handle ) ;
if ( ! ns )
return HRTIMER_NORESTART ;
/* update the playback position */
substream = chip - > playback_substream ;
if ( ! substream )
return HRTIMER_NORESTART ;
2008-03-03 10:53:54 +01:00
period_bytes = snd_pcm_lib_period_bytes ( substream ) ;
buffer_bytes = snd_pcm_lib_buffer_bytes ( substream ) ;
2008-08-11 10:18:39 +02:00
spin_lock_irqsave ( & chip - > substream_lock , flags ) ;
2008-11-26 14:13:03 +01:00
chip - > playback_ptr + = PCSP_INDEX_INC ( ) * chip - > fmt_size ;
2008-03-03 10:53:54 +01:00
periods_elapsed = chip - > playback_ptr - chip - > period_ptr ;
if ( periods_elapsed < 0 ) {
2008-05-18 18:30:03 +02:00
# if PCSP_DEBUG
printk ( KERN_INFO " PCSP: buffer_bytes mod period_bytes != 0 ? "
2008-03-03 10:53:54 +01:00
" (%zi %zi %zi) \n " ,
chip - > playback_ptr , period_bytes , buffer_bytes ) ;
2008-05-18 18:30:03 +02:00
# endif
2008-03-03 10:53:54 +01:00
periods_elapsed + = buffer_bytes ;
}
periods_elapsed / = period_bytes ;
/* wrap the pointer _before_ calling snd_pcm_period_elapsed(),
* or ALSA will BUG on us . */
chip - > playback_ptr % = buffer_bytes ;
if ( periods_elapsed ) {
chip - > period_ptr + = periods_elapsed * period_bytes ;
chip - > period_ptr % = buffer_bytes ;
}
2008-08-11 10:18:39 +02:00
spin_unlock_irqrestore ( & chip - > substream_lock , flags ) ;
2008-03-03 10:53:54 +01:00
2008-11-26 14:13:03 +01:00
if ( periods_elapsed )
tasklet_schedule ( & pcsp_pcm_tasklet ) ;
2008-03-03 10:53:54 +01:00
2008-11-26 14:13:03 +01:00
hrtimer_forward ( handle , hrtimer_get_expires ( handle ) , ns_to_ktime ( ns ) ) ;
2008-03-03 10:53:54 +01:00
2008-11-26 14:13:03 +01:00
return HRTIMER_RESTART ;
2008-03-03 10:53:54 +01:00
}
2008-11-26 14:13:03 +01:00
static int pcsp_start_playing ( struct snd_pcsp * chip )
2008-03-03 10:53:54 +01:00
{
2008-11-26 14:13:03 +01:00
unsigned long ns ;
2008-03-03 10:53:54 +01:00
# if PCSP_DEBUG
printk ( KERN_INFO " PCSP: start_playing called \n " ) ;
# endif
if ( atomic_read ( & chip - > timer_active ) ) {
printk ( KERN_ERR " PCSP: Timer already active \n " ) ;
2008-11-26 14:13:03 +01:00
return - EIO ;
2008-03-03 10:53:54 +01:00
}
spin_lock ( & i8253_lock ) ;
chip - > val61 = inb ( 0x61 ) | 0x03 ;
outb_p ( 0x92 , 0x43 ) ; /* binary, mode 1, LSB only, ch 2 */
spin_unlock ( & i8253_lock ) ;
atomic_set ( & chip - > timer_active , 1 ) ;
chip - > thalf = 0 ;
2008-11-26 14:13:03 +01:00
ns = pcsp_timer_update ( & pcsp_chip . timer ) ;
if ( ! ns )
return - EIO ;
hrtimer_start ( & pcsp_chip . timer , ktime_set ( 0 , ns ) , HRTIMER_MODE_REL ) ;
return 0 ;
2008-03-03 10:53:54 +01:00
}
static void pcsp_stop_playing ( struct snd_pcsp * chip )
{
# if PCSP_DEBUG
printk ( KERN_INFO " PCSP: stop_playing called \n " ) ;
# endif
if ( ! atomic_read ( & chip - > timer_active ) )
return ;
atomic_set ( & chip - > timer_active , 0 ) ;
spin_lock ( & i8253_lock ) ;
/* restore the timer */
outb_p ( 0xb6 , 0x43 ) ; /* binary, mode 3, LSB/MSB, ch 2 */
outb ( chip - > val61 & 0xFC , 0x61 ) ;
spin_unlock ( & i8253_lock ) ;
}
2008-08-11 10:18:39 +02:00
/*
* Force to stop and sync the stream
*/
void pcsp_sync_stop ( struct snd_pcsp * chip )
{
local_irq_disable ( ) ;
pcsp_stop_playing ( chip ) ;
local_irq_enable ( ) ;
hrtimer_cancel ( & chip - > timer ) ;
tasklet_kill ( & pcsp_pcm_tasklet ) ;
}
2008-03-03 10:53:54 +01:00
static int snd_pcsp_playback_close ( struct snd_pcm_substream * substream )
{
struct snd_pcsp * chip = snd_pcm_substream_chip ( substream ) ;
# if PCSP_DEBUG
printk ( KERN_INFO " PCSP: close called \n " ) ;
# endif
2008-08-11 10:18:39 +02:00
pcsp_sync_stop ( chip ) ;
2008-03-03 10:53:54 +01:00
chip - > playback_substream = NULL ;
return 0 ;
}
static int snd_pcsp_playback_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * hw_params )
{
2008-08-11 10:18:39 +02:00
struct snd_pcsp * chip = snd_pcm_substream_chip ( substream ) ;
2008-03-03 10:53:54 +01:00
int err ;
2008-08-11 10:18:39 +02:00
pcsp_sync_stop ( chip ) ;
2008-03-03 10:53:54 +01:00
err = snd_pcm_lib_malloc_pages ( substream ,
params_buffer_bytes ( hw_params ) ) ;
if ( err < 0 )
return err ;
return 0 ;
}
static int snd_pcsp_playback_hw_free ( struct snd_pcm_substream * substream )
{
2008-08-11 10:18:39 +02:00
struct snd_pcsp * chip = snd_pcm_substream_chip ( substream ) ;
2008-03-03 10:53:54 +01:00
# if PCSP_DEBUG
printk ( KERN_INFO " PCSP: hw_free called \n " ) ;
# endif
2008-08-11 10:18:39 +02:00
pcsp_sync_stop ( chip ) ;
2008-03-03 10:53:54 +01:00
return snd_pcm_lib_free_pages ( substream ) ;
}
static int snd_pcsp_playback_prepare ( struct snd_pcm_substream * substream )
{
struct snd_pcsp * chip = snd_pcm_substream_chip ( substream ) ;
# if PCSP_DEBUG
printk ( KERN_INFO " PCSP: prepare called, "
" size=%zi psize=%zi f=%zi f1=%i \n " ,
snd_pcm_lib_buffer_bytes ( substream ) ,
snd_pcm_lib_period_bytes ( substream ) ,
snd_pcm_lib_buffer_bytes ( substream ) /
snd_pcm_lib_period_bytes ( substream ) ,
substream - > runtime - > periods ) ;
# endif
2008-08-11 10:18:39 +02:00
pcsp_sync_stop ( chip ) ;
2008-03-03 10:53:54 +01:00
chip - > playback_ptr = 0 ;
chip - > period_ptr = 0 ;
2008-11-26 14:13:03 +01:00
chip - > fmt_size =
snd_pcm_format_physical_width ( substream - > runtime - > format ) > > 3 ;
chip - > is_signed = snd_pcm_format_signed ( substream - > runtime - > format ) ;
2008-03-03 10:53:54 +01:00
return 0 ;
}
static int snd_pcsp_trigger ( struct snd_pcm_substream * substream , int cmd )
{
struct snd_pcsp * chip = snd_pcm_substream_chip ( substream ) ;
# if PCSP_DEBUG
printk ( KERN_INFO " PCSP: trigger called \n " ) ;
# endif
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_RESUME :
2008-11-26 14:13:03 +01:00
return pcsp_start_playing ( chip ) ;
2008-03-03 10:53:54 +01:00
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_SUSPEND :
pcsp_stop_playing ( chip ) ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
static snd_pcm_uframes_t snd_pcsp_playback_pointer ( struct snd_pcm_substream
* substream )
{
struct snd_pcsp * chip = snd_pcm_substream_chip ( substream ) ;
2008-08-11 10:18:39 +02:00
unsigned int pos ;
spin_lock ( & chip - > substream_lock ) ;
pos = chip - > playback_ptr ;
spin_unlock ( & chip - > substream_lock ) ;
return bytes_to_frames ( substream - > runtime , pos ) ;
2008-03-03 10:53:54 +01:00
}
static struct snd_pcm_hardware snd_pcsp_playback = {
. info = ( SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_HALF_DUPLEX |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID ) ,
2008-05-17 08:44:41 +02:00
. formats = ( SNDRV_PCM_FMTBIT_U8
# if DMIX_WANTS_S16
| SNDRV_PCM_FMTBIT_S16_LE
# endif
) ,
2008-03-03 10:53:54 +01:00
. rates = SNDRV_PCM_RATE_KNOT ,
. rate_min = PCSP_DEFAULT_SRATE ,
. rate_max = PCSP_DEFAULT_SRATE ,
. channels_min = 1 ,
. channels_max = 1 ,
. buffer_bytes_max = PCSP_BUFFER_SIZE ,
. period_bytes_min = 64 ,
. period_bytes_max = PCSP_MAX_PERIOD_SIZE ,
. periods_min = 2 ,
. periods_max = PCSP_MAX_PERIODS ,
. fifo_size = 0 ,
} ;
static int snd_pcsp_playback_open ( struct snd_pcm_substream * substream )
{
struct snd_pcsp * chip = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
# if PCSP_DEBUG
printk ( KERN_INFO " PCSP: open called \n " ) ;
# endif
if ( atomic_read ( & chip - > timer_active ) ) {
printk ( KERN_ERR " PCSP: still active!! \n " ) ;
return - EBUSY ;
}
runtime - > hw = snd_pcsp_playback ;
chip - > playback_substream = substream ;
return 0 ;
}
static struct snd_pcm_ops snd_pcsp_playback_ops = {
. open = snd_pcsp_playback_open ,
. close = snd_pcsp_playback_close ,
. ioctl = snd_pcm_lib_ioctl ,
. hw_params = snd_pcsp_playback_hw_params ,
. hw_free = snd_pcsp_playback_hw_free ,
. prepare = snd_pcsp_playback_prepare ,
. trigger = snd_pcsp_trigger ,
. pointer = snd_pcsp_playback_pointer ,
} ;
int __devinit snd_pcsp_new_pcm ( struct snd_pcsp * chip )
{
int err ;
err = snd_pcm_new ( chip - > card , " pcspeaker " , 0 , 1 , 0 , & chip - > pcm ) ;
if ( err < 0 )
return err ;
snd_pcm_set_ops ( chip - > pcm , SNDRV_PCM_STREAM_PLAYBACK ,
& snd_pcsp_playback_ops ) ;
chip - > pcm - > private_data = chip ;
chip - > pcm - > info_flags = SNDRV_PCM_INFO_HALF_DUPLEX ;
strcpy ( chip - > pcm - > name , " pcsp " ) ;
snd_pcm_lib_preallocate_pages_for_all ( chip - > pcm ,
SNDRV_DMA_TYPE_CONTINUOUS ,
snd_dma_continuous_data
( GFP_KERNEL ) , PCSP_BUFFER_SIZE ,
PCSP_BUFFER_SIZE ) ;
return 0 ;
}