From patchwork Mon Sep 18 13:46:10 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jerome Brunet X-Patchwork-Id: 9956745 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 64B76601E9 for ; Mon, 18 Sep 2017 13:54:42 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 54131289AE for ; Mon, 18 Sep 2017 13:54:42 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 48BC2289CB; Mon, 18 Sep 2017 13:54:42 +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=-4.2 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,RCVD_IN_DNSWL_MED autolearn=unavailable version=3.3.1 Received: from bombadil.infradead.org (bombadil.infradead.org [65.50.211.133]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 4D27C289C7 for ; Mon, 18 Sep 2017 13:54:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:Cc:List-Subscribe: List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id:References: In-Reply-To:Message-Id:Date:Subject:To:From:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Owner; bh=UVyL1RJrwN6cQjZgAFeAzLY0KGsz2aVI6kKiBoK9u34=; b=MDPB+6gulWLjXB8WoO1VCnkOtf ToeDDhhP8PYoJolnkiV2Z3exNKpPOqvP7Ji8epxaM62t0c2b6fC9vcIijWCjoeWJruX7N2kYyhWI+ ZZ57UOznqSgBEOmmEizvPWBsX0SzlcMePNF/AREQ1ov/vHhmjZ3zBJLB9vXIbKeVCcM8al9i5SMht zFFGbEeP4VXxA4AGLZdKKhSgKoAf3G3qulS6n/Q9sgBOud0ejiNBur2IePFLzdMlc6OilVrkGPMQs Zf7nABHCH4nd6eeekwDduVCIZxlOh/ZPdgzbOUfF0EstpoR7oEDHWY9063eW7RKalad11sDLdn7tE HnMkrfbA==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.87 #1 (Red Hat Linux)) id 1dtwVQ-0001AR-Sx; Mon, 18 Sep 2017 13:54:36 +0000 Received: from mail-wm0-x230.google.com ([2a00:1450:400c:c09::230]) by bombadil.infradead.org with esmtps (Exim 4.87 #1 (Red Hat Linux)) id 1dtwNj-0004SQ-10 for linux-amlogic@lists.infradead.org; Mon, 18 Sep 2017 13:46:48 +0000 Received: by mail-wm0-x230.google.com with SMTP id g206so2877169wme.0 for ; Mon, 18 Sep 2017 06:46:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=baylibre-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=agNECDTlRhnMGfuGLmbxIasCT1BsmbhaQ3JMZthZ1E4=; b=mrUDRxcmVGyoGzCmSb4y/t355+CEzAv9RZpt++jMvY5BVO0W0fSRztd1qDuYs7Ma/V 0uBt9tbI37mK+y+HEtnCJhqmWIX9GUyJZeTWAgKIk3NMH3TsNpaaJnybeWCFjFpUq8BO /D4IE3+7x0Jcj1DbBMppZwWPe28YvhPvsbGA3okr1YMTi7ru90FbuBx7GPZVOFre32Cq kfiItcC3kYl/Szaa4HLCJk2Wxm31IZWEXl59ceBUl/FPoGaxWDTH5b5LU2VvobizShTG Bmo2dFey8Ryp1qEt5PR2fn1yiZfjUeJPwh2/MIsbfA0ln0YrD1JrT9xko8Ea5qyQj6Zj HebA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=agNECDTlRhnMGfuGLmbxIasCT1BsmbhaQ3JMZthZ1E4=; b=KDeTzIJTSxfb2UGos2sTpccQgwKA/FL3/XDceJI/lpQp+vidcXqQ3MHkKn6wjkR/BY cLWMmLwQUNEmnjM/0LOmzm/AhBz/hCtR37zxm8/jbpQ9xD21+IydWwPsqfWSLFtXUDH5 IUsRiotcVEMH0CWPfVmQQZ7nGzMHCccKA1lZO+e6pL/1Nss6g1zsIO2OCvqfo5kIBr9W CIfNnbytPGNj+FSi6zTP8bvzjLE3GJhOKtzlhqQUjm523lNqBQ37wJj5QuU8VRKz0eJ9 c7ArIo+XTDWYfxWS9uzk+z/o0m6Xgz4wRFcswbOyHZq9DspxPiWLMVJbv6ue9dMhWZTz C5ZQ== X-Gm-Message-State: AHPjjUgO9I/VByW0LEQ+6LgBB+lzI09NSV/qISeouiWD8CgHkpBBBKAs 3D+egIzI1MRxlV7x X-Google-Smtp-Source: AOwi7QAt9cY96VXfD3wd4LjVD+JHkr3beeuFcTT8TkcQUGjFtvNo9IpAhsh93imoesUsk/rhr1fCDg== X-Received: by 10.28.191.138 with SMTP id o10mr8324136wmi.61.1505742377281; Mon, 18 Sep 2017 06:46:17 -0700 (PDT) Received: from localhost.localdomain ([90.63.244.31]) by smtp.googlemail.com with ESMTPSA id n29sm6888585wmi.46.2017.09.18.06.46.16 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Mon, 18 Sep 2017 06:46:16 -0700 (PDT) From: Jerome Brunet To: Marc Zyngier , Thomas Gleixner , Jason Cooper , Heiner Kallweit Subject: [PATCH v4 2/2] irqchip: meson: add support for gpio interrupt controller Date: Mon, 18 Sep 2017 15:46:10 +0200 Message-Id: <20170918134610.17743-4-jbrunet@baylibre.com> X-Mailer: git-send-email 2.13.5 In-Reply-To: <20170918134610.17743-1-jbrunet@baylibre.com> References: <20170918134610.17743-1-jbrunet@baylibre.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20170918_064639_571398_C3327393 X-CRM114-Status: GOOD ( 24.37 ) X-BeenThere: linux-amlogic@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: devicetree@vger.kernel.org, Kevin Hilman , linux-kernel@vger.kernel.org, Carlo Caione , linux-amlogic@lists.infradead.org, linux-arm-kernel@lists.infradead.org, Jerome Brunet MIME-Version: 1.0 Sender: "linux-amlogic" Errors-To: linux-amlogic-bounces+patchwork-linux-amlogic=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP Add support for the interrupt gpio controller found on Amlogic's meson SoC family. This controller is a separate controller from the gpio controller. It is able to spy on the SoC pad. It is essentially a 256 to 8 router with a filtering block to select level or edge and polarity. The number of actual mappable inputs depends on the SoC. Cc: Heiner Kallweit Signed-off-by: Jerome Brunet --- drivers/irqchip/Kconfig | 8 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-meson-gpio.c | 414 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 423 insertions(+) create mode 100644 drivers/irqchip/irq-meson-gpio.c diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 9d8a1dd2e2c2..8e50dfea0973 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -321,3 +321,11 @@ config IRQ_UNIPHIER_AIDET select IRQ_DOMAIN_HIERARCHY help Support for the UniPhier AIDET (ARM Interrupt Detector). + +config MESON_IRQ_GPIO + bool "Meson GPIO Interrupt Multiplexer" + depends on ARCH_MESON || COMPILE_TEST + select IRQ_DOMAIN + select IRQ_DOMAIN_HIERARCHY + help + Support Meson SoC Family GPIO Interrupt Multiplexer diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 845abc107ad5..065adf4102c9 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -79,3 +79,4 @@ obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o obj-$(CONFIG_QCOM_IRQ_COMBINER) += qcom-irq-combiner.o obj-$(CONFIG_IRQ_UNIPHIER_AIDET) += irq-uniphier-aidet.o +obj-$(CONFIG_MESON_IRQ_GPIO) += irq-meson-gpio.o diff --git a/drivers/irqchip/irq-meson-gpio.c b/drivers/irqchip/irq-meson-gpio.c new file mode 100644 index 000000000000..c7cc7e37a23c --- /dev/null +++ b/drivers/irqchip/irq-meson-gpio.c @@ -0,0 +1,414 @@ +/* + * Copyright (c) 2015 Endless Mobile, Inc. + * Author: Carlo Caione + * Copyright (c) 2016 BayLibre, SAS. + * Author: Jerome Brunet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +#define NUM_CHANNEL 8 +#define MAX_INPUT_MUX 256 + +#define REG_EDGE_POL 0x00 +#define REG_PIN_03_SEL 0x04 +#define REG_PIN_47_SEL 0x08 +#define REG_FILTER_SEL 0x0c + +#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)) +#define REG_PIN_SEL_SHIFT(x) (((x) % 4) * 8) +#define REG_FILTER_SEL_SHIFT(x) ((x) * 4) + +struct meson_gpio_irq_params { + unsigned int nr_hwirq; +}; + +static const struct meson_gpio_irq_params meson8b_params = { + .nr_hwirq = 119, +}; + +static const struct meson_gpio_irq_params gxbb_params = { + .nr_hwirq = 133, +}; + +static const struct meson_gpio_irq_params gxl_params = { + .nr_hwirq = 110, +}; + +static const struct of_device_id meson_irq_gpio_matches[] = { + { .compatible = "amlogic,meson8b-gpio-intc", .data = &meson8b_params }, + { .compatible = "amlogic,meson-gxbb-gpio-intc", .data = &gxbb_params }, + { .compatible = "amlogic,meson-gxl-gpio-intc", .data = &gxl_params }, + { } +}; + +struct meson_gpio_irq_controller { + unsigned int nr_hwirq; + void __iomem *base; + u32 channel_irqs[NUM_CHANNEL]; + DECLARE_BITMAP(channel_map, NUM_CHANNEL); + spinlock_t lock; +}; + +static void meson_gpio_irq_update_bits(struct meson_gpio_irq_controller *ctl, + unsigned int reg, u32 mask, u32 val) +{ + u32 tmp; + + tmp = readl_relaxed(ctl->base + reg); + tmp &= ~mask; + tmp |= val; + writel_relaxed(tmp, ctl->base + reg); +} + +static unsigned int meson_gpio_irq_channel_to_reg(unsigned int channel) +{ + return (channel < 4) ? REG_PIN_03_SEL : REG_PIN_47_SEL; +} + +static int +meson_gpio_irq_request_channel(struct meson_gpio_irq_controller *ctl, + unsigned long hwirq, + u32 **channel_hwirq) +{ + unsigned int reg, idx; + + spin_lock(&ctl->lock); + + /* Find a free channel */ + idx = find_first_zero_bit(ctl->channel_map, NUM_CHANNEL); + if (idx >= NUM_CHANNEL) { + spin_unlock(&ctl->lock); + pr_err("No channel available\n"); + return -ENOSPC; + } + + /* Mark the channel as used */ + set_bit(idx, ctl->channel_map); + + /* + * Setup the mux of the channel to route the signal of the pad + * to the appropriate input of the GIC + */ + reg = meson_gpio_irq_channel_to_reg(idx); + meson_gpio_irq_update_bits(ctl, reg, + 0xff << REG_PIN_SEL_SHIFT(idx), + hwirq << REG_PIN_SEL_SHIFT(idx)); + + /* + * Get the hwirq number assigned to this channel through + * a pointer the channel_irq table. The added benifit of this + * method is that we can also retrieve the channel index with + * it, using the table base. + */ + *channel_hwirq = &(ctl->channel_irqs[idx]); + + spin_unlock(&ctl->lock); + + pr_debug("hwirq %lu assigned to channel %d - irq %u\n", + hwirq, idx, **channel_hwirq); + + return 0; +} + +static unsigned int +meson_gpio_irq_get_channel_idx(struct meson_gpio_irq_controller *ctl, + u32 *channel_hwirq) +{ + return channel_hwirq - ctl->channel_irqs; +} + +static void +meson_gpio_irq_release_channel(struct meson_gpio_irq_controller *ctl, + u32 *channel_hwirq) +{ + unsigned int idx; + + idx = meson_gpio_irq_get_channel_idx(ctl, channel_hwirq); + clear_bit(idx, ctl->channel_map); +} + +static int meson_gpio_irq_type_setup(struct meson_gpio_irq_controller *ctl, + unsigned int type, + u32 *channel_hwirq) +{ + u32 val = 0; + unsigned int idx; + + idx = meson_gpio_irq_get_channel_idx(ctl, channel_hwirq); + + /* + * The controller has a filter block to operate in either LEVEL or + * EDGE mode, then signal is sent to the GIC. To enable LEVEL_LOW and + * EDGE_FALLING support (which the GIC does not support), the filter + * block is also able to invert the input signal it gets before + * providing it to the GIC. + */ + type &= IRQ_TYPE_SENSE_MASK; + + if (type == IRQ_TYPE_EDGE_BOTH) + return -EINVAL; + + if (type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING)) + val |= REG_EDGE_POL_EDGE(idx); + + if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_EDGE_FALLING)) + val |= REG_EDGE_POL_LOW(idx); + + spin_lock(&ctl->lock); + + meson_gpio_irq_update_bits(ctl, REG_EDGE_POL, + REG_EDGE_POL_MASK(idx), val); + + spin_unlock(&ctl->lock); + + return 0; +} + +static unsigned int meson_gpio_irq_type_output(unsigned int type) +{ + unsigned int sense = type & IRQ_TYPE_SENSE_MASK; + + type &= ~IRQ_TYPE_SENSE_MASK; + + /* + * The polarity of the signal provided to the GIC should always + * be high. + */ + if (sense & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) + type |= IRQ_TYPE_LEVEL_HIGH; + else if (sense & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING)) + type |= IRQ_TYPE_EDGE_RISING; + + return type; +} + +static int meson_gpio_irq_set_type(struct irq_data *data, unsigned int type) +{ + struct meson_gpio_irq_controller *ctl = data->domain->host_data; + u32 *channel_hwirq = irq_data_get_irq_chip_data(data); + int ret; + + ret = meson_gpio_irq_type_setup(ctl, type, channel_hwirq); + if (ret) + return ret; + + return irq_chip_set_type_parent(data, + meson_gpio_irq_type_output(type)); +} + +static struct irq_chip meson_gpio_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_gpio_irq_set_type, + .irq_retrigger = irq_chip_retrigger_hierarchy, +#ifdef CONFIG_SMP + .irq_set_affinity = irq_chip_set_affinity_parent, +#endif + .flags = IRQCHIP_SET_TYPE_MASKED, +}; + +static int meson_gpio_irq_domain_translate(struct irq_domain *domain, + struct irq_fwspec *fwspec, + unsigned long *hwirq, + unsigned int *type) +{ + if (is_of_node(fwspec->fwnode) && fwspec->param_count == 2) { + *hwirq = fwspec->param[0]; + *type = fwspec->param[1]; + return 0; + } + + return -EINVAL; +} + +static int meson_gpio_irq_allocate_gic_irq(struct irq_domain *domain, + unsigned int virq, + u32 hwirq, + unsigned int type) +{ + struct irq_fwspec fwspec; + + fwspec.fwnode = domain->parent->fwnode; + fwspec.param_count = 3; + fwspec.param[0] = 0; /* SPI */ + fwspec.param[1] = hwirq; + fwspec.param[2] = meson_gpio_irq_type_output(type); + + return irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); +} + +static int meson_gpio_irq_domain_alloc(struct irq_domain *domain, + unsigned int virq, + unsigned int nr_irqs, + void *data) +{ + struct irq_fwspec *fwspec = data; + struct meson_gpio_irq_controller *ctl = domain->host_data; + unsigned long hwirq; + u32 *channel_hwirq; + unsigned int type; + int ret; + + if (WARN_ON(nr_irqs != 1)) + return -EINVAL; + + ret = meson_gpio_irq_domain_translate(domain, fwspec, &hwirq, &type); + if (ret) + return ret; + + ret = meson_gpio_irq_request_channel(ctl, hwirq, &channel_hwirq); + if (ret) + return ret; + + ret = meson_gpio_irq_allocate_gic_irq(domain, virq, + *channel_hwirq, type); + if (ret < 0) { + pr_err("failed to allocate gic irq %u\n", *channel_hwirq); + meson_gpio_irq_release_channel(ctl, channel_hwirq); + return ret; + } + + irq_domain_set_hwirq_and_chip(domain, virq, hwirq, + &meson_gpio_irq_chip, channel_hwirq); + + return 0; +} + +static void meson_gpio_irq_domain_free(struct irq_domain *domain, + unsigned int virq, + unsigned int nr_irqs) +{ + struct meson_gpio_irq_controller *ctl = domain->host_data; + struct irq_data *irq_data; + u32 *channel_hwirq; + + if (WARN_ON(nr_irqs != 1)) + return; + + irq_domain_free_irqs_parent(domain, virq, 1); + + irq_data = irq_domain_get_irq_data(domain, virq); + channel_hwirq = irq_data_get_irq_chip_data(irq_data); + + meson_gpio_irq_release_channel(ctl, channel_hwirq); +} + +static const struct irq_domain_ops meson_gpio_irq_domain_ops = { + .alloc = meson_gpio_irq_domain_alloc, + .free = meson_gpio_irq_domain_free, + .translate = meson_gpio_irq_domain_translate, +}; + +static int __init meson_gpio_irq_parse_dt(struct device_node *node, + struct meson_gpio_irq_controller *ctl) +{ + const struct of_device_id *match; + const struct meson_gpio_irq_params *params; + int ret; + + match = of_match_node(meson_irq_gpio_matches, node); + if (!match) + return -ENODEV; + + params = match->data; + ctl->nr_hwirq = params->nr_hwirq; + + ret = of_property_read_variable_u32_array(node, + "amlogic,channel-interrupts", + ctl->channel_irqs, + NUM_CHANNEL, + NUM_CHANNEL); + if (ret < 0) { + pr_err("can't get %d channel interrupts\n", NUM_CHANNEL); + return ret; + } + + return 0; +} + +static int __init meson_gpio_irq_of_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_domain *domain, *parent_domain; + struct meson_gpio_irq_controller *ctl; + int ret; + + if (!parent) { + pr_err("missing parent interrupt node\n"); + return -ENODEV; + } + + parent_domain = irq_find_host(parent); + if (!parent_domain) { + pr_err("unable to obtain parent domain\n"); + return -ENXIO; + } + + ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); + if (!ctl) + return -ENOMEM; + + spin_lock_init(&ctl->lock); + + ctl->base = of_iomap(node, 0); + if (!ctl->base) { + ret = -ENOMEM; + goto free_ctl; + } + + ret = meson_gpio_irq_parse_dt(node, ctl); + if (ret) + goto free_channel_irqs; + + domain = irq_domain_create_hierarchy(parent_domain, 0, ctl->nr_hwirq, + of_node_to_fwnode(node), + &meson_gpio_irq_domain_ops, + ctl); + if (!domain) { + pr_err("failed to add domain\n"); + ret = -ENODEV; + goto free_channel_irqs; + } + + pr_info("%d to %d gpio interrupt mux initialized\n", + ctl->nr_hwirq, NUM_CHANNEL); + + return 0; + +free_channel_irqs: + iounmap(ctl->base); +free_ctl: + kfree(ctl); + + return ret; +} + +IRQCHIP_DECLARE(meson_gpio_intc, "amlogic,meson-gpio-intc", + meson_gpio_irq_of_init);