From patchwork Tue Oct 18 17:41:49 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Agustin Vega-Frias X-Patchwork-Id: 9382757 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 74A2A607D0 for ; Tue, 18 Oct 2016 17:42:34 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 67A8129716 for ; Tue, 18 Oct 2016 17:42:34 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 5CA2229719; Tue, 18 Oct 2016 17:42:34 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 8A1DD29717 for ; Tue, 18 Oct 2016 17:42:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S935991AbcJRRm2 (ORCPT ); Tue, 18 Oct 2016 13:42:28 -0400 Received: from smtp.codeaurora.org ([198.145.29.96]:44796 "EHLO smtp.codeaurora.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S935790AbcJRRmT (ORCPT ); Tue, 18 Oct 2016 13:42:19 -0400 Received: by smtp.codeaurora.org (Postfix, from userid 1000) id AA84261876; Tue, 18 Oct 2016 17:42:18 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=codeaurora.org; s=default; t=1476812538; bh=sgym6+ynAzoCt8butNE2lUBXOqS5sjZsRh5xsxAbn/U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RXfizPKzxCYVKe8uaXvlFaV+2Ubb8LwEL7NVX9LRe51nWBtSQ7PXhvwq5km294si1 qvyg7f/ur004O9F7bY2LNhTdiYCv2GsXP950DltccfU/zGLrZdV0DWM9XRH/ejLI5U Do3lDaRc6/ej2CKdBxWu3oOPIzFjZzeLR19byhMo= Received: from midgar.qualcomm.com (global_nat1_iad_fw.qualcomm.com [129.46.232.65]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-SHA256 (128/128 bits)) (No client certificate requested) (Authenticated sender: agustinv@smtp.codeaurora.org) by smtp.codeaurora.org (Postfix) with ESMTPSA id C383161849; Tue, 18 Oct 2016 17:42:14 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=codeaurora.org; s=default; t=1476812537; bh=sgym6+ynAzoCt8butNE2lUBXOqS5sjZsRh5xsxAbn/U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Ae/9RLVcH87n6YWWgOX0KPCGMH9MIjmOfrBy5aR8Y+I1YiQwUm/doaFh5nUHKigMK 6e9cdilbU195TOlgSGW8D2LAJq9CZNMNQPpkcxDaz3x6V70Z8keuOBWit5EwKeSNXp 1FTubGmZFiT7mCgOwK0svlNzW4mjKn8Ca7tWSG9w= DMARC-Filter: OpenDMARC Filter v1.3.1 smtp.codeaurora.org C383161849 Authentication-Results: pdx-caf-mail.web.codeaurora.org; dmarc=none header.from=codeaurora.org Authentication-Results: pdx-caf-mail.web.codeaurora.org; spf=pass smtp.mailfrom=agustinv@codeaurora.org From: Agustin Vega-Frias To: linux-kernel@vger.kernel.org, linux-acpi@vger.kernel.org, linux-arm-kernel@lists.infradead.org, rjw@rjwysocki.net, lenb@kernel.org, tglx@linutronix.de, jason@lakedaemon.net, marc.zyngier@arm.com Cc: timur@codeaurora.org, cov@codeaurora.org, agross@codeaurora.org, harba@codeaurora.org, jcm@redhat.com, msalter@redhat.com, mlangsdo@redhat.com, ahs3@redhat.com, astone@redhat.com, graeme.gregory@linaro.org, guohanjun@huawei.com, charles.garcia-tobin@arm.com, Agustin Vega-Frias Subject: [PATCH V5 2/2] irqchip: qcom: Add IRQ combiner driver Date: Tue, 18 Oct 2016 13:41:49 -0400 Message-Id: <1476812509-2760-3-git-send-email-agustinv@codeaurora.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1476812509-2760-1-git-send-email-agustinv@codeaurora.org> References: <1476812509-2760-1-git-send-email-agustinv@codeaurora.org> Sender: linux-acpi-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-acpi@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Driver for interrupt combiners in the Top-level Control and Status Registers (TCSR) hardware block in Qualcomm Technologies chips. An interrupt combiner in this block combines a set of interrupts by OR'ing the individual interrupt signals into a summary interrupt signal routed to a parent interrupt controller, and provides read- only, 32-bit registers to query the status of individual interrupts. The status bit for IRQ n is bit (n % 32) within register (n / 32) of the given combiner. Thus, each combiner can be described as a set of register offsets and the number of IRQs managed. Signed-off-by: Agustin Vega-Frias --- drivers/irqchip/Kconfig | 8 + drivers/irqchip/Makefile | 1 + drivers/irqchip/qcom-irq-combiner.c | 332 ++++++++++++++++++++++++++++++++++++ 3 files changed, 341 insertions(+) create mode 100644 drivers/irqchip/qcom-irq-combiner.c diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 82b0b5d..8e0cbbb 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -279,3 +279,11 @@ config EZNPS_GIC config STM32_EXTI bool select IRQ_DOMAIN + +config QCOM_IRQ_COMBINER + bool "QCOM IRQ combiner support" + depends on ARCH_QCOM && ACPI + select IRQ_DOMAIN + help + Say yes here to add support for the IRQ combiner devices embedded + in Qualcomm Technologies chips. diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index e4dbfc8..1818a0b 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -74,3 +74,4 @@ obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o +obj-$(CONFIG_QCOM_IRQ_COMBINER) += qcom-irq-combiner.o diff --git a/drivers/irqchip/qcom-irq-combiner.c b/drivers/irqchip/qcom-irq-combiner.c new file mode 100644 index 0000000..b117cd7 --- /dev/null +++ b/drivers/irqchip/qcom-irq-combiner.c @@ -0,0 +1,332 @@ +/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Driver for interrupt combiners in the Top-level Control and Status + * Registers (TCSR) hardware block in Qualcomm Technologies chips. + * An interrupt combiner in this block combines a set of interrupts by + * OR'ing the individual interrupt signals into a summary interrupt + * signal routed to a parent interrupt controller, and provides read- + * only, 32-bit registers to query the status of individual interrupts. + * The status bit for IRQ n is bit (n % 32) within register (n / 32) + * of the given combiner. Thus, each combiner can be described as a set + * of register offsets and the number of IRQs managed. + */ + +#include +#include +#include +#include +#include + +#define REG_SIZE 32 + +struct combiner_reg { + void __iomem *addr; + unsigned long mask; +}; + +struct combiner { + struct irq_chip irq_chip; + struct irq_domain *domain; + int parent_irq; + u32 nirqs; + u32 nregs; + struct combiner_reg regs[0]; +}; + +static inline u32 irq_register(int irq) +{ + return irq / REG_SIZE; +} + +static inline u32 irq_bit(int irq) +{ + return irq % REG_SIZE; + +} + +static inline int irq_nr(u32 reg, u32 bit) +{ + return reg * REG_SIZE + bit; +} + +/* + * Handler for the cascaded IRQ. + */ +static void combiner_handle_irq(struct irq_desc *desc) +{ + struct combiner *combiner = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + u32 reg; + + chained_irq_enter(chip, desc); + + for (reg = 0; reg < combiner->nregs; reg++) { + int virq; + int hwirq; + u32 bit; + u32 status; + + if (combiner->regs[reg].mask == 0) + continue; + + status = readl_relaxed(combiner->regs[reg].addr); + status &= combiner->regs[reg].mask; + + while (status) { + bit = __ffs(status); + status &= ~(1 << bit); + hwirq = irq_nr(reg, bit); + virq = irq_find_mapping(combiner->domain, hwirq); + if (virq >= 0) + generic_handle_irq(virq); + + } + } + + chained_irq_exit(chip, desc); +} + +/* + * irqchip callbacks + */ + +static void combiner_irq_chip_mask_irq(struct irq_data *data) +{ + struct combiner *combiner = irq_data_get_irq_chip_data(data); + struct combiner_reg *reg = combiner->regs + irq_register(data->hwirq); + + clear_bit(irq_bit(data->hwirq), ®->mask); +} + +static void combiner_irq_chip_unmask_irq(struct irq_data *data) +{ + struct combiner *combiner = irq_data_get_irq_chip_data(data); + struct combiner_reg *reg = combiner->regs + irq_register(data->hwirq); + + set_bit(irq_bit(data->hwirq), ®->mask); +} + +/* + * irq_domain_ops callbacks + */ + +static int combiner_irq_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) +{ + struct combiner *combiner = domain->host_data; + + if (hwirq >= combiner->nirqs) + return -EINVAL; + + irq_set_chip_and_handler(irq, &combiner->irq_chip, handle_level_irq); + irq_set_chip_data(irq, combiner); + irq_set_parent(irq, combiner->parent_irq); + irq_set_noprobe(irq); + return 0; +} + +static void combiner_irq_unmap(struct irq_domain *domain, unsigned int irq) +{ + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); + irq_set_parent(irq, -1); +} + +#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY +static int combiner_irq_translate(struct irq_domain *d, struct irq_fwspec *fws, + unsigned long *hwirq, unsigned int *type) +{ + if (is_acpi_node(fws->fwnode)) { + if (fws->param_count != 2) + return -EINVAL; + + *hwirq = fws->param[0]; + *type = fws->param[1]; + return 0; + } + + return -EINVAL; +} +#endif + +static const struct irq_domain_ops domain_ops = { + .map = combiner_irq_map, + .unmap = combiner_irq_unmap, +#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY + .translate = combiner_irq_translate +#endif +}; + +/* + * Device probing + */ + +static acpi_status count_registers_cb(struct acpi_resource *ares, void *context) +{ + int *count = context; + + if (ares->type == ACPI_RESOURCE_TYPE_GENERIC_REGISTER) + ++(*count); + return AE_OK; +} + +static int count_registers(struct acpi_device *adev) +{ + acpi_status status; + int count = 0; + + if (!acpi_has_method(adev->handle, METHOD_NAME__CRS)) + return -EINVAL; + + status = acpi_walk_resources(adev->handle, METHOD_NAME__CRS, + count_registers_cb, &count); + if (ACPI_FAILURE(status)) + return -EINVAL; + return count; +} + +struct get_registers_context { + struct device *dev; + struct combiner *combiner; + int err; +}; + +static acpi_status get_registers_cb(struct acpi_resource *ares, void *context) +{ + struct get_registers_context *ctx = context; + struct acpi_resource_generic_register *reg; + phys_addr_t paddr; + void __iomem *vaddr; + + if (ares->type != ACPI_RESOURCE_TYPE_GENERIC_REGISTER) + return AE_OK; + + reg = &ares->data.generic_reg; + paddr = reg->address; + if ((reg->space_id != ACPI_SPACE_MEM) || + (reg->bit_offset != 0) || + (reg->bit_width > REG_SIZE)) { + dev_err(ctx->dev, "Bad register resource @%pa\n", &paddr); + ctx->err = -EINVAL; + return AE_ERROR; + } + + vaddr = devm_ioremap(ctx->dev, reg->address, REG_SIZE); + if (IS_ERR(vaddr)) { + dev_err(ctx->dev, "Can't map register @%pa\n", &paddr); + ctx->err = PTR_ERR(vaddr); + return AE_ERROR; + } + + ctx->combiner->regs[ctx->combiner->nregs].addr = vaddr; + ctx->combiner->nirqs += reg->bit_width; + ctx->combiner->nregs++; + return AE_OK; +} + +static int get_registers(struct acpi_device *adev, struct combiner *comb) +{ + acpi_status status; + struct get_registers_context ctx; + + if (!acpi_has_method(adev->handle, METHOD_NAME__CRS)) + return -EINVAL; + + ctx.dev = &adev->dev; + ctx.combiner = comb; + ctx.err = 0; + + status = acpi_walk_resources(adev->handle, METHOD_NAME__CRS, + get_registers_cb, &ctx); + if (ACPI_FAILURE(status)) + return ctx.err; + return 0; +} + +static acpi_status get_parent_irq_cb(struct acpi_resource *ares, void *context) +{ + int *irq = context; + struct acpi_resource_extended_irq *eirq; + + if (ares->type != ACPI_RESOURCE_TYPE_EXTENDED_IRQ) + return AE_OK; + + eirq = &ares->data.extended_irq; + *irq = acpi_irq_domain_register_irq(&eirq->resource_source, + eirq->interrupts[0], + eirq->triggering, eirq->polarity); + return AE_OK; +} + +static int get_parent_irq(struct acpi_device *adev) +{ + acpi_status status; + int irq = -1; + + if (!acpi_has_method(adev->handle, METHOD_NAME__CRS)) + return -EINVAL; + + status = acpi_walk_resources(adev->handle, METHOD_NAME__CRS, + get_parent_irq_cb, &irq); + if (ACPI_FAILURE(status)) + return -EINVAL; + return irq; +} + +static int __init combiner_probe(struct acpi_device *adev) +{ + struct combiner *combiner; + size_t alloc_sz; + u32 nregs; + int err; + + nregs = count_registers(adev); + if (nregs <= 0) { + dev_err(&adev->dev, "Error reading register resources\n"); + return -EINVAL; + } + + alloc_sz = sizeof(*combiner) + sizeof(struct combiner_reg) * nregs; + combiner = devm_kzalloc(&adev->dev, alloc_sz, GFP_KERNEL); + if (!combiner) + return -ENOMEM; + + err = get_registers(adev, combiner); + if (err < 0) + return err; + + combiner->parent_irq = get_parent_irq(adev); + if (combiner->parent_irq <= 0) { + dev_err(&adev->dev, "Error getting IRQ resource\n"); + return -EINVAL; + } + + combiner->domain = irq_domain_create_linear( + &adev->fwnode, combiner->nirqs, &domain_ops, combiner); + if (!combiner->domain) + /* Errors printed by irq_domain_create_linear */ + return -ENODEV; + + irq_set_chained_handler_and_data(combiner->parent_irq, + combiner_handle_irq, combiner); + combiner->irq_chip.irq_mask = combiner_irq_chip_mask_irq; + combiner->irq_chip.irq_unmask = combiner_irq_chip_unmask_irq; + combiner->irq_chip.name = dev_name(&adev->dev); + + dev_info(&adev->dev, "Initialized with [p=%d,n=%d,r=%p]\n", + combiner->parent_irq, combiner->nirqs, combiner->regs[0].addr); + return 0; +} + +IRQCHIP_ACPI_DECLARE(qcom_combiner, "QCOM80B1", combiner_probe);