2012-08-17 20:57:22 +04:00
/*
* drivers / extcon / extcon - adc - jack . c
*
* Analog Jack extcon driver with ADC - based detection capability .
*
* 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 {
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 ;
} ;
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 ) ;
u32 state = 0 ;
int ret , adc_val ;
int i ;
ret = iio_read_channel_raw ( data - > chan , & adc_val ) ;
if ( ret < 0 ) {
2014-04-21 15:49:30 +04:00
dev_err ( & data - > edev - > 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 + + ) {
struct adc_jack_cond * def = & data - > adc_conditions [ i ] ;
if ( ! def - > state )
break ;
if ( def - > min_adc < = adc_val & & def - > max_adc > = adc_val ) {
state = def - > state ;
break ;
}
}
/* if no def has met, it means state = 0 (no cables attached) */
2014-04-21 15:49:30 +04:00
extcon_set_state ( data - > edev , state ) ;
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
}
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
if ( ! pdata - > adc_conditions | |
! pdata - > adc_conditions [ 0 ] . state ) {
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 */
for ( i = 0 ; data - > adc_conditions [ i ] . state ; i + + )
;
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 ) ;
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
}
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 ;
}
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 " ,
} ,
} ;
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 " ) ;