2012-04-20 09:16:23 +04:00
/*
2015-09-29 16:59:55 +03:00
* extcon_gpio . c - Single - state GPIO extcon driver based on extcon class
2012-04-20 09:16:23 +04:00
*
* 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 .
2015-09-29 16:59:55 +03:00
*/
2012-04-20 09:16:23 +04:00
2017-09-21 06:11:24 +03:00
# include <linux/extcon-provider.h>
2014-09-09 08:14:34 +04:00
# include <linux/gpio.h>
2015-09-30 08:57:57 +03:00
# include <linux/gpio/consumer.h>
2012-04-20 09:16:23 +04:00
# include <linux/init.h>
# include <linux/interrupt.h>
2014-09-09 08:14:34 +04:00
# include <linux/kernel.h>
# include <linux/module.h>
2012-04-20 09:16:23 +04:00
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <linux/workqueue.h>
2018-02-12 11:53:12 +03:00
/**
* struct gpio_extcon_pdata - A simple GPIO - controlled extcon device .
* @ extcon_id : The unique id of specific external connector .
* @ gpio : Corresponding GPIO .
* @ gpio_active_low : Boolean describing whether gpio active state is 1 or 0
* If true , low state of gpio means active .
* If false , high state of gpio means active .
* @ debounce : Debounce time for GPIO IRQ in ms .
* @ irq_flags : IRQ Flags ( e . g . , IRQF_TRIGGER_LOW ) .
* @ check_on_resume : Boolean describing whether to check the state of gpio
* while resuming from sleep .
*/
struct gpio_extcon_pdata {
unsigned int extcon_id ;
unsigned gpio ;
bool gpio_active_low ;
unsigned long debounce ;
unsigned long irq_flags ;
bool check_on_resume ;
} ;
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
int irq ;
struct delayed_work work ;
unsigned long debounce_jiffies ;
2015-09-29 14:53:12 +03:00
2015-09-30 08:57:57 +03:00
struct gpio_desc * id_gpiod ;
2015-09-29 16:59:55 +03:00
struct gpio_extcon_pdata * pdata ;
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 ) ;
2015-09-30 08:57:57 +03:00
state = gpiod_get_value_cansleep ( data - > id_gpiod ) ;
2015-09-29 14:53:12 +03:00
if ( data - > pdata - > gpio_active_low )
2013-09-12 03:49:54 +04:00
state = ! state ;
2016-09-15 13:16:11 +03:00
2016-08-16 09:55:34 +03:00
extcon_set_state_sync ( data - > edev , data - > pdata - > extcon_id , state ) ;
2012-04-20 09:16:23 +04:00
}
static irqreturn_t gpio_irq_handler ( int irq , void * dev_id )
{
2015-09-29 14:53:12 +03:00
struct gpio_extcon_data * data = dev_id ;
2012-04-20 09:16:23 +04:00
2015-09-29 14:53:12 +03:00
queue_delayed_work ( system_power_efficient_wq , & data - > work ,
data - > debounce_jiffies ) ;
2012-04-20 09:16:23 +04:00
return IRQ_HANDLED ;
}
2015-09-30 08:57:57 +03:00
static int gpio_extcon_init ( struct device * dev , struct gpio_extcon_data * data )
{
struct gpio_extcon_pdata * pdata = data - > pdata ;
int ret ;
ret = devm_gpio_request_one ( dev , pdata - > gpio , GPIOF_DIR_IN ,
dev_name ( dev ) ) ;
if ( ret < 0 )
return ret ;
data - > id_gpiod = gpio_to_desc ( pdata - > gpio ) ;
if ( ! data - > id_gpiod )
return - EINVAL ;
if ( pdata - > debounce ) {
ret = gpiod_set_debounce ( data - > id_gpiod ,
pdata - > debounce * 1000 ) ;
if ( ret < 0 )
data - > debounce_jiffies =
msecs_to_jiffies ( pdata - > debounce ) ;
}
data - > irq = gpiod_to_irq ( data - > id_gpiod ) ;
if ( data - > irq < 0 )
return data - > irq ;
return 0 ;
}
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
{
2015-09-29 16:59:55 +03:00
struct gpio_extcon_pdata * pdata = dev_get_platdata ( & pdev - > dev ) ;
2015-09-29 14:53:12 +03:00
struct gpio_extcon_data * data ;
2013-08-30 08:29:33 +04:00
int ret ;
2012-04-20 09:16:23 +04:00
if ( ! pdata )
return - EBUSY ;
2015-09-29 16:57:24 +03:00
if ( ! pdata - > irq_flags | | pdata - > extcon_id > EXTCON_NONE )
2012-04-20 09:16:23 +04:00
return - EINVAL ;
2015-09-29 14:53:12 +03:00
data = devm_kzalloc ( & pdev - > dev , sizeof ( struct gpio_extcon_data ) ,
2012-04-20 09:16:23 +04:00
GFP_KERNEL ) ;
2015-09-29 14:53:12 +03:00
if ( ! data )
2012-04-20 09:16:23 +04:00
return - ENOMEM ;
2015-09-29 14:53:12 +03:00
data - > pdata = pdata ;
2014-04-21 15:51:08 +04:00
2015-09-30 08:57:57 +03:00
/* Initialize the gpio */
ret = gpio_extcon_init ( & pdev - > dev , data ) ;
2013-11-22 21:26:01 +04:00
if ( ret < 0 )
return ret ;
2015-09-30 08:57:57 +03:00
/* Allocate the memory of extcon devie and register extcon device */
data - > edev = devm_extcon_dev_allocate ( & pdev - > dev , & pdata - > extcon_id ) ;
if ( IS_ERR ( data - > edev ) ) {
dev_err ( & pdev - > dev , " failed to allocate extcon device \n " ) ;
return - ENOMEM ;
2013-09-11 06:16:18 +04:00
}
2012-04-20 09:16:23 +04:00
2015-09-29 14:53:12 +03:00
ret = devm_extcon_dev_register ( & pdev - > dev , 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
2015-09-29 14:53:12 +03:00
INIT_DELAYED_WORK ( & data - > work , gpio_extcon_work ) ;
2012-04-20 09:16:23 +04:00
2015-09-30 08:57:57 +03:00
/*
2015-12-24 08:34:07 +03:00
* Request the interrupt of gpio to detect whether external connector
2015-09-30 08:57:57 +03:00
* is attached or detached .
*/
2015-09-29 14:53:12 +03:00
ret = devm_request_any_context_irq ( & pdev - > dev , data - > irq ,
2015-09-25 16:40:51 +03:00
gpio_irq_handler , pdata - > irq_flags ,
2015-09-29 14:53:12 +03:00
pdev - > name , data ) ;
2012-04-20 09:16:23 +04:00
if ( ret < 0 )
2014-04-21 14:10:10 +04:00
return ret ;
2012-04-20 09:16:23 +04:00
2015-09-29 14:53:12 +03:00
platform_set_drvdata ( pdev , data ) ;
2012-04-20 09:16:23 +04:00
/* Perform initial detection */
2015-09-29 14:53:12 +03:00
gpio_extcon_work ( & data - > work . work ) ;
2012-04-20 09:16:23 +04:00
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
{
2015-09-29 14:53:12 +03:00
struct gpio_extcon_data * data = platform_get_drvdata ( pdev ) ;
2012-04-20 09:16:23 +04:00
2015-09-29 14:53:12 +03:00
cancel_delayed_work_sync ( & data - > work ) ;
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 )
{
2015-09-29 14:53:12 +03:00
struct gpio_extcon_data * data ;
2014-01-09 04:50:13 +04:00
2015-09-29 14:53:12 +03:00
data = dev_get_drvdata ( dev ) ;
if ( data - > pdata - > check_on_resume )
2014-01-09 04:50:13 +04:00
queue_delayed_work ( system_power_efficient_wq ,
2015-09-29 14:53:12 +03:00
& data - > work , data - > debounce_jiffies ) ;
2014-01-09 04:50:13 +04:00
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 " ,
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 " ) ;