2005-06-24 09:05:07 +04:00
/*
*/
2005-04-17 02:20:36 +04:00
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/string.h>
# include <linux/timer.h>
# include <linux/delay.h>
# include <linux/errno.h>
# include <linux/slab.h>
# include <linux/i2c.h>
# include <linux/videodev.h>
# include <linux/init.h>
# include <linux/kdev_t.h>
# include <linux/sound.h>
# include <linux/soundcard.h>
# include <asm/semaphore.h>
# include <asm/uaccess.h>
# define DEV_MAX 4
static int devnr = - 1 ;
module_param ( devnr , int , 0644 ) ;
MODULE_AUTHOR ( " Gerd Knorr " ) ;
MODULE_LICENSE ( " GPL " ) ;
/* ----------------------------------------------------------------------- */
struct TVMIXER {
struct i2c_client * dev ;
int minor ;
int count ;
} ;
static struct TVMIXER devices [ DEV_MAX ] ;
static int tvmixer_adapters ( struct i2c_adapter * adap ) ;
static int tvmixer_clients ( struct i2c_client * client ) ;
/* ----------------------------------------------------------------------- */
static int mix_to_v4l ( int i )
{
int r ;
r = ( ( i & 0xff ) * 65536 + 50 ) / 100 ;
if ( r > 65535 ) r = 65535 ;
if ( r < 0 ) r = 0 ;
return r ;
}
static int v4l_to_mix ( int i )
{
int r ;
r = ( i * 100 + 32768 ) / 65536 ;
if ( r > 100 ) r = 100 ;
if ( r < 0 ) r = 0 ;
return r | ( r < < 8 ) ;
}
static int v4l_to_mix2 ( int l , int r )
{
r = ( r * 100 + 32768 ) / 65536 ;
if ( r > 100 ) r = 100 ;
if ( r < 0 ) r = 0 ;
l = ( l * 100 + 32768 ) / 65536 ;
if ( l > 100 ) l = 100 ;
if ( l < 0 ) l = 0 ;
return ( r < < 8 ) | l ;
}
static int tvmixer_ioctl ( struct inode * inode , struct file * file , unsigned int cmd , unsigned long arg )
{
struct video_audio va ;
int left , right , ret , val = 0 ;
2005-11-09 08:37:43 +03:00
struct TVMIXER * mix = file - > private_data ;
2005-04-17 02:20:36 +04:00
struct i2c_client * client = mix - > dev ;
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
if ( NULL = = client )
return - ENODEV ;
2005-11-09 08:37:43 +03:00
if ( cmd = = SOUND_MIXER_INFO ) {
mixer_info info ;
strlcpy ( info . id , " tv card " , sizeof ( info . id ) ) ;
strlcpy ( info . name , client - > name , sizeof ( info . name ) ) ;
info . modify_counter = 42 /* FIXME */ ;
if ( copy_to_user ( argp , & info , sizeof ( info ) ) )
return - EFAULT ;
return 0 ;
}
if ( cmd = = SOUND_OLD_MIXER_INFO ) {
_old_mixer_info info ;
strlcpy ( info . id , " tv card " , sizeof ( info . id ) ) ;
strlcpy ( info . name , client - > name , sizeof ( info . name ) ) ;
if ( copy_to_user ( argp , & info , sizeof ( info ) ) )
return - EFAULT ;
return 0 ;
}
if ( cmd = = OSS_GETVERSION )
return put_user ( SOUND_VERSION , p ) ;
2005-04-17 02:20:36 +04:00
if ( _SIOC_DIR ( cmd ) & _SIOC_WRITE )
if ( get_user ( val , p ) )
return - EFAULT ;
/* read state */
memset ( & va , 0 , sizeof ( va ) ) ;
client - > driver - > command ( client , VIDIOCGAUDIO , & va ) ;
switch ( cmd ) {
case MIXER_READ ( SOUND_MIXER_RECMASK ) :
case MIXER_READ ( SOUND_MIXER_CAPS ) :
case MIXER_READ ( SOUND_MIXER_RECSRC ) :
case MIXER_WRITE ( SOUND_MIXER_RECSRC ) :
ret = 0 ;
break ;
case MIXER_READ ( SOUND_MIXER_STEREODEVS ) :
ret = SOUND_MASK_VOLUME ;
break ;
case MIXER_READ ( SOUND_MIXER_DEVMASK ) :
ret = SOUND_MASK_VOLUME ;
if ( va . flags & VIDEO_AUDIO_BASS )
ret | = SOUND_MASK_BASS ;
if ( va . flags & VIDEO_AUDIO_TREBLE )
ret | = SOUND_MASK_TREBLE ;
break ;
case MIXER_WRITE ( SOUND_MIXER_VOLUME ) :
left = mix_to_v4l ( val ) ;
right = mix_to_v4l ( val > > 8 ) ;
va . volume = max ( left , right ) ;
va . balance = ( 32768 * min ( left , right ) ) / ( va . volume ? va . volume : 1 ) ;
va . balance = ( left < right ) ? ( 65535 - va . balance ) : va . balance ;
if ( va . volume )
va . flags & = ~ VIDEO_AUDIO_MUTE ;
client - > driver - > command ( client , VIDIOCSAUDIO , & va ) ;
client - > driver - > command ( client , VIDIOCGAUDIO , & va ) ;
/* fall throuth */
case MIXER_READ ( SOUND_MIXER_VOLUME ) :
left = ( min ( 65536 - va . balance , 32768 ) *
va . volume ) / 32768 ;
right = ( min ( va . balance , ( u16 ) 32768 ) *
va . volume ) / 32768 ;
ret = v4l_to_mix2 ( left , right ) ;
break ;
case MIXER_WRITE ( SOUND_MIXER_BASS ) :
va . bass = mix_to_v4l ( val ) ;
client - > driver - > command ( client , VIDIOCSAUDIO , & va ) ;
client - > driver - > command ( client , VIDIOCGAUDIO , & va ) ;
/* fall throuth */
case MIXER_READ ( SOUND_MIXER_BASS ) :
ret = v4l_to_mix ( va . bass ) ;
break ;
case MIXER_WRITE ( SOUND_MIXER_TREBLE ) :
va . treble = mix_to_v4l ( val ) ;
client - > driver - > command ( client , VIDIOCSAUDIO , & va ) ;
client - > driver - > command ( client , VIDIOCGAUDIO , & va ) ;
/* fall throuth */
case MIXER_READ ( SOUND_MIXER_TREBLE ) :
ret = v4l_to_mix ( va . treble ) ;
break ;
default :
return - EINVAL ;
}
if ( put_user ( ret , p ) )
return - EFAULT ;
return 0 ;
}
static int tvmixer_open ( struct inode * inode , struct file * file )
{
2005-11-09 08:37:43 +03:00
int i , minor = iminor ( inode ) ;
struct TVMIXER * mix = NULL ;
2005-04-17 02:20:36 +04:00
struct i2c_client * client = NULL ;
for ( i = 0 ; i < DEV_MAX ; i + + ) {
if ( devices [ i ] . minor = = minor ) {
mix = devices + i ;
client = mix - > dev ;
break ;
}
}
if ( NULL = = client )
return - ENODEV ;
/* lock bttv in memory while the mixer is in use */
file - > private_data = mix ;
# ifndef I2C_PEC
if ( client - > adapter - > inc_use )
client - > adapter - > inc_use ( client - > adapter ) ;
# endif
if ( client - > adapter - > owner )
try_module_get ( client - > adapter - > owner ) ;
2005-11-09 08:37:43 +03:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
static int tvmixer_release ( struct inode * inode , struct file * file )
{
struct TVMIXER * mix = file - > private_data ;
struct i2c_client * client ;
client = mix - > dev ;
if ( NULL = = client ) {
return - ENODEV ;
}
# ifndef I2C_PEC
if ( client - > adapter - > dec_use )
client - > adapter - > dec_use ( client - > adapter ) ;
# endif
if ( client - > adapter - > owner )
module_put ( client - > adapter - > owner ) ;
return 0 ;
}
static struct i2c_driver driver = {
2005-11-26 22:43:39 +03:00
. driver = {
2006-01-09 20:53:26 +03:00
. name = " tvmixer " ,
2005-11-26 22:43:39 +03:00
} ,
2005-11-09 08:37:43 +03:00
. id = I2C_DRIVERID_TVMIXER ,
. detach_adapter = tvmixer_adapters ,
. attach_adapter = tvmixer_adapters ,
. detach_client = tvmixer_clients ,
2005-04-17 02:20:36 +04:00
} ;
static struct file_operations tvmixer_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. ioctl = tvmixer_ioctl ,
. open = tvmixer_open ,
. release = tvmixer_release ,
} ;
/* ----------------------------------------------------------------------- */
static int tvmixer_adapters ( struct i2c_adapter * adap )
{
struct list_head * item ;
struct i2c_client * client ;
list_for_each ( item , & adap - > clients ) {
client = list_entry ( item , struct i2c_client , list ) ;
tvmixer_clients ( client ) ;
}
return 0 ;
}
static int tvmixer_clients ( struct i2c_client * client )
{
struct video_audio va ;
int i , minor ;
if ( ! ( client - > adapter - > class & I2C_CLASS_TV_ANALOG ) )
return - 1 ;
/* unregister ?? */
for ( i = 0 ; i < DEV_MAX ; i + + ) {
if ( devices [ i ] . dev = = client ) {
/* unregister */
unregister_sound_mixer ( devices [ i ] . minor ) ;
devices [ i ] . dev = NULL ;
devices [ i ] . minor = - 1 ;
printk ( " tvmixer: %s unregistered (#1) \n " ,
2005-08-15 21:57:04 +04:00
client - > name ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
}
/* look for a free slot */
for ( i = 0 ; i < DEV_MAX ; i + + )
if ( NULL = = devices [ i ] . dev )
break ;
if ( i = = DEV_MAX ) {
printk ( KERN_WARNING " tvmixer: DEV_MAX too small \n " ) ;
return - 1 ;
}
/* audio chip with mixer ??? */
if ( NULL = = client - > driver - > command )
return - 1 ;
memset ( & va , 0 , sizeof ( va ) ) ;
if ( 0 ! = client - > driver - > command ( client , VIDIOCGAUDIO , & va ) )
return - 1 ;
if ( 0 = = ( va . flags & VIDEO_AUDIO_VOLUME ) )
return - 1 ;
/* everything is fine, register */
if ( ( minor = register_sound_mixer ( & tvmixer_fops , devnr ) ) < 0 ) {
printk ( KERN_ERR " tvmixer: cannot allocate mixer device \n " ) ;
return - 1 ;
}
devices [ i ] . minor = minor ;
devices [ i ] . count = 0 ;
devices [ i ] . dev = client ;
printk ( " tvmixer: %s (%s) registered with minor %d \n " ,
client - > name , client - > adapter - > name , minor ) ;
return 0 ;
}
/* ----------------------------------------------------------------------- */
static int __init tvmixer_init_module ( void )
{
int i ;
for ( i = 0 ; i < DEV_MAX ; i + + )
devices [ i ] . minor = - 1 ;
return i2c_add_driver ( & driver ) ;
}
static void __exit tvmixer_cleanup_module ( void )
{
int i ;
i2c_del_driver ( & driver ) ;
for ( i = 0 ; i < DEV_MAX ; i + + ) {
if ( devices [ i ] . minor ! = - 1 ) {
unregister_sound_mixer ( devices [ i ] . minor ) ;
printk ( " tvmixer: %s unregistered (#2) \n " ,
2005-08-15 21:57:04 +04:00
devices [ i ] . dev - > name ) ;
2005-04-17 02:20:36 +04:00
}
}
}
module_init ( tvmixer_init_module ) ;
module_exit ( tvmixer_cleanup_module ) ;
/*
* Overrides for Emacs so that we follow Linus ' s tabbing style .
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Local variables :
* c - basic - offset : 8
* End :
*/