2008-01-10 18:52:42 +03:00
/*
* Virtual master and slave controls
*
* Copyright ( c ) 2008 by 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 , version 2.
*
*/
# include <linux/slab.h>
2011-09-22 17:34:58 +04:00
# include <linux/export.h>
2008-01-10 18:52:42 +03:00
# include <sound/core.h>
# include <sound/control.h>
2008-02-18 15:05:50 +03:00
# include <sound/tlv.h>
2008-01-10 18:52:42 +03:00
/*
* a subset of information returned via ctl info callback
*/
struct link_ctl_info {
2011-02-14 13:00:47 +03:00
snd_ctl_elem_type_t type ; /* value type */
2008-01-10 18:52:42 +03:00
int count ; /* item count */
int min_val , max_val ; /* min, max values */
} ;
/*
* link master - this contains a list of slave controls that are
* identical types , i . e . info returns the same value type and value
* ranges , but may have different number of counts .
*
* The master control is so far only mono volume / switch for simplicity .
* The same value will be applied to all slaves .
*/
struct link_master {
struct list_head slaves ;
struct link_ctl_info info ;
int val ; /* the master value */
2008-02-18 15:05:50 +03:00
unsigned int tlv [ 4 ] ;
2012-03-12 15:18:37 +04:00
void ( * hook ) ( void * private_data , int ) ;
void * hook_private_data ;
2008-01-10 18:52:42 +03:00
} ;
/*
* link slave - this contains a slave control element
*
* It fakes the control callbacsk with additional attenuation by the
* master control . A slave may have either one or two channels .
*/
struct link_slave {
struct list_head list ;
struct link_master * master ;
struct link_ctl_info info ;
int vals [ 2 ] ; /* current values */
2009-01-16 20:15:22 +03:00
unsigned int flags ;
2011-11-10 15:34:24 +04:00
struct snd_kcontrol * kctl ; /* original kcontrol pointer */
2008-01-10 18:52:42 +03:00
struct snd_kcontrol slave ; /* the copy of original control entry */
} ;
2009-01-16 20:15:22 +03:00
static int slave_update ( struct link_slave * slave )
{
struct snd_ctl_elem_value * uctl ;
int err , ch ;
uctl = kmalloc ( sizeof ( * uctl ) , GFP_KERNEL ) ;
if ( ! uctl )
return - ENOMEM ;
uctl - > id = slave - > slave . id ;
err = slave - > slave . get ( & slave - > slave , uctl ) ;
for ( ch = 0 ; ch < slave - > info . count ; ch + + )
slave - > vals [ ch ] = uctl - > value . integer . value [ ch ] ;
kfree ( uctl ) ;
return 0 ;
}
2008-01-10 18:52:42 +03:00
/* get the slave ctl info and save the initial values */
static int slave_init ( struct link_slave * slave )
{
struct snd_ctl_elem_info * uinfo ;
2009-01-16 20:15:22 +03:00
int err ;
2008-01-10 18:52:42 +03:00
2009-01-16 20:15:22 +03:00
if ( slave - > info . count ) {
/* already initialized */
if ( slave - > flags & SND_CTL_SLAVE_NEED_UPDATE )
return slave_update ( slave ) ;
return 0 ;
}
2008-01-10 18:52:42 +03:00
uinfo = kmalloc ( sizeof ( * uinfo ) , GFP_KERNEL ) ;
if ( ! uinfo )
return - ENOMEM ;
uinfo - > id = slave - > slave . id ;
err = slave - > slave . info ( & slave - > slave , uinfo ) ;
if ( err < 0 ) {
kfree ( uinfo ) ;
return err ;
}
slave - > info . type = uinfo - > type ;
slave - > info . count = uinfo - > count ;
if ( slave - > info . count > 2 | |
( slave - > info . type ! = SNDRV_CTL_ELEM_TYPE_INTEGER & &
slave - > info . type ! = SNDRV_CTL_ELEM_TYPE_BOOLEAN ) ) {
2014-02-04 21:21:03 +04:00
pr_err ( " ALSA: vmaster: invalid slave element \n " ) ;
2008-01-10 18:52:42 +03:00
kfree ( uinfo ) ;
return - EINVAL ;
}
slave - > info . min_val = uinfo - > value . integer . min ;
slave - > info . max_val = uinfo - > value . integer . max ;
kfree ( uinfo ) ;
2009-01-16 20:15:22 +03:00
return slave_update ( slave ) ;
2008-01-10 18:52:42 +03:00
}
/* initialize master volume */
static int master_init ( struct link_master * master )
{
struct link_slave * slave ;
if ( master - > info . count )
return 0 ; /* already initialized */
list_for_each_entry ( slave , & master - > slaves , list ) {
int err = slave_init ( slave ) ;
if ( err < 0 )
return err ;
master - > info = slave - > info ;
master - > info . count = 1 ; /* always mono */
/* set full volume as default (= no attenuation) */
master - > val = master - > info . max_val ;
2012-03-12 15:18:37 +04:00
if ( master - > hook )
master - > hook ( master - > hook_private_data , master - > val ) ;
return 1 ;
2008-01-10 18:52:42 +03:00
}
return - ENOENT ;
}
static int slave_get_val ( struct link_slave * slave ,
struct snd_ctl_elem_value * ucontrol )
{
int err , ch ;
err = slave_init ( slave ) ;
if ( err < 0 )
return err ;
for ( ch = 0 ; ch < slave - > info . count ; ch + + )
ucontrol - > value . integer . value [ ch ] = slave - > vals [ ch ] ;
return 0 ;
}
static int slave_put_val ( struct link_slave * slave ,
struct snd_ctl_elem_value * ucontrol )
{
int err , ch , vol ;
err = master_init ( slave - > master ) ;
if ( err < 0 )
return err ;
switch ( slave - > info . type ) {
case SNDRV_CTL_ELEM_TYPE_BOOLEAN :
for ( ch = 0 ; ch < slave - > info . count ; ch + + )
ucontrol - > value . integer . value [ ch ] & =
! ! slave - > master - > val ;
break ;
case SNDRV_CTL_ELEM_TYPE_INTEGER :
for ( ch = 0 ; ch < slave - > info . count ; ch + + ) {
/* max master volume is supposed to be 0 dB */
vol = ucontrol - > value . integer . value [ ch ] ;
vol + = slave - > master - > val - slave - > master - > info . max_val ;
if ( vol < slave - > info . min_val )
vol = slave - > info . min_val ;
else if ( vol > slave - > info . max_val )
vol = slave - > info . max_val ;
ucontrol - > value . integer . value [ ch ] = vol ;
}
break ;
}
return slave - > slave . put ( & slave - > slave , ucontrol ) ;
}
/*
* ctl callbacks for slaves
*/
static int slave_info ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_info * uinfo )
{
struct link_slave * slave = snd_kcontrol_chip ( kcontrol ) ;
return slave - > slave . info ( & slave - > slave , uinfo ) ;
}
static int slave_get ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct link_slave * slave = snd_kcontrol_chip ( kcontrol ) ;
return slave_get_val ( slave , ucontrol ) ;
}
static int slave_put ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct link_slave * slave = snd_kcontrol_chip ( kcontrol ) ;
int err , ch , changed = 0 ;
err = slave_init ( slave ) ;
if ( err < 0 )
return err ;
for ( ch = 0 ; ch < slave - > info . count ; ch + + ) {
if ( slave - > vals [ ch ] ! = ucontrol - > value . integer . value [ ch ] ) {
changed = 1 ;
slave - > vals [ ch ] = ucontrol - > value . integer . value [ ch ] ;
}
}
if ( ! changed )
return 0 ;
2013-03-05 18:43:39 +04:00
err = slave_put_val ( slave , ucontrol ) ;
if ( err < 0 )
return err ;
return 1 ;
2008-01-10 18:52:42 +03:00
}
static int slave_tlv_cmd ( struct snd_kcontrol * kcontrol ,
int op_flag , unsigned int size ,
unsigned int __user * tlv )
{
struct link_slave * slave = snd_kcontrol_chip ( kcontrol ) ;
/* FIXME: this assumes that the max volume is 0 dB */
return slave - > slave . tlv . c ( & slave - > slave , op_flag , size , tlv ) ;
}
static void slave_free ( struct snd_kcontrol * kcontrol )
{
struct link_slave * slave = snd_kcontrol_chip ( kcontrol ) ;
if ( slave - > slave . private_free )
slave - > slave . private_free ( & slave - > slave ) ;
if ( slave - > master )
list_del ( & slave - > list ) ;
kfree ( slave ) ;
}
/*
* Add a slave control to the group with the given master control
*
* All slaves must be the same type ( returning the same information
2011-03-31 05:57:33 +04:00
* via info callback ) . The function doesn ' t check it , so it ' s your
2008-01-10 18:52:42 +03:00
* responsibility .
*
* Also , some additional limitations :
* - at most two channels
* - logarithmic volume control ( dB level ) , no linear volume
* - master can only attenuate the volume , no gain
*/
2009-01-16 20:15:22 +03:00
int _snd_ctl_add_slave ( struct snd_kcontrol * master , struct snd_kcontrol * slave ,
unsigned int flags )
2008-01-10 18:52:42 +03:00
{
struct link_master * master_link = snd_kcontrol_chip ( master ) ;
struct link_slave * srec ;
srec = kzalloc ( sizeof ( * srec ) +
slave - > count * sizeof ( * slave - > vd ) , GFP_KERNEL ) ;
if ( ! srec )
return - ENOMEM ;
2011-11-10 15:34:24 +04:00
srec - > kctl = slave ;
2008-01-10 18:52:42 +03:00
srec - > slave = * slave ;
memcpy ( srec - > slave . vd , slave - > vd , slave - > count * sizeof ( * slave - > vd ) ) ;
srec - > master = master_link ;
2009-01-16 20:15:22 +03:00
srec - > flags = flags ;
2008-01-10 18:52:42 +03:00
/* override callbacks */
slave - > info = slave_info ;
slave - > get = slave_get ;
slave - > put = slave_put ;
if ( slave - > vd [ 0 ] . access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK )
slave - > tlv . c = slave_tlv_cmd ;
slave - > private_data = srec ;
slave - > private_free = slave_free ;
list_add_tail ( & srec - > list , & master_link - > slaves ) ;
return 0 ;
}
2009-01-16 20:15:22 +03:00
EXPORT_SYMBOL ( _snd_ctl_add_slave ) ;
2008-02-18 15:03:13 +03:00
2008-01-10 18:52:42 +03:00
/*
* ctl callbacks for master controls
*/
static int master_info ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_info * uinfo )
{
struct link_master * master = snd_kcontrol_chip ( kcontrol ) ;
int ret ;
ret = master_init ( master ) ;
if ( ret < 0 )
return ret ;
uinfo - > type = master - > info . type ;
uinfo - > count = master - > info . count ;
uinfo - > value . integer . min = master - > info . min_val ;
uinfo - > value . integer . max = master - > info . max_val ;
return 0 ;
}
static int master_get ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct link_master * master = snd_kcontrol_chip ( kcontrol ) ;
int err = master_init ( master ) ;
if ( err < 0 )
return err ;
ucontrol - > value . integer . value [ 0 ] = master - > val ;
return 0 ;
}
2013-06-24 17:51:54 +04:00
static int sync_slaves ( struct link_master * master , int old_val , int new_val )
2008-01-10 18:52:42 +03:00
{
struct link_slave * slave ;
struct snd_ctl_elem_value * uval ;
uval = kmalloc ( sizeof ( * uval ) , GFP_KERNEL ) ;
if ( ! uval )
return - ENOMEM ;
list_for_each_entry ( slave , & master - > slaves , list ) {
master - > val = old_val ;
uval - > id = slave - > slave . id ;
slave_get_val ( slave , uval ) ;
2013-06-24 17:51:54 +04:00
master - > val = new_val ;
2008-01-10 18:52:42 +03:00
slave_put_val ( slave , uval ) ;
}
kfree ( uval ) ;
2013-06-24 17:51:54 +04:00
return 0 ;
}
static int master_put ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct link_master * master = snd_kcontrol_chip ( kcontrol ) ;
int err , new_val , old_val ;
bool first_init ;
err = master_init ( master ) ;
if ( err < 0 )
return err ;
first_init = err ;
old_val = master - > val ;
new_val = ucontrol - > value . integer . value [ 0 ] ;
if ( new_val = = old_val )
return 0 ;
err = sync_slaves ( master , old_val , new_val ) ;
if ( err < 0 )
return err ;
2013-07-03 16:01:32 +04:00
if ( master - > hook & & ! first_init )
2012-03-12 15:18:37 +04:00
master - > hook ( master - > hook_private_data , master - > val ) ;
2008-01-10 18:52:42 +03:00
return 1 ;
}
static void master_free ( struct snd_kcontrol * kcontrol )
{
struct link_master * master = snd_kcontrol_chip ( kcontrol ) ;
2011-11-10 15:34:24 +04:00
struct link_slave * slave , * n ;
/* free all slave links and retore the original slave kctls */
list_for_each_entry_safe ( slave , n , & master - > slaves , list ) {
struct snd_kcontrol * sctl = slave - > kctl ;
struct list_head olist = sctl - > list ;
memcpy ( sctl , & slave - > slave , sizeof ( * sctl ) ) ;
memcpy ( sctl - > vd , slave - > slave . vd ,
sctl - > count * sizeof ( * sctl - > vd ) ) ;
sctl - > list = olist ; /* keep the current linked-list */
kfree ( slave ) ;
}
2008-01-10 18:52:42 +03:00
kfree ( master ) ;
}
2009-02-09 16:47:19 +03:00
/**
* snd_ctl_make_virtual_master - Create a virtual master control
* @ name : name string of the control element to create
* @ tlv : optional TLV int array for dB information
*
2013-03-12 01:05:14 +04:00
* Creates a virtual master control with the given name string .
2009-02-09 16:47:19 +03:00
*
* After creating a vmaster element , you can add the slave controls
* via snd_ctl_add_slave ( ) or snd_ctl_add_slave_uncached ( ) .
*
* The optional argument @ tlv can be used to specify the TLV information
* for dB scale of the master control . It should be a single element
2009-06-16 15:57:07 +04:00
* with # SNDRV_CTL_TLVT_DB_SCALE , # SNDRV_CTL_TLV_DB_MINMAX or
* # SNDRV_CTL_TLVT_DB_MINMAX_MUTE type , and should be the max 0 dB .
2013-03-12 01:05:14 +04:00
*
* Return : The created control element , or % NULL for errors ( ENOMEM ) .
2008-01-10 18:52:42 +03:00
*/
struct snd_kcontrol * snd_ctl_make_virtual_master ( char * name ,
const unsigned int * tlv )
{
struct link_master * master ;
struct snd_kcontrol * kctl ;
struct snd_kcontrol_new knew ;
memset ( & knew , 0 , sizeof ( knew ) ) ;
knew . iface = SNDRV_CTL_ELEM_IFACE_MIXER ;
knew . name = name ;
knew . info = master_info ;
master = kzalloc ( sizeof ( * master ) , GFP_KERNEL ) ;
if ( ! master )
return NULL ;
INIT_LIST_HEAD ( & master - > slaves ) ;
kctl = snd_ctl_new1 ( & knew , master ) ;
if ( ! kctl ) {
kfree ( master ) ;
return NULL ;
}
/* override some callbacks */
kctl - > info = master_info ;
kctl - > get = master_get ;
kctl - > put = master_put ;
kctl - > private_free = master_free ;
/* additional (constant) TLV read */
2009-06-16 15:57:07 +04:00
if ( tlv & &
( tlv [ 0 ] = = SNDRV_CTL_TLVT_DB_SCALE | |
tlv [ 0 ] = = SNDRV_CTL_TLVT_DB_MINMAX | |
tlv [ 0 ] = = SNDRV_CTL_TLVT_DB_MINMAX_MUTE ) ) {
2008-01-10 18:52:42 +03:00
kctl - > vd [ 0 ] . access | = SNDRV_CTL_ELEM_ACCESS_TLV_READ ;
2008-02-18 15:05:50 +03:00
memcpy ( master - > tlv , tlv , sizeof ( master - > tlv ) ) ;
kctl - > tlv . p = master - > tlv ;
2008-01-10 18:52:42 +03:00
}
2008-02-18 15:05:50 +03:00
2008-01-10 18:52:42 +03:00
return kctl ;
}
2008-02-18 15:03:13 +03:00
EXPORT_SYMBOL ( snd_ctl_make_virtual_master ) ;
2012-03-12 15:18:37 +04:00
/**
* snd_ctl_add_vmaster_hook - Add a hook to a vmaster control
* @ kcontrol : vmaster kctl element
* @ hook : the hook function
2012-04-18 04:03:42 +04:00
* @ private_data : the private_data pointer to be saved
2012-03-12 15:18:37 +04:00
*
* Adds the given hook to the vmaster control element so that it ' s called
* at each time when the value is changed .
2013-03-12 01:05:14 +04:00
*
* Return : Zero .
2012-03-12 15:18:37 +04:00
*/
int snd_ctl_add_vmaster_hook ( struct snd_kcontrol * kcontrol ,
void ( * hook ) ( void * private_data , int ) ,
void * private_data )
{
struct link_master * master = snd_kcontrol_chip ( kcontrol ) ;
master - > hook = hook ;
master - > hook_private_data = private_data ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_ctl_add_vmaster_hook ) ;
/**
2013-06-24 17:51:54 +04:00
* snd_ctl_sync_vmaster - Sync the vmaster slaves and hook
2012-03-12 15:18:37 +04:00
* @ kcontrol : vmaster kctl element
2013-06-24 17:51:54 +04:00
* @ hook_only : sync only the hook
2012-03-12 15:18:37 +04:00
*
2013-06-24 17:51:54 +04:00
* Forcibly call the put callback of each slave and call the hook function
* to synchronize with the current value of the given vmaster element .
* NOP when NULL is passed to @ kcontrol .
2012-03-12 15:18:37 +04:00
*/
2013-06-24 17:51:54 +04:00
void snd_ctl_sync_vmaster ( struct snd_kcontrol * kcontrol , bool hook_only )
2012-03-12 15:18:37 +04:00
{
struct link_master * master ;
2013-06-24 17:51:54 +04:00
bool first_init = false ;
2012-03-12 15:18:37 +04:00
if ( ! kcontrol )
return ;
master = snd_kcontrol_chip ( kcontrol ) ;
2013-06-24 17:51:54 +04:00
if ( ! hook_only ) {
int err = master_init ( master ) ;
if ( err < 0 )
return ;
first_init = err ;
err = sync_slaves ( master , master - > val , master - > val ) ;
if ( err < 0 )
return ;
}
if ( master - > hook & & ! first_init )
2012-03-12 15:18:37 +04:00
master - > hook ( master - > hook_private_data , master - > val ) ;
}
2013-06-24 17:51:54 +04:00
EXPORT_SYMBOL_GPL ( snd_ctl_sync_vmaster ) ;