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 >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* 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/acpi.h>
# include <linux/extcon.h>
# include <linux/gpio.h>
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# 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 ;
int usb_id_irq ;
} ;
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 [ ] = {
{ " id-gpios " , & id_gpios , 1 } ,
{ " vbus-gpios " , & vbus_gpios , 1 } ,
{ " mux-gpios " , & mux_gpios , 1 } ,
{ } ,
} ;
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 ) ;
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 ;
2017-02-24 15:35:55 +03:00
ret = acpi_dev_add_driver_gpios ( ACPI_COMPANION ( dev ) ,
acpi_int3496_default_gpios ) ;
if ( ret ) {
dev_err ( dev , " can't add GPIO ACPI mapping \n " ) ;
return ret ;
}
2016-12-21 14:20:25 +03:00
data = devm_kzalloc ( dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
data - > dev = dev ;
INIT_DELAYED_WORK ( & data - > work , int3496_do_usb_id ) ;
2017-03-10 23:52:08 +03:00
data - > gpio_usb_id = devm_gpiod_get ( dev , " id " , GPIOD_IN ) ;
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 ;
2017-03-13 14:28:34 +03:00
} else if ( gpiod_get_direction ( data - > gpio_usb_id ) ! = GPIOF_DIR_IN ) {
dev_warn ( dev , FW_BUG " USB ID GPIO not in input mode, fixing \n " ) ;
gpiod_direction_input ( data - > gpio_usb_id ) ;
2016-12-21 14:20:25 +03:00
}
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 ) ;
2016-12-21 14:20:25 +03:00
if ( IS_ERR ( data - > gpio_vbus_en ) )
dev_info ( dev , " can't request VBUS EN GPIO \n " ) ;
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 ) )
dev_info ( dev , " can't request USB MUX GPIO \n " ) ;
/* 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 ;
}
/* queue initial processing of id-pin */
queue_delayed_work ( system_wq , & data - > work , 0 ) ;
platform_set_drvdata ( pdev , data ) ;
return 0 ;
}
static int int3496_remove ( struct platform_device * pdev )
{
struct int3496_data * data = platform_get_drvdata ( pdev ) ;
devm_free_irq ( & pdev - > dev , data - > usb_id_irq , data ) ;
cancel_delayed_work_sync ( & data - > work ) ;
2017-02-24 15:35:55 +03:00
acpi_dev_remove_driver_gpios ( ACPI_COMPANION ( & pdev - > dev ) ) ;
2016-12-21 14:20:25 +03:00
return 0 ;
}
static struct acpi_device_id int3496_acpi_match [ ] = {
{ " INT3496 " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( acpi , int3496_acpi_match ) ;
static struct platform_driver int3496_driver = {
. driver = {
. name = " intel-int3496 " ,
. acpi_match_table = int3496_acpi_match ,
} ,
. probe = int3496_probe ,
. remove = int3496_remove ,
} ;
module_platform_driver ( int3496_driver ) ;
MODULE_AUTHOR ( " Hans de Goede <hdegoede@redhat.com> " ) ;
MODULE_DESCRIPTION ( " Intel INT3496 ACPI device extcon driver " ) ;
MODULE_LICENSE ( " GPL " ) ;