[PATCH 2/3] drivers/irqchip: Add Actions external interrupts support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Actions Semi Owl family SoC's S500, S700 and S900 provides support
for 3 external interrupt controllers through SIRQ pins.

Each line can be independently configured as interrupt or wake-up source,
and triggers either on rising, falling or both edges. Each line can also
be masked independently.

Signed-off-by: Parthiban Nallathambi <pn@xxxxxxx>
Signed-off-by: Saravanan Sekar <sravanhome@xxxxxxxxx>
---
 drivers/irqchip/Makefile       |   1 +
 drivers/irqchip/irq-owl-sirq.c | 275 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 276 insertions(+)
 create mode 100644 drivers/irqchip/irq-owl-sirq.c

diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 15f268f646bf..072c4409e7c4 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_ATH79)			+= irq-ath79-misc.o
 obj-$(CONFIG_ARCH_BCM2835)		+= irq-bcm2835.o
 obj-$(CONFIG_ARCH_BCM2835)		+= irq-bcm2836.o
 obj-$(CONFIG_ARCH_EXYNOS)		+= exynos-combiner.o
+obj-$(CONFIG_ARCH_ACTIONS)		+= irq-owl-sirq.o
 obj-$(CONFIG_FARADAY_FTINTC010)		+= irq-ftintc010.o
 obj-$(CONFIG_ARCH_HIP04)		+= irq-hip04.o
 obj-$(CONFIG_ARCH_LPC32XX)		+= irq-lpc32xx.o
