.. | .. |
---|
| 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"); |
---|