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>
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
/**
2018-02-12 11:53:13 +03:00
* struct gpio_extcon_data - A simple GPIO - controlled extcon device state container .
* @ edev : Extcon device .
* @ irq : Interrupt line for the external connector .
* @ work : Work fired by the interrupt .
* @ debounce_jiffies : Number of jiffies to wait for the GPIO to stabilize , from the debounce
* value .
2018-02-12 11:53:14 +03:00
* @ gpiod : GPIO descriptor for this external connector .
2018-02-12 11:53:12 +03:00
* @ extcon_id : The unique id of specific external connector .
* @ 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 .
*/
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 ;
2018-02-12 11:53:14 +03:00
struct gpio_desc * gpiod ;
2018-02-12 11:53:13 +03:00
unsigned int extcon_id ;
unsigned long debounce ;
unsigned long irq_flags ;
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 ) ;
2018-02-12 11:53:14 +03:00
state = gpiod_get_value_cansleep ( data - > gpiod ) ;
2018-02-12 11:53:13 +03:00
extcon_set_state_sync ( data - > edev , data - > 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 ;
}
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 14:53:12 +03:00
struct gpio_extcon_data * data ;
2018-02-12 11:53:14 +03:00
struct device * dev = & pdev - > dev ;
2013-08-30 08:29:33 +04:00
int ret ;
2012-04-20 09:16:23 +04:00
2018-02-12 11:53:14 +03:00
data = devm_kzalloc ( dev , sizeof ( struct gpio_extcon_data ) , GFP_KERNEL ) ;
2015-09-29 14:53:12 +03:00
if ( ! data )
2012-04-20 09:16:23 +04:00
return - ENOMEM ;
2018-02-12 11:53:13 +03:00
/*
* FIXME : extcon_id represents the unique identifier of external
* connectors such as EXTCON_USB , EXTCON_DISP_HDMI and so on . extcon_id
* is necessary to register the extcon device . But , it ' s not yet
* developed to get the extcon id from device - tree or others .
* On later , it have to be solved .
*/
if ( ! data - > irq_flags | | data - > extcon_id > EXTCON_NONE )
return - EINVAL ;
2014-04-21 15:51:08 +04:00
2018-02-12 11:53:14 +03:00
data - > gpiod = devm_gpiod_get ( dev , " extcon " , GPIOD_IN ) ;
if ( IS_ERR ( data - > gpiod ) )
return PTR_ERR ( data - > gpiod ) ;
data - > irq = gpiod_to_irq ( data - > gpiod ) ;
if ( data - > irq < = 0 )
return data - > irq ;
2013-11-22 21:26:01 +04:00
2015-09-30 08:57:57 +03:00
/* Allocate the memory of extcon devie and register extcon device */
2018-02-12 11:53:14 +03:00
data - > edev = devm_extcon_dev_allocate ( dev , & data - > extcon_id ) ;
2015-09-30 08:57:57 +03:00
if ( IS_ERR ( data - > edev ) ) {
2018-02-12 11:53:14 +03:00
dev_err ( dev , " failed to allocate extcon device \n " ) ;
2015-09-30 08:57:57 +03:00
return - ENOMEM ;
2013-09-11 06:16:18 +04:00
}
2012-04-20 09:16:23 +04:00
2018-02-12 11:53:14 +03:00
ret = devm_extcon_dev_register ( 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 .
*/
2018-02-12 11:53:14 +03:00
ret = devm_request_any_context_irq ( dev , data - > irq ,
2018-02-12 11:53:13 +03:00
gpio_irq_handler , data - > 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 ) ;
2018-02-12 11:53:13 +03:00
if ( data - > 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 " ) ;