irq.c 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * Copyright (c) 2021, The Linux Foundation. All rights reserved.
  4. *
  5. */
  6. #include <linux/of.h>
  7. #include <linux/of_irq.h>
  8. #include <linux/irqdomain.h>
  9. #include <dt-bindings/interrupt-controller/arm-gic.h>
  10. #include <linux/gunyah/gh_rm_drv.h>
  11. #define GIC_V3_SPI_MAX 1019
  12. #define GH_RM_NO_IRQ_ALLOC -1
  13. #define IRQ_OFFSET 32
  14. static DEFINE_IDR(gh_rm_free_virq_idr);
  15. /**
  16. * gh_get_irq: Get a Linux IRQ from a Gunyah-compatible vIRQ
  17. * @virq: Gunyah-compatible vIRQ
  18. * @type: IRQ trigger type (IRQ_TYPE_EDGE_RISING)
  19. * @fw_handle: fw node handle
  20. *
  21. * Returns the mapped Linux IRQ# at Gunyah's IRQ domain (i.e. GIC SPI)
  22. */
  23. int gh_get_irq(u32 virq, u32 type, struct fwnode_handle *fw_handle)
  24. {
  25. struct irq_fwspec fwspec = {};
  26. if (virq < IRQ_OFFSET || virq >= GIC_V3_SPI_MAX) {
  27. pr_warn("%s: expecting an SPI from RM, but got GIC IRQ %d\n",
  28. __func__, virq);
  29. }
  30. fwspec.fwnode = fw_handle;
  31. fwspec.param_count = 3;
  32. fwspec.param[0] = GIC_SPI;
  33. fwspec.param[1] = virq - IRQ_OFFSET;
  34. fwspec.param[2] = type;
  35. return irq_create_fwspec_mapping(&fwspec);
  36. }
  37. EXPORT_SYMBOL(gh_get_irq);
  38. /**
  39. * gh_get_virq: Allocate a new IRQ if RM-VM hasn't already done already
  40. * @base_virq: The base virtual IRQ number.
  41. * @virq: The virtual IRQ number.
  42. *
  43. * Returns Gunyah compatible vIRQ to bind to.
  44. */
  45. int gh_get_virq(int base_virq, int virq)
  46. {
  47. int ret;
  48. /* Get the next free vIRQ.
  49. * Subtract IRQ_OFFSET from the base virq to get the base SPI.
  50. *
  51. * Assoiate the address of the idr variable itself as a lookup
  52. * ptr. This will help us to free the virq later.
  53. */
  54. ret = virq = idr_alloc(&gh_rm_free_virq_idr,
  55. &gh_rm_free_virq_idr,
  56. base_virq - IRQ_OFFSET,
  57. GIC_V3_SPI_MAX, GFP_KERNEL);
  58. if (ret < 0)
  59. return ret;
  60. /* Add IRQ_OFFSET offset to make interrupt as hwirq */
  61. virq += IRQ_OFFSET;
  62. return virq;
  63. }
  64. EXPORT_SYMBOL(gh_get_virq);
  65. /**
  66. * gh_put_virq: Deallocates a vIRQ.
  67. * @irq: The IRQ number.
  68. *
  69. * Returns 0 on success and EINVAL if no IRQ was found.
  70. */
  71. int gh_put_virq(int virq)
  72. {
  73. void *idr_ptr;
  74. int virq_num;
  75. virq_num = virq - IRQ_OFFSET;
  76. /* If the idr_find() returns a valid ptr, it means that the
  77. * virq was allocated by the kernel itself and not by hyp.
  78. * Release the IRQ and free the allocation if that's true.
  79. */
  80. idr_ptr = idr_find(&gh_rm_free_virq_idr, virq_num);
  81. if (idr_ptr) {
  82. idr_remove(&gh_rm_free_virq_idr, virq_num);
  83. return 0;
  84. }
  85. return -EINVAL;
  86. }
  87. EXPORT_SYMBOL(gh_put_virq);
  88. /**
  89. * gh_put_irq: Deallocate an Linux IRQ.
  90. * @irq: The IRQ number.
  91. *
  92. * Returns 0 on success and EINVAL if no IRQ was found.
  93. */
  94. int gh_put_irq(int irq)
  95. {
  96. struct irq_data *irq_data;
  97. unsigned long virq;
  98. if (irq <= 0)
  99. return -EINVAL;
  100. irq_data = irq_get_irq_data(irq);
  101. if (!irq_data)
  102. return -EINVAL;
  103. virq = irq_data->hwirq;
  104. irq_dispose_mapping(irq);
  105. return gh_put_virq(virq);
  106. }
  107. EXPORT_SYMBOL(gh_put_irq);