2019-05-29 17:17:56 +03:00
// SPDX-License-Identifier: GPL-2.0-only
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 )
2015-09-29 16:59:55 +03:00
*/
2012-04-20 09:16:23 +04:00
2021-03-23 16:57:08 +03:00
# include <linux/devm-helpers.h>
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 .
* @ 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 .
* @ 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
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 ;
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 ;
2019-05-30 21:39:32 +03:00
unsigned long irq_flags ;
int irq ;
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 .
*/
2019-05-30 21:39:32 +03:00
if ( data - > extcon_id > EXTCON_NONE )
2018-02-12 11:53:13 +03:00
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 ) ;
2019-05-30 21:39:32 +03:00
irq = gpiod_to_irq ( data - > gpiod ) ;
if ( irq < = 0 )
return irq ;
/*
* It is unlikely that this is an acknowledged interrupt that goes
* away after handling , what we are looking for are falling edges
* if the signal is active low , and rising edges if the signal is
* active high .
*/
if ( gpiod_is_active_low ( data - > gpiod ) )
irq_flags = IRQF_TRIGGER_FALLING ;
else
irq_flags = IRQF_TRIGGER_RISING ;
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
2021-03-23 16:57:08 +03:00
ret = devm_delayed_work_autocancel ( dev , & data - > work , gpio_extcon_work ) ;
if ( ret )
return ret ;
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 .
*/
2019-05-30 21:39:32 +03:00
ret = devm_request_any_context_irq ( dev , irq ,
gpio_irq_handler , 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 ;
}
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 ,
. 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 " ) ;