2018-08-27 18:35:57 +03:00
// SPDX-License-Identifier: GPL-2.0
2016-12-21 14:20:25 +03:00
/*
* Intel INT3496 ACPI device extcon driver
*
* Copyright ( c ) 2016 Hans de Goede < hdegoede @ redhat . com >
*
* Based on android x86 kernel code which is :
*
* Copyright ( c ) 2014 , Intel Corporation .
* Author : David Cohen < david . a . cohen @ linux . intel . com >
*/
# include <linux/acpi.h>
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>
2018-04-10 15:43:02 +03:00
# include <linux/gpio/consumer.h>
2016-12-21 14:20:25 +03:00
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/platform_device.h>
2021-12-28 20:01:41 +03:00
# include <linux/regulator/consumer.h>
2016-12-21 14:20:25 +03:00
# define INT3496_GPIO_USB_ID 0
# define INT3496_GPIO_VBUS_EN 1
# define INT3496_GPIO_USB_MUX 2
# define DEBOUNCE_TIME msecs_to_jiffies(50)
struct int3496_data {
struct device * dev ;
struct extcon_dev * edev ;
struct delayed_work work ;
struct gpio_desc * gpio_usb_id ;
struct gpio_desc * gpio_vbus_en ;
struct gpio_desc * gpio_usb_mux ;
2021-12-28 20:01:41 +03:00
struct regulator * vbus_boost ;
2016-12-21 14:20:25 +03:00
int usb_id_irq ;
2021-12-28 20:01:41 +03:00
bool vbus_boost_enabled ;
2016-12-21 14:20:25 +03:00
} ;
static const unsigned int int3496_cable [ ] = {
EXTCON_USB_HOST ,
EXTCON_NONE ,
} ;
2017-02-24 15:35:55 +03:00
static const struct acpi_gpio_params id_gpios = { INT3496_GPIO_USB_ID , 0 , false } ;
static const struct acpi_gpio_params vbus_gpios = { INT3496_GPIO_VBUS_EN , 0 , false } ;
static const struct acpi_gpio_params mux_gpios = { INT3496_GPIO_USB_MUX , 0 , false } ;
static const struct acpi_gpio_mapping acpi_int3496_default_gpios [ ] = {
2018-02-28 19:22:08 +03:00
/*
* Some platforms have a bug in ACPI GPIO description making IRQ
* GPIO to be output only . Ask the GPIO core to ignore this limit .
*/
{ " id-gpios " , & id_gpios , 1 , ACPI_GPIO_QUIRK_NO_IO_RESTRICTION } ,
2017-02-24 15:35:55 +03:00
{ " vbus-gpios " , & vbus_gpios , 1 } ,
{ " mux-gpios " , & mux_gpios , 1 } ,
{ } ,
} ;
2021-12-28 20:01:41 +03:00
static void int3496_set_vbus_boost ( struct int3496_data * data , bool enable )
{
int ret ;
if ( IS_ERR_OR_NULL ( data - > vbus_boost ) )
return ;
if ( data - > vbus_boost_enabled = = enable )
return ;
if ( enable )
ret = regulator_enable ( data - > vbus_boost ) ;
else
ret = regulator_disable ( data - > vbus_boost ) ;
if ( ret = = 0 )
data - > vbus_boost_enabled = enable ;
else
dev_err ( data - > dev , " Error updating Vbus boost regulator: %d \n " , ret ) ;
}
2016-12-21 14:20:25 +03:00
static void int3496_do_usb_id ( struct work_struct * work )
{
struct int3496_data * data =
container_of ( work , struct int3496_data , work . work ) ;
int id = gpiod_get_value_cansleep ( data - > gpio_usb_id ) ;
/* id == 1: PERIPHERAL, id == 0: HOST */
dev_dbg ( data - > dev , " Connected %s cable \n " , id ? " PERIPHERAL " : " HOST " ) ;
/*
* Peripheral : set USB mux to peripheral and disable VBUS
* Host : set USB mux to host and enable VBUS
*/
if ( ! IS_ERR ( data - > gpio_usb_mux ) )
gpiod_direction_output ( data - > gpio_usb_mux , id ) ;
if ( ! IS_ERR ( data - > gpio_vbus_en ) )
gpiod_direction_output ( data - > gpio_vbus_en , ! id ) ;
2021-12-28 20:01:41 +03:00
else
int3496_set_vbus_boost ( data , ! id ) ;
2016-12-21 14:20:25 +03:00
extcon_set_state_sync ( data - > edev , EXTCON_USB_HOST , ! id ) ;
}
static irqreturn_t int3496_thread_isr ( int irq , void * priv )
{
struct int3496_data * data = priv ;
/* Let the pin settle before processing it */
mod_delayed_work ( system_wq , & data - > work , DEBOUNCE_TIME ) ;
return IRQ_HANDLED ;
}
static int int3496_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct int3496_data * data ;
int ret ;
2021-12-28 20:01:40 +03:00
if ( has_acpi_companion ( dev ) ) {
ret = devm_acpi_dev_add_driver_gpios ( dev , acpi_int3496_default_gpios ) ;
if ( ret ) {
dev_err ( dev , " can't add GPIO ACPI mapping \n " ) ;
return ret ;
}
2017-02-24 15:35:55 +03:00
}
2016-12-21 14:20:25 +03:00
data = devm_kzalloc ( dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
data - > dev = dev ;
2021-03-23 16:57:08 +03:00
ret = devm_delayed_work_autocancel ( dev , & data - > work , int3496_do_usb_id ) ;
if ( ret )
return ret ;
2016-12-21 14:20:25 +03:00
2021-12-28 20:01:39 +03:00
data - > gpio_usb_id =
devm_gpiod_get ( dev , " id " , GPIOD_IN | GPIOD_FLAGS_BIT_NONEXCLUSIVE ) ;
2016-12-21 14:20:25 +03:00
if ( IS_ERR ( data - > gpio_usb_id ) ) {
ret = PTR_ERR ( data - > gpio_usb_id ) ;
dev_err ( dev , " can't request USB ID GPIO: %d \n " , ret ) ;
return ret ;
}
data - > usb_id_irq = gpiod_to_irq ( data - > gpio_usb_id ) ;
2017-02-23 13:31:54 +03:00
if ( data - > usb_id_irq < 0 ) {
2016-12-21 14:20:25 +03:00
dev_err ( dev , " can't get USB ID IRQ: %d \n " , data - > usb_id_irq ) ;
2017-02-23 13:31:54 +03:00
return data - > usb_id_irq ;
2016-12-21 14:20:25 +03:00
}
2017-03-10 23:52:08 +03:00
data - > gpio_vbus_en = devm_gpiod_get ( dev , " vbus " , GPIOD_ASIS ) ;
2021-12-28 20:01:41 +03:00
if ( IS_ERR ( data - > gpio_vbus_en ) ) {
2021-12-28 20:01:38 +03:00
dev_dbg ( dev , " can't request VBUS EN GPIO \n " ) ;
2021-12-28 20:01:41 +03:00
data - > vbus_boost = devm_regulator_get_optional ( dev , " vbus " ) ;
}
2016-12-21 14:20:25 +03:00
2017-03-10 23:52:08 +03:00
data - > gpio_usb_mux = devm_gpiod_get ( dev , " mux " , GPIOD_ASIS ) ;
2016-12-21 14:20:25 +03:00
if ( IS_ERR ( data - > gpio_usb_mux ) )
2021-12-28 20:01:38 +03:00
dev_dbg ( dev , " can't request USB MUX GPIO \n " ) ;
2016-12-21 14:20:25 +03:00
/* register extcon device */
data - > edev = devm_extcon_dev_allocate ( dev , int3496_cable ) ;
if ( IS_ERR ( data - > edev ) )
return - ENOMEM ;
ret = devm_extcon_dev_register ( dev , data - > edev ) ;
if ( ret < 0 ) {
dev_err ( dev , " can't register extcon device: %d \n " , ret ) ;
return ret ;
}
ret = devm_request_threaded_irq ( dev , data - > usb_id_irq ,
NULL , int3496_thread_isr ,
IRQF_SHARED | IRQF_ONESHOT |
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING ,
dev_name ( dev ) , data ) ;
if ( ret < 0 ) {
dev_err ( dev , " can't request IRQ for USB ID GPIO: %d \n " , ret ) ;
return ret ;
}
2018-02-13 22:25:50 +03:00
/* process id-pin so that we start with the right status */
2016-12-21 14:20:25 +03:00
queue_delayed_work ( system_wq , & data - > work , 0 ) ;
2018-02-13 22:25:50 +03:00
flush_delayed_work ( & data - > work ) ;
2016-12-21 14:20:25 +03:00
platform_set_drvdata ( pdev , data ) ;
return 0 ;
}
2017-07-06 19:55:56 +03:00
static const struct acpi_device_id int3496_acpi_match [ ] = {
2016-12-21 14:20:25 +03:00
{ " INT3496 " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( acpi , int3496_acpi_match ) ;
2021-12-28 20:01:40 +03:00
static const struct platform_device_id int3496_ids [ ] = {
{ . name = " intel-int3496 " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( platform , int3496_ids ) ;
2016-12-21 14:20:25 +03:00
static struct platform_driver int3496_driver = {
. driver = {
. name = " intel-int3496 " ,
. acpi_match_table = int3496_acpi_match ,
} ,
. probe = int3496_probe ,
2021-12-28 20:01:40 +03:00
. id_table = int3496_ids ,
2016-12-21 14:20:25 +03:00
} ;
module_platform_driver ( int3496_driver ) ;
MODULE_AUTHOR ( " Hans de Goede <hdegoede@redhat.com> " ) ;
MODULE_DESCRIPTION ( " Intel INT3496 ACPI device extcon driver " ) ;
2018-08-27 18:35:57 +03:00
MODULE_LICENSE ( " GPL v2 " ) ;