-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
platform/x86: Add driver for ACPI INT0002 Virtual GPIO device
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 explicitly 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 methods as would be done for a real GPIO interrupt in ACPI. This commit adds a driver which registers the Virtual GPIOs expected by the DSDT on these devices, letting gpiolib-acpi claim the virtual GPIO and install a GPIO-interrupt handler which call the _L02 handler as it would for a real GPIO controller. Signed-off-by: Hans de Goede <hdegoede@redhat.com> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Reviewed-by: Linus Walleij <linus.walleij@linaro.org> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
- Loading branch information
Hans de Goede
authored and
Rafael J. Wysocki
committed
Jun 14, 2017
1 parent
dc15e71
commit 63dada8
Showing
3 changed files
with
239 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
/* | ||
* 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> | ||
* | ||
* 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. | ||
* | ||
* 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 | ||
|
||
#define ICPU(model) { X86_VENDOR_INTEL, 6, model, X86_FEATURE_ANY, } | ||
|
||
static const struct x86_cpu_id int0002_cpu_ids[] = { | ||
/* | ||
* Limit ourselves to Cherry Trail for now, until testing shows we | ||
* need to handle the INT0002 device on Baytrail too. | ||
* ICPU(INTEL_FAM6_ATOM_SILVERMONT1), * Valleyview, Bay Trail * | ||
*/ | ||
ICPU(INTEL_FAM6_ATOM_AIRMONT), /* Braswell, Cherry Trail */ | ||
{} | ||
}; | ||
|
||
/* | ||
* 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); | ||
} | ||
|
||
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; | ||
|
||
generic_handle_irq(irq_find_mapping(chip->irqdomain, | ||
GPE0A_PME_B0_VIRT_GPIO_PIN)); | ||
|
||
pm_system_wakeup(); | ||
|
||
return IRQ_HANDLED; | ||
} | ||
|
||
static struct irq_chip int0002_irqchip = { | ||
.name = DRV_NAME, | ||
.irq_ack = int0002_irq_ack, | ||
.irq_mask = int0002_irq_mask, | ||
.irq_unmask = int0002_irq_unmask, | ||
}; | ||
|
||
static int int0002_probe(struct platform_device *pdev) | ||
{ | ||
struct device *dev = &pdev->dev; | ||
const struct x86_cpu_id *cpu_id; | ||
struct gpio_chip *chip; | ||
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); | ||
if (irq < 0) { | ||
dev_err(dev, "Error getting IRQ: %d\n", irq); | ||
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; | ||
chip->irq_need_valid_mask = true; | ||
|
||
ret = devm_gpiochip_add_data(&pdev->dev, chip, NULL); | ||
if (ret) { | ||
dev_err(dev, "Error adding gpio chip: %d\n", ret); | ||
return ret; | ||
} | ||
|
||
bitmap_clear(chip->irq_valid_mask, 0, GPE0A_PME_B0_VIRT_GPIO_PIN); | ||
|
||
/* | ||
* We manually request the irq here instead of passing a flow-handler | ||
* to gpiochip_set_chained_irqchip, because the irq is shared. | ||
*/ | ||
ret = devm_request_irq(dev, irq, int0002_irq, | ||
IRQF_SHARED | IRQF_NO_THREAD, "INT0002", chip); | ||
if (ret) { | ||
dev_err(dev, "Error requesting IRQ %d: %d\n", irq, ret); | ||
return ret; | ||
} | ||
|
||
ret = gpiochip_irqchip_add(chip, &int0002_irqchip, 0, handle_edge_irq, | ||
IRQ_TYPE_NONE); | ||
if (ret) { | ||
dev_err(dev, "Error adding irqchip: %d\n", ret); | ||
return ret; | ||
} | ||
|
||
gpiochip_set_chained_irqchip(chip, &int0002_irqchip, irq, NULL); | ||
|
||
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, | ||
}; | ||
|
||
module_platform_driver(int0002_driver); | ||
|
||
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); | ||
MODULE_DESCRIPTION("Intel INT0002 Virtual GPIO driver"); | ||
MODULE_LICENSE("GPL"); |