2007-07-27 18:58:06 +02:00
/*
* HWDEP Interface for HD - audio codec
*
* Copyright ( c ) 2007 Takashi Iwai < tiwai @ suse . de >
*
* This driver 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 ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This driver is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/pci.h>
# include <linux/compat.h>
# include <linux/mutex.h>
2008-07-30 15:01:46 +02:00
# include <linux/ctype.h>
2007-07-27 18:58:06 +02:00
# include <sound/core.h>
# include "hda_codec.h"
# include "hda_local.h"
# include <sound/hda_hwdep.h>
2008-07-30 15:01:46 +02:00
# include <sound/minors.h>
2007-07-27 18:58:06 +02:00
/*
* write / read an out - of - bound verb
*/
static int verb_write_ioctl ( struct hda_codec * codec ,
struct hda_verb_ioctl __user * arg )
{
u32 verb , res ;
if ( get_user ( verb , & arg - > verb ) )
return - EFAULT ;
res = snd_hda_codec_read ( codec , verb > > 24 , 0 ,
( verb > > 8 ) & 0xffff , verb & 0xff ) ;
if ( put_user ( res , & arg - > res ) )
return - EFAULT ;
return 0 ;
}
static int get_wcap_ioctl ( struct hda_codec * codec ,
struct hda_verb_ioctl __user * arg )
{
u32 verb , res ;
if ( get_user ( verb , & arg - > verb ) )
return - EFAULT ;
res = get_wcaps ( codec , verb > > 24 ) ;
if ( put_user ( res , & arg - > res ) )
return - EFAULT ;
return 0 ;
}
/*
*/
static int hda_hwdep_ioctl ( struct snd_hwdep * hw , struct file * file ,
unsigned int cmd , unsigned long arg )
{
struct hda_codec * codec = hw - > private_data ;
void __user * argp = ( void __user * ) arg ;
switch ( cmd ) {
case HDA_IOCTL_PVERSION :
return put_user ( HDA_HWDEP_VERSION , ( int __user * ) argp ) ;
case HDA_IOCTL_VERB_WRITE :
return verb_write_ioctl ( codec , argp ) ;
case HDA_IOCTL_GET_WCAP :
return get_wcap_ioctl ( codec , argp ) ;
}
return - ENOIOCTLCMD ;
}
# ifdef CONFIG_COMPAT
static int hda_hwdep_ioctl_compat ( struct snd_hwdep * hw , struct file * file ,
unsigned int cmd , unsigned long arg )
{
2007-07-31 11:08:10 +02:00
return hda_hwdep_ioctl ( hw , file , cmd , ( unsigned long ) compat_ptr ( arg ) ) ;
2007-07-27 18:58:06 +02:00
}
# endif
static int hda_hwdep_open ( struct snd_hwdep * hw , struct file * file )
{
2008-05-20 12:15:15 +02:00
# ifndef CONFIG_SND_DEBUG_VERBOSE
2007-07-27 18:58:06 +02:00
if ( ! capable ( CAP_SYS_RAWIO ) )
return - EACCES ;
# endif
return 0 ;
}
2008-07-30 15:01:46 +02:00
static void clear_hwdep_elements ( struct hda_codec * codec )
{
2008-07-30 15:01:46 +02:00
char * * head ;
int i ;
2008-07-30 15:01:46 +02:00
/* clear init verbs */
snd_array_free ( & codec - > init_verbs ) ;
2008-07-30 15:01:46 +02:00
/* clear hints */
head = codec - > hints . list ;
for ( i = 0 ; i < codec - > hints . used ; i + + , head + + )
kfree ( * head ) ;
snd_array_free ( & codec - > hints ) ;
2009-02-23 09:42:57 +01:00
snd_array_free ( & codec - > user_pins ) ;
2008-07-30 15:01:46 +02:00
}
static void hwdep_free ( struct snd_hwdep * hwdep )
{
clear_hwdep_elements ( hwdep - > private_data ) ;
}
2008-11-27 15:47:11 +01:00
int /*__devinit*/ snd_hda_create_hwdep ( struct hda_codec * codec )
2007-07-27 18:58:06 +02:00
{
char hwname [ 16 ] ;
struct snd_hwdep * hwdep ;
int err ;
sprintf ( hwname , " HDA Codec %d " , codec - > addr ) ;
err = snd_hwdep_new ( codec - > bus - > card , hwname , codec - > addr , & hwdep ) ;
if ( err < 0 )
return err ;
codec - > hwdep = hwdep ;
sprintf ( hwdep - > name , " HDA Codec %d " , codec - > addr ) ;
hwdep - > iface = SNDRV_HWDEP_IFACE_HDA ;
hwdep - > private_data = codec ;
2008-07-30 15:01:46 +02:00
hwdep - > private_free = hwdep_free ;
2007-07-27 18:58:06 +02:00
hwdep - > exclusive = 1 ;
hwdep - > ops . open = hda_hwdep_open ;
hwdep - > ops . ioctl = hda_hwdep_ioctl ;
# ifdef CONFIG_COMPAT
hwdep - > ops . ioctl_compat = hda_hwdep_ioctl_compat ;
# endif
2008-07-30 15:01:46 +02:00
snd_array_init ( & codec - > init_verbs , sizeof ( struct hda_verb ) , 32 ) ;
2008-07-30 15:01:46 +02:00
snd_array_init ( & codec - > hints , sizeof ( char * ) , 32 ) ;
2009-02-23 09:42:57 +01:00
snd_array_init ( & codec - > user_pins , sizeof ( struct hda_pincfg ) , 16 ) ;
2008-07-30 15:01:46 +02:00
2007-07-27 18:58:06 +02:00
return 0 ;
}
2008-07-30 15:01:46 +02:00
2008-11-21 09:26:20 +01:00
# ifdef CONFIG_SND_HDA_RECONFIG
2008-07-30 15:01:46 +02:00
/*
* sysfs interface
*/
static int clear_codec ( struct hda_codec * codec )
{
2009-02-23 16:57:04 +01:00
int err ;
err = snd_hda_codec_reset ( codec ) ;
if ( err < 0 ) {
snd_printk ( KERN_ERR " The codec is being used, can't free. \n " ) ;
return err ;
}
2008-07-30 15:01:46 +02:00
clear_hwdep_elements ( codec ) ;
2008-07-30 15:01:46 +02:00
return 0 ;
}
static int reconfig_codec ( struct hda_codec * codec )
{
int err ;
snd_printk ( KERN_INFO " hda-codec: reconfiguring \n " ) ;
2009-02-23 16:57:04 +01:00
err = snd_hda_codec_reset ( codec ) ;
if ( err < 0 ) {
snd_printk ( KERN_ERR
" The codec is being used, can't reconfigure. \n " ) ;
return err ;
}
2008-07-30 15:01:46 +02:00
err = snd_hda_codec_configure ( codec ) ;
if ( err < 0 )
return err ;
/* rebuild PCMs */
2008-11-27 14:17:01 +01:00
err = snd_hda_codec_build_pcms ( codec ) ;
2008-07-30 15:01:46 +02:00
if ( err < 0 )
return err ;
/* rebuild mixers */
err = snd_hda_codec_build_controls ( codec ) ;
if ( err < 0 )
return err ;
2009-02-12 00:13:19 +01:00
return snd_card_register ( codec - > bus - > card ) ;
2008-07-30 15:01:46 +02:00
}
/*
* allocate a string at most len chars , and remove the trailing EOL
*/
static char * kstrndup_noeol ( const char * src , size_t len )
{
char * s = kstrndup ( src , len , GFP_KERNEL ) ;
char * p ;
if ( ! s )
return NULL ;
p = strchr ( s , ' \n ' ) ;
if ( p )
* p = 0 ;
return s ;
}
# define CODEC_INFO_SHOW(type) \
static ssize_t type # # _show ( struct device * dev , \
struct device_attribute * attr , \
char * buf ) \
{ \
struct snd_hwdep * hwdep = dev_get_drvdata ( dev ) ; \
struct hda_codec * codec = hwdep - > private_data ; \
return sprintf ( buf , " 0x%x \n " , codec - > type ) ; \
}
# define CODEC_INFO_STR_SHOW(type) \
static ssize_t type # # _show ( struct device * dev , \
struct device_attribute * attr , \
char * buf ) \
{ \
struct snd_hwdep * hwdep = dev_get_drvdata ( dev ) ; \
struct hda_codec * codec = hwdep - > private_data ; \
return sprintf ( buf , " %s \n " , \
codec - > type ? codec - > type : " " ) ; \
}
CODEC_INFO_SHOW ( vendor_id ) ;
CODEC_INFO_SHOW ( subsystem_id ) ;
CODEC_INFO_SHOW ( revision_id ) ;
CODEC_INFO_SHOW ( afg ) ;
CODEC_INFO_SHOW ( mfg ) ;
CODEC_INFO_STR_SHOW ( name ) ;
CODEC_INFO_STR_SHOW ( modelname ) ;
# define CODEC_INFO_STORE(type) \
static ssize_t type # # _store ( struct device * dev , \
struct device_attribute * attr , \
const char * buf , size_t count ) \
{ \
struct snd_hwdep * hwdep = dev_get_drvdata ( dev ) ; \
struct hda_codec * codec = hwdep - > private_data ; \
char * after ; \
codec - > type = simple_strtoul ( buf , & after , 0 ) ; \
return count ; \
}
# define CODEC_INFO_STR_STORE(type) \
static ssize_t type # # _store ( struct device * dev , \
struct device_attribute * attr , \
const char * buf , size_t count ) \
{ \
struct snd_hwdep * hwdep = dev_get_drvdata ( dev ) ; \
struct hda_codec * codec = hwdep - > private_data ; \
char * s = kstrndup_noeol ( buf , 64 ) ; \
if ( ! s ) \
return - ENOMEM ; \
kfree ( codec - > type ) ; \
codec - > type = s ; \
return count ; \
}
CODEC_INFO_STORE ( vendor_id ) ;
CODEC_INFO_STORE ( subsystem_id ) ;
CODEC_INFO_STORE ( revision_id ) ;
CODEC_INFO_STR_STORE ( name ) ;
CODEC_INFO_STR_STORE ( modelname ) ;
# define CODEC_ACTION_STORE(type) \
static ssize_t type # # _store ( struct device * dev , \
struct device_attribute * attr , \
const char * buf , size_t count ) \
{ \
struct snd_hwdep * hwdep = dev_get_drvdata ( dev ) ; \
struct hda_codec * codec = hwdep - > private_data ; \
int err = 0 ; \
if ( * buf ) \
err = type # # _codec ( codec ) ; \
return err < 0 ? err : count ; \
}
CODEC_ACTION_STORE ( reconfig ) ;
CODEC_ACTION_STORE ( clear ) ;
2008-07-30 15:01:46 +02:00
static ssize_t init_verbs_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
struct snd_hwdep * hwdep = dev_get_drvdata ( dev ) ;
struct hda_codec * codec = hwdep - > private_data ;
2009-02-20 15:59:01 +01:00
struct hda_verb * v ;
int nid , verb , param ;
2008-07-30 15:01:46 +02:00
2009-02-20 15:59:01 +01:00
if ( sscanf ( buf , " %i %i %i " , & nid , & verb , & param ) ! = 3 )
return - EINVAL ;
if ( ! nid | | ! verb )
2008-07-30 15:01:46 +02:00
return - EINVAL ;
v = snd_array_new ( & codec - > init_verbs ) ;
if ( ! v )
return - ENOMEM ;
2009-02-20 15:59:01 +01:00
v - > nid = nid ;
v - > verb = verb ;
v - > param = param ;
2008-07-30 15:01:46 +02:00
return count ;
}
2008-07-30 15:01:46 +02:00
static ssize_t hints_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
struct snd_hwdep * hwdep = dev_get_drvdata ( dev ) ;
struct hda_codec * codec = hwdep - > private_data ;
char * p ;
char * * hint ;
if ( ! * buf | | isspace ( * buf ) | | * buf = = ' # ' | | * buf = = ' \n ' )
return count ;
p = kstrndup_noeol ( buf , 1024 ) ;
if ( ! p )
return - ENOMEM ;
hint = snd_array_new ( & codec - > hints ) ;
if ( ! hint ) {
kfree ( p ) ;
return - ENOMEM ;
}
* hint = p ;
return count ;
}
2009-02-20 14:11:16 +01:00
static ssize_t pin_configs_show ( struct hda_codec * codec ,
struct snd_array * list ,
char * buf )
{
int i , len = 0 ;
for ( i = 0 ; i < list - > used ; i + + ) {
struct hda_pincfg * pin = snd_array_elem ( list , i ) ;
len + = sprintf ( buf + len , " 0x%02x 0x%08x \n " ,
pin - > nid , pin - > cfg ) ;
}
return len ;
}
static ssize_t init_pin_configs_show ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
struct snd_hwdep * hwdep = dev_get_drvdata ( dev ) ;
struct hda_codec * codec = hwdep - > private_data ;
return pin_configs_show ( codec , & codec - > init_pins , buf ) ;
}
2009-02-23 09:42:57 +01:00
static ssize_t user_pin_configs_show ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
2009-02-20 14:11:16 +01:00
{
struct snd_hwdep * hwdep = dev_get_drvdata ( dev ) ;
struct hda_codec * codec = hwdep - > private_data ;
2009-02-23 09:42:57 +01:00
return pin_configs_show ( codec , & codec - > user_pins , buf ) ;
2009-02-20 14:11:16 +01:00
}
2009-02-23 09:42:57 +01:00
static ssize_t driver_pin_configs_show ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
2009-02-20 14:11:16 +01:00
{
struct snd_hwdep * hwdep = dev_get_drvdata ( dev ) ;
struct hda_codec * codec = hwdep - > private_data ;
2009-02-23 09:42:57 +01:00
return pin_configs_show ( codec , & codec - > driver_pins , buf ) ;
2009-02-20 14:11:16 +01:00
}
# define MAX_PIN_CONFIGS 32
2009-02-23 09:42:57 +01:00
static ssize_t user_pin_configs_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
2009-02-20 14:11:16 +01:00
{
struct snd_hwdep * hwdep = dev_get_drvdata ( dev ) ;
struct hda_codec * codec = hwdep - > private_data ;
int nid , cfg ;
int err ;
if ( sscanf ( buf , " %i %i " , & nid , & cfg ) ! = 2 )
return - EINVAL ;
if ( ! nid )
return - EINVAL ;
2009-02-23 09:42:57 +01:00
err = snd_hda_add_pincfg ( codec , & codec - > user_pins , nid , cfg ) ;
2009-02-20 14:11:16 +01:00
if ( err < 0 )
return err ;
return count ;
}
2008-07-30 15:01:46 +02:00
# define CODEC_ATTR_RW(type) \
__ATTR ( type , 0644 , type # # _show , type # # _store )
# define CODEC_ATTR_RO(type) \
__ATTR_RO ( type )
# define CODEC_ATTR_WO(type) \
__ATTR ( type , 0200 , NULL , type # # _store )
static struct device_attribute codec_attrs [ ] = {
CODEC_ATTR_RW ( vendor_id ) ,
CODEC_ATTR_RW ( subsystem_id ) ,
CODEC_ATTR_RW ( revision_id ) ,
CODEC_ATTR_RO ( afg ) ,
CODEC_ATTR_RO ( mfg ) ,
CODEC_ATTR_RW ( name ) ,
CODEC_ATTR_RW ( modelname ) ,
2008-07-30 15:01:46 +02:00
CODEC_ATTR_WO ( init_verbs ) ,
2008-07-30 15:01:46 +02:00
CODEC_ATTR_WO ( hints ) ,
2009-02-20 14:11:16 +01:00
CODEC_ATTR_RO ( init_pin_configs ) ,
2009-02-23 09:42:57 +01:00
CODEC_ATTR_RW ( user_pin_configs ) ,
CODEC_ATTR_RO ( driver_pin_configs ) ,
2008-07-30 15:01:46 +02:00
CODEC_ATTR_WO ( reconfig ) ,
CODEC_ATTR_WO ( clear ) ,
} ;
/*
* create sysfs files on hwdep directory
*/
int snd_hda_hwdep_add_sysfs ( struct hda_codec * codec )
{
struct snd_hwdep * hwdep = codec - > hwdep ;
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( codec_attrs ) ; i + + )
snd_add_device_sysfs_file ( SNDRV_DEVICE_TYPE_HWDEP , hwdep - > card ,
hwdep - > device , & codec_attrs [ i ] ) ;
return 0 ;
}
2008-11-21 09:26:20 +01:00
# endif /* CONFIG_SND_HDA_RECONFIG */