18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Intel INT0002 "Virtual GPIO" driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Loosely based on android x86 kernel code which is: 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * Copyright (c) 2014, Intel Corporation. 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * Author: Dyut Kumar Sil <dyut.k.sil@intel.com> 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * Some peripherals on Bay Trail and Cherry Trail platforms signal a Power 148c2ecf20Sopenharmony_ci * Management Event (PME) to the Power Management Controller (PMC) to wakeup 158c2ecf20Sopenharmony_ci * the system. When this happens software needs to clear the PME bus 0 status 168c2ecf20Sopenharmony_ci * bit in the GPE0a_STS register to avoid an IRQ storm on IRQ 9. 178c2ecf20Sopenharmony_ci * 188c2ecf20Sopenharmony_ci * This is modelled in ACPI through the INT0002 ACPI device, which is 198c2ecf20Sopenharmony_ci * called a "Virtual GPIO controller" in ACPI because it defines the event 208c2ecf20Sopenharmony_ci * handler to call when the PME triggers through _AEI and _L02 / _E02 218c2ecf20Sopenharmony_ci * methods as would be done for a real GPIO interrupt in ACPI. Note this 228c2ecf20Sopenharmony_ci * is a hack to define an AML event handler for the PME while using existing 238c2ecf20Sopenharmony_ci * ACPI mechanisms, this is not a real GPIO at all. 248c2ecf20Sopenharmony_ci * 258c2ecf20Sopenharmony_ci * This driver will bind to the INT0002 device, and register as a GPIO 268c2ecf20Sopenharmony_ci * controller, letting gpiolib-acpi.c call the _L02 handler as it would 278c2ecf20Sopenharmony_ci * for a real GPIO controller. 288c2ecf20Sopenharmony_ci */ 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#include <linux/acpi.h> 318c2ecf20Sopenharmony_ci#include <linux/bitmap.h> 328c2ecf20Sopenharmony_ci#include <linux/gpio/driver.h> 338c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 348c2ecf20Sopenharmony_ci#include <linux/io.h> 358c2ecf20Sopenharmony_ci#include <linux/kernel.h> 368c2ecf20Sopenharmony_ci#include <linux/module.h> 378c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 388c2ecf20Sopenharmony_ci#include <linux/slab.h> 398c2ecf20Sopenharmony_ci#include <linux/suspend.h> 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci#include <asm/cpu_device_id.h> 428c2ecf20Sopenharmony_ci#include <asm/intel-family.h> 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci#define DRV_NAME "INT0002 Virtual GPIO" 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci/* For some reason the virtual GPIO pin tied to the GPE is numbered pin 2 */ 478c2ecf20Sopenharmony_ci#define GPE0A_PME_B0_VIRT_GPIO_PIN 2 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci#define GPE0A_PME_B0_STS_BIT BIT(13) 508c2ecf20Sopenharmony_ci#define GPE0A_PME_B0_EN_BIT BIT(13) 518c2ecf20Sopenharmony_ci#define GPE0A_STS_PORT 0x420 528c2ecf20Sopenharmony_ci#define GPE0A_EN_PORT 0x428 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cistruct int0002_data { 558c2ecf20Sopenharmony_ci struct gpio_chip chip; 568c2ecf20Sopenharmony_ci int parent_irq; 578c2ecf20Sopenharmony_ci int wake_enable_count; 588c2ecf20Sopenharmony_ci}; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci/* 618c2ecf20Sopenharmony_ci * As this is not a real GPIO at all, but just a hack to model an event in 628c2ecf20Sopenharmony_ci * ACPI the get / set functions are dummy functions. 638c2ecf20Sopenharmony_ci */ 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic int int0002_gpio_get(struct gpio_chip *chip, unsigned int offset) 668c2ecf20Sopenharmony_ci{ 678c2ecf20Sopenharmony_ci return 0; 688c2ecf20Sopenharmony_ci} 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistatic void int0002_gpio_set(struct gpio_chip *chip, unsigned int offset, 718c2ecf20Sopenharmony_ci int value) 728c2ecf20Sopenharmony_ci{ 738c2ecf20Sopenharmony_ci} 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_cistatic int int0002_gpio_direction_output(struct gpio_chip *chip, 768c2ecf20Sopenharmony_ci unsigned int offset, int value) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci return 0; 798c2ecf20Sopenharmony_ci} 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistatic void int0002_irq_ack(struct irq_data *data) 828c2ecf20Sopenharmony_ci{ 838c2ecf20Sopenharmony_ci outl(GPE0A_PME_B0_STS_BIT, GPE0A_STS_PORT); 848c2ecf20Sopenharmony_ci} 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic void int0002_irq_unmask(struct irq_data *data) 878c2ecf20Sopenharmony_ci{ 888c2ecf20Sopenharmony_ci u32 gpe_en_reg; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci gpe_en_reg = inl(GPE0A_EN_PORT); 918c2ecf20Sopenharmony_ci gpe_en_reg |= GPE0A_PME_B0_EN_BIT; 928c2ecf20Sopenharmony_ci outl(gpe_en_reg, GPE0A_EN_PORT); 938c2ecf20Sopenharmony_ci} 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic void int0002_irq_mask(struct irq_data *data) 968c2ecf20Sopenharmony_ci{ 978c2ecf20Sopenharmony_ci u32 gpe_en_reg; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci gpe_en_reg = inl(GPE0A_EN_PORT); 1008c2ecf20Sopenharmony_ci gpe_en_reg &= ~GPE0A_PME_B0_EN_BIT; 1018c2ecf20Sopenharmony_ci outl(gpe_en_reg, GPE0A_EN_PORT); 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic int int0002_irq_set_wake(struct irq_data *data, unsigned int on) 1058c2ecf20Sopenharmony_ci{ 1068c2ecf20Sopenharmony_ci struct gpio_chip *chip = irq_data_get_irq_chip_data(data); 1078c2ecf20Sopenharmony_ci struct int0002_data *int0002 = container_of(chip, struct int0002_data, chip); 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci /* 1108c2ecf20Sopenharmony_ci * Applying of the wakeup flag to our parent IRQ is delayed till system 1118c2ecf20Sopenharmony_ci * suspend, because we only want to do this when using s2idle. 1128c2ecf20Sopenharmony_ci */ 1138c2ecf20Sopenharmony_ci if (on) 1148c2ecf20Sopenharmony_ci int0002->wake_enable_count++; 1158c2ecf20Sopenharmony_ci else 1168c2ecf20Sopenharmony_ci int0002->wake_enable_count--; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci return 0; 1198c2ecf20Sopenharmony_ci} 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_cistatic irqreturn_t int0002_irq(int irq, void *data) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci struct gpio_chip *chip = data; 1248c2ecf20Sopenharmony_ci u32 gpe_sts_reg; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci gpe_sts_reg = inl(GPE0A_STS_PORT); 1278c2ecf20Sopenharmony_ci if (!(gpe_sts_reg & GPE0A_PME_B0_STS_BIT)) 1288c2ecf20Sopenharmony_ci return IRQ_NONE; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci generic_handle_irq(irq_find_mapping(chip->irq.domain, 1318c2ecf20Sopenharmony_ci GPE0A_PME_B0_VIRT_GPIO_PIN)); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci pm_wakeup_hard_event(chip->parent); 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1368c2ecf20Sopenharmony_ci} 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_cistatic bool int0002_check_wake(void *data) 1398c2ecf20Sopenharmony_ci{ 1408c2ecf20Sopenharmony_ci u32 gpe_sts_reg; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci gpe_sts_reg = inl(GPE0A_STS_PORT); 1438c2ecf20Sopenharmony_ci return (gpe_sts_reg & GPE0A_PME_B0_STS_BIT); 1448c2ecf20Sopenharmony_ci} 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_cistatic struct irq_chip int0002_irqchip = { 1478c2ecf20Sopenharmony_ci .name = DRV_NAME, 1488c2ecf20Sopenharmony_ci .irq_ack = int0002_irq_ack, 1498c2ecf20Sopenharmony_ci .irq_mask = int0002_irq_mask, 1508c2ecf20Sopenharmony_ci .irq_unmask = int0002_irq_unmask, 1518c2ecf20Sopenharmony_ci .irq_set_wake = int0002_irq_set_wake, 1528c2ecf20Sopenharmony_ci}; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic const struct x86_cpu_id int0002_cpu_ids[] = { 1558c2ecf20Sopenharmony_ci X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT, NULL), 1568c2ecf20Sopenharmony_ci X86_MATCH_INTEL_FAM6_MODEL(ATOM_AIRMONT, NULL), 1578c2ecf20Sopenharmony_ci {} 1588c2ecf20Sopenharmony_ci}; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_cistatic void int0002_init_irq_valid_mask(struct gpio_chip *chip, 1618c2ecf20Sopenharmony_ci unsigned long *valid_mask, 1628c2ecf20Sopenharmony_ci unsigned int ngpios) 1638c2ecf20Sopenharmony_ci{ 1648c2ecf20Sopenharmony_ci bitmap_clear(valid_mask, 0, GPE0A_PME_B0_VIRT_GPIO_PIN); 1658c2ecf20Sopenharmony_ci} 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_cistatic int int0002_probe(struct platform_device *pdev) 1688c2ecf20Sopenharmony_ci{ 1698c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 1708c2ecf20Sopenharmony_ci const struct x86_cpu_id *cpu_id; 1718c2ecf20Sopenharmony_ci struct int0002_data *int0002; 1728c2ecf20Sopenharmony_ci struct gpio_irq_chip *girq; 1738c2ecf20Sopenharmony_ci struct gpio_chip *chip; 1748c2ecf20Sopenharmony_ci int irq, ret; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci /* Menlow has a different INT0002 device? <sigh> */ 1778c2ecf20Sopenharmony_ci cpu_id = x86_match_cpu(int0002_cpu_ids); 1788c2ecf20Sopenharmony_ci if (!cpu_id) 1798c2ecf20Sopenharmony_ci return -ENODEV; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci irq = platform_get_irq(pdev, 0); 1828c2ecf20Sopenharmony_ci if (irq < 0) 1838c2ecf20Sopenharmony_ci return irq; 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci int0002 = devm_kzalloc(dev, sizeof(*int0002), GFP_KERNEL); 1868c2ecf20Sopenharmony_ci if (!int0002) 1878c2ecf20Sopenharmony_ci return -ENOMEM; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci int0002->parent_irq = irq; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci chip = &int0002->chip; 1928c2ecf20Sopenharmony_ci chip->label = DRV_NAME; 1938c2ecf20Sopenharmony_ci chip->parent = dev; 1948c2ecf20Sopenharmony_ci chip->owner = THIS_MODULE; 1958c2ecf20Sopenharmony_ci chip->get = int0002_gpio_get; 1968c2ecf20Sopenharmony_ci chip->set = int0002_gpio_set; 1978c2ecf20Sopenharmony_ci chip->direction_input = int0002_gpio_get; 1988c2ecf20Sopenharmony_ci chip->direction_output = int0002_gpio_direction_output; 1998c2ecf20Sopenharmony_ci chip->base = -1; 2008c2ecf20Sopenharmony_ci chip->ngpio = GPE0A_PME_B0_VIRT_GPIO_PIN + 1; 2018c2ecf20Sopenharmony_ci chip->irq.init_valid_mask = int0002_init_irq_valid_mask; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci /* 2048c2ecf20Sopenharmony_ci * We directly request the irq here instead of passing a flow-handler 2058c2ecf20Sopenharmony_ci * to gpiochip_set_chained_irqchip, because the irq is shared. 2068c2ecf20Sopenharmony_ci * FIXME: augment this if we managed to pull handling of shared 2078c2ecf20Sopenharmony_ci * IRQs into gpiolib. 2088c2ecf20Sopenharmony_ci */ 2098c2ecf20Sopenharmony_ci ret = devm_request_irq(dev, irq, int0002_irq, 2108c2ecf20Sopenharmony_ci IRQF_SHARED, "INT0002", chip); 2118c2ecf20Sopenharmony_ci if (ret) { 2128c2ecf20Sopenharmony_ci dev_err(dev, "Error requesting IRQ %d: %d\n", irq, ret); 2138c2ecf20Sopenharmony_ci return ret; 2148c2ecf20Sopenharmony_ci } 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci girq = &chip->irq; 2178c2ecf20Sopenharmony_ci girq->chip = &int0002_irqchip; 2188c2ecf20Sopenharmony_ci /* This let us handle the parent IRQ in the driver */ 2198c2ecf20Sopenharmony_ci girq->parent_handler = NULL; 2208c2ecf20Sopenharmony_ci girq->num_parents = 0; 2218c2ecf20Sopenharmony_ci girq->parents = NULL; 2228c2ecf20Sopenharmony_ci girq->default_type = IRQ_TYPE_NONE; 2238c2ecf20Sopenharmony_ci girq->handler = handle_edge_irq; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci ret = devm_gpiochip_add_data(dev, chip, NULL); 2268c2ecf20Sopenharmony_ci if (ret) { 2278c2ecf20Sopenharmony_ci dev_err(dev, "Error adding gpio chip: %d\n", ret); 2288c2ecf20Sopenharmony_ci return ret; 2298c2ecf20Sopenharmony_ci } 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci acpi_register_wakeup_handler(irq, int0002_check_wake, NULL); 2328c2ecf20Sopenharmony_ci device_init_wakeup(dev, true); 2338c2ecf20Sopenharmony_ci dev_set_drvdata(dev, int0002); 2348c2ecf20Sopenharmony_ci return 0; 2358c2ecf20Sopenharmony_ci} 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_cistatic int int0002_remove(struct platform_device *pdev) 2388c2ecf20Sopenharmony_ci{ 2398c2ecf20Sopenharmony_ci device_init_wakeup(&pdev->dev, false); 2408c2ecf20Sopenharmony_ci acpi_unregister_wakeup_handler(int0002_check_wake, NULL); 2418c2ecf20Sopenharmony_ci return 0; 2428c2ecf20Sopenharmony_ci} 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_cistatic int int0002_suspend(struct device *dev) 2458c2ecf20Sopenharmony_ci{ 2468c2ecf20Sopenharmony_ci struct int0002_data *int0002 = dev_get_drvdata(dev); 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci /* 2498c2ecf20Sopenharmony_ci * The INT0002 parent IRQ is often shared with the ACPI GPE IRQ, don't 2508c2ecf20Sopenharmony_ci * muck with it when firmware based suspend is used, otherwise we may 2518c2ecf20Sopenharmony_ci * cause spurious wakeups from firmware managed suspend. 2528c2ecf20Sopenharmony_ci */ 2538c2ecf20Sopenharmony_ci if (!pm_suspend_via_firmware() && int0002->wake_enable_count) 2548c2ecf20Sopenharmony_ci enable_irq_wake(int0002->parent_irq); 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci return 0; 2578c2ecf20Sopenharmony_ci} 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_cistatic int int0002_resume(struct device *dev) 2608c2ecf20Sopenharmony_ci{ 2618c2ecf20Sopenharmony_ci struct int0002_data *int0002 = dev_get_drvdata(dev); 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci if (!pm_suspend_via_firmware() && int0002->wake_enable_count) 2648c2ecf20Sopenharmony_ci disable_irq_wake(int0002->parent_irq); 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci return 0; 2678c2ecf20Sopenharmony_ci} 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_cistatic const struct dev_pm_ops int0002_pm_ops = { 2708c2ecf20Sopenharmony_ci .suspend = int0002_suspend, 2718c2ecf20Sopenharmony_ci .resume = int0002_resume, 2728c2ecf20Sopenharmony_ci}; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_cistatic const struct acpi_device_id int0002_acpi_ids[] = { 2758c2ecf20Sopenharmony_ci { "INT0002", 0 }, 2768c2ecf20Sopenharmony_ci { }, 2778c2ecf20Sopenharmony_ci}; 2788c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, int0002_acpi_ids); 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_cistatic struct platform_driver int0002_driver = { 2818c2ecf20Sopenharmony_ci .driver = { 2828c2ecf20Sopenharmony_ci .name = DRV_NAME, 2838c2ecf20Sopenharmony_ci .acpi_match_table = int0002_acpi_ids, 2848c2ecf20Sopenharmony_ci .pm = &int0002_pm_ops, 2858c2ecf20Sopenharmony_ci }, 2868c2ecf20Sopenharmony_ci .probe = int0002_probe, 2878c2ecf20Sopenharmony_ci .remove = int0002_remove, 2888c2ecf20Sopenharmony_ci}; 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_cimodule_platform_driver(int0002_driver); 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); 2938c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Intel INT0002 Virtual GPIO driver"); 2948c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 295