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>
2014-05-26 15:34:36 +04:00
# include <linux/gpio/consumer.h>
2009-03-04 00:25:04 +03:00
# include <linux/interrupt.h>
# include <linux/workqueue.h>
# include <linux/delay.h>
2011-09-22 17:34:58 +04:00
# include <linux/export.h>
2010-12-05 15:22:46 +03:00
# include <trace/events/asoc.h>
2009-01-07 20:31:10 +03:00
/**
2015-03-04 12:33:17 +03:00
* snd_soc_card_jack_new - Create a new jack
* @ card : ASoC card
2009-01-07 20:31:10 +03:00
* @ 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
2015-03-04 12:33:17 +03:00
* @ pins : Array of jack pins to be added to the jack or NULL
* @ num_pins : Number of elements in the @ pins array
2009-01-07 20:31:10 +03:00
*
* Creates a new jack object .
*
* Returns zero if successful , or a negative error code on failure .
* On success jack will be initialised .
*/
2015-03-04 12:33:17 +03:00
int snd_soc_card_jack_new ( struct snd_soc_card * card , const char * id , int type ,
struct snd_soc_jack * jack , struct snd_soc_jack_pin * pins ,
unsigned int num_pins )
2009-01-07 20:31:10 +03:00
{
2015-03-04 12:33:17 +03:00
int ret ;
2012-03-12 18:07:49 +04:00
mutex_init ( & jack - > mutex ) ;
2015-03-04 12:33:17 +03:00
jack - > card = card ;
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
2015-03-04 12:33:17 +03:00
ret = snd_jack_new ( card - > snd_card , id , type , & jack - > jack ) ;
if ( ret )
return ret ;
if ( num_pins )
return snd_soc_jack_add_pins ( jack , num_pins , pins ) ;
return 0 ;
2009-01-07 20:31:10 +03:00
}
2015-03-04 12:33:17 +03:00
EXPORT_SYMBOL_GPL ( snd_soc_card_jack_new ) ;
2009-01-07 20:31:10 +03:00
/**
* 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 )
{
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 ;
2013-10-21 17:37:34 +04:00
unsigned int sync = 0 ;
2009-01-07 20:31:10 +03:00
int enable ;
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
2015-03-04 12:33:17 +03:00
dapm = & jack - > card - > dapm ;
2009-01-07 20:31:10 +03:00
2012-03-12 18:07:49 +04:00
mutex_lock ( & jack - > mutex ) ;
2009-01-07 20:31:10 +03:00
jack - > status & = ~ mask ;
2009-07-24 04:48:57 +04:00
jack - > status | = status & mask ;
2009-01-07 20:31:10 +03:00
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 ) ;
2013-10-21 17:37:34 +04:00
/* we need to sync for this case only */
sync = 1 ;
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 */
2012-08-13 19:28:36 +04:00
blocking_notifier_call_chain ( & jack - > notifier , jack - > status , jack ) ;
2010-03-22 15:06:30 +03:00
2013-10-21 17:37:34 +04:00
if ( sync )
snd_soc_dapm_sync ( dapm ) ;
2009-01-07 20:31:10 +03:00
2011-09-04 19:18:18 +04:00
snd_jack_report ( jack - > jack , jack - > status ) ;
2009-01-07 20:31:10 +03:00
2012-03-12 18:07:49 +04:00
mutex_unlock ( & jack - > mutex ) ;
2009-01-07 20:31:10 +03:00
}
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
2014-11-11 18:52:23 +03:00
* @ zones : Array of zones
2011-02-09 12:14:17 +03:00
*
* 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
2012-10-02 16:08:19 +04:00
* the type of jack from the zones declared in the jack type
2011-02-09 12:14:17 +03:00
*
2012-10-02 16:08:19 +04:00
* @ jack : ASoC jack
2011-02-09 12:14:17 +03:00
* @ micbias_voltage : mic bias voltage at adc channel when jack is plugged in
*
* Based on the mic bias value passed , this function helps identify
2012-10-02 16:08:19 +04:00
* the type of jack from the already declared jack zones
2011-02-09 12:14:17 +03:00
*/
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 ) {
2015-03-04 12:33:17 +03:00
dev_err ( jack - > card - > dev , " ASoC: No name for pin %d \n " ,
2012-11-19 18:39:14 +04:00
i ) ;
2009-01-07 20:31:10 +03:00
return - EINVAL ;
}
if ( ! pins [ i ] . mask ) {
2015-03-04 12:33:17 +03:00
dev_err ( jack - > card - > dev , " ASoC: No mask for pin %d "
2012-11-19 18:39:14 +04:00
" (%s) \n " , i , pins [ i ] . pin ) ;
2009-01-07 20:31:10 +03:00
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 ;
2014-05-26 15:34:36 +04:00
enable = gpiod_get_value_cansleep ( gpio - > desc ) ;
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 )
2014-02-23 10:40:44 +04:00
report = gpio - > jack_status_check ( gpio - > data ) ;
2009-11-12 11:14:04 +03:00
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 ;
2015-03-04 12:33:17 +03:00
struct device * dev = gpio - > jack - > card - > dev ;
2010-11-14 22:25:09 +03:00
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
2013-07-19 01:47:10 +04:00
queue_delayed_work ( system_power_efficient_wq , & gpio - > work ,
2010-10-07 02:54:28 +04:00
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 ( ! gpios [ i ] . name ) {
2015-03-04 12:33:17 +03:00
dev_err ( jack - > card - > dev ,
2014-05-26 15:34:37 +04:00
" ASoC: No name for gpio at index %d \n " , i ) ;
2009-03-04 00:25:04 +03:00
ret = - EINVAL ;
goto undo ;
}
2014-05-26 15:34:37 +04:00
if ( gpios [ i ] . gpiod_dev ) {
/* GPIO descriptor */
gpios [ i ] . desc = gpiod_get_index ( gpios [ i ] . gpiod_dev ,
gpios [ i ] . name ,
2014-10-23 12:15:18 +04:00
gpios [ i ] . idx , GPIOD_IN ) ;
2014-05-26 15:34:37 +04:00
if ( IS_ERR ( gpios [ i ] . desc ) ) {
ret = PTR_ERR ( gpios [ i ] . desc ) ;
dev_err ( gpios [ i ] . gpiod_dev ,
" ASoC: Cannot get gpio at index %d: %d " ,
i , ret ) ;
goto undo ;
}
} else {
/* legacy GPIO number */
if ( ! gpio_is_valid ( gpios [ i ] . gpio ) ) {
2015-03-04 12:33:17 +03:00
dev_err ( jack - > card - > dev ,
2014-05-26 15:34:37 +04:00
" ASoC: Invalid gpio %d \n " ,
gpios [ i ] . gpio ) ;
ret = - EINVAL ;
goto undo ;
}
2014-10-23 12:15:18 +04:00
ret = gpio_request_one ( gpios [ i ] . gpio , GPIOF_IN ,
gpios [ i ] . name ) ;
2014-05-26 15:34:37 +04:00
if ( ret )
goto undo ;
gpios [ i ] . desc = gpio_to_desc ( gpios [ i ] . gpio ) ;
}
2014-05-26 15:34:36 +04:00
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 ;
2014-05-26 15:34:36 +04:00
ret = request_any_context_irq ( gpiod_to_irq ( gpios [ i ] . desc ) ,
2010-11-03 16:35:31 +03:00
gpio_handler ,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING ,
2011-04-02 00:50:43 +04:00
gpios [ i ] . name ,
2010-11-03 16:35:31 +03:00
& gpios [ i ] ) ;
2011-08-13 15:15:01 +04:00
if ( ret < 0 )
2009-03-04 00:25:04 +03:00
goto err ;
2011-02-18 03:35:55 +03:00
if ( gpios [ i ] . wake ) {
2014-05-26 15:34:36 +04:00
ret = irq_set_irq_wake ( gpiod_to_irq ( gpios [ i ] . desc ) , 1 ) ;
2011-02-18 03:35:55 +03:00
if ( ret ! = 0 )
2015-03-04 12:33:17 +03:00
dev_err ( jack - > card - > dev ,
2014-05-26 15:34:37 +04:00
" ASoC: Failed to mark GPIO at index %d as wake source: %d \n " ,
i , ret ) ;
2011-02-18 03:35:55 +03:00
}
2009-07-24 04:48:57 +04:00
/* Expose GPIO value over sysfs for diagnostic purposes */
2014-05-26 15:34:36 +04:00
gpiod_export ( gpios [ i ] . desc , false ) ;
2009-07-24 04:48:57 +04:00
/* Update initial jack status */
2014-02-23 10:44:52 +04:00
schedule_delayed_work ( & gpios [ i ] . work ,
msecs_to_jiffies ( gpios [ i ] . debounce_time ) ) ;
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 ) ;
2014-05-26 15:34:37 +04:00
/**
* snd_soc_jack_add_gpiods - Associate GPIO descriptor pins with an ASoC jack
*
* @ gpiod_dev : GPIO consumer device
* @ 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_gpiods ( struct device * gpiod_dev ,
struct snd_soc_jack * jack ,
int count , struct snd_soc_jack_gpio * gpios )
{
int i ;
for ( i = 0 ; i < count ; i + + )
gpios [ i ] . gpiod_dev = gpiod_dev ;
return snd_soc_jack_add_gpios ( jack , count , gpios ) ;
}
EXPORT_SYMBOL_GPL ( snd_soc_jack_add_gpiods ) ;
2009-03-04 00:25:04 +03:00
/**
* 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 + + ) {
2014-05-26 15:34:36 +04:00
gpiod_unexport ( gpios [ i ] . desc ) ;
free_irq ( gpiod_to_irq ( gpios [ i ] . desc ) , & gpios [ i ] ) ;
2010-10-07 02:54:28 +04:00
cancel_delayed_work_sync ( & gpios [ i ] . work ) ;
2014-05-26 15:34:36 +04:00
gpiod_put ( gpios [ i ] . desc ) ;
2009-03-04 00:25:04 +03:00
gpios [ i ] . jack = NULL ;
}
}
EXPORT_SYMBOL_GPL ( snd_soc_jack_free_gpios ) ;
# endif /* CONFIG_GPIOLIB */