2005-04-16 15:20:36 -07:00
/*
* linux / drivers / sound / vidc . c
*
* Copyright ( C ) 1997 - 2000 by Russell King < rmk @ arm . linux . org . uk >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* VIDC20 audio driver .
*
* The VIDC20 sound hardware consists of the VIDC20 itself , a DAC and a DMA
* engine . The DMA transfers fixed - format ( 16 - bit little - endian linear )
* samples to the VIDC20 , which then transfers this data serially to the
* DACs . The samplerate is controlled by the VIDC .
*
* We currently support a mixer device , but it is currently non - functional .
*/
# include <linux/init.h>
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/interrupt.h>
# include <asm/hardware.h>
# include <asm/dma.h>
# include <asm/io.h>
# include <asm/hardware/iomd.h>
# include <asm/irq.h>
# include <asm/system.h>
# include "sound_config.h"
# include "vidc.h"
# ifndef _SIOC_TYPE
# define _SIOC_TYPE(x) _IOC_TYPE(x)
# endif
# ifndef _SIOC_NR
# define _SIOC_NR(x) _IOC_NR(x)
# endif
# define VIDC_SOUND_CLOCK (250000)
# define VIDC_SOUND_CLOCK_EXT (176400)
/*
* When using SERIAL SOUND mode ( external DAC ) , the number of physical
* channels is fixed at 2.
*/
static int vidc_busy ;
static int vidc_adev ;
static int vidc_audio_rate ;
static char vidc_audio_format ;
static char vidc_audio_channels ;
static unsigned char vidc_level_l [ SOUND_MIXER_NRDEVICES ] = {
85 , /* master */
50 , /* bass */
50 , /* treble */
0 , /* synth */
75 , /* pcm */
0 , /* speaker */
100 , /* ext line */
0 , /* mic */
100 , /* CD */
0 ,
} ;
static unsigned char vidc_level_r [ SOUND_MIXER_NRDEVICES ] = {
85 , /* master */
50 , /* bass */
50 , /* treble */
0 , /* synth */
75 , /* pcm */
0 , /* speaker */
100 , /* ext line */
0 , /* mic */
100 , /* CD */
0 ,
} ;
static unsigned int vidc_audio_volume_l ; /* left PCM vol, 0 - 65536 */
static unsigned int vidc_audio_volume_r ; /* right PCM vol, 0 - 65536 */
extern void vidc_update_filler ( int bits , int channels ) ;
extern int softoss_dev ;
static void
vidc_mixer_set ( int mdev , unsigned int level )
{
unsigned int lev_l = level & 0x007f ;
unsigned int lev_r = ( level & 0x7f00 ) > > 8 ;
unsigned int mlev_l , mlev_r ;
if ( lev_l > 100 )
lev_l = 100 ;
if ( lev_r > 100 )
lev_r = 100 ;
# define SCALE(lev,master) ((lev) * (master) * 65536 / 10000)
mlev_l = vidc_level_l [ SOUND_MIXER_VOLUME ] ;
mlev_r = vidc_level_r [ SOUND_MIXER_VOLUME ] ;
switch ( mdev ) {
case SOUND_MIXER_VOLUME :
case SOUND_MIXER_PCM :
vidc_level_l [ mdev ] = lev_l ;
vidc_level_r [ mdev ] = lev_r ;
vidc_audio_volume_l = SCALE ( lev_l , mlev_l ) ;
vidc_audio_volume_r = SCALE ( lev_r , mlev_r ) ;
/*printk("VIDC: PCM vol %05X %05X\n", vidc_audio_volume_l, vidc_audio_volume_r);*/
break ;
}
# undef SCALE
}
static int vidc_mixer_ioctl ( int dev , unsigned int cmd , void __user * arg )
{
unsigned int val ;
unsigned int mdev ;
if ( _SIOC_TYPE ( cmd ) ! = ' M ' )
return - EINVAL ;
mdev = _SIOC_NR ( cmd ) ;
if ( _SIOC_DIR ( cmd ) & _SIOC_WRITE ) {
if ( get_user ( val , ( unsigned int __user * ) arg ) )
return - EFAULT ;
if ( mdev < SOUND_MIXER_NRDEVICES )
vidc_mixer_set ( mdev , val ) ;
else
return - EINVAL ;
}
/*
* Return parameters
*/
switch ( mdev ) {
case SOUND_MIXER_RECSRC :
val = 0 ;
break ;
case SOUND_MIXER_DEVMASK :
val = SOUND_MASK_VOLUME | SOUND_MASK_PCM | SOUND_MASK_SYNTH ;
break ;
case SOUND_MIXER_STEREODEVS :
val = SOUND_MASK_VOLUME | SOUND_MASK_PCM | SOUND_MASK_SYNTH ;
break ;
case SOUND_MIXER_RECMASK :
val = 0 ;
break ;
case SOUND_MIXER_CAPS :
val = 0 ;
break ;
default :
if ( mdev < SOUND_MIXER_NRDEVICES )
val = vidc_level_l [ mdev ] | vidc_level_r [ mdev ] < < 8 ;
else
return - EINVAL ;
}
return put_user ( val , ( unsigned int __user * ) arg ) ? - EFAULT : 0 ;
}
static unsigned int vidc_audio_set_format ( int dev , unsigned int fmt )
{
switch ( fmt ) {
default :
fmt = AFMT_S16_LE ;
case AFMT_U8 :
case AFMT_S8 :
case AFMT_S16_LE :
vidc_audio_format = fmt ;
vidc_update_filler ( vidc_audio_format , vidc_audio_channels ) ;
case AFMT_QUERY :
break ;
}
return vidc_audio_format ;
}
# define my_abs(i) ((i)<0 ? -(i) : (i))
static int vidc_audio_set_speed ( int dev , int rate )
{
if ( rate ) {
unsigned int hwctrl , hwrate , hwrate_ext , rate_int , rate_ext ;
unsigned int diff_int , diff_ext ;
unsigned int newsize , new2size ;
hwctrl = 0x00000003 ;
/* Using internal clock */
hwrate = ( ( ( VIDC_SOUND_CLOCK * 2 ) / rate ) + 1 ) > > 1 ;
if ( hwrate < 3 )
hwrate = 3 ;
if ( hwrate > 255 )
hwrate = 255 ;
/* Using exernal clock */
hwrate_ext = ( ( ( VIDC_SOUND_CLOCK_EXT * 2 ) / rate ) + 1 ) > > 1 ;
if ( hwrate_ext < 3 )
hwrate_ext = 3 ;
if ( hwrate_ext > 255 )
hwrate_ext = 255 ;
rate_int = VIDC_SOUND_CLOCK / hwrate ;
rate_ext = VIDC_SOUND_CLOCK_EXT / hwrate_ext ;
/* Chose between external and internal clock */
diff_int = my_abs ( rate_ext - rate ) ;
diff_ext = my_abs ( rate_int - rate ) ;
if ( diff_ext < diff_int ) {
/*printk("VIDC: external %d %d %d\n", rate, rate_ext, hwrate_ext);*/
hwrate = hwrate_ext ;
hwctrl = 0x00000002 ;
/* Allow roughly 0.4% tolerance */
if ( diff_ext > ( rate / 256 ) )
rate = rate_ext ;
} else {
/*printk("VIDC: internal %d %d %d\n", rate, rate_int, hwrate);*/
hwctrl = 0x00000003 ;
/* Allow rougly 0.4% tolerance */
if ( diff_int > ( rate / 256 ) )
rate = rate_int ;
}
vidc_writel ( 0xb0000000 | ( hwrate - 2 ) ) ;
vidc_writel ( 0xb1000000 | hwctrl ) ;
newsize = ( 10000 / hwrate ) & ~ 3 ;
if ( newsize < 208 )
newsize = 208 ;
if ( newsize > 4096 )
newsize = 4096 ;
for ( new2size = 128 ; new2size < newsize ; new2size < < = 1 ) ;
if ( new2size - newsize > newsize - ( new2size > > 1 ) )
new2size > > = 1 ;
if ( new2size > 4096 ) {
printk ( KERN_ERR " VIDC: error: dma buffer (%d) %d > 4K \n " ,
newsize , new2size ) ;
new2size = 4096 ;
}
/*printk("VIDC: dma size %d\n", new2size);*/
dma_bufsize = new2size ;
vidc_audio_rate = rate ;
}
return vidc_audio_rate ;
}
static short vidc_audio_set_channels ( int dev , short channels )
{
switch ( channels ) {
default :
channels = 2 ;
case 1 :
case 2 :
vidc_audio_channels = channels ;
vidc_update_filler ( vidc_audio_format , vidc_audio_channels ) ;
case 0 :
break ;
}
return vidc_audio_channels ;
}
/*
* Open the device
*/
static int vidc_audio_open ( int dev , int mode )
{
/* This audio device does not have recording capability */
if ( mode = = OPEN_READ )
return - EPERM ;
if ( vidc_busy )
return - EBUSY ;
vidc_busy = 1 ;
return 0 ;
}
/*
* Close the device
*/
static void vidc_audio_close ( int dev )
{
vidc_busy = 0 ;
}
/*
* Output a block via DMA to sound device .
*
* We just set the DMA start and count ; the DMA interrupt routine
* will take care of formatting the samples ( via the appropriate
* vidc_filler routine ) , and flag via vidc_audio_dma_interrupt when
* more data is required .
*/
static void
vidc_audio_output_block ( int dev , unsigned long buf , int total_count , int one )
{
struct dma_buffparms * dmap = audio_devs [ dev ] - > dmap_out ;
unsigned long flags ;
local_irq_save ( flags ) ;
dma_start = buf - ( unsigned long ) dmap - > raw_buf_phys + ( unsigned long ) dmap - > raw_buf ;
dma_count = total_count ;
local_irq_restore ( flags ) ;
}
static void
vidc_audio_start_input ( int dev , unsigned long buf , int count , int intrflag )
{
}
static int vidc_audio_prepare_for_input ( int dev , int bsize , int bcount )
{
return - EINVAL ;
}
static irqreturn_t vidc_audio_dma_interrupt ( void )
{
DMAbuf_outputintr ( vidc_adev , 1 ) ;
return IRQ_HANDLED ;
}
/*
* Prepare for outputting samples .
*
* Each buffer that will be passed will be ` bsize ' bytes long ,
* with a total of ` bcount ' buffers .
*/
static int vidc_audio_prepare_for_output ( int dev , int bsize , int bcount )
{
struct audio_operations * adev = audio_devs [ dev ] ;
dma_interrupt = NULL ;
adev - > dmap_out - > flags | = DMA_NODMA ;
return 0 ;
}
/*
* Stop our current operation .
*/
static void vidc_audio_reset ( int dev )
{
dma_interrupt = NULL ;
}
static int vidc_audio_local_qlen ( int dev )
{
return /*dma_count !=*/ 0 ;
}
static void vidc_audio_trigger ( int dev , int enable_bits )
{
struct audio_operations * adev = audio_devs [ dev ] ;
if ( enable_bits & PCM_ENABLE_OUTPUT ) {
if ( ! ( adev - > flags & DMA_ACTIVE ) ) {
unsigned long flags ;
local_irq_save ( flags ) ;
/* prevent recusion */
adev - > flags | = DMA_ACTIVE ;
dma_interrupt = vidc_audio_dma_interrupt ;
2006-10-08 15:00:12 +01:00
vidc_sound_dma_irq ( 0 , NULL ) ;
2005-04-16 15:20:36 -07:00
iomd_writeb ( DMA_CR_E | 0x10 , IOMD_SD0CR ) ;
local_irq_restore ( flags ) ;
}
}
}
static struct audio_driver vidc_audio_driver =
{
. owner = THIS_MODULE ,
. open = vidc_audio_open ,
. close = vidc_audio_close ,
. output_block = vidc_audio_output_block ,
. start_input = vidc_audio_start_input ,
. prepare_for_input = vidc_audio_prepare_for_input ,
. prepare_for_output = vidc_audio_prepare_for_output ,
. halt_io = vidc_audio_reset ,
. local_qlen = vidc_audio_local_qlen ,
. trigger = vidc_audio_trigger ,
. set_speed = vidc_audio_set_speed ,
. set_bits = vidc_audio_set_format ,
. set_channels = vidc_audio_set_channels
} ;
static struct mixer_operations vidc_mixer_operations = {
. owner = THIS_MODULE ,
. id = " VIDC " ,
. name = " VIDCsound " ,
. ioctl = vidc_mixer_ioctl
} ;
void vidc_update_filler ( int format , int channels )
{
# define TYPE(fmt,ch) (((fmt)<<2) | ((ch)&3))
switch ( TYPE ( format , channels ) ) {
default :
case TYPE ( AFMT_U8 , 1 ) :
vidc_filler = vidc_fill_1x8_u ;
break ;
case TYPE ( AFMT_U8 , 2 ) :
vidc_filler = vidc_fill_2x8_u ;
break ;
case TYPE ( AFMT_S8 , 1 ) :
vidc_filler = vidc_fill_1x8_s ;
break ;
case TYPE ( AFMT_S8 , 2 ) :
vidc_filler = vidc_fill_2x8_s ;
break ;
case TYPE ( AFMT_S16_LE , 1 ) :
vidc_filler = vidc_fill_1x16_s ;
break ;
case TYPE ( AFMT_S16_LE , 2 ) :
vidc_filler = vidc_fill_2x16_s ;
break ;
}
}
static void __init attach_vidc ( struct address_info * hw_config )
{
char name [ 32 ] ;
int i , adev ;
sprintf ( name , " VIDC %d-bit sound " , hw_config - > card_subtype ) ;
conf_printf ( name , hw_config ) ;
memset ( dma_buf , 0 , sizeof ( dma_buf ) ) ;
adev = sound_install_audiodrv ( AUDIO_DRIVER_VERSION , name ,
& vidc_audio_driver , sizeof ( vidc_audio_driver ) ,
DMA_AUTOMODE , AFMT_U8 | AFMT_S8 | AFMT_S16_LE ,
NULL , hw_config - > dma , hw_config - > dma2 ) ;
if ( adev < 0 )
goto audio_failed ;
/*
* 1024 bytes = > 64 buffers
*/
audio_devs [ adev ] - > min_fragment = 10 ;
audio_devs [ adev ] - > mixer_dev = num_mixers ;
audio_devs [ adev ] - > mixer_dev =
sound_install_mixer ( MIXER_DRIVER_VERSION ,
name , & vidc_mixer_operations ,
sizeof ( vidc_mixer_operations ) , NULL ) ;
if ( audio_devs [ adev ] - > mixer_dev < 0 )
goto mixer_failed ;
for ( i = 0 ; i < 2 ; i + + ) {
dma_buf [ i ] = get_zeroed_page ( GFP_KERNEL ) ;
if ( ! dma_buf [ i ] ) {
printk ( KERN_ERR " %s: can't allocate required buffers \n " ,
name ) ;
goto mem_failed ;
}
dma_pbuf [ i ] = virt_to_phys ( ( void * ) dma_buf [ i ] ) ;
}
if ( sound_alloc_dma ( hw_config - > dma , hw_config - > name ) ) {
printk ( KERN_ERR " %s: DMA %d is in use \n " , name , hw_config - > dma ) ;
goto dma_failed ;
}
if ( request_irq ( hw_config - > irq , vidc_sound_dma_irq , 0 ,
hw_config - > name , & dma_start ) ) {
printk ( KERN_ERR " %s: IRQ %d is in use \n " , name , hw_config - > irq ) ;
goto irq_failed ;
}
vidc_adev = adev ;
vidc_mixer_set ( SOUND_MIXER_VOLUME , ( 85 | 85 < < 8 ) ) ;
# if defined(CONFIG_SOUND_SOFTOSS) || defined(CONFIG_SOUND_SOFTOSS_MODULE)
softoss_dev = adev ;
# endif
return ;
irq_failed :
sound_free_dma ( hw_config - > dma ) ;
dma_failed :
mem_failed :
for ( i = 0 ; i < 2 ; i + + )
free_page ( dma_buf [ i ] ) ;
sound_unload_mixerdev ( audio_devs [ adev ] - > mixer_dev ) ;
mixer_failed :
sound_unload_audiodev ( adev ) ;
audio_failed :
return ;
}
static int __init probe_vidc ( struct address_info * hw_config )
{
hw_config - > irq = IRQ_DMAS0 ;
hw_config - > dma = DMA_VIRTUAL_SOUND ;
hw_config - > dma2 = - 1 ;
hw_config - > card_subtype = 16 ;
hw_config - > name = " VIDC20 " ;
return 1 ;
}
static void __exit unload_vidc ( struct address_info * hw_config )
{
int i , adev = vidc_adev ;
vidc_adev = - 1 ;
free_irq ( hw_config - > irq , & dma_start ) ;
sound_free_dma ( hw_config - > dma ) ;
if ( adev > = 0 ) {
sound_unload_mixerdev ( audio_devs [ adev ] - > mixer_dev ) ;
sound_unload_audiodev ( adev ) ;
for ( i = 0 ; i < 2 ; i + + )
free_page ( dma_buf [ i ] ) ;
}
}
static struct address_info cfg ;
static int __init init_vidc ( void )
{
if ( probe_vidc ( & cfg ) = = 0 )
return - ENODEV ;
attach_vidc ( & cfg ) ;
return 0 ;
}
static void __exit cleanup_vidc ( void )
{
unload_vidc ( & cfg ) ;
}
module_init ( init_vidc ) ;
module_exit ( cleanup_vidc ) ;
MODULE_AUTHOR ( " Russell King " ) ;
MODULE_DESCRIPTION ( " VIDC20 audio driver " ) ;
MODULE_LICENSE ( " GPL " ) ;