| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Intel INT0002 "Virtual GPIO" driver |
|---|
| 3 | 4 | * |
|---|
| .. | .. |
|---|
| 8 | 9 | * Copyright (c) 2014, Intel Corporation. |
|---|
| 9 | 10 | * |
|---|
| 10 | 11 | * Author: Dyut Kumar Sil <dyut.k.sil@intel.com> |
|---|
| 11 | | - * |
|---|
| 12 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 13 | | - * it under the terms of the GNU General Public License version 2 as |
|---|
| 14 | | - * published by the Free Software Foundation. |
|---|
| 15 | 12 | * |
|---|
| 16 | 13 | * Some peripherals on Bay Trail and Cherry Trail platforms signal a Power |
|---|
| 17 | 14 | * Management Event (PME) to the Power Management Controller (PMC) to wakeup |
|---|
| .. | .. |
|---|
| 54 | 51 | #define GPE0A_STS_PORT 0x420 |
|---|
| 55 | 52 | #define GPE0A_EN_PORT 0x428 |
|---|
| 56 | 53 | |
|---|
| 57 | | -#define ICPU(model) { X86_VENDOR_INTEL, 6, model, X86_FEATURE_ANY, } |
|---|
| 58 | | - |
|---|
| 59 | | -static const struct x86_cpu_id int0002_cpu_ids[] = { |
|---|
| 60 | | -/* |
|---|
| 61 | | - * Limit ourselves to Cherry Trail for now, until testing shows we |
|---|
| 62 | | - * need to handle the INT0002 device on Baytrail too. |
|---|
| 63 | | - * ICPU(INTEL_FAM6_ATOM_SILVERMONT), * Valleyview, Bay Trail * |
|---|
| 64 | | - */ |
|---|
| 65 | | - ICPU(INTEL_FAM6_ATOM_AIRMONT), /* Braswell, Cherry Trail */ |
|---|
| 66 | | - {} |
|---|
| 54 | +struct int0002_data { |
|---|
| 55 | + struct gpio_chip chip; |
|---|
| 56 | + int parent_irq; |
|---|
| 57 | + int wake_enable_count; |
|---|
| 67 | 58 | }; |
|---|
| 68 | 59 | |
|---|
| 69 | 60 | /* |
|---|
| .. | .. |
|---|
| 110 | 101 | outl(gpe_en_reg, GPE0A_EN_PORT); |
|---|
| 111 | 102 | } |
|---|
| 112 | 103 | |
|---|
| 104 | +static int int0002_irq_set_wake(struct irq_data *data, unsigned int on) |
|---|
| 105 | +{ |
|---|
| 106 | + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); |
|---|
| 107 | + struct int0002_data *int0002 = container_of(chip, struct int0002_data, chip); |
|---|
| 108 | + |
|---|
| 109 | + /* |
|---|
| 110 | + * Applying of the wakeup flag to our parent IRQ is delayed till system |
|---|
| 111 | + * suspend, because we only want to do this when using s2idle. |
|---|
| 112 | + */ |
|---|
| 113 | + if (on) |
|---|
| 114 | + int0002->wake_enable_count++; |
|---|
| 115 | + else |
|---|
| 116 | + int0002->wake_enable_count--; |
|---|
| 117 | + |
|---|
| 118 | + return 0; |
|---|
| 119 | +} |
|---|
| 120 | + |
|---|
| 113 | 121 | static irqreturn_t int0002_irq(int irq, void *data) |
|---|
| 114 | 122 | { |
|---|
| 115 | 123 | struct gpio_chip *chip = data; |
|---|
| .. | .. |
|---|
| 122 | 130 | generic_handle_irq(irq_find_mapping(chip->irq.domain, |
|---|
| 123 | 131 | GPE0A_PME_B0_VIRT_GPIO_PIN)); |
|---|
| 124 | 132 | |
|---|
| 125 | | - pm_system_wakeup(); |
|---|
| 133 | + pm_wakeup_hard_event(chip->parent); |
|---|
| 126 | 134 | |
|---|
| 127 | 135 | return IRQ_HANDLED; |
|---|
| 136 | +} |
|---|
| 137 | + |
|---|
| 138 | +static bool int0002_check_wake(void *data) |
|---|
| 139 | +{ |
|---|
| 140 | + u32 gpe_sts_reg; |
|---|
| 141 | + |
|---|
| 142 | + gpe_sts_reg = inl(GPE0A_STS_PORT); |
|---|
| 143 | + return (gpe_sts_reg & GPE0A_PME_B0_STS_BIT); |
|---|
| 128 | 144 | } |
|---|
| 129 | 145 | |
|---|
| 130 | 146 | static struct irq_chip int0002_irqchip = { |
|---|
| .. | .. |
|---|
| 132 | 148 | .irq_ack = int0002_irq_ack, |
|---|
| 133 | 149 | .irq_mask = int0002_irq_mask, |
|---|
| 134 | 150 | .irq_unmask = int0002_irq_unmask, |
|---|
| 151 | + .irq_set_wake = int0002_irq_set_wake, |
|---|
| 135 | 152 | }; |
|---|
| 153 | + |
|---|
| 154 | +static const struct x86_cpu_id int0002_cpu_ids[] = { |
|---|
| 155 | + X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT, NULL), |
|---|
| 156 | + X86_MATCH_INTEL_FAM6_MODEL(ATOM_AIRMONT, NULL), |
|---|
| 157 | + {} |
|---|
| 158 | +}; |
|---|
| 159 | + |
|---|
| 160 | +static void int0002_init_irq_valid_mask(struct gpio_chip *chip, |
|---|
| 161 | + unsigned long *valid_mask, |
|---|
| 162 | + unsigned int ngpios) |
|---|
| 163 | +{ |
|---|
| 164 | + bitmap_clear(valid_mask, 0, GPE0A_PME_B0_VIRT_GPIO_PIN); |
|---|
| 165 | +} |
|---|
| 136 | 166 | |
|---|
| 137 | 167 | static int int0002_probe(struct platform_device *pdev) |
|---|
| 138 | 168 | { |
|---|
| 139 | 169 | struct device *dev = &pdev->dev; |
|---|
| 140 | 170 | const struct x86_cpu_id *cpu_id; |
|---|
| 171 | + struct int0002_data *int0002; |
|---|
| 172 | + struct gpio_irq_chip *girq; |
|---|
| 141 | 173 | struct gpio_chip *chip; |
|---|
| 142 | 174 | int irq, ret; |
|---|
| 143 | 175 | |
|---|
| .. | .. |
|---|
| 147 | 179 | return -ENODEV; |
|---|
| 148 | 180 | |
|---|
| 149 | 181 | irq = platform_get_irq(pdev, 0); |
|---|
| 150 | | - if (irq < 0) { |
|---|
| 151 | | - dev_err(dev, "Error getting IRQ: %d\n", irq); |
|---|
| 182 | + if (irq < 0) |
|---|
| 152 | 183 | return irq; |
|---|
| 153 | | - } |
|---|
| 154 | 184 | |
|---|
| 155 | | - chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); |
|---|
| 156 | | - if (!chip) |
|---|
| 185 | + int0002 = devm_kzalloc(dev, sizeof(*int0002), GFP_KERNEL); |
|---|
| 186 | + if (!int0002) |
|---|
| 157 | 187 | return -ENOMEM; |
|---|
| 158 | 188 | |
|---|
| 189 | + int0002->parent_irq = irq; |
|---|
| 190 | + |
|---|
| 191 | + chip = &int0002->chip; |
|---|
| 159 | 192 | chip->label = DRV_NAME; |
|---|
| 160 | 193 | chip->parent = dev; |
|---|
| 161 | 194 | chip->owner = THIS_MODULE; |
|---|
| .. | .. |
|---|
| 165 | 198 | chip->direction_output = int0002_gpio_direction_output; |
|---|
| 166 | 199 | chip->base = -1; |
|---|
| 167 | 200 | chip->ngpio = GPE0A_PME_B0_VIRT_GPIO_PIN + 1; |
|---|
| 168 | | - chip->irq.need_valid_mask = true; |
|---|
| 169 | | - |
|---|
| 170 | | - ret = devm_gpiochip_add_data(&pdev->dev, chip, NULL); |
|---|
| 171 | | - if (ret) { |
|---|
| 172 | | - dev_err(dev, "Error adding gpio chip: %d\n", ret); |
|---|
| 173 | | - return ret; |
|---|
| 174 | | - } |
|---|
| 175 | | - |
|---|
| 176 | | - bitmap_clear(chip->irq.valid_mask, 0, GPE0A_PME_B0_VIRT_GPIO_PIN); |
|---|
| 201 | + chip->irq.init_valid_mask = int0002_init_irq_valid_mask; |
|---|
| 177 | 202 | |
|---|
| 178 | 203 | /* |
|---|
| 179 | | - * We manually request the irq here instead of passing a flow-handler |
|---|
| 204 | + * We directly request the irq here instead of passing a flow-handler |
|---|
| 180 | 205 | * to gpiochip_set_chained_irqchip, because the irq is shared. |
|---|
| 206 | + * FIXME: augment this if we managed to pull handling of shared |
|---|
| 207 | + * IRQs into gpiolib. |
|---|
| 181 | 208 | */ |
|---|
| 182 | 209 | ret = devm_request_irq(dev, irq, int0002_irq, |
|---|
| 183 | 210 | IRQF_SHARED, "INT0002", chip); |
|---|
| .. | .. |
|---|
| 186 | 213 | return ret; |
|---|
| 187 | 214 | } |
|---|
| 188 | 215 | |
|---|
| 189 | | - ret = gpiochip_irqchip_add(chip, &int0002_irqchip, 0, handle_edge_irq, |
|---|
| 190 | | - IRQ_TYPE_NONE); |
|---|
| 216 | + girq = &chip->irq; |
|---|
| 217 | + girq->chip = &int0002_irqchip; |
|---|
| 218 | + /* This let us handle the parent IRQ in the driver */ |
|---|
| 219 | + girq->parent_handler = NULL; |
|---|
| 220 | + girq->num_parents = 0; |
|---|
| 221 | + girq->parents = NULL; |
|---|
| 222 | + girq->default_type = IRQ_TYPE_NONE; |
|---|
| 223 | + girq->handler = handle_edge_irq; |
|---|
| 224 | + |
|---|
| 225 | + ret = devm_gpiochip_add_data(dev, chip, NULL); |
|---|
| 191 | 226 | if (ret) { |
|---|
| 192 | | - dev_err(dev, "Error adding irqchip: %d\n", ret); |
|---|
| 227 | + dev_err(dev, "Error adding gpio chip: %d\n", ret); |
|---|
| 193 | 228 | return ret; |
|---|
| 194 | 229 | } |
|---|
| 195 | 230 | |
|---|
| 196 | | - gpiochip_set_chained_irqchip(chip, &int0002_irqchip, irq, NULL); |
|---|
| 231 | + acpi_register_wakeup_handler(irq, int0002_check_wake, NULL); |
|---|
| 232 | + device_init_wakeup(dev, true); |
|---|
| 233 | + dev_set_drvdata(dev, int0002); |
|---|
| 234 | + return 0; |
|---|
| 235 | +} |
|---|
| 236 | + |
|---|
| 237 | +static int int0002_remove(struct platform_device *pdev) |
|---|
| 238 | +{ |
|---|
| 239 | + device_init_wakeup(&pdev->dev, false); |
|---|
| 240 | + acpi_unregister_wakeup_handler(int0002_check_wake, NULL); |
|---|
| 241 | + return 0; |
|---|
| 242 | +} |
|---|
| 243 | + |
|---|
| 244 | +static int int0002_suspend(struct device *dev) |
|---|
| 245 | +{ |
|---|
| 246 | + struct int0002_data *int0002 = dev_get_drvdata(dev); |
|---|
| 247 | + |
|---|
| 248 | + /* |
|---|
| 249 | + * The INT0002 parent IRQ is often shared with the ACPI GPE IRQ, don't |
|---|
| 250 | + * muck with it when firmware based suspend is used, otherwise we may |
|---|
| 251 | + * cause spurious wakeups from firmware managed suspend. |
|---|
| 252 | + */ |
|---|
| 253 | + if (!pm_suspend_via_firmware() && int0002->wake_enable_count) |
|---|
| 254 | + enable_irq_wake(int0002->parent_irq); |
|---|
| 197 | 255 | |
|---|
| 198 | 256 | return 0; |
|---|
| 199 | 257 | } |
|---|
| 258 | + |
|---|
| 259 | +static int int0002_resume(struct device *dev) |
|---|
| 260 | +{ |
|---|
| 261 | + struct int0002_data *int0002 = dev_get_drvdata(dev); |
|---|
| 262 | + |
|---|
| 263 | + if (!pm_suspend_via_firmware() && int0002->wake_enable_count) |
|---|
| 264 | + disable_irq_wake(int0002->parent_irq); |
|---|
| 265 | + |
|---|
| 266 | + return 0; |
|---|
| 267 | +} |
|---|
| 268 | + |
|---|
| 269 | +static const struct dev_pm_ops int0002_pm_ops = { |
|---|
| 270 | + .suspend = int0002_suspend, |
|---|
| 271 | + .resume = int0002_resume, |
|---|
| 272 | +}; |
|---|
| 200 | 273 | |
|---|
| 201 | 274 | static const struct acpi_device_id int0002_acpi_ids[] = { |
|---|
| 202 | 275 | { "INT0002", 0 }, |
|---|
| .. | .. |
|---|
| 208 | 281 | .driver = { |
|---|
| 209 | 282 | .name = DRV_NAME, |
|---|
| 210 | 283 | .acpi_match_table = int0002_acpi_ids, |
|---|
| 284 | + .pm = &int0002_pm_ops, |
|---|
| 211 | 285 | }, |
|---|
| 212 | 286 | .probe = int0002_probe, |
|---|
| 287 | + .remove = int0002_remove, |
|---|
| 213 | 288 | }; |
|---|
| 214 | 289 | |
|---|
| 215 | 290 | module_platform_driver(int0002_driver); |
|---|
| 216 | 291 | |
|---|
| 217 | 292 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); |
|---|
| 218 | 293 | MODULE_DESCRIPTION("Intel INT0002 Virtual GPIO driver"); |
|---|
| 219 | | -MODULE_LICENSE("GPL"); |
|---|
| 294 | +MODULE_LICENSE("GPL v2"); |
|---|