From patchwork Tue Dec 1 16:24:20 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Carlo Caione X-Patchwork-Id: 7738781 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 3E9E8BEEE1 for ; Tue, 1 Dec 2015 16:29:12 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id C4B95204EB for ; Tue, 1 Dec 2015 16:29:10 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 1911D204A9 for ; Tue, 1 Dec 2015 16:29:09 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1a3nlU-0002hS-QY; Tue, 01 Dec 2015 16:26:52 +0000 Received: from mail-wm0-x234.google.com ([2a00:1450:400c:c09::234]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1a3njo-000861-Fp for linux-arm-kernel@lists.infradead.org; Tue, 01 Dec 2015 16:25:13 +0000 Received: by wmec201 with SMTP id c201so213602493wme.0 for ; Tue, 01 Dec 2015 08:24:46 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:cc:subject:date:message-id:in-reply-to:references; bh=6d/i/MH0INjZpn0H8z3xgouQo2ypjDcHsS9ix/TBd5Q=; b=llkaNPs677+OiSrmfSY0iW7NF28clsk0mDAMVB6SyjhlsIhCP1NCF4NvxbUkZFbIxt 2GkqUJ/8Z9DyAlyiz5DRxK4kuor3NkDib3NQU7d8GjpTa4JtHVbPOtOykcv7KtDOv9Bw qmThP3ENUbk5YtIo5DuOnk/0oEfE9LnETLZwxDyUje/X/K7Ia2FMLoxVUJRZDe7a546s 8r8RXfRgN6yNJqfAYKvTReLo6CHN0k89T6ypeN3Kf/uTefFBIqRMBVM0hKkQ5QNRhNFz NC3ILpK5EE+h+JedD8tnTy+wRIflHg0Rz+naN1tZv+4R9t1Tf5VgnsAE3NytC/DcttIV s+qw== X-Received: by 10.28.218.17 with SMTP id r17mr37738956wmg.90.1448987086805; Tue, 01 Dec 2015 08:24:46 -0800 (PST) Received: from localhost.localdomain ([212.91.95.170]) by smtp.gmail.com with ESMTPSA id u4sm52078451wjz.4.2015.12.01.08.24.45 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 01 Dec 2015 08:24:45 -0800 (PST) From: Carlo Caione To: robh+dt@kernel.org, devicetree@vger.kernel.org, jiang.liu@linux.intel.com, marc.zyngier@arm.com, tglx@linutronix.de, linus.walleij@linaro.org, b.galvani@gmail.com, linux-arm-kernel@lists.infradead.org, linux-meson@googlegroups.com, drake@endlessm.com, jerry.cao@amlogic.com, victor.wan@amlogic.com Subject: [PATCH v3 4/6] pinctrl: meson: Enable GPIO IRQs Date: Tue, 1 Dec 2015 17:24:20 +0100 Message-Id: <1448987062-31225-5-git-send-email-carlo@caione.org> X-Mailer: git-send-email 2.5.0 In-Reply-To: <1448987062-31225-1-git-send-email-carlo@caione.org> References: <1448987062-31225-1-git-send-email-carlo@caione.org> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20151201_082508_953034_62B437E0 X-CRM114-Status: GOOD ( 27.24 ) X-Spam-Score: -2.6 (--) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Carlo Caione MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_MED, T_DKIM_INVALID, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Carlo Caione On Meson8 and Meson8b SoCs there are 8 independent filtered GPIO interrupt modules that can be programmed to use any of the GPIOs in the chip as an interrupt source. For each GPIO IRQ we have: GPIOs --> [mux]--> [polarity]--> [filter]--> [edge select]--> GIC The eight GPIO interrupts respond to mask/unmask/clear/etc.. just like any other interrupt in the chip. The difference for the GPIO interrupts is that they can be filtered and conditioned. This patch adds support for the external GPIOs interrupts and enables them for Meson8 and Meson8b SoCs. Signed-off-by: Carlo Caione Signed-off-by: Beniamino Galvani --- drivers/pinctrl/Kconfig | 1 + drivers/pinctrl/meson/Makefile | 2 +- drivers/pinctrl/meson/irqchip-gpio-meson.c | 321 +++++++++++++++++++++++++++++ drivers/pinctrl/meson/pinctrl-meson.c | 30 +++ drivers/pinctrl/meson/pinctrl-meson.h | 15 ++ 5 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 drivers/pinctrl/meson/irqchip-gpio-meson.c diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index b422e4e..07097d3 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -130,6 +130,7 @@ config PINCTRL_MESON select GPIOLIB select OF_GPIO select REGMAP_MMIO + select IRQ_DOMAIN_HIERARCHY config PINCTRL_ROCKCHIP bool diff --git a/drivers/pinctrl/meson/Makefile b/drivers/pinctrl/meson/Makefile index c751d22..8e83780 100644 --- a/drivers/pinctrl/meson/Makefile +++ b/drivers/pinctrl/meson/Makefile @@ -1,2 +1,2 @@ obj-y += pinctrl-meson8.o pinctrl-meson8b.o -obj-y += pinctrl-meson.o +obj-y += pinctrl-meson.o irqchip-gpio-meson.o diff --git a/drivers/pinctrl/meson/irqchip-gpio-meson.c b/drivers/pinctrl/meson/irqchip-gpio-meson.c new file mode 100644 index 0000000..0a4b250 --- /dev/null +++ b/drivers/pinctrl/meson/irqchip-gpio-meson.c @@ -0,0 +1,321 @@ +/* + * GPIO IRQ driver for Amlogic Meson SoCs + * + * Copyright (C) 2015 Endless Mobile, Inc. + * Author: Carlo Caione + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Amlogic Meson SoCs have only a limited number of IRQs on the GIC side that + * can be used for the GPIOs. + * + * GPIO# -> [mux] -> [polarity] -> [filter] -> [edge select] -> GIC IRQ# + * + * The GPIO used to trigger the IRQ is chosen by filling a bitmask in the 'mux' + * registers. + * + * The bitmask position determines the IRQ + * + * GPIO -> [mux1 [7:0]] -> ... -> GIC / GPIO IRQ0 + * GPIO -> [mux1 [15:8]] -> ... -> GIC / GPIO IRQ1 + * ... + * GPIO -> [mux2 [23:16]] -> ... -> GIC / GPIO IRQ6 + * ... + * + * The bitmask value determines the GPIO used to trigger the IRQ + * + * GPIOX_21 -> 118 in the mux# bitmask register + * ... + * GPIOH_9 -> 23 in the mux# bitmask register + * ... + * + */ + +#include +#include +#include +#include + +#include "pinctrl-meson.h" + +#define REG_EDGE_POL 0x00 +#define REG_GPIO_SEL0 0x04 +#define REG_GPIO_SEL1 0x08 +#define REG_FILTER 0x0c + +#define IRQ_FREE (-1) + +#define REG_EDGE_POL_MASK(x) (BIT(x) | BIT(16 + (x))) +#define REG_EDGE_POL_EDGE(x) BIT(x) +#define REG_EDGE_POL_LOW(x) BIT(16 + (x)) + +static int meson_get_gic_irq(struct meson_pinctrl *pc, int hwirq) +{ + int i = 0; + + for (i = 0; i < pc->num_gic_irqs; i++) { + if (pc->irq_map[i] == hwirq) + return i; + } + + return -1; +} + +static int meson_irq_set_type(struct irq_data *data, unsigned int type) +{ + struct meson_pinctrl *pc = irq_data_get_irq_chip_data(data); + u32 val = 0; + int index; + + dev_dbg(pc->dev, "set type of hwirq %lu to %u\n", data->hwirq, type); + spin_lock(&pc->lock); + index = meson_get_gic_irq(pc, data->hwirq); + + if (index < 0) { + spin_unlock(&pc->lock); + dev_err(pc->dev, "hwirq %lu not allocated\n", data->hwirq); + return -EINVAL; + } + + if (type == IRQ_TYPE_EDGE_FALLING || type == IRQ_TYPE_EDGE_RISING) + val |= REG_EDGE_POL_EDGE(index); + if (type == IRQ_TYPE_EDGE_FALLING || type == IRQ_TYPE_LEVEL_LOW) + val |= REG_EDGE_POL_LOW(index); + + regmap_update_bits(pc->reg_irq, REG_EDGE_POL, REG_EDGE_POL_MASK(index), + val); + spin_unlock(&pc->lock); + + if (type == IRQ_TYPE_LEVEL_LOW) + type = IRQ_TYPE_LEVEL_HIGH; + else if (type == IRQ_TYPE_EDGE_FALLING) + type = IRQ_TYPE_EDGE_RISING; + + return irq_chip_set_type_parent(data, type); +} + +int meson_irq_request_resources(struct irq_data *data) +{ + struct meson_pinctrl *pc = irq_data_get_irq_chip_data(data); + struct meson_domain *domain; + struct meson_bank *bank; + + if (meson_get_domain_and_bank(pc, data->hwirq, &domain, &bank)) + return -EINVAL; + + if (gpiochip_lock_as_irq(&domain->chip, data->hwirq)) + return -EINVAL; + + return 0; +} + +void meson_irq_release_resources(struct irq_data *data) +{ + struct meson_pinctrl *pc = irq_data_get_irq_chip_data(data); + struct meson_domain *domain; + struct meson_bank *bank; + + if (meson_get_domain_and_bank(pc, data->hwirq, &domain, &bank)) + return; + + gpiochip_unlock_as_irq(&domain->chip, data->hwirq); +} + +static struct irq_chip meson_irq_chip = { + .name = "meson-gpio-irqchip", + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_eoi = irq_chip_eoi_parent, + .irq_set_type = meson_irq_set_type, + .irq_retrigger = irq_chip_retrigger_hierarchy, + .irq_set_affinity = irq_chip_set_affinity_parent, + .irq_request_resources = meson_irq_request_resources, + .irq_release_resources = meson_irq_release_resources, +}; + +static int meson_map_gic_irq(struct irq_domain *irq_domain, + irq_hw_number_t hwirq) +{ + struct meson_pinctrl *pc = irq_domain->host_data; + struct meson_domain *domain; + struct meson_bank *bank; + int index, reg, ret; + + ret = meson_get_domain_and_bank(pc, hwirq, &domain, &bank); + if (ret) + return ret; + + spin_lock(&pc->lock); + + index = meson_get_gic_irq(pc, IRQ_FREE); + if (index < 0) { + spin_unlock(&pc->lock); + dev_err(pc->dev, "no free GIC interrupt found"); + return -ENOSPC; + } + + dev_dbg(pc->dev, "found free GIC interrupt %d\n", index); + pc->irq_map[index] = hwirq; + + /* Setup IRQ mapping */ + reg = index < 4 ? REG_GPIO_SEL0 : REG_GPIO_SEL1; + regmap_update_bits(pc->reg_irq, reg, 0xff << (index % 4) * 8, + (bank->irq + hwirq - bank->first) << (index % 4) * 8); + + /* Set filter to the default, undocumented value of 7 */ + regmap_update_bits(pc->reg_irq, REG_FILTER, 0xf << index * 4, + 7 << index * 4); + + spin_unlock(&pc->lock); + + return index; +} + +static int meson_irq_domain_alloc(struct irq_domain *domain, unsigned int irq, + unsigned int nr_irqs, void *arg) +{ + struct meson_pinctrl *pc = domain->host_data; + struct irq_fwspec *irq_data = arg; + struct irq_fwspec gic_data; + irq_hw_number_t hwirq; + int index, ret, i; + + if (irq_data->param_count != 2) + return -EINVAL; + + hwirq = irq_data->param[0]; + dev_dbg(pc->dev, "%s irq %d, nr %d, hwirq %lu\n", + __func__, irq, nr_irqs, hwirq); + + for (i = 0; i < nr_irqs; i++) { + index = meson_map_gic_irq(domain, hwirq + i); + if (index < 0) + return index; + + irq_domain_set_hwirq_and_chip(domain, irq + i, + hwirq + i, + &meson_irq_chip, + pc); + + gic_data.param_count = 3; + gic_data.fwnode = domain->parent->fwnode; + gic_data.param[0] = 0; /* SPI */ + gic_data.param[1] = pc->gic_irqs[index]; + gic_data.param[1] = IRQ_TYPE_EDGE_RISING; + + ret = irq_domain_alloc_irqs_parent(domain, irq + i, nr_irqs, + &gic_data); + } + + return 0; +} + +static void meson_irq_domain_free(struct irq_domain *domain, unsigned int irq, + unsigned int nr_irqs) +{ + struct meson_pinctrl *pc = domain->host_data; + struct irq_data *irq_data; + int index, i; + + spin_lock(&pc->lock); + for (i = 0; i < nr_irqs; i++) { + irq_data = irq_domain_get_irq_data(domain, irq + i); + index = meson_get_gic_irq(pc, irq_data->hwirq); + if (index < 0) + continue; + pc->irq_map[index] = IRQ_FREE; + } + spin_unlock(&pc->lock); + + irq_domain_free_irqs_parent(domain, irq, nr_irqs); +} + +static int meson_irq_domain_translate(struct irq_domain *d, + struct irq_fwspec *fwspec, + unsigned long *hwirq, + unsigned int *type) +{ + if (is_of_node(fwspec->fwnode)) { + if (fwspec->param_count != 2) + return -EINVAL; + + *hwirq = fwspec->param[0]; + *type = fwspec->param[1]; + + return 0; + } + + return -EINVAL; +} + +static struct irq_domain_ops meson_irq_domain_ops = { + .alloc = meson_irq_domain_alloc, + .free = meson_irq_domain_free, + .translate = meson_irq_domain_translate, +}; + +int meson_gpio_irq_init(struct meson_pinctrl *pc) +{ + struct device_node *node = pc->dev->of_node; + struct device_node *parent_node; + struct irq_domain *parent_domain; + const __be32 *irqs; + int i, size; + + parent_node = of_irq_find_parent(node); + if (!parent_node) { + dev_err(pc->dev, "can't find parent interrupt controller\n"); + return -EINVAL; + } + + parent_domain = irq_find_host(parent_node); + if (!parent_domain) { + dev_err(pc->dev, "can't find parent IRQ domain\n"); + return -EINVAL; + } + + pc->reg_irq = meson_map_resource(pc, node, "irq"); + if (!pc->reg_irq) { + dev_err(pc->dev, "can't find irq registers\n"); + return -EINVAL; + } + + irqs = of_get_property(node, "amlogic,irqs-gpio", &size); + if (!irqs) { + dev_err(pc->dev, "no parent interrupts specified\n"); + return -EINVAL; + } + pc->num_gic_irqs = size / sizeof(__be32); + + pc->irq_map = devm_kmalloc(pc->dev, sizeof(int) * pc->num_gic_irqs, + GFP_KERNEL); + if (!pc->irq_map) + return -ENOMEM; + + pc->gic_irqs = devm_kzalloc(pc->dev, sizeof(int) * pc->num_gic_irqs, + GFP_KERNEL); + if (!pc->gic_irqs) + return -ENOMEM; + + for (i = 0; i < pc->num_gic_irqs; i++) { + pc->irq_map[i] = IRQ_FREE; + of_property_read_u32_index(node, "amlogic,irqs-gpio", i, + &pc->gic_irqs[i]); + } + + pc->irq_domain = irq_domain_add_hierarchy(parent_domain, 0, + pc->data->last_pin, + node, &meson_irq_domain_ops, + pc); + if (!pc->irq_domain) + return -EINVAL; + + return 0; +} diff --git a/drivers/pinctrl/meson/pinctrl-meson.c b/drivers/pinctrl/meson/pinctrl-meson.c index 0c5655b..894b9ad 100644 --- a/drivers/pinctrl/meson/pinctrl-meson.c +++ b/drivers/pinctrl/meson/pinctrl-meson.c @@ -540,6 +540,30 @@ static int meson_gpio_get(struct gpio_chip *chip, unsigned gpio) return !!(val & BIT(bit)); } +static int meson_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct meson_domain *domain = to_meson_domain(chip); + struct meson_pinctrl *pc = domain->pinctrl; + struct meson_bank *bank; + struct irq_fwspec irq_data; + unsigned int hwirq, irq; + + hwirq = domain->data->pin_base + offset; + + if (meson_get_bank(domain, hwirq, &bank)) + return -ENXIO; + + irq_data.param_count = 2; + irq_data.param[0] = hwirq; + + /* dummy. It will be changed later in meson_irq_set_type */ + irq_data.param[1] = IRQ_TYPE_EDGE_RISING; + + irq = irq_domain_alloc_irqs(pc->irq_domain, 1, NUMA_NO_NODE, &irq_data); + + return irq ? irq : -ENXIO; +} + static const struct of_device_id meson_pinctrl_dt_match[] = { { .compatible = "amlogic,meson8-pinctrl", @@ -569,6 +593,7 @@ static int meson_gpiolib_register(struct meson_pinctrl *pc) domain->chip.direction_output = meson_gpio_direction_output; domain->chip.get = meson_gpio_get; domain->chip.set = meson_gpio_set; + domain->chip.to_irq = meson_gpio_to_irq; domain->chip.base = domain->data->pin_base; domain->chip.ngpio = domain->data->num_pins; domain->chip.can_sleep = false; @@ -680,6 +705,7 @@ static int meson_pinctrl_parse_dt(struct meson_pinctrl *pc, } domain->of_node = np; + domain->pinctrl = pc; domain->reg_mux = meson_map_resource(pc, np, "mux"); if (IS_ERR(domain->reg_mux)) { @@ -749,6 +775,10 @@ static int meson_pinctrl_probe(struct platform_device *pdev) return ret; } + ret = meson_gpio_irq_init(pc); + if (ret) + dev_err(pc->dev, "can't setup gpio interrupts\n"); + return 0; } diff --git a/drivers/pinctrl/meson/pinctrl-meson.h b/drivers/pinctrl/meson/pinctrl-meson.h index a0bf705..c597bf1 100644 --- a/drivers/pinctrl/meson/pinctrl-meson.h +++ b/drivers/pinctrl/meson/pinctrl-meson.h @@ -16,6 +16,8 @@ #include #include +struct meson_pinctrl; + /** * struct meson_pmx_group - a pinmux group * @@ -111,6 +113,7 @@ struct meson_domain_data { unsigned int num_banks; unsigned int pin_base; unsigned int num_pins; + struct meson_pinctrl *pinctrl; }; /** @@ -123,6 +126,7 @@ struct meson_domain_data { * @chip: gpio chip associated with the domain * @data; platform data for the domain * @node: device tree node for the domain + * @pinctrl: pinctrl struct associated with the domain * * A domain represents a set of banks controlled by the same set of * registers. @@ -136,6 +140,7 @@ struct meson_domain { struct gpio_chip chip; struct meson_domain_data *data; struct device_node *of_node; + struct meson_pinctrl *pinctrl; }; struct meson_pinctrl_data { @@ -156,6 +161,14 @@ struct meson_pinctrl { struct pinctrl_desc desc; struct meson_pinctrl_data *data; struct meson_domain *domains; + + struct irq_domain *irq_domain; + struct regmap *reg_irq; + spinlock_t lock; + + int num_gic_irqs; + int *irq_map; + int *gic_irqs; }; #define PIN(x, b) (b + x) @@ -221,3 +234,5 @@ int meson_get_domain_and_bank(struct meson_pinctrl *pc, unsigned int pin, struct regmap *meson_map_resource(struct meson_pinctrl *pc, struct device_node *node, char *name); + +int meson_gpio_irq_init(struct meson_pinctrl *pc);