2021-03-17 20:29:42 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* LED state routines for driver control interface
* Copyright ( c ) 2021 by Jaroslav Kysela < perex @ perex . cz >
*/
# include <linux/slab.h>
# include <linux/module.h>
# include <linux/leds.h>
# include <sound/core.h>
# include <sound/control.h>
MODULE_AUTHOR ( " Jaroslav Kysela <perex@perex.cz> " ) ;
MODULE_DESCRIPTION ( " ALSA control interface to LED trigger code. " ) ;
MODULE_LICENSE ( " GPL " ) ;
# define MAX_LED (((SNDRV_CTL_ELEM_ACCESS_MIC_LED - SNDRV_CTL_ELEM_ACCESS_SPK_LED) \
> > SNDRV_CTL_ELEM_ACCESS_LED_SHIFT ) + 1 )
2021-06-02 06:41:36 +03:00
# define to_led_card_dev(_dev) \
container_of ( _dev , struct snd_ctl_led_card , dev )
2021-03-17 20:29:44 +03:00
enum snd_ctl_led_mode {
MODE_FOLLOW_MUTE = 0 ,
MODE_FOLLOW_ROUTE ,
MODE_OFF ,
MODE_ON ,
} ;
2021-03-17 20:29:45 +03:00
struct snd_ctl_led_card {
struct device dev ;
int number ;
struct snd_ctl_led * led ;
} ;
2021-03-17 20:29:42 +03:00
struct snd_ctl_led {
2021-03-17 20:29:44 +03:00
struct device dev ;
struct list_head controls ;
const char * name ;
unsigned int group ;
enum led_audio trigger_type ;
enum snd_ctl_led_mode mode ;
2021-03-17 20:29:45 +03:00
struct snd_ctl_led_card * cards [ SNDRV_CARDS ] ;
2021-03-17 20:29:44 +03:00
} ;
struct snd_ctl_led_ctl {
2021-03-17 20:29:42 +03:00
struct list_head list ;
struct snd_card * card ;
unsigned int access ;
struct snd_kcontrol * kctl ;
unsigned int index_offset ;
} ;
static DEFINE_MUTEX ( snd_ctl_led_mutex ) ;
static bool snd_ctl_led_card_valid [ SNDRV_CARDS ] ;
2021-03-17 20:29:44 +03:00
static struct snd_ctl_led snd_ctl_leds [ MAX_LED ] = {
{
. name = " speaker " ,
. group = ( SNDRV_CTL_ELEM_ACCESS_SPK_LED > > SNDRV_CTL_ELEM_ACCESS_LED_SHIFT ) - 1 ,
. trigger_type = LED_AUDIO_MUTE ,
. mode = MODE_FOLLOW_MUTE ,
} ,
{
. name = " mic " ,
. group = ( SNDRV_CTL_ELEM_ACCESS_MIC_LED > > SNDRV_CTL_ELEM_ACCESS_LED_SHIFT ) - 1 ,
. trigger_type = LED_AUDIO_MICMUTE ,
. mode = MODE_FOLLOW_MUTE ,
} ,
} ;
2021-03-17 20:29:42 +03:00
2021-03-17 20:29:45 +03:00
static void snd_ctl_led_sysfs_add ( struct snd_card * card ) ;
static void snd_ctl_led_sysfs_remove ( struct snd_card * card ) ;
2021-03-17 20:29:42 +03:00
# define UPDATE_ROUTE(route, cb) \
do { \
int route2 = ( cb ) ; \
if ( route2 > = 0 ) \
route = route < 0 ? route2 : ( route | route2 ) ; \
} while ( 0 )
static inline unsigned int access_to_group ( unsigned int access )
{
return ( ( access & SNDRV_CTL_ELEM_ACCESS_LED_MASK ) > >
SNDRV_CTL_ELEM_ACCESS_LED_SHIFT ) - 1 ;
}
static inline unsigned int group_to_access ( unsigned int group )
{
return ( group + 1 ) < < SNDRV_CTL_ELEM_ACCESS_LED_SHIFT ;
}
2021-03-17 20:29:44 +03:00
static struct snd_ctl_led * snd_ctl_led_get_by_access ( unsigned int access )
2021-03-17 20:29:42 +03:00
{
unsigned int group = access_to_group ( access ) ;
if ( group > = MAX_LED )
return NULL ;
2021-03-17 20:29:44 +03:00
return & snd_ctl_leds [ group ] ;
2021-03-17 20:29:42 +03:00
}
2021-04-14 13:58:58 +03:00
/*
* A note for callers :
* The two static variables info and value are protected using snd_ctl_led_mutex .
*/
2021-03-17 20:29:44 +03:00
static int snd_ctl_led_get ( struct snd_ctl_led_ctl * lctl )
2021-03-17 20:29:42 +03:00
{
2021-04-14 13:58:58 +03:00
static struct snd_ctl_elem_info info ;
static struct snd_ctl_elem_value value ;
2021-03-17 20:29:42 +03:00
struct snd_kcontrol * kctl = lctl - > kctl ;
unsigned int i ;
int result ;
memset ( & info , 0 , sizeof ( info ) ) ;
info . id = kctl - > id ;
info . id . index + = lctl - > index_offset ;
info . id . numid + = lctl - > index_offset ;
result = kctl - > info ( kctl , & info ) ;
if ( result < 0 )
return - 1 ;
memset ( & value , 0 , sizeof ( value ) ) ;
value . id = info . id ;
result = kctl - > get ( kctl , & value ) ;
if ( result < 0 )
return - 1 ;
if ( info . type = = SNDRV_CTL_ELEM_TYPE_BOOLEAN | |
info . type = = SNDRV_CTL_ELEM_TYPE_INTEGER ) {
for ( i = 0 ; i < info . count ; i + + )
if ( value . value . integer . value [ i ] ! = info . value . integer . min )
return 1 ;
} else if ( info . type = = SNDRV_CTL_ELEM_TYPE_INTEGER64 ) {
for ( i = 0 ; i < info . count ; i + + )
if ( value . value . integer64 . value [ i ] ! = info . value . integer64 . min )
return 1 ;
}
return 0 ;
}
static void snd_ctl_led_set_state ( struct snd_card * card , unsigned int access ,
struct snd_kcontrol * kctl , unsigned int ioff )
{
2021-03-17 20:29:44 +03:00
struct snd_ctl_led * led ;
struct snd_ctl_led_ctl * lctl ;
2021-03-17 20:29:42 +03:00
int route ;
bool found ;
2021-03-17 20:29:44 +03:00
led = snd_ctl_led_get_by_access ( access ) ;
if ( ! led )
2021-03-17 20:29:42 +03:00
return ;
route = - 1 ;
found = false ;
mutex_lock ( & snd_ctl_led_mutex ) ;
/* the card may not be registered (active) at this point */
if ( card & & ! snd_ctl_led_card_valid [ card - > number ] ) {
mutex_unlock ( & snd_ctl_led_mutex ) ;
return ;
}
2021-03-17 20:29:44 +03:00
list_for_each_entry ( lctl , & led - > controls , list ) {
2021-03-17 20:29:42 +03:00
if ( lctl - > kctl = = kctl & & lctl - > index_offset = = ioff )
found = true ;
UPDATE_ROUTE ( route , snd_ctl_led_get ( lctl ) ) ;
}
if ( ! found & & kctl & & card ) {
lctl = kzalloc ( sizeof ( * lctl ) , GFP_KERNEL ) ;
if ( lctl ) {
lctl - > card = card ;
lctl - > access = access ;
lctl - > kctl = kctl ;
lctl - > index_offset = ioff ;
2021-03-17 20:29:44 +03:00
list_add ( & lctl - > list , & led - > controls ) ;
2021-03-17 20:29:42 +03:00
UPDATE_ROUTE ( route , snd_ctl_led_get ( lctl ) ) ;
}
}
mutex_unlock ( & snd_ctl_led_mutex ) ;
2021-03-17 20:29:44 +03:00
switch ( led - > mode ) {
case MODE_OFF : route = 1 ; break ;
case MODE_ON : route = 0 ; break ;
case MODE_FOLLOW_ROUTE : if ( route > = 0 ) route ^ = 1 ; break ;
case MODE_FOLLOW_MUTE : /* noop */ break ;
}
2021-03-17 20:29:42 +03:00
if ( route > = 0 )
2021-03-17 20:29:44 +03:00
ledtrig_audio_set ( led - > trigger_type , route ? LED_OFF : LED_ON ) ;
2021-03-17 20:29:42 +03:00
}
2021-03-17 20:29:44 +03:00
static struct snd_ctl_led_ctl * snd_ctl_led_find ( struct snd_kcontrol * kctl , unsigned int ioff )
2021-03-17 20:29:42 +03:00
{
struct list_head * controls ;
2021-03-17 20:29:44 +03:00
struct snd_ctl_led_ctl * lctl ;
2021-03-17 20:29:42 +03:00
unsigned int group ;
for ( group = 0 ; group < MAX_LED ; group + + ) {
2021-03-17 20:29:44 +03:00
controls = & snd_ctl_leds [ group ] . controls ;
2021-03-17 20:29:42 +03:00
list_for_each_entry ( lctl , controls , list )
if ( lctl - > kctl = = kctl & & lctl - > index_offset = = ioff )
return lctl ;
}
return NULL ;
}
static unsigned int snd_ctl_led_remove ( struct snd_kcontrol * kctl , unsigned int ioff ,
unsigned int access )
{
2021-03-17 20:29:44 +03:00
struct snd_ctl_led_ctl * lctl ;
2021-03-17 20:29:42 +03:00
unsigned int ret = 0 ;
mutex_lock ( & snd_ctl_led_mutex ) ;
lctl = snd_ctl_led_find ( kctl , ioff ) ;
if ( lctl & & ( access = = 0 | | access ! = lctl - > access ) ) {
ret = lctl - > access ;
list_del ( & lctl - > list ) ;
kfree ( lctl ) ;
}
mutex_unlock ( & snd_ctl_led_mutex ) ;
return ret ;
}
static void snd_ctl_led_notify ( struct snd_card * card , unsigned int mask ,
struct snd_kcontrol * kctl , unsigned int ioff )
{
struct snd_kcontrol_volatile * vd ;
unsigned int access , access2 ;
if ( mask = = SNDRV_CTL_EVENT_MASK_REMOVE ) {
access = snd_ctl_led_remove ( kctl , ioff , 0 ) ;
if ( access )
snd_ctl_led_set_state ( card , access , NULL , 0 ) ;
} else if ( mask & SNDRV_CTL_EVENT_MASK_INFO ) {
vd = & kctl - > vd [ ioff ] ;
access = vd - > access & SNDRV_CTL_ELEM_ACCESS_LED_MASK ;
access2 = snd_ctl_led_remove ( kctl , ioff , access ) ;
if ( access2 )
snd_ctl_led_set_state ( card , access2 , NULL , 0 ) ;
if ( access )
snd_ctl_led_set_state ( card , access , kctl , ioff ) ;
} else if ( ( mask & ( SNDRV_CTL_EVENT_MASK_ADD |
SNDRV_CTL_EVENT_MASK_VALUE ) ) ! = 0 ) {
vd = & kctl - > vd [ ioff ] ;
access = vd - > access & SNDRV_CTL_ELEM_ACCESS_LED_MASK ;
if ( access )
snd_ctl_led_set_state ( card , access , kctl , ioff ) ;
}
}
2021-03-17 20:29:45 +03:00
static int snd_ctl_led_set_id ( int card_number , struct snd_ctl_elem_id * id ,
unsigned int group , bool set )
{
struct snd_card * card ;
struct snd_kcontrol * kctl ;
struct snd_kcontrol_volatile * vd ;
unsigned int ioff , access , new_access ;
int err = 0 ;
card = snd_card_ref ( card_number ) ;
if ( card ) {
down_write ( & card - > controls_rwsem ) ;
kctl = snd_ctl_find_id ( card , id ) ;
if ( kctl ) {
ioff = snd_ctl_get_ioff ( kctl , id ) ;
vd = & kctl - > vd [ ioff ] ;
access = vd - > access & SNDRV_CTL_ELEM_ACCESS_LED_MASK ;
if ( access ! = 0 & & access ! = group_to_access ( group ) ) {
err = - EXDEV ;
goto unlock ;
}
new_access = vd - > access & ~ SNDRV_CTL_ELEM_ACCESS_LED_MASK ;
if ( set )
new_access | = group_to_access ( group ) ;
if ( new_access ! = vd - > access ) {
vd - > access = new_access ;
snd_ctl_led_notify ( card , SNDRV_CTL_EVENT_MASK_INFO , kctl , ioff ) ;
}
} else {
err = - ENOENT ;
}
unlock :
up_write ( & card - > controls_rwsem ) ;
snd_card_unref ( card ) ;
} else {
err = - ENXIO ;
}
return err ;
}
2021-03-17 20:29:42 +03:00
static void snd_ctl_led_refresh ( void )
{
unsigned int group ;
for ( group = 0 ; group < MAX_LED ; group + + )
snd_ctl_led_set_state ( NULL , group_to_access ( group ) , NULL , 0 ) ;
}
2021-03-17 20:29:45 +03:00
static void snd_ctl_led_ctl_destroy ( struct snd_ctl_led_ctl * lctl )
{
list_del ( & lctl - > list ) ;
kfree ( lctl ) ;
}
2021-03-17 20:29:42 +03:00
static void snd_ctl_led_clean ( struct snd_card * card )
{
unsigned int group ;
2021-03-17 20:29:44 +03:00
struct snd_ctl_led * led ;
struct snd_ctl_led_ctl * lctl ;
2021-03-17 20:29:42 +03:00
for ( group = 0 ; group < MAX_LED ; group + + ) {
2021-03-17 20:29:44 +03:00
led = & snd_ctl_leds [ group ] ;
2021-03-17 20:29:42 +03:00
repeat :
2021-03-17 20:29:44 +03:00
list_for_each_entry ( lctl , & led - > controls , list )
2021-03-17 20:29:42 +03:00
if ( ! card | | lctl - > card = = card ) {
2021-03-17 20:29:45 +03:00
snd_ctl_led_ctl_destroy ( lctl ) ;
2021-03-17 20:29:42 +03:00
goto repeat ;
}
}
}
2021-03-17 20:29:45 +03:00
static int snd_ctl_led_reset ( int card_number , unsigned int group )
{
struct snd_card * card ;
struct snd_ctl_led * led ;
struct snd_ctl_led_ctl * lctl ;
struct snd_kcontrol_volatile * vd ;
bool change = false ;
card = snd_card_ref ( card_number ) ;
if ( ! card )
return - ENXIO ;
mutex_lock ( & snd_ctl_led_mutex ) ;
if ( ! snd_ctl_led_card_valid [ card_number ] ) {
mutex_unlock ( & snd_ctl_led_mutex ) ;
snd_card_unref ( card ) ;
return - ENXIO ;
}
led = & snd_ctl_leds [ group ] ;
repeat :
list_for_each_entry ( lctl , & led - > controls , list )
if ( lctl - > card = = card ) {
vd = & lctl - > kctl - > vd [ lctl - > index_offset ] ;
vd - > access & = ~ group_to_access ( group ) ;
snd_ctl_led_ctl_destroy ( lctl ) ;
change = true ;
goto repeat ;
}
mutex_unlock ( & snd_ctl_led_mutex ) ;
if ( change )
snd_ctl_led_set_state ( NULL , group_to_access ( group ) , NULL , 0 ) ;
snd_card_unref ( card ) ;
return 0 ;
}
2021-03-17 20:29:42 +03:00
static void snd_ctl_led_register ( struct snd_card * card )
{
struct snd_kcontrol * kctl ;
unsigned int ioff ;
if ( snd_BUG_ON ( card - > number < 0 | |
card - > number > = ARRAY_SIZE ( snd_ctl_led_card_valid ) ) )
return ;
mutex_lock ( & snd_ctl_led_mutex ) ;
snd_ctl_led_card_valid [ card - > number ] = true ;
mutex_unlock ( & snd_ctl_led_mutex ) ;
/* the register callback is already called with held card->controls_rwsem */
list_for_each_entry ( kctl , & card - > controls , list )
for ( ioff = 0 ; ioff < kctl - > count ; ioff + + )
snd_ctl_led_notify ( card , SNDRV_CTL_EVENT_MASK_VALUE , kctl , ioff ) ;
snd_ctl_led_refresh ( ) ;
2021-03-17 20:29:45 +03:00
snd_ctl_led_sysfs_add ( card ) ;
2021-03-17 20:29:42 +03:00
}
static void snd_ctl_led_disconnect ( struct snd_card * card )
{
2021-03-17 20:29:45 +03:00
snd_ctl_led_sysfs_remove ( card ) ;
2021-03-17 20:29:42 +03:00
mutex_lock ( & snd_ctl_led_mutex ) ;
snd_ctl_led_card_valid [ card - > number ] = false ;
snd_ctl_led_clean ( card ) ;
mutex_unlock ( & snd_ctl_led_mutex ) ;
snd_ctl_led_refresh ( ) ;
}
2021-06-02 06:41:36 +03:00
static void snd_ctl_led_card_release ( struct device * dev )
{
struct snd_ctl_led_card * led_card = to_led_card_dev ( dev ) ;
kfree ( led_card ) ;
}
static void snd_ctl_led_release ( struct device * dev )
{
}
static void snd_ctl_led_dev_release ( struct device * dev )
{
}
2021-03-17 20:29:44 +03:00
/*
* sysfs
*/
2021-05-23 10:11:09 +03:00
static ssize_t mode_show ( struct device * dev ,
2021-03-17 20:29:44 +03:00
struct device_attribute * attr , char * buf )
{
struct snd_ctl_led * led = container_of ( dev , struct snd_ctl_led , dev ) ;
2021-06-14 10:17:10 +03:00
const char * str = NULL ;
2021-03-17 20:29:44 +03:00
switch ( led - > mode ) {
case MODE_FOLLOW_MUTE : str = " follow-mute " ; break ;
case MODE_FOLLOW_ROUTE : str = " follow-route " ; break ;
case MODE_ON : str = " on " ; break ;
case MODE_OFF : str = " off " ; break ;
}
2022-08-01 19:56:35 +03:00
return sysfs_emit ( buf , " %s \n " , str ) ;
2021-03-17 20:29:44 +03:00
}
2021-05-23 10:11:09 +03:00
static ssize_t mode_store ( struct device * dev ,
struct device_attribute * attr ,
2021-03-17 20:29:44 +03:00
const char * buf , size_t count )
{
struct snd_ctl_led * led = container_of ( dev , struct snd_ctl_led , dev ) ;
char _buf [ 16 ] ;
2021-04-02 14:42:50 +03:00
size_t l = min ( count , sizeof ( _buf ) - 1 ) ;
2021-03-17 20:29:44 +03:00
enum snd_ctl_led_mode mode ;
memcpy ( _buf , buf , l ) ;
_buf [ l ] = ' \0 ' ;
if ( strstr ( _buf , " mute " ) )
mode = MODE_FOLLOW_MUTE ;
else if ( strstr ( _buf , " route " ) )
mode = MODE_FOLLOW_ROUTE ;
else if ( strncmp ( _buf , " off " , 3 ) = = 0 | | strncmp ( _buf , " 0 " , 1 ) = = 0 )
mode = MODE_OFF ;
else if ( strncmp ( _buf , " on " , 2 ) = = 0 | | strncmp ( _buf , " 1 " , 1 ) = = 0 )
mode = MODE_ON ;
else
return count ;
mutex_lock ( & snd_ctl_led_mutex ) ;
led - > mode = mode ;
mutex_unlock ( & snd_ctl_led_mutex ) ;
snd_ctl_led_set_state ( NULL , group_to_access ( led - > group ) , NULL , 0 ) ;
return count ;
}
2021-05-23 10:11:09 +03:00
static ssize_t brightness_show ( struct device * dev ,
2021-03-17 20:29:44 +03:00
struct device_attribute * attr , char * buf )
{
struct snd_ctl_led * led = container_of ( dev , struct snd_ctl_led , dev ) ;
2022-08-01 19:56:35 +03:00
return sysfs_emit ( buf , " %u \n " , ledtrig_audio_get ( led - > trigger_type ) ) ;
2021-03-17 20:29:44 +03:00
}
2021-05-23 10:11:09 +03:00
static DEVICE_ATTR_RW ( mode ) ;
static DEVICE_ATTR_RO ( brightness ) ;
2021-03-17 20:29:44 +03:00
static struct attribute * snd_ctl_led_dev_attrs [ ] = {
& dev_attr_mode . attr ,
& dev_attr_brightness . attr ,
NULL ,
} ;
static const struct attribute_group snd_ctl_led_dev_attr_group = {
. attrs = snd_ctl_led_dev_attrs ,
} ;
static const struct attribute_group * snd_ctl_led_dev_attr_groups [ ] = {
& snd_ctl_led_dev_attr_group ,
NULL ,
} ;
2021-03-17 20:29:45 +03:00
static char * find_eos ( char * s )
{
while ( * s & & * s ! = ' , ' )
s + + ;
if ( * s )
s + + ;
return s ;
}
static char * parse_uint ( char * s , unsigned int * val )
{
unsigned long long res ;
if ( kstrtoull ( s , 10 , & res ) )
res = 0 ;
* val = res ;
return find_eos ( s ) ;
}
static char * parse_string ( char * s , char * val , size_t val_size )
{
if ( * s = = ' " ' | | * s = = ' \' ' ) {
char c = * s ;
s + + ;
while ( * s & & * s ! = c ) {
if ( val_size > 1 ) {
* val + + = * s ;
val_size - - ;
}
s + + ;
}
} else {
while ( * s & & * s ! = ' , ' ) {
if ( val_size > 1 ) {
* val + + = * s ;
val_size - - ;
}
s + + ;
}
}
* val = ' \0 ' ;
if ( * s )
s + + ;
return s ;
}
2021-11-23 20:02:47 +03:00
static char * parse_iface ( char * s , snd_ctl_elem_iface_t * val )
2021-03-17 20:29:45 +03:00
{
if ( ! strncasecmp ( s , " card " , 4 ) )
* val = SNDRV_CTL_ELEM_IFACE_CARD ;
else if ( ! strncasecmp ( s , " mixer " , 5 ) )
* val = SNDRV_CTL_ELEM_IFACE_MIXER ;
return find_eos ( s ) ;
}
/*
* These types of input strings are accepted :
*
* unsigned integer - numid ( equivaled to numid = UINT )
* string - basic mixer name ( equivalent to iface = MIXER , name = STR )
* numid = UINT
* [ iface = MIXER , ] [ device = UINT , ] [ subdevice = UINT , ] name = STR [ , index = UINT ]
*/
static ssize_t set_led_id ( struct snd_ctl_led_card * led_card , const char * buf , size_t count ,
bool attach )
{
2021-03-31 21:07:25 +03:00
char buf2 [ 256 ] , * s , * os ;
2021-03-17 20:29:45 +03:00
size_t len = max ( sizeof ( s ) - 1 , count ) ;
struct snd_ctl_elem_id id ;
int err ;
strncpy ( buf2 , buf , len ) ;
buf2 [ len ] = ' \0 ' ;
memset ( & id , 0 , sizeof ( id ) ) ;
id . iface = SNDRV_CTL_ELEM_IFACE_MIXER ;
s = buf2 ;
while ( * s ) {
2021-03-31 21:07:25 +03:00
os = s ;
2021-03-17 20:29:45 +03:00
if ( ! strncasecmp ( s , " numid= " , 6 ) ) {
s = parse_uint ( s + 6 , & id . numid ) ;
} else if ( ! strncasecmp ( s , " iface= " , 6 ) ) {
s = parse_iface ( s + 6 , & id . iface ) ;
} else if ( ! strncasecmp ( s , " device= " , 7 ) ) {
s = parse_uint ( s + 7 , & id . device ) ;
} else if ( ! strncasecmp ( s , " subdevice= " , 10 ) ) {
s = parse_uint ( s + 10 , & id . subdevice ) ;
} else if ( ! strncasecmp ( s , " name= " , 5 ) ) {
s = parse_string ( s + 5 , id . name , sizeof ( id . name ) ) ;
} else if ( ! strncasecmp ( s , " index= " , 6 ) ) {
s = parse_uint ( s + 6 , & id . index ) ;
} else if ( s = = buf2 ) {
while ( * s ) {
if ( * s < ' 0 ' | | * s > ' 9 ' )
break ;
s + + ;
}
if ( * s = = ' \0 ' )
parse_uint ( buf2 , & id . numid ) ;
else {
for ( ; * s > = ' ' ; s + + ) ;
* s = ' \0 ' ;
2021-08-13 01:59:03 +03:00
strscpy ( id . name , buf2 , sizeof ( id . name ) ) ;
2021-03-17 20:29:45 +03:00
}
break ;
}
if ( * s = = ' , ' )
s + + ;
2021-03-31 21:07:25 +03:00
if ( s = = os )
break ;
2021-03-17 20:29:45 +03:00
}
err = snd_ctl_led_set_id ( led_card - > number , & id , led_card - > led - > group , attach ) ;
if ( err < 0 )
return err ;
return count ;
}
2021-05-23 10:11:09 +03:00
static ssize_t attach_store ( struct device * dev ,
struct device_attribute * attr ,
2021-03-17 20:29:45 +03:00
const char * buf , size_t count )
{
struct snd_ctl_led_card * led_card = container_of ( dev , struct snd_ctl_led_card , dev ) ;
return set_led_id ( led_card , buf , count , true ) ;
}
2021-05-23 10:11:09 +03:00
static ssize_t detach_store ( struct device * dev ,
struct device_attribute * attr ,
2021-03-17 20:29:45 +03:00
const char * buf , size_t count )
{
struct snd_ctl_led_card * led_card = container_of ( dev , struct snd_ctl_led_card , dev ) ;
return set_led_id ( led_card , buf , count , false ) ;
}
2021-05-23 10:11:09 +03:00
static ssize_t reset_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
2021-03-17 20:29:45 +03:00
{
struct snd_ctl_led_card * led_card = container_of ( dev , struct snd_ctl_led_card , dev ) ;
int err ;
if ( count > 0 & & buf [ 0 ] = = ' 1 ' ) {
err = snd_ctl_led_reset ( led_card - > number , led_card - > led - > group ) ;
if ( err < 0 )
return err ;
}
return count ;
}
2021-05-23 10:11:09 +03:00
static ssize_t list_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
2021-03-17 20:29:45 +03:00
{
struct snd_ctl_led_card * led_card = container_of ( dev , struct snd_ctl_led_card , dev ) ;
struct snd_card * card ;
struct snd_ctl_led_ctl * lctl ;
2022-08-01 19:56:35 +03:00
size_t l = 0 ;
2021-03-17 20:29:45 +03:00
card = snd_card_ref ( led_card - > number ) ;
if ( ! card )
return - ENXIO ;
down_read ( & card - > controls_rwsem ) ;
mutex_lock ( & snd_ctl_led_mutex ) ;
if ( snd_ctl_led_card_valid [ led_card - > number ] ) {
2022-08-01 19:56:35 +03:00
list_for_each_entry ( lctl , & led_card - > led - > controls , list ) {
if ( lctl - > card ! = card )
continue ;
if ( l )
l + = sysfs_emit_at ( buf , l , " " ) ;
l + = sysfs_emit_at ( buf , l , " %u " ,
lctl - > kctl - > id . numid + lctl - > index_offset ) ;
}
2021-03-17 20:29:45 +03:00
}
mutex_unlock ( & snd_ctl_led_mutex ) ;
up_read ( & card - > controls_rwsem ) ;
snd_card_unref ( card ) ;
2022-08-01 19:56:35 +03:00
return l ;
2021-03-17 20:29:45 +03:00
}
2021-05-23 10:11:09 +03:00
static DEVICE_ATTR_WO ( attach ) ;
static DEVICE_ATTR_WO ( detach ) ;
static DEVICE_ATTR_WO ( reset ) ;
static DEVICE_ATTR_RO ( list ) ;
2021-03-17 20:29:45 +03:00
static struct attribute * snd_ctl_led_card_attrs [ ] = {
& dev_attr_attach . attr ,
& dev_attr_detach . attr ,
& dev_attr_reset . attr ,
& dev_attr_list . attr ,
NULL ,
} ;
static const struct attribute_group snd_ctl_led_card_attr_group = {
. attrs = snd_ctl_led_card_attrs ,
} ;
static const struct attribute_group * snd_ctl_led_card_attr_groups [ ] = {
& snd_ctl_led_card_attr_group ,
NULL ,
} ;
2021-03-17 20:29:44 +03:00
static struct device snd_ctl_led_dev ;
2021-03-17 20:29:45 +03:00
static void snd_ctl_led_sysfs_add ( struct snd_card * card )
{
unsigned int group ;
struct snd_ctl_led_card * led_card ;
struct snd_ctl_led * led ;
char link_name [ 32 ] ;
for ( group = 0 ; group < MAX_LED ; group + + ) {
led = & snd_ctl_leds [ group ] ;
led_card = kzalloc ( sizeof ( * led_card ) , GFP_KERNEL ) ;
if ( ! led_card )
goto cerr2 ;
led_card - > number = card - > number ;
led_card - > led = led ;
device_initialize ( & led_card - > dev ) ;
2021-06-02 06:41:36 +03:00
led_card - > dev . release = snd_ctl_led_card_release ;
2021-03-17 20:29:45 +03:00
if ( dev_set_name ( & led_card - > dev , " card%d " , card - > number ) < 0 )
goto cerr ;
led_card - > dev . parent = & led - > dev ;
led_card - > dev . groups = snd_ctl_led_card_attr_groups ;
if ( device_add ( & led_card - > dev ) )
goto cerr ;
led - > cards [ card - > number ] = led_card ;
snprintf ( link_name , sizeof ( link_name ) , " led-%s " , led - > name ) ;
WARN ( sysfs_create_link ( & card - > ctl_dev . kobj , & led_card - > dev . kobj , link_name ) ,
" can't create symlink to controlC%i device \n " , card - > number ) ;
WARN ( sysfs_create_link ( & led_card - > dev . kobj , & card - > card_dev . kobj , " card " ) ,
" can't create symlink to card%i \n " , card - > number ) ;
continue ;
cerr :
put_device ( & led_card - > dev ) ;
cerr2 :
printk ( KERN_ERR " snd_ctl_led: unable to add card%d " , card - > number ) ;
}
}
static void snd_ctl_led_sysfs_remove ( struct snd_card * card )
{
unsigned int group ;
struct snd_ctl_led_card * led_card ;
struct snd_ctl_led * led ;
char link_name [ 32 ] ;
for ( group = 0 ; group < MAX_LED ; group + + ) {
led = & snd_ctl_leds [ group ] ;
led_card = led - > cards [ card - > number ] ;
if ( ! led_card )
continue ;
snprintf ( link_name , sizeof ( link_name ) , " led-%s " , led - > name ) ;
sysfs_remove_link ( & card - > ctl_dev . kobj , link_name ) ;
sysfs_remove_link ( & led_card - > dev . kobj , " card " ) ;
2021-06-02 06:41:36 +03:00
device_unregister ( & led_card - > dev ) ;
2021-03-17 20:29:45 +03:00
led - > cards [ card - > number ] = NULL ;
}
}
2021-03-17 20:29:42 +03:00
/*
* Control layer registration
*/
static struct snd_ctl_layer_ops snd_ctl_led_lops = {
. module_name = SND_CTL_LAYER_MODULE_LED ,
. lregister = snd_ctl_led_register ,
. ldisconnect = snd_ctl_led_disconnect ,
. lnotify = snd_ctl_led_notify ,
} ;
static int __init snd_ctl_led_init ( void )
{
2021-03-17 20:29:44 +03:00
struct snd_ctl_led * led ;
2021-03-17 20:29:42 +03:00
unsigned int group ;
2021-03-17 20:29:44 +03:00
device_initialize ( & snd_ctl_led_dev ) ;
snd_ctl_led_dev . class = sound_class ;
2021-06-02 06:41:36 +03:00
snd_ctl_led_dev . release = snd_ctl_led_dev_release ;
2021-03-17 20:29:44 +03:00
dev_set_name ( & snd_ctl_led_dev , " ctl-led " ) ;
if ( device_add ( & snd_ctl_led_dev ) ) {
put_device ( & snd_ctl_led_dev ) ;
return - ENOMEM ;
}
for ( group = 0 ; group < MAX_LED ; group + + ) {
led = & snd_ctl_leds [ group ] ;
INIT_LIST_HEAD ( & led - > controls ) ;
device_initialize ( & led - > dev ) ;
led - > dev . parent = & snd_ctl_led_dev ;
2021-06-02 06:41:36 +03:00
led - > dev . release = snd_ctl_led_release ;
2021-03-17 20:29:44 +03:00
led - > dev . groups = snd_ctl_led_dev_attr_groups ;
dev_set_name ( & led - > dev , led - > name ) ;
if ( device_add ( & led - > dev ) ) {
put_device ( & led - > dev ) ;
for ( ; group > 0 ; group - - ) {
2021-04-09 15:34:41 +03:00
led = & snd_ctl_leds [ group - 1 ] ;
2021-06-02 06:41:36 +03:00
device_unregister ( & led - > dev ) ;
2021-03-17 20:29:44 +03:00
}
2021-06-02 06:41:36 +03:00
device_unregister ( & snd_ctl_led_dev ) ;
2021-03-17 20:29:44 +03:00
return - ENOMEM ;
}
}
2021-03-17 20:29:42 +03:00
snd_ctl_register_layer ( & snd_ctl_led_lops ) ;
return 0 ;
}
static void __exit snd_ctl_led_exit ( void )
{
2021-03-17 20:29:44 +03:00
struct snd_ctl_led * led ;
2021-03-17 20:29:45 +03:00
struct snd_card * card ;
unsigned int group , card_number ;
2021-03-17 20:29:44 +03:00
2021-03-17 20:29:45 +03:00
snd_ctl_disconnect_layer ( & snd_ctl_led_lops ) ;
for ( card_number = 0 ; card_number < SNDRV_CARDS ; card_number + + ) {
if ( ! snd_ctl_led_card_valid [ card_number ] )
continue ;
card = snd_card_ref ( card_number ) ;
if ( card ) {
snd_ctl_led_sysfs_remove ( card ) ;
snd_card_unref ( card ) ;
}
}
2021-03-17 20:29:44 +03:00
for ( group = 0 ; group < MAX_LED ; group + + ) {
led = & snd_ctl_leds [ group ] ;
2021-06-02 06:41:36 +03:00
device_unregister ( & led - > dev ) ;
2021-03-17 20:29:44 +03:00
}
2021-06-02 06:41:36 +03:00
device_unregister ( & snd_ctl_led_dev ) ;
2021-03-17 20:29:42 +03:00
snd_ctl_led_clean ( NULL ) ;
}
module_init ( snd_ctl_led_init )
module_exit ( snd_ctl_led_exit )