2012-08-17 20:57:22 +04:00
/*
* drivers / extcon / extcon - adc - jack . c
*
* Analog Jack extcon driver with ADC - based detection capability .
*
2016-07-18 10:16:29 +03:00
* Copyright ( C ) 2016 Samsung Electronics
* Chanwoo Choi < cw00 . choi @ samsung . com >
*
2012-08-17 20:57:22 +04:00
* Copyright ( C ) 2012 Samsung Electronics
* MyungJoo Ham < myungjoo . ham @ samsung . com >
*
* Modified for calling to IIO to get adc by < anish . singh @ samsung . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
*/
2012-10-02 04:16:53 +04:00
# include <linux/module.h>
2012-08-17 20:57:22 +04:00
# include <linux/slab.h>
# include <linux/device.h>
# include <linux/platform_device.h>
# include <linux/err.h>
# include <linux/interrupt.h>
# include <linux/workqueue.h>
# include <linux/iio/consumer.h>
# include <linux/extcon/extcon-adc-jack.h>
# include <linux/extcon.h>
/**
* struct adc_jack_data - internal data for adc_jack device driver
2013-08-31 08:16:49 +04:00
* @ edev : extcon device .
* @ cable_names : list of supported cables .
* @ adc_conditions : list of adc value conditions .
* @ num_conditions : size of adc_conditions .
* @ irq : irq number of attach / detach event ( 0 if not exist ) .
* @ handling_delay : interrupt handler will schedule extcon event
* handling at handling_delay jiffies .
* @ handler : extcon event handler called by interrupt handler .
* @ chan : iio channel being queried .
2012-08-17 20:57:22 +04:00
*/
struct adc_jack_data {
2016-06-30 11:54:00 +03:00
struct device * dev ;
2014-04-21 15:49:30 +04:00
struct extcon_dev * edev ;
2012-08-17 20:57:22 +04:00
2015-06-12 05:10:06 +03:00
const unsigned int * * cable_names ;
2012-08-17 20:57:22 +04:00
struct adc_jack_cond * adc_conditions ;
int num_conditions ;
int irq ;
unsigned long handling_delay ; /* in jiffies */
struct delayed_work handler ;
struct iio_channel * chan ;
2016-06-30 11:54:00 +03:00
bool wakeup_source ;
2012-08-17 20:57:22 +04:00
} ;
static void adc_jack_handler ( struct work_struct * work )
{
struct adc_jack_data * data = container_of ( to_delayed_work ( work ) ,
struct adc_jack_data ,
handler ) ;
2016-07-18 10:16:29 +03:00
struct adc_jack_cond * def ;
2012-08-17 20:57:22 +04:00
int ret , adc_val ;
int i ;
ret = iio_read_channel_raw ( data - > chan , & adc_val ) ;
if ( ret < 0 ) {
2016-12-26 14:47:13 +03:00
dev_err ( data - > dev , " read channel() error: %d \n " , ret ) ;
2012-08-17 20:57:22 +04:00
return ;
}
/* Get state from adc value with adc_conditions */
for ( i = 0 ; i < data - > num_conditions ; i + + ) {
2016-07-18 10:16:29 +03:00
def = & data - > adc_conditions [ i ] ;
2012-08-17 20:57:22 +04:00
if ( def - > min_adc < = adc_val & & def - > max_adc > = adc_val ) {
2016-08-16 09:55:34 +03:00
extcon_set_state_sync ( data - > edev , def - > id , true ) ;
2016-07-18 10:16:29 +03:00
return ;
2012-08-17 20:57:22 +04:00
}
}
2016-07-18 10:16:29 +03:00
/* Set the detached state if adc value is not included in the range */
for ( i = 0 ; i < data - > num_conditions ; i + + ) {
def = & data - > adc_conditions [ i ] ;
2016-08-16 09:55:34 +03:00
extcon_set_state_sync ( data - > edev , def - > id , false ) ;
2016-07-18 10:16:29 +03:00
}
2012-08-17 20:57:22 +04:00
}
static irqreturn_t adc_jack_irq_thread ( int irq , void * _data )
{
struct adc_jack_data * data = _data ;
2013-07-19 21:47:35 +04:00
queue_delayed_work ( system_power_efficient_wq ,
& data - > handler , data - > handling_delay ) ;
2012-08-17 20:57:22 +04:00
return IRQ_HANDLED ;
}
2012-11-19 22:23:21 +04:00
static int adc_jack_probe ( struct platform_device * pdev )
2012-08-17 20:57:22 +04:00
{
struct adc_jack_data * data ;
2013-09-11 08:22:18 +04:00
struct adc_jack_pdata * pdata = dev_get_platdata ( & pdev - > dev ) ;
2012-08-17 20:57:22 +04:00
int i , err = 0 ;
data = devm_kzalloc ( & pdev - > dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
if ( ! pdata - > cable_names ) {
dev_err ( & pdev - > dev , " error: cable_names not defined. \n " ) ;
2014-04-21 14:10:09 +04:00
return - EINVAL ;
2012-08-17 20:57:22 +04:00
}
2016-06-30 11:54:00 +03:00
data - > dev = & pdev - > dev ;
2014-04-21 15:49:30 +04:00
data - > edev = devm_extcon_dev_allocate ( & pdev - > dev , pdata - > cable_names ) ;
if ( IS_ERR ( data - > edev ) ) {
dev_err ( & pdev - > dev , " failed to allocate extcon device \n " ) ;
return - ENOMEM ;
}
2012-08-17 20:57:22 +04:00
2016-07-18 10:16:29 +03:00
if ( ! pdata - > adc_conditions ) {
2012-08-17 20:57:22 +04:00
dev_err ( & pdev - > dev , " error: adc_conditions not defined. \n " ) ;
2014-04-21 14:10:09 +04:00
return - EINVAL ;
2012-08-17 20:57:22 +04:00
}
data - > adc_conditions = pdata - > adc_conditions ;
/* Check the length of array and set num_conditions */
2016-07-18 10:16:29 +03:00
for ( i = 0 ; data - > adc_conditions [ i ] . id ! = EXTCON_NONE ; i + + ) ;
2012-08-17 20:57:22 +04:00
data - > num_conditions = i ;
2013-02-05 00:26:00 +04:00
data - > chan = iio_channel_get ( & pdev - > dev , pdata - > consumer_channel ) ;
2014-04-21 14:10:09 +04:00
if ( IS_ERR ( data - > chan ) )
return PTR_ERR ( data - > chan ) ;
2012-08-17 20:57:22 +04:00
data - > handling_delay = msecs_to_jiffies ( pdata - > handling_delay_ms ) ;
2016-06-30 11:54:00 +03:00
data - > wakeup_source = pdata - > wakeup_source ;
2012-08-17 20:57:22 +04:00
2012-10-02 20:54:49 +04:00
INIT_DEFERRABLE_WORK ( & data - > handler , adc_jack_handler ) ;
2012-08-17 20:57:22 +04:00
platform_set_drvdata ( pdev , data ) ;
2014-04-21 15:49:30 +04:00
err = devm_extcon_dev_register ( & pdev - > dev , data - > edev ) ;
2012-08-17 20:57:22 +04:00
if ( err )
2014-04-21 14:10:09 +04:00
return err ;
2012-08-17 20:57:22 +04:00
data - > irq = platform_get_irq ( pdev , 0 ) ;
if ( ! data - > irq ) {
dev_err ( & pdev - > dev , " platform_get_irq failed \n " ) ;
2014-04-21 14:10:09 +04:00
return - ENODEV ;
2012-08-17 20:57:22 +04:00
}
err = request_any_context_irq ( data - > irq , adc_jack_irq_thread ,
pdata - > irq_flags , pdata - > name , data ) ;
2012-10-02 04:14:59 +04:00
if ( err < 0 ) {
2012-08-17 20:57:22 +04:00
dev_err ( & pdev - > dev , " error: irq %d \n " , data - > irq ) ;
2014-04-21 14:10:09 +04:00
return err ;
2012-08-17 20:57:22 +04:00
}
2016-06-30 11:54:00 +03:00
if ( data - > wakeup_source )
device_init_wakeup ( & pdev - > dev , 1 ) ;
2016-07-05 16:56:21 +03:00
adc_jack_handler ( & data - > handler . work ) ;
2012-10-02 04:14:59 +04:00
return 0 ;
2012-08-17 20:57:22 +04:00
}
2012-11-19 22:25:49 +04:00
static int adc_jack_remove ( struct platform_device * pdev )
2012-08-17 20:57:22 +04:00
{
struct adc_jack_data * data = platform_get_drvdata ( pdev ) ;
free_irq ( data - > irq , data ) ;
cancel_work_sync ( & data - > handler . work ) ;
2014-12-17 18:59:27 +03:00
iio_channel_release ( data - > chan ) ;
2012-08-17 20:57:22 +04:00
return 0 ;
}
2016-06-30 11:54:00 +03:00
# ifdef CONFIG_PM_SLEEP
static int adc_jack_suspend ( struct device * dev )
{
struct adc_jack_data * data = dev_get_drvdata ( dev ) ;
cancel_delayed_work_sync ( & data - > handler ) ;
if ( device_may_wakeup ( data - > dev ) )
enable_irq_wake ( data - > irq ) ;
return 0 ;
}
static int adc_jack_resume ( struct device * dev )
{
struct adc_jack_data * data = dev_get_drvdata ( dev ) ;
if ( device_may_wakeup ( data - > dev ) )
disable_irq_wake ( data - > irq ) ;
return 0 ;
}
# endif /* CONFIG_PM_SLEEP */
static SIMPLE_DEV_PM_OPS ( adc_jack_pm_ops ,
adc_jack_suspend , adc_jack_resume ) ;
2012-08-17 20:57:22 +04:00
static struct platform_driver adc_jack_driver = {
. probe = adc_jack_probe ,
2012-11-19 22:20:06 +04:00
. remove = adc_jack_remove ,
2012-08-17 20:57:22 +04:00
. driver = {
. name = " adc-jack " ,
2016-06-30 11:54:00 +03:00
. pm = & adc_jack_pm_ops ,
2012-08-17 20:57:22 +04:00
} ,
} ;
module_platform_driver ( adc_jack_driver ) ;
2012-10-02 04:16:53 +04:00
MODULE_AUTHOR ( " MyungJoo Ham <myungjoo.ham@samsung.com> " ) ;
MODULE_DESCRIPTION ( " ADC Jack extcon driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;