2011-10-28 00:12:46 +04:00
/*
* Jack - detection handling for HD - audio
*
* Copyright ( c ) 2011 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 .
*/
# include <linux/init.h>
# include <linux/slab.h>
# include <sound/core.h>
2011-10-28 02:03:22 +04:00
# include <sound/control.h>
2011-11-02 11:54:51 +04:00
# include <sound/jack.h>
2011-10-28 00:12:46 +04:00
# include "hda_codec.h"
# include "hda_local.h"
# include "hda_jack.h"
/* execute pin sense measurement */
static u32 read_pin_sense ( struct hda_codec * codec , hda_nid_t nid )
{
u32 pincap ;
if ( ! codec - > no_trigger_sense ) {
pincap = snd_hda_query_pin_caps ( codec , nid ) ;
if ( pincap & AC_PINCAP_TRIG_REQ ) /* need trigger? */
snd_hda_codec_read ( codec , nid , 0 ,
AC_VERB_SET_PIN_SENSE , 0 ) ;
}
return snd_hda_codec_read ( codec , nid , 0 ,
AC_VERB_GET_PIN_SENSE , 0 ) ;
}
/**
* snd_hda_jack_tbl_get - query the jack - table entry for the given NID
*/
struct hda_jack_tbl *
snd_hda_jack_tbl_get ( struct hda_codec * codec , hda_nid_t nid )
{
struct hda_jack_tbl * jack = codec - > jacktbl . list ;
int i ;
if ( ! nid | | ! jack )
return NULL ;
for ( i = 0 ; i < codec - > jacktbl . used ; i + + , jack + + )
if ( jack - > nid = = nid )
return jack ;
return NULL ;
}
EXPORT_SYMBOL_HDA ( snd_hda_jack_tbl_get ) ;
2011-10-28 03:16:55 +04:00
/**
* snd_hda_jack_tbl_get_from_tag - query the jack - table entry for the given tag
*/
struct hda_jack_tbl *
snd_hda_jack_tbl_get_from_tag ( struct hda_codec * codec , unsigned char tag )
{
struct hda_jack_tbl * jack = codec - > jacktbl . list ;
int i ;
if ( ! tag | | ! jack )
return NULL ;
for ( i = 0 ; i < codec - > jacktbl . used ; i + + , jack + + )
if ( jack - > tag = = tag )
return jack ;
return NULL ;
}
EXPORT_SYMBOL_HDA ( snd_hda_jack_tbl_get_from_tag ) ;
2011-10-28 00:12:46 +04:00
/**
* snd_hda_jack_tbl_new - create a jack - table entry for the given NID
*/
struct hda_jack_tbl *
snd_hda_jack_tbl_new ( struct hda_codec * codec , hda_nid_t nid )
{
struct hda_jack_tbl * jack = snd_hda_jack_tbl_get ( codec , nid ) ;
if ( jack )
return jack ;
snd_array_init ( & codec - > jacktbl , sizeof ( * jack ) , 16 ) ;
jack = snd_array_new ( & codec - > jacktbl ) ;
if ( ! jack )
return NULL ;
jack - > nid = nid ;
jack - > jack_dirty = 1 ;
2011-10-28 03:16:55 +04:00
jack - > tag = codec - > jacktbl . used ;
2011-10-28 00:12:46 +04:00
return jack ;
}
2011-11-02 11:54:51 +04:00
# ifdef CONFIG_SND_HDA_INPUT_JACK
static void snd_hda_input_jack_free ( struct hda_codec * codec ) ;
# else
# define snd_hda_input_jack_free(codec)
# endif
2011-10-28 00:12:46 +04:00
void snd_hda_jack_tbl_clear ( struct hda_codec * codec )
{
2011-11-02 11:54:51 +04:00
snd_hda_input_jack_free ( codec ) ;
2011-10-28 00:12:46 +04:00
snd_array_free ( & codec - > jacktbl ) ;
}
/* update the cached value and notification flag if needed */
static void jack_detect_update ( struct hda_codec * codec ,
struct hda_jack_tbl * jack )
{
2011-10-28 03:16:55 +04:00
if ( jack - > jack_dirty | | ! jack - > jack_detect ) {
2011-11-02 11:36:06 +04:00
jack - > pin_sense = read_pin_sense ( codec , jack - > nid ) ;
2011-10-28 00:12:46 +04:00
jack - > jack_dirty = 0 ;
}
}
/**
* snd_hda_set_dirty_all - Mark all the cached as dirty
*
* This function sets the dirty flag to all entries of jack table .
* It ' s called from the resume path in hda_codec . c .
*/
void snd_hda_jack_set_dirty_all ( struct hda_codec * codec )
{
struct hda_jack_tbl * jack = codec - > jacktbl . list ;
int i ;
for ( i = 0 ; i < codec - > jacktbl . used ; i + + , jack + + )
if ( jack - > nid )
jack - > jack_dirty = 1 ;
}
EXPORT_SYMBOL_HDA ( snd_hda_jack_set_dirty_all ) ;
/**
* snd_hda_pin_sense - execute pin sense measurement
* @ codec : the CODEC to sense
* @ nid : the pin NID to sense
*
* Execute necessary pin sense measurement and return its Presence Detect ,
* Impedance , ELD Valid etc . status bits .
*/
u32 snd_hda_pin_sense ( struct hda_codec * codec , hda_nid_t nid )
{
struct hda_jack_tbl * jack = snd_hda_jack_tbl_get ( codec , nid ) ;
if ( jack ) {
jack_detect_update ( codec , jack ) ;
return jack - > pin_sense ;
}
return read_pin_sense ( codec , nid ) ;
}
EXPORT_SYMBOL_HDA ( snd_hda_pin_sense ) ;
2011-11-02 11:36:06 +04:00
# define get_jack_plug_state(sense) !!(sense & AC_PINSENSE_PRESENCE)
2011-10-28 00:12:46 +04:00
/**
* snd_hda_jack_detect - query pin Presence Detect status
* @ codec : the CODEC to sense
* @ nid : the pin NID to sense
*
* Query and return the pin ' s Presence Detect status .
*/
int snd_hda_jack_detect ( struct hda_codec * codec , hda_nid_t nid )
{
u32 sense = snd_hda_pin_sense ( codec , nid ) ;
2011-11-02 11:36:06 +04:00
return get_jack_plug_state ( sense ) ;
2011-10-28 00:12:46 +04:00
}
EXPORT_SYMBOL_HDA ( snd_hda_jack_detect ) ;
/**
* snd_hda_jack_detect_enable - enable the jack - detection
*/
int snd_hda_jack_detect_enable ( struct hda_codec * codec , hda_nid_t nid ,
2011-10-28 03:16:55 +04:00
unsigned char action )
2011-10-28 00:12:46 +04:00
{
struct hda_jack_tbl * jack = snd_hda_jack_tbl_new ( codec , nid ) ;
if ( ! jack )
return - ENOMEM ;
2011-10-28 03:16:55 +04:00
if ( jack - > jack_detect )
2011-10-28 02:03:22 +04:00
return 0 ; /* already registered */
2011-10-28 03:16:55 +04:00
jack - > jack_detect = 1 ;
if ( action )
jack - > action = action ;
2011-10-28 00:12:46 +04:00
return snd_hda_codec_write_cache ( codec , nid , 0 ,
AC_VERB_SET_UNSOLICITED_ENABLE ,
2011-10-28 03:16:55 +04:00
AC_USRSP_EN | jack - > tag ) ;
2011-10-28 00:12:46 +04:00
}
EXPORT_SYMBOL_HDA ( snd_hda_jack_detect_enable ) ;
2011-10-28 02:03:22 +04:00
/**
* snd_hda_jack_report_sync - sync the states of all jacks and report if changed
*/
void snd_hda_jack_report_sync ( struct hda_codec * codec )
{
struct hda_jack_tbl * jack = codec - > jacktbl . list ;
2011-11-02 11:36:06 +04:00
int i , state ;
2011-10-28 02:03:22 +04:00
for ( i = 0 ; i < codec - > jacktbl . used ; i + + , jack + + )
if ( jack - > nid ) {
jack_detect_update ( codec , jack ) ;
2011-11-02 11:36:06 +04:00
state = get_jack_plug_state ( jack - > pin_sense ) ;
2011-11-02 11:54:51 +04:00
snd_kctl_jack_report ( codec - > bus - > card , jack - > kctl , state ) ;
2011-10-28 02:03:22 +04:00
}
}
EXPORT_SYMBOL_HDA ( snd_hda_jack_report_sync ) ;
/**
* snd_hda_jack_add_kctl - Add a kctl for the given pin
*
* This assigns a jack - detection kctl to the given pin . The kcontrol
* will have the given name and index .
*/
int snd_hda_jack_add_kctl ( struct hda_codec * codec , hda_nid_t nid ,
const char * name , int idx )
{
struct hda_jack_tbl * jack ;
struct snd_kcontrol * kctl ;
2011-10-28 03:16:55 +04:00
jack = snd_hda_jack_tbl_new ( codec , nid ) ;
2011-10-28 02:03:22 +04:00
if ( ! jack )
return 0 ;
if ( jack - > kctl )
return 0 ; /* already created */
2011-11-02 11:36:06 +04:00
kctl = snd_kctl_jack_new ( name , idx , codec ) ;
2011-10-28 02:03:22 +04:00
if ( ! kctl )
return - ENOMEM ;
if ( snd_hda_ctl_add ( codec , nid , kctl ) < 0 )
return - ENOMEM ;
jack - > kctl = kctl ;
return 0 ;
}
static int add_jack_kctl ( struct hda_codec * codec , hda_nid_t nid , int idx ,
const struct auto_pin_cfg * cfg )
{
2011-10-28 03:16:55 +04:00
unsigned int def_conf , conn ;
int err ;
2011-10-28 02:03:22 +04:00
if ( ! nid )
return 0 ;
if ( ! is_jack_detectable ( codec , nid ) )
return 0 ;
2011-10-28 03:16:55 +04:00
def_conf = snd_hda_codec_get_pincfg ( codec , nid ) ;
conn = get_defcfg_connect ( def_conf ) ;
if ( conn ! = AC_JACK_PORT_COMPLEX )
return 0 ;
err = snd_hda_jack_add_kctl ( codec , nid ,
2011-10-28 02:03:22 +04:00
snd_hda_get_pin_label ( codec , nid , cfg ) ,
idx ) ;
2011-10-28 03:16:55 +04:00
if ( err < 0 )
return err ;
return snd_hda_jack_detect_enable ( codec , nid , 0 ) ;
2011-10-28 02:03:22 +04:00
}
/**
* snd_hda_jack_add_kctls - Add kctls for all pins included in the given pincfg
*/
int snd_hda_jack_add_kctls ( struct hda_codec * codec ,
const struct auto_pin_cfg * cfg )
{
const hda_nid_t * p ;
int i , err ;
for ( i = 0 , p = cfg - > line_out_pins ; i < cfg - > line_outs ; i + + , p + + ) {
err = add_jack_kctl ( codec , * p , i , cfg ) ;
if ( err < 0 )
return err ;
}
for ( i = 0 , p = cfg - > hp_pins ; i < cfg - > hp_outs ; i + + , p + + ) {
if ( * p = = * cfg - > line_out_pins ) /* might be duplicated */
break ;
err = add_jack_kctl ( codec , * p , i , cfg ) ;
if ( err < 0 )
return err ;
}
for ( i = 0 , p = cfg - > speaker_pins ; i < cfg - > speaker_outs ; i + + , p + + ) {
if ( * p = = * cfg - > line_out_pins ) /* might be duplicated */
break ;
err = add_jack_kctl ( codec , * p , i , cfg ) ;
if ( err < 0 )
return err ;
}
for ( i = 0 ; i < cfg - > num_inputs ; i + + ) {
err = add_jack_kctl ( codec , cfg - > inputs [ i ] . pin , 0 , cfg ) ;
if ( err < 0 )
return err ;
}
for ( i = 0 , p = cfg - > dig_out_pins ; i < cfg - > dig_outs ; i + + , p + + ) {
err = add_jack_kctl ( codec , * p , i , cfg ) ;
if ( err < 0 )
return err ;
}
err = add_jack_kctl ( codec , cfg - > dig_in_pin , 0 , cfg ) ;
if ( err < 0 )
return err ;
err = add_jack_kctl ( codec , cfg - > mono_out_pin , 0 , cfg ) ;
if ( err < 0 )
return err ;
return 0 ;
}
EXPORT_SYMBOL_HDA ( snd_hda_jack_add_kctls ) ;
2011-11-02 11:54:51 +04:00
# ifdef CONFIG_SND_HDA_INPUT_JACK
/*
* Input - jack notification support
*/
static const char * get_jack_default_name ( struct hda_codec * codec , hda_nid_t nid ,
int type )
{
switch ( type ) {
case SND_JACK_HEADPHONE :
return " Headphone " ;
case SND_JACK_MICROPHONE :
return " Mic " ;
case SND_JACK_LINEOUT :
return " Line-out " ;
case SND_JACK_LINEIN :
return " Line-in " ;
case SND_JACK_HEADSET :
return " Headset " ;
case SND_JACK_VIDEOOUT :
return " HDMI/DP " ;
default :
return " Misc " ;
}
}
static void hda_free_jack_priv ( struct snd_jack * jack )
{
struct hda_jack_tbl * jacks = jack - > private_data ;
jacks - > nid = 0 ;
jacks - > jack = NULL ;
}
int snd_hda_input_jack_add ( struct hda_codec * codec , hda_nid_t nid , int type ,
const char * name )
{
struct hda_jack_tbl * jack = snd_hda_jack_tbl_new ( codec , nid ) ;
int err ;
if ( ! jack )
return - ENOMEM ;
if ( ! name )
name = get_jack_default_name ( codec , nid , type ) ;
err = snd_jack_new ( codec - > bus - > card , name , type , & jack - > jack ) ;
if ( err < 0 )
return err ;
jack - > jack - > private_data = jack ;
jack - > jack - > private_free = hda_free_jack_priv ;
return 0 ;
}
EXPORT_SYMBOL_HDA ( snd_hda_input_jack_add ) ;
void snd_hda_input_jack_report ( struct hda_codec * codec , hda_nid_t nid )
{
struct hda_jack_tbl * jack = snd_hda_jack_tbl_get ( codec , nid ) ;
unsigned int pin_ctl ;
unsigned int present ;
int type ;
if ( ! jack )
return ;
present = snd_hda_jack_detect ( codec , nid ) ;
type = jack - > type ;
if ( type = = ( SND_JACK_HEADPHONE | SND_JACK_LINEOUT ) ) {
pin_ctl = snd_hda_codec_read ( codec , nid , 0 ,
AC_VERB_GET_PIN_WIDGET_CONTROL , 0 ) ;
type = ( pin_ctl & AC_PINCTL_HP_EN ) ?
SND_JACK_HEADPHONE : SND_JACK_LINEOUT ;
}
snd_jack_report ( jack - > jack , present ? type : 0 ) ;
}
EXPORT_SYMBOL_HDA ( snd_hda_input_jack_report ) ;
/* free jack instances manually when clearing/reconfiguring */
static void snd_hda_input_jack_free ( struct hda_codec * codec )
{
if ( ! codec - > bus - > shutdown & & codec - > jacktbl . list ) {
struct hda_jack_tbl * jack = codec - > jacktbl . list ;
int i ;
for ( i = 0 ; i < codec - > jacktbl . used ; i + + , jack + + ) {
if ( jack - > jack )
snd_device_free ( codec - > bus - > card , jack - > jack ) ;
}
}
}
# endif /* CONFIG_SND_HDA_INPUT_JACK */