2009-01-07 20:31:10 +03:00
/*
* soc - jack . c - - ALSA SoC jack handling
*
* Copyright 2008 Wolfson Microelectronics PLC .
*
* Author : Mark Brown < broonie @ opensource . wolfsonmicro . com >
*
* 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 ; either version 2 of the License , or ( at your
* option ) any later version .
*/
# include <sound/jack.h>
# include <sound/soc.h>
2009-03-04 00:25:04 +03:00
# include <linux/gpio.h>
# include <linux/interrupt.h>
# include <linux/workqueue.h>
# include <linux/delay.h>
2010-12-05 15:22:46 +03:00
# include <trace/events/asoc.h>
2009-01-07 20:31:10 +03:00
/**
* snd_soc_jack_new - Create a new jack
* @ card : ASoC card
* @ id : an identifying string for this jack
* @ type : a bitmask of enum snd_jack_type values that can be detected by
* this jack
* @ jack : structure to use for the jack
*
* Creates a new jack object .
*
* Returns zero if successful , or a negative error code on failure .
* On success jack will be initialised .
*/
2010-03-17 23:15:21 +03:00
int snd_soc_jack_new ( struct snd_soc_codec * codec , const char * id , int type ,
2009-01-07 20:31:10 +03:00
struct snd_soc_jack * jack )
{
2010-03-17 23:15:21 +03:00
jack - > codec = codec ;
2009-01-07 20:31:10 +03:00
INIT_LIST_HEAD ( & jack - > pins ) ;
2011-02-09 12:14:17 +03:00
INIT_LIST_HEAD ( & jack - > jack_zones ) ;
2010-03-22 15:06:30 +03:00
BLOCKING_INIT_NOTIFIER_HEAD ( & jack - > notifier ) ;
2009-01-07 20:31:10 +03:00
2010-03-17 23:15:21 +03:00
return snd_jack_new ( codec - > card - > snd_card , id , type , & jack - > jack ) ;
2009-01-07 20:31:10 +03:00
}
EXPORT_SYMBOL_GPL ( snd_soc_jack_new ) ;
/**
* snd_soc_jack_report - Report the current status for a jack
*
* @ jack : the jack
* @ status : a bitmask of enum snd_jack_type values that are currently detected .
* @ mask : a bitmask of enum snd_jack_type values that being reported .
*
* If configured using snd_soc_jack_add_pins ( ) then the associated
* DAPM pins will be enabled or disabled as appropriate and DAPM
* synchronised .
*
* Note : This function uses mutexes and should be called from a
* context which can sleep ( such as a workqueue ) .
*/
void snd_soc_jack_report ( struct snd_soc_jack * jack , int status , int mask )
{
2009-10-17 10:32:56 +04:00
struct snd_soc_codec * codec ;
2010-11-05 16:53:46 +03:00
struct snd_soc_dapm_context * dapm ;
2009-01-07 20:31:10 +03:00
struct snd_soc_jack_pin * pin ;
int enable ;
int oldstatus ;
2010-12-05 15:22:46 +03:00
trace_snd_soc_jack_report ( jack , mask , status ) ;
2010-03-29 23:31:14 +04:00
if ( ! jack )
2009-01-07 20:31:10 +03:00
return ;
2010-03-29 23:31:14 +04:00
2010-03-17 23:15:21 +03:00
codec = jack - > codec ;
2010-11-05 16:53:46 +03:00
dapm = & codec - > dapm ;
2009-01-07 20:31:10 +03:00
mutex_lock ( & codec - > mutex ) ;
oldstatus = jack - > status ;
jack - > status & = ~ mask ;
2009-07-24 04:48:57 +04:00
jack - > status | = status & mask ;
2009-01-07 20:31:10 +03:00
2009-07-24 04:48:57 +04:00
/* The DAPM sync is expensive enough to be worth skipping.
* However , empty mask means pin synchronization is desired . */
if ( mask & & ( jack - > status = = oldstatus ) )
2009-01-07 20:31:10 +03:00
goto out ;
2010-12-05 15:22:46 +03:00
trace_snd_soc_jack_notify ( jack , status ) ;
2009-01-07 20:31:10 +03:00
list_for_each_entry ( pin , & jack - > pins , list ) {
2009-07-24 04:48:57 +04:00
enable = pin - > mask & jack - > status ;
2009-01-07 20:31:10 +03:00
if ( pin - > invert )
enable = ! enable ;
if ( enable )
2010-11-05 16:53:46 +03:00
snd_soc_dapm_enable_pin ( dapm , pin - > pin ) ;
2009-01-07 20:31:10 +03:00
else
2010-11-05 16:53:46 +03:00
snd_soc_dapm_disable_pin ( dapm , pin - > pin ) ;
2009-01-07 20:31:10 +03:00
}
2010-03-22 15:06:30 +03:00
/* Report before the DAPM sync to help users updating micbias status */
2011-02-22 23:42:35 +03:00
blocking_notifier_call_chain ( & jack - > notifier , status , jack ) ;
2010-03-22 15:06:30 +03:00
2010-11-05 16:53:46 +03:00
snd_soc_dapm_sync ( dapm ) ;
2009-01-07 20:31:10 +03:00
snd_jack_report ( jack - > jack , status ) ;
out :
mutex_unlock ( & codec - > mutex ) ;
}
EXPORT_SYMBOL_GPL ( snd_soc_jack_report ) ;
2011-02-09 12:14:17 +03:00
/**
* snd_soc_jack_add_zones - Associate voltage zones with jack
*
* @ jack : ASoC jack
* @ count : Number of zones
* @ zone : Array of zones
*
* After this function has been called the zones specified in the
* array will be associated with the jack .
*/
int snd_soc_jack_add_zones ( struct snd_soc_jack * jack , int count ,
struct snd_soc_jack_zone * zones )
{
int i ;
for ( i = 0 ; i < count ; i + + ) {
INIT_LIST_HEAD ( & zones [ i ] . list ) ;
list_add ( & ( zones [ i ] . list ) , & jack - > jack_zones ) ;
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_soc_jack_add_zones ) ;
/**
* snd_soc_jack_get_type - Based on the mic bias value , this function returns
* the type of jack from the zones delcared in the jack type
*
* @ micbias_voltage : mic bias voltage at adc channel when jack is plugged in
*
* Based on the mic bias value passed , this function helps identify
* the type of jack from the already delcared jack zones
*/
int snd_soc_jack_get_type ( struct snd_soc_jack * jack , int micbias_voltage )
{
struct snd_soc_jack_zone * zone ;
list_for_each_entry ( zone , & jack - > jack_zones , list ) {
if ( micbias_voltage > = zone - > min_mv & &
micbias_voltage < zone - > max_mv )
return zone - > jack_type ;
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_soc_jack_get_type ) ;
2009-01-07 20:31:10 +03:00
/**
* snd_soc_jack_add_pins - Associate DAPM pins with an ASoC jack
*
* @ jack : ASoC jack
* @ count : Number of pins
* @ pins : Array of pins
*
* After this function has been called the DAPM pins specified in the
* pins array will have their status updated to reflect the current
* state of the jack whenever the jack status is updated .
*/
int snd_soc_jack_add_pins ( struct snd_soc_jack * jack , int count ,
struct snd_soc_jack_pin * pins )
{
int i ;
for ( i = 0 ; i < count ; i + + ) {
if ( ! pins [ i ] . pin ) {
printk ( KERN_ERR " No name for pin %d \n " , i ) ;
return - EINVAL ;
}
if ( ! pins [ i ] . mask ) {
printk ( KERN_ERR " No mask for pin %d (%s) \n " , i ,
pins [ i ] . pin ) ;
return - EINVAL ;
}
INIT_LIST_HEAD ( & pins [ i ] . list ) ;
list_add ( & ( pins [ i ] . list ) , & jack - > pins ) ;
}
/* Update to reflect the last reported status; canned jack
* implementations are likely to set their state before the
* card has an opportunity to associate pins .
*/
snd_soc_jack_report ( jack , 0 , 0 ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_soc_jack_add_pins ) ;
2009-03-04 00:25:04 +03:00
2010-03-22 15:06:30 +03:00
/**
* snd_soc_jack_notifier_register - Register a notifier for jack status
*
* @ jack : ASoC jack
* @ nb : Notifier block to register
*
* Register for notification of the current status of the jack . Note
* that it is not possible to report additional jack events in the
* callback from the notifier , this is intended to support
* applications such as enabling electrical detection only when a
* mechanical detection event has occurred .
*/
void snd_soc_jack_notifier_register ( struct snd_soc_jack * jack ,
struct notifier_block * nb )
{
blocking_notifier_chain_register ( & jack - > notifier , nb ) ;
}
EXPORT_SYMBOL_GPL ( snd_soc_jack_notifier_register ) ;
/**
* snd_soc_jack_notifier_unregister - Unregister a notifier for jack status
*
* @ jack : ASoC jack
* @ nb : Notifier block to unregister
*
* Stop notifying for status changes .
*/
void snd_soc_jack_notifier_unregister ( struct snd_soc_jack * jack ,
struct notifier_block * nb )
{
blocking_notifier_chain_unregister ( & jack - > notifier , nb ) ;
}
EXPORT_SYMBOL_GPL ( snd_soc_jack_notifier_unregister ) ;
2009-03-04 00:25:04 +03:00
# ifdef CONFIG_GPIOLIB
/* gpio detect */
2009-03-06 14:32:17 +03:00
static void snd_soc_jack_gpio_detect ( struct snd_soc_jack_gpio * gpio )
2009-03-04 00:25:04 +03:00
{
struct snd_soc_jack * jack = gpio - > jack ;
int enable ;
int report ;
2011-02-10 18:22:23 +03:00
enable = gpio_get_value_cansleep ( gpio - > gpio ) ;
2009-03-04 00:25:04 +03:00
if ( gpio - > invert )
enable = ! enable ;
if ( enable )
report = gpio - > report ;
else
report = 0 ;
2009-11-12 11:14:04 +03:00
if ( gpio - > jack_status_check )
report = gpio - > jack_status_check ( ) ;
2009-03-04 00:25:04 +03:00
snd_soc_jack_report ( jack , report , gpio - > report ) ;
}
/* irq handler for gpio pin */
static irqreturn_t gpio_handler ( int irq , void * data )
{
struct snd_soc_jack_gpio * gpio = data ;
2010-11-14 22:25:09 +03:00
struct device * dev = gpio - > jack - > codec - > card - > dev ;
2010-12-05 15:22:46 +03:00
trace_snd_soc_jack_irq ( gpio - > name ) ;
2010-11-14 22:25:09 +03:00
if ( device_may_wakeup ( dev ) )
pm_wakeup_event ( dev , gpio - > debounce_time + 50 ) ;
2009-03-04 00:25:04 +03:00
2010-10-07 02:54:28 +04:00
schedule_delayed_work ( & gpio - > work ,
msecs_to_jiffies ( gpio - > debounce_time ) ) ;
2009-03-04 00:25:04 +03:00
return IRQ_HANDLED ;
}
/* gpio work */
static void gpio_work ( struct work_struct * work )
{
struct snd_soc_jack_gpio * gpio ;
2010-10-07 02:54:28 +04:00
gpio = container_of ( work , struct snd_soc_jack_gpio , work . work ) ;
2009-03-04 00:25:04 +03:00
snd_soc_jack_gpio_detect ( gpio ) ;
}
/**
* snd_soc_jack_add_gpios - Associate GPIO pins with an ASoC jack
*
* @ jack : ASoC jack
* @ count : number of pins
* @ gpios : array of gpio pins
*
* This function will request gpio , set data direction and request irq
* for each gpio in the array .
*/
int snd_soc_jack_add_gpios ( struct snd_soc_jack * jack , int count ,
struct snd_soc_jack_gpio * gpios )
{
int i , ret ;
for ( i = 0 ; i < count ; i + + ) {
if ( ! gpio_is_valid ( gpios [ i ] . gpio ) ) {
printk ( KERN_ERR " Invalid gpio %d \n " ,
gpios [ i ] . gpio ) ;
ret = - EINVAL ;
goto undo ;
}
if ( ! gpios [ i ] . name ) {
printk ( KERN_ERR " No name for gpio %d \n " ,
gpios [ i ] . gpio ) ;
ret = - EINVAL ;
goto undo ;
}
ret = gpio_request ( gpios [ i ] . gpio , gpios [ i ] . name ) ;
if ( ret )
goto undo ;
ret = gpio_direction_input ( gpios [ i ] . gpio ) ;
if ( ret )
goto err ;
2010-10-07 02:54:28 +04:00
INIT_DELAYED_WORK ( & gpios [ i ] . work , gpio_work ) ;
2009-07-31 23:15:25 +04:00
gpios [ i ] . jack = jack ;
2010-11-03 16:35:31 +03:00
ret = request_any_context_irq ( gpio_to_irq ( gpios [ i ] . gpio ) ,
gpio_handler ,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING ,
jack - > codec - > dev - > driver - > name ,
& gpios [ i ] ) ;
2009-03-04 00:25:04 +03:00
if ( ret )
goto err ;
2011-02-18 03:35:55 +03:00
if ( gpios [ i ] . wake ) {
2011-03-30 02:39:44 +04:00
ret = irq_set_irq_wake ( gpio_to_irq ( gpios [ i ] . gpio ) , 1 ) ;
2011-02-18 03:35:55 +03:00
if ( ret ! = 0 )
printk ( KERN_ERR
" Failed to mark GPIO %d as wake source: %d \n " ,
gpios [ i ] . gpio , ret ) ;
}
2009-07-24 04:48:57 +04:00
# ifdef CONFIG_GPIO_SYSFS
/* Expose GPIO value over sysfs for diagnostic purposes */
gpio_export ( gpios [ i ] . gpio , false ) ;
# endif
/* Update initial jack status */
snd_soc_jack_gpio_detect ( & gpios [ i ] ) ;
2009-03-04 00:25:04 +03:00
}
return 0 ;
err :
gpio_free ( gpios [ i ] . gpio ) ;
undo :
snd_soc_jack_free_gpios ( jack , i , gpios ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( snd_soc_jack_add_gpios ) ;
/**
* snd_soc_jack_free_gpios - Release GPIO pins ' resources of an ASoC jack
*
* @ jack : ASoC jack
* @ count : number of pins
* @ gpios : array of gpio pins
*
* Release gpio and irq resources for gpio pins associated with an ASoC jack .
*/
void snd_soc_jack_free_gpios ( struct snd_soc_jack * jack , int count ,
struct snd_soc_jack_gpio * gpios )
{
int i ;
for ( i = 0 ; i < count ; i + + ) {
2009-07-24 04:48:57 +04:00
# ifdef CONFIG_GPIO_SYSFS
gpio_unexport ( gpios [ i ] . gpio ) ;
# endif
2009-03-04 00:25:04 +03:00
free_irq ( gpio_to_irq ( gpios [ i ] . gpio ) , & gpios [ i ] ) ;
2010-10-07 02:54:28 +04:00
cancel_delayed_work_sync ( & gpios [ i ] . work ) ;
2009-03-04 00:25:04 +03:00
gpio_free ( gpios [ i ] . gpio ) ;
gpios [ i ] . jack = NULL ;
}
}
EXPORT_SYMBOL_GPL ( snd_soc_jack_free_gpios ) ;
# endif /* CONFIG_GPIOLIB */