.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * Broadcom BCM7038 style Level 1 interrupt controller driver |
---|
3 | 4 | * |
---|
4 | 5 | * Copyright (C) 2014 Broadcom Corporation |
---|
5 | 6 | * Author: Kevin Cernekee |
---|
6 | | - * |
---|
7 | | - * This program is free software; you can redistribute it and/or modify |
---|
8 | | - * it under the terms of the GNU General Public License version 2 as |
---|
9 | | - * published by the Free Software Foundation. |
---|
10 | 7 | */ |
---|
11 | 8 | |
---|
12 | 9 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
---|
.. | .. |
---|
30 | 27 | #include <linux/types.h> |
---|
31 | 28 | #include <linux/irqchip.h> |
---|
32 | 29 | #include <linux/irqchip/chained_irq.h> |
---|
| 30 | +#include <linux/syscore_ops.h> |
---|
| 31 | +#ifdef CONFIG_ARM |
---|
| 32 | +#include <asm/smp_plat.h> |
---|
| 33 | +#endif |
---|
33 | 34 | |
---|
34 | 35 | #define IRQS_PER_WORD 32 |
---|
35 | 36 | #define REG_BYTES_PER_IRQ_WORD (sizeof(u32) * 4) |
---|
.. | .. |
---|
42 | 43 | unsigned int n_words; |
---|
43 | 44 | struct irq_domain *domain; |
---|
44 | 45 | struct bcm7038_l1_cpu *cpus[NR_CPUS]; |
---|
| 46 | +#ifdef CONFIG_PM_SLEEP |
---|
| 47 | + struct list_head list; |
---|
| 48 | + u32 wake_mask[MAX_WORDS]; |
---|
| 49 | +#endif |
---|
| 50 | + u32 irq_fwd_mask[MAX_WORDS]; |
---|
45 | 51 | u8 affinity[MAX_WORDS * IRQS_PER_WORD]; |
---|
46 | 52 | }; |
---|
47 | 53 | |
---|
48 | 54 | struct bcm7038_l1_cpu { |
---|
49 | 55 | void __iomem *map_base; |
---|
50 | | - u32 mask_cache[0]; |
---|
| 56 | + u32 mask_cache[]; |
---|
51 | 57 | }; |
---|
52 | 58 | |
---|
53 | 59 | /* |
---|
.. | .. |
---|
252 | 258 | resource_size_t sz; |
---|
253 | 259 | struct bcm7038_l1_cpu *cpu; |
---|
254 | 260 | unsigned int i, n_words, parent_irq; |
---|
| 261 | + int ret; |
---|
255 | 262 | |
---|
256 | 263 | if (of_address_to_resource(dn, idx, &res)) |
---|
257 | 264 | return -EINVAL; |
---|
.. | .. |
---|
265 | 272 | else if (intc->n_words != n_words) |
---|
266 | 273 | return -EINVAL; |
---|
267 | 274 | |
---|
| 275 | + ret = of_property_read_u32_array(dn , "brcm,int-fwd-mask", |
---|
| 276 | + intc->irq_fwd_mask, n_words); |
---|
| 277 | + if (ret != 0 && ret != -EINVAL) { |
---|
| 278 | + /* property exists but has the wrong number of words */ |
---|
| 279 | + pr_err("invalid brcm,int-fwd-mask property\n"); |
---|
| 280 | + return -EINVAL; |
---|
| 281 | + } |
---|
| 282 | + |
---|
268 | 283 | cpu = intc->cpus[idx] = kzalloc(sizeof(*cpu) + n_words * sizeof(u32), |
---|
269 | 284 | GFP_KERNEL); |
---|
270 | 285 | if (!cpu) |
---|
.. | .. |
---|
275 | 290 | return -ENOMEM; |
---|
276 | 291 | |
---|
277 | 292 | for (i = 0; i < n_words; i++) { |
---|
278 | | - l1_writel(0xffffffff, cpu->map_base + reg_mask_set(intc, i)); |
---|
279 | | - cpu->mask_cache[i] = 0xffffffff; |
---|
| 293 | + l1_writel(~intc->irq_fwd_mask[i], |
---|
| 294 | + cpu->map_base + reg_mask_set(intc, i)); |
---|
| 295 | + l1_writel(intc->irq_fwd_mask[i], |
---|
| 296 | + cpu->map_base + reg_mask_clr(intc, i)); |
---|
| 297 | + cpu->mask_cache[i] = ~intc->irq_fwd_mask[i]; |
---|
280 | 298 | } |
---|
281 | 299 | |
---|
282 | 300 | parent_irq = irq_of_parse_and_map(dn, idx); |
---|
.. | .. |
---|
294 | 312 | return 0; |
---|
295 | 313 | } |
---|
296 | 314 | |
---|
| 315 | +#ifdef CONFIG_PM_SLEEP |
---|
| 316 | +/* |
---|
| 317 | + * We keep a list of bcm7038_l1_chip used for suspend/resume. This hack is |
---|
| 318 | + * used because the struct chip_type suspend/resume hooks are not called |
---|
| 319 | + * unless chip_type is hooked onto a generic_chip. Since this driver does |
---|
| 320 | + * not use generic_chip, we need to manually hook our resume/suspend to |
---|
| 321 | + * syscore_ops. |
---|
| 322 | + */ |
---|
| 323 | +static LIST_HEAD(bcm7038_l1_intcs_list); |
---|
| 324 | +static DEFINE_RAW_SPINLOCK(bcm7038_l1_intcs_lock); |
---|
| 325 | + |
---|
| 326 | +static int bcm7038_l1_suspend(void) |
---|
| 327 | +{ |
---|
| 328 | + struct bcm7038_l1_chip *intc; |
---|
| 329 | + int boot_cpu, word; |
---|
| 330 | + u32 val; |
---|
| 331 | + |
---|
| 332 | + /* Wakeup interrupt should only come from the boot cpu */ |
---|
| 333 | +#ifdef CONFIG_SMP |
---|
| 334 | + boot_cpu = cpu_logical_map(0); |
---|
| 335 | +#else |
---|
| 336 | + boot_cpu = 0; |
---|
| 337 | +#endif |
---|
| 338 | + |
---|
| 339 | + list_for_each_entry(intc, &bcm7038_l1_intcs_list, list) { |
---|
| 340 | + for (word = 0; word < intc->n_words; word++) { |
---|
| 341 | + val = intc->wake_mask[word] | intc->irq_fwd_mask[word]; |
---|
| 342 | + l1_writel(~val, |
---|
| 343 | + intc->cpus[boot_cpu]->map_base + reg_mask_set(intc, word)); |
---|
| 344 | + l1_writel(val, |
---|
| 345 | + intc->cpus[boot_cpu]->map_base + reg_mask_clr(intc, word)); |
---|
| 346 | + } |
---|
| 347 | + } |
---|
| 348 | + |
---|
| 349 | + return 0; |
---|
| 350 | +} |
---|
| 351 | + |
---|
| 352 | +static void bcm7038_l1_resume(void) |
---|
| 353 | +{ |
---|
| 354 | + struct bcm7038_l1_chip *intc; |
---|
| 355 | + int boot_cpu, word; |
---|
| 356 | + |
---|
| 357 | +#ifdef CONFIG_SMP |
---|
| 358 | + boot_cpu = cpu_logical_map(0); |
---|
| 359 | +#else |
---|
| 360 | + boot_cpu = 0; |
---|
| 361 | +#endif |
---|
| 362 | + |
---|
| 363 | + list_for_each_entry(intc, &bcm7038_l1_intcs_list, list) { |
---|
| 364 | + for (word = 0; word < intc->n_words; word++) { |
---|
| 365 | + l1_writel(intc->cpus[boot_cpu]->mask_cache[word], |
---|
| 366 | + intc->cpus[boot_cpu]->map_base + reg_mask_set(intc, word)); |
---|
| 367 | + l1_writel(~intc->cpus[boot_cpu]->mask_cache[word], |
---|
| 368 | + intc->cpus[boot_cpu]->map_base + reg_mask_clr(intc, word)); |
---|
| 369 | + } |
---|
| 370 | + } |
---|
| 371 | +} |
---|
| 372 | + |
---|
| 373 | +static struct syscore_ops bcm7038_l1_syscore_ops = { |
---|
| 374 | + .suspend = bcm7038_l1_suspend, |
---|
| 375 | + .resume = bcm7038_l1_resume, |
---|
| 376 | +}; |
---|
| 377 | + |
---|
| 378 | +static int bcm7038_l1_set_wake(struct irq_data *d, unsigned int on) |
---|
| 379 | +{ |
---|
| 380 | + struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d); |
---|
| 381 | + unsigned long flags; |
---|
| 382 | + u32 word = d->hwirq / IRQS_PER_WORD; |
---|
| 383 | + u32 mask = BIT(d->hwirq % IRQS_PER_WORD); |
---|
| 384 | + |
---|
| 385 | + raw_spin_lock_irqsave(&intc->lock, flags); |
---|
| 386 | + if (on) |
---|
| 387 | + intc->wake_mask[word] |= mask; |
---|
| 388 | + else |
---|
| 389 | + intc->wake_mask[word] &= ~mask; |
---|
| 390 | + raw_spin_unlock_irqrestore(&intc->lock, flags); |
---|
| 391 | + |
---|
| 392 | + return 0; |
---|
| 393 | +} |
---|
| 394 | +#endif |
---|
| 395 | + |
---|
297 | 396 | static struct irq_chip bcm7038_l1_irq_chip = { |
---|
298 | 397 | .name = "bcm7038-l1", |
---|
299 | 398 | .irq_mask = bcm7038_l1_mask, |
---|
.. | .. |
---|
302 | 401 | #ifdef CONFIG_SMP |
---|
303 | 402 | .irq_cpu_offline = bcm7038_l1_cpu_offline, |
---|
304 | 403 | #endif |
---|
| 404 | +#ifdef CONFIG_PM_SLEEP |
---|
| 405 | + .irq_set_wake = bcm7038_l1_set_wake, |
---|
| 406 | +#endif |
---|
305 | 407 | }; |
---|
306 | 408 | |
---|
307 | 409 | static int bcm7038_l1_map(struct irq_domain *d, unsigned int virq, |
---|
308 | 410 | irq_hw_number_t hw_irq) |
---|
309 | 411 | { |
---|
| 412 | + struct bcm7038_l1_chip *intc = d->host_data; |
---|
| 413 | + u32 mask = BIT(hw_irq % IRQS_PER_WORD); |
---|
| 414 | + u32 word = hw_irq / IRQS_PER_WORD; |
---|
| 415 | + |
---|
| 416 | + if (intc->irq_fwd_mask[word] & mask) |
---|
| 417 | + return -EPERM; |
---|
| 418 | + |
---|
310 | 419 | irq_set_chip_and_handler(virq, &bcm7038_l1_irq_chip, handle_level_irq); |
---|
311 | 420 | irq_set_chip_data(virq, d->host_data); |
---|
312 | 421 | irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(virq))); |
---|
.. | .. |
---|
318 | 427 | .map = bcm7038_l1_map, |
---|
319 | 428 | }; |
---|
320 | 429 | |
---|
321 | | -int __init bcm7038_l1_of_init(struct device_node *dn, |
---|
| 430 | +static int __init bcm7038_l1_of_init(struct device_node *dn, |
---|
322 | 431 | struct device_node *parent) |
---|
323 | 432 | { |
---|
324 | 433 | struct bcm7038_l1_chip *intc; |
---|
.. | .. |
---|
347 | 456 | goto out_unmap; |
---|
348 | 457 | } |
---|
349 | 458 | |
---|
| 459 | +#ifdef CONFIG_PM_SLEEP |
---|
| 460 | + /* Add bcm7038_l1_chip into a list */ |
---|
| 461 | + raw_spin_lock(&bcm7038_l1_intcs_lock); |
---|
| 462 | + list_add_tail(&intc->list, &bcm7038_l1_intcs_list); |
---|
| 463 | + raw_spin_unlock(&bcm7038_l1_intcs_lock); |
---|
| 464 | + |
---|
| 465 | + if (list_is_singular(&bcm7038_l1_intcs_list)) |
---|
| 466 | + register_syscore_ops(&bcm7038_l1_syscore_ops); |
---|
| 467 | +#endif |
---|
| 468 | + |
---|
| 469 | + pr_info("registered BCM7038 L1 intc (%pOF, IRQs: %d)\n", |
---|
| 470 | + dn, IRQS_PER_WORD * intc->n_words); |
---|
| 471 | + |
---|
350 | 472 | return 0; |
---|
351 | 473 | |
---|
352 | 474 | out_unmap: |
---|