diff --git a/drivers/irqchip/irq-owl-sirq.c b/drivers/irqchip/irq-owl-sirq.c
new file mode 100644
index 000000000000..8605da99d77d
--- /dev/null
+++ b/drivers/irqchip/irq-owl-sirq.c
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *
+ * Actions Semi Owl SoCs SIRQ interrupt controller driver
+ *
+ * Copyright (C) 2014 Actions Semi Inc.
+ * David Liu <liuwei@xxxxxxxxxxxxxxxx>
+ *
+ * Author: Parthiban Nallathambi <pn@xxxxxxx>
+ * Author: Saravanan Sekar <sravanhome@xxxxxxxxx>
+ */
+
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/of_irq.h>
+#include <linux/irqchip.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+#include <linux/of_address.h>
+#include <linux/irqchip/chained_irq.h>
+
+#define OWL_MAX_NR_SIRQS	3
+
+/* INTC_EXTCTL register offset for S900 */
+#define S900_INTC_EXTCTL0	0x200
+#define S900_INTC_EXTCTL1	0x528
+#define S900_INTC_EXTCTL2	0x52C
+
+/* INTC_EXTCTL register offset for S700 */
+#define S700_INTC_EXTCTL	0x200
+
+#define INTC_EXTCTL_PENDING		BIT(0)
+#define INTC_EXTCTL_CLK_SEL		BIT(4)
+#define INTC_EXTCTL_EN			BIT(5)
+#define	INTC_EXTCTL_TYPE_MASK		GENMASK(6, 7)
+#define	INTC_EXTCTL_TYPE_HIGH		0
+#define	INTC_EXTCTL_TYPE_LOW		BIT(6)
+#define	INTC_EXTCTL_TYPE_RISING		BIT(7)
+#define	INTC_EXTCTL_TYPE_FALLING	(BIT(6) | BIT(7))
+
+struct owl_sirq_info {
+	void __iomem		*base;
+	struct irq_domain	*irq_domain;
+	unsigned long		reg;
+	unsigned long		hwirq;
+	unsigned int		virq;
+	unsigned int		parent_irq;
+	bool			share_reg;
+	spinlock_t		lock;
+};
+
+/* s900 has INTC_EXTCTL individual register to handle each line */
+static struct owl_sirq_info s900_sirq_info[OWL_MAX_NR_SIRQS] = {
+	{ .reg = S900_INTC_EXTCTL0, .share_reg = false },
+	{ .reg = S900_INTC_EXTCTL1, .share_reg = false },
+	{ .reg = S900_INTC_EXTCTL2, .share_reg = false },
+};
+
+/* s500 and s700 shares the 32 bit (24 usable) register for each line */
+static struct owl_sirq_info s700_sirq_info[OWL_MAX_NR_SIRQS] = {
+	{ .reg = S700_INTC_EXTCTL, .share_reg = true },
+	{ .reg = S700_INTC_EXTCTL, .share_reg = true },
+	{ .reg = S700_INTC_EXTCTL, .share_reg = true },
+};
+
+static unsigned int sirq_read_extctl(struct owl_sirq_info *sirq)
+{
+	unsigned int val;
+
+	val = readl_relaxed(sirq->base + sirq->reg);
+	if (sirq->share_reg)
+		val = (val >> (2 - sirq->hwirq) * 8) & 0xff;
+
+	return val;
+}
+
+static void sirq_write_extctl(struct owl_sirq_info *sirq, unsigned int extctl)
+{
+	unsigned int val;
+
+	if (sirq->share_reg) {
+		val = readl_relaxed(sirq->base + sirq->reg);
+		val &= ~(0xff << (2 - sirq->hwirq) * 8);
+		extctl &= 0xff;
+		extctl = (extctl << (2 - sirq->hwirq) * 8) | val;
+	}
+
+	writel_relaxed(extctl, sirq->base + sirq->reg);
+}
+
+static void owl_sirq_ack(struct irq_data *d)
+{
+	struct owl_sirq_info *sirq = irq_data_get_irq_chip_data(d);
+	unsigned int extctl;
+	unsigned long flags;
+
+	spin_lock_irqsave(&sirq->lock, flags);
+
+	extctl = sirq_read_extctl(sirq);
+	extctl |= INTC_EXTCTL_PENDING;
+	sirq_write_extctl(sirq, extctl);
+
+	spin_unlock_irqrestore(&sirq->lock, flags);
+}
+
+static void owl_sirq_mask(struct irq_data *d)
+{
+	struct owl_sirq_info *sirq = irq_data_get_irq_chip_data(d);
+	unsigned int extctl;
+	unsigned long flags;
+
+	spin_lock_irqsave(&sirq->lock, flags);
+
+	extctl = sirq_read_extctl(sirq);
+	extctl &= ~(INTC_EXTCTL_EN);
+	sirq_write_extctl(sirq, extctl);
+
+	spin_unlock_irqrestore(&sirq->lock, flags);
+}
+
+static void owl_sirq_unmask(struct irq_data *d)
+{
+	struct owl_sirq_info *sirq = irq_data_get_irq_chip_data(d);
+	unsigned int extctl;
+	unsigned long flags;
+
+	spin_lock_irqsave(&sirq->lock, flags);
+
+	/* we don't hold the irq pending generated before irq enabled */
+	extctl = sirq_read_extctl(sirq);
+	extctl |= INTC_EXTCTL_EN;
+	sirq_write_extctl(sirq, extctl);
+
+	spin_unlock_irqrestore(&sirq->lock, flags);
+}
+
+/* PAD_PULLCTL needs to be defined in pinctrl */
+static int owl_sirq_set_type(struct irq_data *d, unsigned int flow_type)
+{
+	struct owl_sirq_info *sirq = irq_data_get_irq_chip_data(d);
+	unsigned int extctl, type;
+	unsigned long flags;
+
+	switch (flow_type) {
+	case IRQF_TRIGGER_LOW:
+		type = INTC_EXTCTL_TYPE_LOW;
+		break;
+	case IRQF_TRIGGER_HIGH:
+		type = INTC_EXTCTL_TYPE_HIGH;
+		break;
+	case IRQF_TRIGGER_FALLING:
+		type = INTC_EXTCTL_TYPE_FALLING;
+		break;
+	case IRQF_TRIGGER_RISING:
+		type = INTC_EXTCTL_TYPE_RISING;
+		break;
+	default:
+		return  -EINVAL;
+	}
+
+	spin_lock_irqsave(&sirq->lock, flags);
+
+	extctl = sirq_read_extctl(sirq);
+	extctl &= ~(INTC_EXTCTL_PENDING | INTC_EXTCTL_TYPE_MASK);
+	extctl |= type;
+	sirq_write_extctl(sirq, extctl);
+
+	spin_unlock_irqrestore(&sirq->lock, flags);
+
+	return 0;
+}
+
+static void owl_sirq_handler(struct irq_desc *desc)
+{
+	struct owl_sirq_info *sirq = irq_desc_get_handler_data(desc);
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	unsigned int extctl;
+
+	chained_irq_enter(chip, desc);
+
+	extctl = sirq_read_extctl(sirq);
+	if (extctl & INTC_EXTCTL_PENDING)
+		generic_handle_irq(sirq->virq);
+
+	chained_irq_exit(chip, desc);
+}
+
+static struct irq_chip owl_irq_chip = {
+	.name = "owl-sirq",
+	.irq_ack = owl_sirq_ack,
+	.irq_mask = owl_sirq_mask,
+	.irq_unmask = owl_sirq_unmask,
+	.irq_set_type = owl_sirq_set_type,
+};
+
+static int __init owl_sirq_init(struct owl_sirq_info *sirq_info, int nr_sirq,
+				struct device_node *np)
+{
+	struct owl_sirq_info *sirq;
+	void __iomem *base;
+	struct irq_domain *irq_domain;
+	int i, irq_base;
+	unsigned int extctl;
+	u8 sample_clk[OWL_MAX_NR_SIRQS];
+
+	base = of_iomap(np, 0);
+	if (!base)
+		return -ENOMEM;
+
+	irq_base = irq_alloc_descs(-1, 32, nr_sirq, 0);
+	if (irq_base < 0) {
+		pr_err("sirq: failed to allocate IRQ numbers\n");
+		goto out_unmap;
+	}
+
+	irq_domain = irq_domain_add_legacy(np, nr_sirq, irq_base, 0,
+					&irq_domain_simple_ops, NULL);
+	if (!irq_domain) {
+		pr_err("sirq: irq domain init failed\n");
+		goto out_free_desc;
+	}
+
+	memset(sample_clk, 0, sizeof(sample_clk));
+	of_property_read_u8_array(np, "sampling-rate-24m", sample_clk,
+				nr_sirq);
+
+	for (i = 0; i < nr_sirq; i++) {
+		sirq = &sirq_info[i];
+
+		sirq->base = base;
+		sirq->irq_domain = irq_domain;
+		sirq->hwirq = i;
+		sirq->virq = irq_base + i;
+
+		sirq->parent_irq = irq_of_parse_and_map(np, i);
+		irq_set_handler_data(sirq->parent_irq, sirq);
+		irq_set_chained_handler_and_data(sirq->parent_irq,
+						owl_sirq_handler, sirq);
+
+		irq_set_chip_and_handler(sirq->virq, &owl_irq_chip,
+				handle_level_irq);
+		irq_set_chip_data(sirq->virq, sirq);
+
+		if (sample_clk[i]) {
+			extctl = sirq_read_extctl(sirq);
+			extctl |= INTC_EXTCTL_CLK_SEL;
+			sirq_write_extctl(sirq, extctl);
+		}
+		spin_lock_init(&sirq->lock);
+	}
+
+	return 0;
+
+out_free_desc:
+	irq_free_descs(irq_base, nr_sirq);
+out_unmap:
+	iounmap(base);
+
+	return -EINVAL;
+}
+
+static int __init s700_sirq_of_init(struct device_node *np,
+					struct device_node *parent)
+{
+	return owl_sirq_init(s700_sirq_info, ARRAY_SIZE(s700_sirq_info), np);
+}
+IRQCHIP_DECLARE(s700_sirq, "actions,s700-sirq", s700_sirq_of_init);
+
+static int __init s900_sirq_of_init(struct device_node *np,
+					struct device_node *parent)
+{
+	return owl_sirq_init(s900_sirq_info, ARRAY_SIZE(s900_sirq_info), np);
+}
+IRQCHIP_DECLARE(s900_sirq, "actions,s900-sirq", s900_sirq_of_init);
-- 
2.14.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux