// SPDX-License-Identifier: GPL-2.0-only1/*2* ACPI GSI IRQ layer3*4* Copyright (C) 2015 ARM Ltd.5* Author: Lorenzo Pieralisi <[email protected]>6*/7#include <linux/acpi.h>8#include <linux/irq.h>9#include <linux/irqdomain.h>10#include <linux/of.h>1112enum acpi_irq_model_id acpi_irq_model;1314static acpi_gsi_domain_disp_fn acpi_get_gsi_domain_id;15static u32 (*acpi_gsi_to_irq_fallback)(u32 gsi);1617/**18* acpi_gsi_to_irq() - Retrieve the linux irq number for a given GSI19* @gsi: GSI IRQ number to map20* @irq: pointer where linux IRQ number is stored21*22* irq location updated with irq value [>0 on success, 0 on failure]23*24* Returns: 0 on success25* -EINVAL on failure26*/27int acpi_gsi_to_irq(u32 gsi, unsigned int *irq)28{29struct irq_domain *d;3031d = irq_find_matching_fwnode(acpi_get_gsi_domain_id(gsi),32DOMAIN_BUS_ANY);33*irq = irq_find_mapping(d, gsi);34/*35* *irq == 0 means no mapping, that should be reported as a36* failure, unless there is an arch-specific fallback handler.37*/38if (!*irq && acpi_gsi_to_irq_fallback)39*irq = acpi_gsi_to_irq_fallback(gsi);4041return (*irq > 0) ? 0 : -EINVAL;42}43EXPORT_SYMBOL_GPL(acpi_gsi_to_irq);4445/**46* acpi_register_gsi() - Map a GSI to a linux IRQ number47* @dev: device for which IRQ has to be mapped48* @gsi: GSI IRQ number49* @trigger: trigger type of the GSI number to be mapped50* @polarity: polarity of the GSI to be mapped51*52* Returns: a valid linux IRQ number on success53* -EINVAL on failure54*/55int acpi_register_gsi(struct device *dev, u32 gsi, int trigger,56int polarity)57{58struct irq_fwspec fwspec;59unsigned int irq;6061fwspec.fwnode = acpi_get_gsi_domain_id(gsi);62if (WARN_ON(!fwspec.fwnode)) {63pr_warn("GSI: No registered irqchip, giving up\n");64return -EINVAL;65}6667fwspec.param[0] = gsi;68fwspec.param[1] = acpi_dev_get_irq_type(trigger, polarity);69fwspec.param_count = 2;7071irq = irq_create_fwspec_mapping(&fwspec);72if (!irq)73return -EINVAL;7475return irq;76}77EXPORT_SYMBOL_GPL(acpi_register_gsi);7879/**80* acpi_unregister_gsi() - Free a GSI<->linux IRQ number mapping81* @gsi: GSI IRQ number82*/83void acpi_unregister_gsi(u32 gsi)84{85struct irq_domain *d;86int irq;8788if (WARN_ON(acpi_irq_model == ACPI_IRQ_MODEL_GIC && gsi < 16))89return;9091d = irq_find_matching_fwnode(acpi_get_gsi_domain_id(gsi),92DOMAIN_BUS_ANY);93irq = irq_find_mapping(d, gsi);94irq_dispose_mapping(irq);95}96EXPORT_SYMBOL_GPL(acpi_unregister_gsi);9798/**99* acpi_get_irq_source_fwhandle() - Retrieve fwhandle from IRQ resource source.100* @source: acpi_resource_source to use for the lookup.101* @gsi: GSI IRQ number102*103* Description:104* Retrieve the fwhandle of the device referenced by the given IRQ resource105* source.106*107* Return:108* The referenced device fwhandle or NULL on failure109*/110static struct fwnode_handle *111acpi_get_irq_source_fwhandle(const struct acpi_resource_source *source,112u32 gsi)113{114struct fwnode_handle *result;115struct acpi_device *device;116acpi_handle handle;117acpi_status status;118119if (!source->string_length)120return acpi_get_gsi_domain_id(gsi);121122status = acpi_get_handle(NULL, source->string_ptr, &handle);123if (WARN_ON(ACPI_FAILURE(status)))124return NULL;125126device = acpi_get_acpi_dev(handle);127if (WARN_ON(!device))128return NULL;129130result = &device->fwnode;131acpi_put_acpi_dev(device);132return result;133}134135/*136* Context for the resource walk used to lookup IRQ resources.137* Contains a return code, the lookup index, and references to the flags138* and fwspec where the result is returned.139*/140struct acpi_irq_parse_one_ctx {141int rc;142unsigned int index;143unsigned long *res_flags;144struct irq_fwspec *fwspec;145};146147/**148* acpi_irq_parse_one_match - Handle a matching IRQ resource.149* @fwnode: matching fwnode150* @hwirq: hardware IRQ number151* @triggering: triggering attributes of hwirq152* @polarity: polarity attributes of hwirq153* @polarity: polarity attributes of hwirq154* @shareable: shareable attributes of hwirq155* @wake_capable: wake capable attribute of hwirq156* @ctx: acpi_irq_parse_one_ctx updated by this function157*158* Description:159* Handle a matching IRQ resource by populating the given ctx with160* the information passed.161*/162static inline void acpi_irq_parse_one_match(struct fwnode_handle *fwnode,163u32 hwirq, u8 triggering,164u8 polarity, u8 shareable,165u8 wake_capable,166struct acpi_irq_parse_one_ctx *ctx)167{168if (!fwnode)169return;170ctx->rc = 0;171*ctx->res_flags = acpi_dev_irq_flags(triggering, polarity, shareable, wake_capable);172ctx->fwspec->fwnode = fwnode;173ctx->fwspec->param[0] = hwirq;174ctx->fwspec->param[1] = acpi_dev_get_irq_type(triggering, polarity);175ctx->fwspec->param_count = 2;176}177178/**179* acpi_irq_parse_one_cb - Handle the given resource.180* @ares: resource to handle181* @context: context for the walk182*183* Description:184* This is called by acpi_walk_resources passing each resource returned by185* the _CRS method. We only inspect IRQ resources. Since IRQ resources186* might contain multiple interrupts we check if the index is within this187* one's interrupt array, otherwise we subtract the current resource IRQ188* count from the lookup index to prepare for the next resource.189* Once a match is found we call acpi_irq_parse_one_match to populate190* the result and end the walk by returning AE_CTRL_TERMINATE.191*192* Return:193* AE_OK if the walk should continue, AE_CTRL_TERMINATE if a matching194* IRQ resource was found.195*/196static acpi_status acpi_irq_parse_one_cb(struct acpi_resource *ares,197void *context)198{199struct acpi_irq_parse_one_ctx *ctx = context;200struct acpi_resource_irq *irq;201struct acpi_resource_extended_irq *eirq;202struct fwnode_handle *fwnode;203204switch (ares->type) {205case ACPI_RESOURCE_TYPE_IRQ:206irq = &ares->data.irq;207if (ctx->index >= irq->interrupt_count) {208ctx->index -= irq->interrupt_count;209return AE_OK;210}211fwnode = acpi_get_gsi_domain_id(irq->interrupts[ctx->index]);212acpi_irq_parse_one_match(fwnode, irq->interrupts[ctx->index],213irq->triggering, irq->polarity,214irq->shareable, irq->wake_capable, ctx);215return AE_CTRL_TERMINATE;216case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:217eirq = &ares->data.extended_irq;218if (eirq->producer_consumer == ACPI_PRODUCER)219return AE_OK;220if (ctx->index >= eirq->interrupt_count) {221ctx->index -= eirq->interrupt_count;222return AE_OK;223}224fwnode = acpi_get_irq_source_fwhandle(&eirq->resource_source,225eirq->interrupts[ctx->index]);226acpi_irq_parse_one_match(fwnode, eirq->interrupts[ctx->index],227eirq->triggering, eirq->polarity,228eirq->shareable, eirq->wake_capable, ctx);229return AE_CTRL_TERMINATE;230}231232return AE_OK;233}234235/**236* acpi_irq_parse_one - Resolve an interrupt for a device237* @handle: the device whose interrupt is to be resolved238* @index: index of the interrupt to resolve239* @fwspec: structure irq_fwspec filled by this function240* @flags: resource flags filled by this function241*242* Description:243* Resolves an interrupt for a device by walking its CRS resources to find244* the appropriate ACPI IRQ resource and populating the given struct irq_fwspec245* and flags.246*247* Return:248* The result stored in ctx.rc by the callback, or the default -EINVAL value249* if an error occurs.250*/251static int acpi_irq_parse_one(acpi_handle handle, unsigned int index,252struct irq_fwspec *fwspec, unsigned long *flags)253{254struct acpi_irq_parse_one_ctx ctx = { -EINVAL, index, flags, fwspec };255256acpi_walk_resources(handle, METHOD_NAME__CRS, acpi_irq_parse_one_cb, &ctx);257return ctx.rc;258}259260/**261* acpi_irq_get - Lookup an ACPI IRQ resource and use it to initialize resource.262* @handle: ACPI device handle263* @index: ACPI IRQ resource index to lookup264* @res: Linux IRQ resource to initialize265*266* Description:267* Look for the ACPI IRQ resource with the given index and use it to initialize268* the given Linux IRQ resource.269*270* Return:271* 0 on success272* -EINVAL if an error occurs273* -EPROBE_DEFER if the IRQ lookup/conversion failed274*/275int acpi_irq_get(acpi_handle handle, unsigned int index, struct resource *res)276{277struct irq_fwspec fwspec;278struct irq_domain *domain;279unsigned long flags;280int rc;281282rc = acpi_irq_parse_one(handle, index, &fwspec, &flags);283if (rc)284return rc;285286domain = irq_find_matching_fwnode(fwspec.fwnode, DOMAIN_BUS_ANY);287if (!domain)288return -EPROBE_DEFER;289290rc = irq_create_fwspec_mapping(&fwspec);291if (rc <= 0)292return -EINVAL;293294res->start = rc;295res->end = rc;296res->flags = flags;297298return 0;299}300EXPORT_SYMBOL_GPL(acpi_irq_get);301302/**303* acpi_set_irq_model - Setup the GSI irqdomain information304* @model: the value assigned to acpi_irq_model305* @fn: a dispatcher function that will return the domain fwnode306* for a given GSI307*/308void __init acpi_set_irq_model(enum acpi_irq_model_id model,309acpi_gsi_domain_disp_fn fn)310{311acpi_irq_model = model;312acpi_get_gsi_domain_id = fn;313}314315/*316* acpi_get_gsi_dispatcher() - Get the GSI dispatcher function317*318* Return the dispatcher function that computes the domain fwnode for319* a given GSI.320*/321acpi_gsi_domain_disp_fn acpi_get_gsi_dispatcher(void)322{323return acpi_get_gsi_domain_id;324}325EXPORT_SYMBOL_GPL(acpi_get_gsi_dispatcher);326327/**328* acpi_set_gsi_to_irq_fallback - Register a GSI transfer329* callback to fallback to arch specified implementation.330* @fn: arch-specific fallback handler331*/332void __init acpi_set_gsi_to_irq_fallback(u32 (*fn)(u32))333{334acpi_gsi_to_irq_fallback = fn;335}336337/**338* acpi_irq_create_hierarchy - Create a hierarchical IRQ domain with the default339* GSI domain as its parent.340* @flags: Irq domain flags associated with the domain341* @size: Size of the domain.342* @fwnode: Optional fwnode of the interrupt controller343* @ops: Pointer to the interrupt domain callbacks344* @host_data: Controller private data pointer345*/346struct irq_domain *acpi_irq_create_hierarchy(unsigned int flags,347unsigned int size,348struct fwnode_handle *fwnode,349const struct irq_domain_ops *ops,350void *host_data)351{352struct irq_domain *d;353354/* This only works for the GIC model... */355if (acpi_irq_model != ACPI_IRQ_MODEL_GIC)356return NULL;357358d = irq_find_matching_fwnode(acpi_get_gsi_domain_id(0),359DOMAIN_BUS_ANY);360361if (!d)362return NULL;363364return irq_domain_create_hierarchy(d, flags, size, fwnode, ops,365host_data);366}367EXPORT_SYMBOL_GPL(acpi_irq_create_hierarchy);368369370