357 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * ACPI GSI IRQ layer4 *5 * Copyright (C) 2015 ARM Ltd.6 * Author: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>7 */8#include <linux/acpi.h>9#include <linux/irq.h>10#include <linux/irqdomain.h>11#include <linux/of.h>12 13enum acpi_irq_model_id acpi_irq_model;14 15static struct fwnode_handle *(*acpi_get_gsi_domain_id)(u32 gsi);16static u32 (*acpi_gsi_to_irq_fallback)(u32 gsi);17 18/**19 * acpi_gsi_to_irq() - Retrieve the linux irq number for a given GSI20 * @gsi: GSI IRQ number to map21 * @irq: pointer where linux IRQ number is stored22 *23 * irq location updated with irq value [>0 on success, 0 on failure]24 *25 * Returns: 0 on success26 * -EINVAL on failure27 */28int acpi_gsi_to_irq(u32 gsi, unsigned int *irq)29{30 struct irq_domain *d;31 32 d = irq_find_matching_fwnode(acpi_get_gsi_domain_id(gsi),33 DOMAIN_BUS_ANY);34 *irq = irq_find_mapping(d, gsi);35 /*36 * *irq == 0 means no mapping, that should be reported as a37 * failure, unless there is an arch-specific fallback handler.38 */39 if (!*irq && acpi_gsi_to_irq_fallback)40 *irq = acpi_gsi_to_irq_fallback(gsi);41 42 return (*irq > 0) ? 0 : -EINVAL;43}44EXPORT_SYMBOL_GPL(acpi_gsi_to_irq);45 46/**47 * acpi_register_gsi() - Map a GSI to a linux IRQ number48 * @dev: device for which IRQ has to be mapped49 * @gsi: GSI IRQ number50 * @trigger: trigger type of the GSI number to be mapped51 * @polarity: polarity of the GSI to be mapped52 *53 * Returns: a valid linux IRQ number on success54 * -EINVAL on failure55 */56int acpi_register_gsi(struct device *dev, u32 gsi, int trigger,57 int polarity)58{59 struct irq_fwspec fwspec;60 unsigned int irq;61 62 fwspec.fwnode = acpi_get_gsi_domain_id(gsi);63 if (WARN_ON(!fwspec.fwnode)) {64 pr_warn("GSI: No registered irqchip, giving up\n");65 return -EINVAL;66 }67 68 fwspec.param[0] = gsi;69 fwspec.param[1] = acpi_dev_get_irq_type(trigger, polarity);70 fwspec.param_count = 2;71 72 irq = irq_create_fwspec_mapping(&fwspec);73 if (!irq)74 return -EINVAL;75 76 return irq;77}78EXPORT_SYMBOL_GPL(acpi_register_gsi);79 80/**81 * acpi_unregister_gsi() - Free a GSI<->linux IRQ number mapping82 * @gsi: GSI IRQ number83 */84void acpi_unregister_gsi(u32 gsi)85{86 struct irq_domain *d;87 int irq;88 89 if (WARN_ON(acpi_irq_model == ACPI_IRQ_MODEL_GIC && gsi < 16))90 return;91 92 d = irq_find_matching_fwnode(acpi_get_gsi_domain_id(gsi),93 DOMAIN_BUS_ANY);94 irq = irq_find_mapping(d, gsi);95 irq_dispose_mapping(irq);96}97EXPORT_SYMBOL_GPL(acpi_unregister_gsi);98 99/**100 * acpi_get_irq_source_fwhandle() - Retrieve fwhandle from IRQ resource source.101 * @source: acpi_resource_source to use for the lookup.102 * @gsi: GSI IRQ number103 *104 * Description:105 * Retrieve the fwhandle of the device referenced by the given IRQ resource106 * source.107 *108 * Return:109 * The referenced device fwhandle or NULL on failure110 */111static struct fwnode_handle *112acpi_get_irq_source_fwhandle(const struct acpi_resource_source *source,113 u32 gsi)114{115 struct fwnode_handle *result;116 struct acpi_device *device;117 acpi_handle handle;118 acpi_status status;119 120 if (!source->string_length)121 return acpi_get_gsi_domain_id(gsi);122 123 status = acpi_get_handle(NULL, source->string_ptr, &handle);124 if (WARN_ON(ACPI_FAILURE(status)))125 return NULL;126 127 device = acpi_get_acpi_dev(handle);128 if (WARN_ON(!device))129 return NULL;130 131 result = &device->fwnode;132 acpi_put_acpi_dev(device);133 return result;134}135 136/*137 * Context for the resource walk used to lookup IRQ resources.138 * Contains a return code, the lookup index, and references to the flags139 * and fwspec where the result is returned.140 */141struct acpi_irq_parse_one_ctx {142 int rc;143 unsigned int index;144 unsigned long *res_flags;145 struct irq_fwspec *fwspec;146};147 148/**149 * acpi_irq_parse_one_match - Handle a matching IRQ resource.150 * @fwnode: matching fwnode151 * @hwirq: hardware IRQ number152 * @triggering: triggering attributes of hwirq153 * @polarity: polarity attributes of hwirq154 * @polarity: polarity attributes of hwirq155 * @shareable: shareable attributes of hwirq156 * @wake_capable: wake capable attribute of hwirq157 * @ctx: acpi_irq_parse_one_ctx updated by this function158 *159 * Description:160 * Handle a matching IRQ resource by populating the given ctx with161 * the information passed.162 */163static inline void acpi_irq_parse_one_match(struct fwnode_handle *fwnode,164 u32 hwirq, u8 triggering,165 u8 polarity, u8 shareable,166 u8 wake_capable,167 struct acpi_irq_parse_one_ctx *ctx)168{169 if (!fwnode)170 return;171 ctx->rc = 0;172 *ctx->res_flags = acpi_dev_irq_flags(triggering, polarity, shareable, wake_capable);173 ctx->fwspec->fwnode = fwnode;174 ctx->fwspec->param[0] = hwirq;175 ctx->fwspec->param[1] = acpi_dev_get_irq_type(triggering, polarity);176 ctx->fwspec->param_count = 2;177}178 179/**180 * acpi_irq_parse_one_cb - Handle the given resource.181 * @ares: resource to handle182 * @context: context for the walk183 *184 * Description:185 * This is called by acpi_walk_resources passing each resource returned by186 * the _CRS method. We only inspect IRQ resources. Since IRQ resources187 * might contain multiple interrupts we check if the index is within this188 * one's interrupt array, otherwise we subtract the current resource IRQ189 * count from the lookup index to prepare for the next resource.190 * Once a match is found we call acpi_irq_parse_one_match to populate191 * the result and end the walk by returning AE_CTRL_TERMINATE.192 *193 * Return:194 * AE_OK if the walk should continue, AE_CTRL_TERMINATE if a matching195 * IRQ resource was found.196 */197static acpi_status acpi_irq_parse_one_cb(struct acpi_resource *ares,198 void *context)199{200 struct acpi_irq_parse_one_ctx *ctx = context;201 struct acpi_resource_irq *irq;202 struct acpi_resource_extended_irq *eirq;203 struct fwnode_handle *fwnode;204 205 switch (ares->type) {206 case ACPI_RESOURCE_TYPE_IRQ:207 irq = &ares->data.irq;208 if (ctx->index >= irq->interrupt_count) {209 ctx->index -= irq->interrupt_count;210 return AE_OK;211 }212 fwnode = acpi_get_gsi_domain_id(irq->interrupts[ctx->index]);213 acpi_irq_parse_one_match(fwnode, irq->interrupts[ctx->index],214 irq->triggering, irq->polarity,215 irq->shareable, irq->wake_capable, ctx);216 return AE_CTRL_TERMINATE;217 case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:218 eirq = &ares->data.extended_irq;219 if (eirq->producer_consumer == ACPI_PRODUCER)220 return AE_OK;221 if (ctx->index >= eirq->interrupt_count) {222 ctx->index -= eirq->interrupt_count;223 return AE_OK;224 }225 fwnode = acpi_get_irq_source_fwhandle(&eirq->resource_source,226 eirq->interrupts[ctx->index]);227 acpi_irq_parse_one_match(fwnode, eirq->interrupts[ctx->index],228 eirq->triggering, eirq->polarity,229 eirq->shareable, eirq->wake_capable, ctx);230 return AE_CTRL_TERMINATE;231 }232 233 return AE_OK;234}235 236/**237 * acpi_irq_parse_one - Resolve an interrupt for a device238 * @handle: the device whose interrupt is to be resolved239 * @index: index of the interrupt to resolve240 * @fwspec: structure irq_fwspec filled by this function241 * @flags: resource flags filled by this function242 *243 * Description:244 * Resolves an interrupt for a device by walking its CRS resources to find245 * the appropriate ACPI IRQ resource and populating the given struct irq_fwspec246 * and flags.247 *248 * Return:249 * The result stored in ctx.rc by the callback, or the default -EINVAL value250 * if an error occurs.251 */252static int acpi_irq_parse_one(acpi_handle handle, unsigned int index,253 struct irq_fwspec *fwspec, unsigned long *flags)254{255 struct acpi_irq_parse_one_ctx ctx = { -EINVAL, index, flags, fwspec };256 257 acpi_walk_resources(handle, METHOD_NAME__CRS, acpi_irq_parse_one_cb, &ctx);258 return ctx.rc;259}260 261/**262 * acpi_irq_get - Lookup an ACPI IRQ resource and use it to initialize resource.263 * @handle: ACPI device handle264 * @index: ACPI IRQ resource index to lookup265 * @res: Linux IRQ resource to initialize266 *267 * Description:268 * Look for the ACPI IRQ resource with the given index and use it to initialize269 * the given Linux IRQ resource.270 *271 * Return:272 * 0 on success273 * -EINVAL if an error occurs274 * -EPROBE_DEFER if the IRQ lookup/conversion failed275 */276int acpi_irq_get(acpi_handle handle, unsigned int index, struct resource *res)277{278 struct irq_fwspec fwspec;279 struct irq_domain *domain;280 unsigned long flags;281 int rc;282 283 rc = acpi_irq_parse_one(handle, index, &fwspec, &flags);284 if (rc)285 return rc;286 287 domain = irq_find_matching_fwnode(fwspec.fwnode, DOMAIN_BUS_ANY);288 if (!domain)289 return -EPROBE_DEFER;290 291 rc = irq_create_fwspec_mapping(&fwspec);292 if (rc <= 0)293 return -EINVAL;294 295 res->start = rc;296 res->end = rc;297 res->flags = flags;298 299 return 0;300}301EXPORT_SYMBOL_GPL(acpi_irq_get);302 303/**304 * acpi_set_irq_model - Setup the GSI irqdomain information305 * @model: the value assigned to acpi_irq_model306 * @fn: a dispatcher function that will return the domain fwnode307 * for a given GSI308 */309void __init acpi_set_irq_model(enum acpi_irq_model_id model,310 struct fwnode_handle *(*fn)(u32))311{312 acpi_irq_model = model;313 acpi_get_gsi_domain_id = fn;314}315 316/**317 * acpi_set_gsi_to_irq_fallback - Register a GSI transfer318 * callback to fallback to arch specified implementation.319 * @fn: arch-specific fallback handler320 */321void __init acpi_set_gsi_to_irq_fallback(u32 (*fn)(u32))322{323 acpi_gsi_to_irq_fallback = fn;324}325 326/**327 * acpi_irq_create_hierarchy - Create a hierarchical IRQ domain with the default328 * GSI domain as its parent.329 * @flags: Irq domain flags associated with the domain330 * @size: Size of the domain.331 * @fwnode: Optional fwnode of the interrupt controller332 * @ops: Pointer to the interrupt domain callbacks333 * @host_data: Controller private data pointer334 */335struct irq_domain *acpi_irq_create_hierarchy(unsigned int flags,336 unsigned int size,337 struct fwnode_handle *fwnode,338 const struct irq_domain_ops *ops,339 void *host_data)340{341 struct irq_domain *d;342 343 /* This only works for the GIC model... */344 if (acpi_irq_model != ACPI_IRQ_MODEL_GIC)345 return NULL;346 347 d = irq_find_matching_fwnode(acpi_get_gsi_domain_id(0),348 DOMAIN_BUS_ANY);349 350 if (!d)351 return NULL;352 353 return irq_domain_create_hierarchy(d, flags, size, fwnode, ops,354 host_data);355}356EXPORT_SYMBOL_GPL(acpi_irq_create_hierarchy);357