2018-07-02 09:23:16 +03:00
// SPDX-License-Identifier: GPL-2.0+
//
// soc-jack.c -- ALSA SoC jack handling
//
// Copyright 2008 Wolfson Microelectronics PLC.
//
// Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
2009-01-07 20:31:10 +03:00
# include <sound/jack.h>
# include <sound/soc.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>
2017-03-22 02:50:43 +03:00
# include <linux/suspend.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_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
2023-08-11 17:25:11 +03:00
if ( ! jack | | ! jack - > jack )
2009-01-07 20:31:10 +03:00
return ;
2019-11-12 16:02:36 +03:00
trace_snd_soc_jack_report ( jack , mask , status ) ;
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 ) {
2021-08-16 07:56:10 +03:00
int 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
*
2022-04-08 07:11:14 +03:00
* @ jack : ASoC jack created with snd_soc_card_jack_new_pins ( )
2009-01-07 20:31:10 +03:00
* @ 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 ) ;
2015-04-27 16:21:00 +03:00
snd_jack_add_new_kctl ( jack - > jack , pins [ i ] . pin , pins [ i ] . mask ) ;
2009-01-07 20:31:10 +03:00
}
/* 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
2021-08-16 07:56:15 +03:00
struct jack_gpio_tbl {
int count ;
struct snd_soc_jack * jack ;
struct snd_soc_jack_gpio * gpios ;
} ;
2009-03-04 00:25:04 +03:00
/* 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 ) ;
}
2017-03-22 02:50:43 +03:00
static int snd_soc_jack_pm_notifier ( struct notifier_block * nb ,
unsigned long action , void * data )
{
struct snd_soc_jack_gpio * gpio =
container_of ( nb , struct snd_soc_jack_gpio , pm_notifier ) ;
switch ( action ) {
case PM_POST_SUSPEND :
case PM_POST_HIBERNATION :
case PM_POST_RESTORE :
/*
* Use workqueue so we do not have to care about running
* concurrently with work triggered by the interrupt handler .
*/
queue_delayed_work ( system_power_efficient_wq , & gpio - > work , 0 ) ;
break ;
}
return NOTIFY_DONE ;
}
2017-08-22 14:59:44 +03:00
static void jack_free_gpios ( struct snd_soc_jack * jack , int count ,
struct snd_soc_jack_gpio * gpios )
{
int i ;
for ( i = 0 ; i < count ; i + + ) {
gpiod_unexport ( gpios [ i ] . desc ) ;
unregister_pm_notifier ( & gpios [ i ] . pm_notifier ) ;
free_irq ( gpiod_to_irq ( gpios [ i ] . desc ) , & gpios [ i ] ) ;
cancel_delayed_work_sync ( & gpios [ i ] . work ) ;
gpiod_put ( gpios [ i ] . desc ) ;
gpios [ i ] . jack = NULL ;
}
}
static void jack_devres_free_gpios ( struct device * dev , void * res )
{
struct jack_gpio_tbl * tbl = res ;
jack_free_gpios ( tbl - > jack , tbl - > count , tbl - > gpios ) ;
}
2009-03-04 00:25:04 +03:00
/**
* 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 ;
2017-08-22 14:59:44 +03:00
struct jack_gpio_tbl * tbl ;
tbl = devres_alloc ( jack_devres_free_gpios , sizeof ( * tbl ) , GFP_KERNEL ) ;
if ( ! tbl )
return - ENOMEM ;
tbl - > jack = jack ;
tbl - > count = count ;
tbl - > gpios = gpios ;
2009-03-04 00:25:04 +03:00
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 ;
}
2015-05-23 01:09:20 +03:00
if ( gpios [ i ] . desc ) {
/* Already have a GPIO descriptor. */
goto got_gpio ;
} else if ( gpios [ i ] . gpiod_dev ) {
/* Get a GPIO descriptor */
2014-05-26 15:34:37 +04:00
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 {
2024-03-26 20:09:48 +03:00
dev_err ( jack - > card - > dev , " ASoC: Invalid gpio at index %d \n " , i ) ;
ret = - EINVAL ;
goto undo ;
2014-05-26 15:34:37 +04:00
}
2015-05-23 01:09:20 +03:00
got_gpio :
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 ,
2023-02-21 21:32:03 +03:00
IRQF_SHARED |
2010-11-03 16:35:31 +03:00
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 )
2024-03-26 20:09:48 +03:00
goto undo ;
2009-03-04 00:25:04 +03:00
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
}
2017-03-22 02:50:43 +03:00
/*
* Register PM notifier so we do not miss state transitions
* happening while system is asleep .
*/
gpios [ i ] . pm_notifier . notifier_call = snd_soc_jack_pm_notifier ;
register_pm_notifier ( & gpios [ i ] . pm_notifier ) ;
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
}
2017-08-22 14:59:44 +03:00
devres_add ( jack - > card - > dev , tbl ) ;
2009-03-04 00:25:04 +03:00
return 0 ;
undo :
2017-08-22 14:59:44 +03:00
jack_free_gpios ( jack , i , gpios ) ;
devres_free ( tbl ) ;
2009-03-04 00:25:04 +03:00
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 )
{
2017-08-22 14:59:44 +03:00
jack_free_gpios ( jack , count , gpios ) ;
devres_destroy ( jack - > card - > dev , jack_devres_free_gpios , NULL , NULL ) ;
2009-03-04 00:25:04 +03:00
}
EXPORT_SYMBOL_GPL ( snd_soc_jack_free_gpios ) ;
# endif /* CONFIG_GPIOLIB */