2012-04-20 09:16:23 +04:00
/*
* drivers / extcon / extcon_gpio . c
*
* Single - state GPIO extcon driver based on extcon class
*
* Copyright ( C ) 2008 Google , Inc .
* Author : Mike Lockwood < lockwood @ android . com >
*
* Modified by MyungJoo Ham < myungjoo . ham @ samsung . com > to support extcon
* ( originally switch class is supported )
*
* This software is licensed under the terms of the GNU General Public
* License version 2 , as published by the Free Software Foundation , and
* may be copied , distributed , and modified under those terms .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <linux/workqueue.h>
# include <linux/gpio.h>
# include <linux/extcon.h>
2013-02-13 13:35:00 +04:00
# include <linux/extcon/extcon-gpio.h>
2012-04-20 09:16:23 +04:00
struct gpio_extcon_data {
2014-04-21 15:51:08 +04:00
struct extcon_dev * edev ;
2012-04-20 09:16:23 +04:00
unsigned gpio ;
2013-09-12 03:49:54 +04:00
bool gpio_active_low ;
2012-04-20 09:16:23 +04:00
const char * state_on ;
const char * state_off ;
int irq ;
struct delayed_work work ;
unsigned long debounce_jiffies ;
2014-01-09 04:50:13 +04:00
bool check_on_resume ;
2012-04-20 09:16:23 +04:00
} ;
static void gpio_extcon_work ( struct work_struct * work )
{
int state ;
struct gpio_extcon_data * data =
container_of ( to_delayed_work ( work ) , struct gpio_extcon_data ,
work ) ;
state = gpio_get_value ( data - > gpio ) ;
2013-09-12 03:49:54 +04:00
if ( data - > gpio_active_low )
state = ! state ;
2014-04-21 15:51:08 +04:00
extcon_set_state ( data - > edev , state ) ;
2012-04-20 09:16:23 +04:00
}
static irqreturn_t gpio_irq_handler ( int irq , void * dev_id )
{
struct gpio_extcon_data * extcon_data = dev_id ;
2013-07-19 21:47:34 +04:00
queue_delayed_work ( system_power_efficient_wq , & extcon_data - > work ,
2012-04-20 09:16:23 +04:00
extcon_data - > debounce_jiffies ) ;
return IRQ_HANDLED ;
}
static ssize_t extcon_gpio_print_state ( struct extcon_dev * edev , char * buf )
{
2014-04-21 15:51:08 +04:00
struct device * dev = edev - > dev . parent ;
struct gpio_extcon_data * extcon_data = dev_get_drvdata ( dev ) ;
2012-04-20 09:16:23 +04:00
const char * state ;
2014-04-21 15:51:08 +04:00
2012-04-20 09:16:23 +04:00
if ( extcon_get_state ( edev ) )
state = extcon_data - > state_on ;
else
state = extcon_data - > state_off ;
if ( state )
return sprintf ( buf , " %s \n " , state ) ;
return - EINVAL ;
}
2012-11-19 22:23:21 +04:00
static int gpio_extcon_probe ( struct platform_device * pdev )
2012-04-20 09:16:23 +04:00
{
2013-09-11 08:22:18 +04:00
struct gpio_extcon_platform_data * pdata = dev_get_platdata ( & pdev - > dev ) ;
2012-04-20 09:16:23 +04:00
struct gpio_extcon_data * extcon_data ;
2013-08-30 08:29:33 +04:00
int ret ;
2012-04-20 09:16:23 +04:00
if ( ! pdata )
return - EBUSY ;
if ( ! pdata - > irq_flags ) {
dev_err ( & pdev - > dev , " IRQ flag is not specified. \n " ) ;
return - EINVAL ;
}
extcon_data = devm_kzalloc ( & pdev - > dev , sizeof ( struct gpio_extcon_data ) ,
GFP_KERNEL ) ;
if ( ! extcon_data )
return - ENOMEM ;
2014-04-21 15:51:08 +04:00
extcon_data - > edev = devm_extcon_dev_allocate ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( extcon_data - > edev ) ) {
dev_err ( & pdev - > dev , " failed to allocate extcon device \n " ) ;
return - ENOMEM ;
}
extcon_data - > edev - > name = pdata - > name ;
extcon_data - > edev - > dev . parent = & pdev - > dev ;
2012-04-20 09:16:23 +04:00
extcon_data - > gpio = pdata - > gpio ;
2013-09-12 03:49:54 +04:00
extcon_data - > gpio_active_low = pdata - > gpio_active_low ;
2012-04-20 09:16:23 +04:00
extcon_data - > state_on = pdata - > state_on ;
extcon_data - > state_off = pdata - > state_off ;
2014-01-09 04:50:13 +04:00
extcon_data - > check_on_resume = pdata - > check_on_resume ;
2012-04-20 09:16:23 +04:00
if ( pdata - > state_on & & pdata - > state_off )
2014-04-21 15:51:08 +04:00
extcon_data - > edev - > print_state = extcon_gpio_print_state ;
2013-11-22 21:26:01 +04:00
ret = devm_gpio_request_one ( & pdev - > dev , extcon_data - > gpio , GPIOF_DIR_IN ,
pdev - > name ) ;
if ( ret < 0 )
return ret ;
2013-09-11 06:16:18 +04:00
if ( pdata - > debounce ) {
ret = gpio_set_debounce ( extcon_data - > gpio ,
pdata - > debounce * 1000 ) ;
if ( ret < 0 )
extcon_data - > debounce_jiffies =
msecs_to_jiffies ( pdata - > debounce ) ;
}
2012-04-20 09:16:23 +04:00
2014-04-21 15:51:08 +04:00
ret = devm_extcon_dev_register ( & pdev - > dev , extcon_data - > edev ) ;
2012-04-20 09:16:23 +04:00
if ( ret < 0 )
2012-06-16 07:56:24 +04:00
return ret ;
2012-04-20 09:16:23 +04:00
INIT_DELAYED_WORK ( & extcon_data - > work , gpio_extcon_work ) ;
extcon_data - > irq = gpio_to_irq ( extcon_data - > gpio ) ;
2014-04-21 14:10:10 +04:00
if ( extcon_data - > irq < 0 )
return extcon_data - > irq ;
2012-04-20 09:16:23 +04:00
ret = request_any_context_irq ( extcon_data - > irq , gpio_irq_handler ,
pdata - > irq_flags , pdev - > name ,
extcon_data ) ;
if ( ret < 0 )
2014-04-21 14:10:10 +04:00
return ret ;
2012-04-20 09:16:23 +04:00
2012-06-16 07:55:18 +04:00
platform_set_drvdata ( pdev , extcon_data ) ;
2012-04-20 09:16:23 +04:00
/* Perform initial detection */
gpio_extcon_work ( & extcon_data - > work . work ) ;
return 0 ;
}
2012-11-19 22:25:49 +04:00
static int gpio_extcon_remove ( struct platform_device * pdev )
2012-04-20 09:16:23 +04:00
{
struct gpio_extcon_data * extcon_data = platform_get_drvdata ( pdev ) ;
cancel_delayed_work_sync ( & extcon_data - > work ) ;
2012-06-16 07:55:18 +04:00
free_irq ( extcon_data - > irq , extcon_data ) ;
2012-04-20 09:16:23 +04:00
return 0 ;
}
2014-01-09 04:50:13 +04:00
# ifdef CONFIG_PM_SLEEP
static int gpio_extcon_resume ( struct device * dev )
{
struct gpio_extcon_data * extcon_data ;
extcon_data = dev_get_drvdata ( dev ) ;
if ( extcon_data - > check_on_resume )
queue_delayed_work ( system_power_efficient_wq ,
& extcon_data - > work , extcon_data - > debounce_jiffies ) ;
return 0 ;
}
# endif
2014-02-27 15:37:15 +04:00
static SIMPLE_DEV_PM_OPS ( gpio_extcon_pm_ops , NULL , gpio_extcon_resume ) ;
2014-01-09 04:50:13 +04:00
2012-05-03 02:38:44 +04:00
static struct platform_driver gpio_extcon_driver = {
2012-04-20 09:16:23 +04:00
. probe = gpio_extcon_probe ,
2012-11-19 22:20:06 +04:00
. remove = gpio_extcon_remove ,
2012-04-20 09:16:23 +04:00
. driver = {
. name = " extcon-gpio " ,
. owner = THIS_MODULE ,
2014-01-09 04:50:13 +04:00
. pm = & gpio_extcon_pm_ops ,
2012-04-20 09:16:23 +04:00
} ,
} ;
2012-05-03 02:38:44 +04:00
module_platform_driver ( gpio_extcon_driver ) ;
2012-04-20 09:16:23 +04:00
MODULE_AUTHOR ( " Mike Lockwood <lockwood@android.com> " ) ;
MODULE_DESCRIPTION ( " GPIO extcon driver " ) ;
MODULE_LICENSE ( " GPL " ) ;