2018-09-26 18:27:40 +03:00
// SPDX-License-Identifier: GPL-2.0
2017-06-12 22:55:46 +02:00
/*
* Intel INT0002 " Virtual GPIO " driver
*
* Copyright ( C ) 2017 Hans de Goede < hdegoede @ redhat . com >
*
* Loosely based on android x86 kernel code which is :
*
* Copyright ( c ) 2014 , Intel Corporation .
*
* Author : Dyut Kumar Sil < dyut . k . sil @ intel . com >
*
* Some peripherals on Bay Trail and Cherry Trail platforms signal a Power
* Management Event ( PME ) to the Power Management Controller ( PMC ) to wakeup
* the system . When this happens software needs to clear the PME bus 0 status
* bit in the GPE0a_STS register to avoid an IRQ storm on IRQ 9.
*
* This is modelled in ACPI through the INT0002 ACPI device , which is
* called a " Virtual GPIO controller " in ACPI because it defines the event
* handler to call when the PME triggers through _AEI and _L02 / _E02
* methods as would be done for a real GPIO interrupt in ACPI . Note this
* is a hack to define an AML event handler for the PME while using existing
* ACPI mechanisms , this is not a real GPIO at all .
*
* This driver will bind to the INT0002 device , and register as a GPIO
* controller , letting gpiolib - acpi . c call the _L02 handler as it would
* for a real GPIO controller .
*/
# include <linux/acpi.h>
# include <linux/bitmap.h>
# include <linux/gpio/driver.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <linux/suspend.h>
# include <asm/cpu_device_id.h>
# include <asm/intel-family.h>
# define DRV_NAME "INT0002 Virtual GPIO"
/* For some reason the virtual GPIO pin tied to the GPE is numbered pin 2 */
# define GPE0A_PME_B0_VIRT_GPIO_PIN 2
# define GPE0A_PME_B0_STS_BIT BIT(13)
# define GPE0A_PME_B0_EN_BIT BIT(13)
# define GPE0A_STS_PORT 0x420
# define GPE0A_EN_PORT 0x428
/*
* As this is not a real GPIO at all , but just a hack to model an event in
* ACPI the get / set functions are dummy functions .
*/
static int int0002_gpio_get ( struct gpio_chip * chip , unsigned int offset )
{
return 0 ;
}
static void int0002_gpio_set ( struct gpio_chip * chip , unsigned int offset ,
int value )
{
}
static int int0002_gpio_direction_output ( struct gpio_chip * chip ,
unsigned int offset , int value )
{
return 0 ;
}
static void int0002_irq_ack ( struct irq_data * data )
{
outl ( GPE0A_PME_B0_STS_BIT , GPE0A_STS_PORT ) ;
}
static void int0002_irq_unmask ( struct irq_data * data )
{
u32 gpe_en_reg ;
gpe_en_reg = inl ( GPE0A_EN_PORT ) ;
gpe_en_reg | = GPE0A_PME_B0_EN_BIT ;
outl ( gpe_en_reg , GPE0A_EN_PORT ) ;
}
static void int0002_irq_mask ( struct irq_data * data )
{
u32 gpe_en_reg ;
gpe_en_reg = inl ( GPE0A_EN_PORT ) ;
gpe_en_reg & = ~ GPE0A_PME_B0_EN_BIT ;
outl ( gpe_en_reg , GPE0A_EN_PORT ) ;
}
2018-09-24 16:37:19 +02:00
static int int0002_irq_set_wake ( struct irq_data * data , unsigned int on )
{
struct gpio_chip * chip = irq_data_get_irq_chip_data ( data ) ;
struct platform_device * pdev = to_platform_device ( chip - > parent ) ;
int irq = platform_get_irq ( pdev , 0 ) ;
/* Propagate to parent irq */
if ( on )
enable_irq_wake ( irq ) ;
else
disable_irq_wake ( irq ) ;
return 0 ;
}
2017-06-12 22:55:46 +02:00
static irqreturn_t int0002_irq ( int irq , void * data )
{
struct gpio_chip * chip = data ;
u32 gpe_sts_reg ;
gpe_sts_reg = inl ( GPE0A_STS_PORT ) ;
if ( ! ( gpe_sts_reg & GPE0A_PME_B0_STS_BIT ) )
return IRQ_NONE ;
2017-11-07 19:15:47 +01:00
generic_handle_irq ( irq_find_mapping ( chip - > irq . domain ,
2017-06-12 22:55:46 +02:00
GPE0A_PME_B0_VIRT_GPIO_PIN ) ) ;
2019-08-23 19:48:15 +02:00
pm_wakeup_hard_event ( chip - > parent ) ;
2017-06-12 22:55:46 +02:00
return IRQ_HANDLED ;
}
2020-04-03 17:48:34 +02:00
static bool int0002_check_wake ( void * data )
{
u32 gpe_sts_reg ;
gpe_sts_reg = inl ( GPE0A_STS_PORT ) ;
return ( gpe_sts_reg & GPE0A_PME_B0_STS_BIT ) ;
}
2019-02-03 10:42:33 +01:00
static struct irq_chip int0002_byt_irqchip = {
2017-06-12 22:55:46 +02:00
. name = DRV_NAME ,
. irq_ack = int0002_irq_ack ,
. irq_mask = int0002_irq_mask ,
. irq_unmask = int0002_irq_unmask ,
2018-09-24 16:37:19 +02:00
. irq_set_wake = int0002_irq_set_wake ,
2017-06-12 22:55:46 +02:00
} ;
2019-02-03 10:42:33 +01:00
static struct irq_chip int0002_cht_irqchip = {
. name = DRV_NAME ,
. irq_ack = int0002_irq_ack ,
. irq_mask = int0002_irq_mask ,
. irq_unmask = int0002_irq_unmask ,
/*
* No set_wake , on CHT the IRQ is typically shared with the ACPI SCI
* and we don ' t want to mess with the ACPI SCI irq settings .
*/
2019-08-23 19:48:14 +02:00
. flags = IRQCHIP_SKIP_SET_WAKE ,
2019-02-03 10:42:33 +01:00
} ;
2019-06-19 17:50:50 +03:00
static const struct x86_cpu_id int0002_cpu_ids [ ] = {
2020-03-20 14:13:56 +01:00
X86_MATCH_INTEL_FAM6_MODEL ( ATOM_SILVERMONT , & int0002_byt_irqchip ) ,
X86_MATCH_INTEL_FAM6_MODEL ( ATOM_AIRMONT , & int0002_cht_irqchip ) ,
2019-06-19 17:50:50 +03:00
{ }
} ;
2019-09-04 16:01:04 +02:00
static void int0002_init_irq_valid_mask ( struct gpio_chip * chip ,
unsigned long * valid_mask ,
unsigned int ngpios )
{
bitmap_clear ( valid_mask , 0 , GPE0A_PME_B0_VIRT_GPIO_PIN ) ;
}
2017-06-12 22:55:46 +02:00
static int int0002_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
const struct x86_cpu_id * cpu_id ;
struct gpio_chip * chip ;
2019-10-22 23:01:28 +02:00
struct gpio_irq_chip * girq ;
2017-06-12 22:55:46 +02:00
int irq , ret ;
/* Menlow has a different INT0002 device? <sigh> */
cpu_id = x86_match_cpu ( int0002_cpu_ids ) ;
if ( ! cpu_id )
return - ENODEV ;
irq = platform_get_irq ( pdev , 0 ) ;
2019-07-30 11:15:55 -07:00
if ( irq < 0 )
2017-06-12 22:55:46 +02:00
return irq ;
chip = devm_kzalloc ( dev , sizeof ( * chip ) , GFP_KERNEL ) ;
if ( ! chip )
return - ENOMEM ;
chip - > label = DRV_NAME ;
chip - > parent = dev ;
chip - > owner = THIS_MODULE ;
chip - > get = int0002_gpio_get ;
chip - > set = int0002_gpio_set ;
chip - > direction_input = int0002_gpio_get ;
chip - > direction_output = int0002_gpio_direction_output ;
chip - > base = - 1 ;
chip - > ngpio = GPE0A_PME_B0_VIRT_GPIO_PIN + 1 ;
2019-09-04 16:01:04 +02:00
chip - > irq . init_valid_mask = int0002_init_irq_valid_mask ;
2017-06-12 22:55:46 +02:00
/*
2019-10-22 23:01:28 +02:00
* We directly request the irq here instead of passing a flow - handler
2017-06-12 22:55:46 +02:00
* to gpiochip_set_chained_irqchip , because the irq is shared .
2019-10-22 23:01:28 +02:00
* FIXME : augment this if we managed to pull handling of shared
* IRQs into gpiolib .
2017-06-12 22:55:46 +02:00
*/
ret = devm_request_irq ( dev , irq , int0002_irq ,
2017-11-22 16:50:33 +01:00
IRQF_SHARED , " INT0002 " , chip ) ;
2017-06-12 22:55:46 +02:00
if ( ret ) {
dev_err ( dev , " Error requesting IRQ %d: %d \n " , irq , ret ) ;
return ret ;
}
2019-10-22 23:01:28 +02:00
girq = & chip - > irq ;
girq - > chip = ( struct irq_chip * ) cpu_id - > driver_data ;
/* This let us handle the parent IRQ in the driver */
girq - > parent_handler = NULL ;
girq - > num_parents = 0 ;
girq - > parents = NULL ;
girq - > default_type = IRQ_TYPE_NONE ;
girq - > handler = handle_edge_irq ;
2019-02-03 10:42:33 +01:00
2019-10-22 23:01:28 +02:00
ret = devm_gpiochip_add_data ( dev , chip , NULL ) ;
2017-06-12 22:55:46 +02:00
if ( ret ) {
2019-10-22 23:01:28 +02:00
dev_err ( dev , " Error adding gpio chip: %d \n " , ret ) ;
2017-06-12 22:55:46 +02:00
return ret ;
}
2020-04-03 17:48:34 +02:00
acpi_register_wakeup_handler ( irq , int0002_check_wake , NULL ) ;
2019-08-23 19:48:15 +02:00
device_init_wakeup ( dev , true ) ;
return 0 ;
}
static int int0002_remove ( struct platform_device * pdev )
{
device_init_wakeup ( & pdev - > dev , false ) ;
2020-04-03 17:48:34 +02:00
acpi_unregister_wakeup_handler ( int0002_check_wake , NULL ) ;
2017-06-12 22:55:46 +02:00
return 0 ;
}
static const struct acpi_device_id int0002_acpi_ids [ ] = {
{ " INT0002 " , 0 } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( acpi , int0002_acpi_ids ) ;
static struct platform_driver int0002_driver = {
. driver = {
. name = DRV_NAME ,
. acpi_match_table = int0002_acpi_ids ,
} ,
. probe = int0002_probe ,
2019-08-23 19:48:15 +02:00
. remove = int0002_remove ,
2017-06-12 22:55:46 +02:00
} ;
module_platform_driver ( int0002_driver ) ;
MODULE_AUTHOR ( " Hans de Goede <hdegoede@redhat.com> " ) ;
MODULE_DESCRIPTION ( " Intel INT0002 Virtual GPIO driver " ) ;
2018-09-26 18:27:40 +03:00
MODULE_LICENSE ( " GPL v2 " ) ;