diff options
Diffstat (limited to 'drivers/irqchip/irq-vt8500.c')
| -rw-r--r-- | drivers/irqchip/irq-vt8500.c | 259 | 
1 files changed, 259 insertions, 0 deletions
diff --git a/drivers/irqchip/irq-vt8500.c b/drivers/irqchip/irq-vt8500.c new file mode 100644 index 00000000000..d97059550a2 --- /dev/null +++ b/drivers/irqchip/irq-vt8500.c @@ -0,0 +1,259 @@ +/* + *  arch/arm/mach-vt8500/irq.c + * + *  Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz> + *  Copyright (C) 2010 Alexey Charkov <alchark@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + */ + +/* + * This file is copied and modified from the original irq.c provided by + * Alexey Charkov. Minor changes have been made for Device Tree Support. + */ + +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/interrupt.h> +#include <linux/bitops.h> + +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> + +#include <asm/irq.h> +#include <asm/exception.h> +#include <asm/mach/irq.h> + +#include "irqchip.h" + +#define VT8500_ICPC_IRQ		0x20 +#define VT8500_ICPC_FIQ		0x24 +#define VT8500_ICDC		0x40		/* Destination Control 64*u32 */ +#define VT8500_ICIS		0x80		/* Interrupt status, 16*u32 */ + +/* ICPC */ +#define ICPC_MASK		0x3F +#define ICPC_ROTATE		BIT(6) + +/* IC_DCTR */ +#define ICDC_IRQ		0x00 +#define ICDC_FIQ		0x01 +#define ICDC_DSS0		0x02 +#define ICDC_DSS1		0x03 +#define ICDC_DSS2		0x04 +#define ICDC_DSS3		0x05 +#define ICDC_DSS4		0x06 +#define ICDC_DSS5		0x07 + +#define VT8500_INT_DISABLE	0 +#define VT8500_INT_ENABLE	BIT(3) + +#define VT8500_TRIGGER_HIGH	0 +#define VT8500_TRIGGER_RISING	BIT(5) +#define VT8500_TRIGGER_FALLING	BIT(6) +#define VT8500_EDGE		( VT8500_TRIGGER_RISING \ +				| VT8500_TRIGGER_FALLING) + +/* vt8500 has 1 intc, wm8505 and wm8650 have 2 */ +#define VT8500_INTC_MAX		2 + +struct vt8500_irq_data { +	void __iomem 		*base;		/* IO Memory base address */ +	struct irq_domain	*domain;	/* Domain for this controller */ +}; + +/* Global variable for accessing io-mem addresses */ +static struct vt8500_irq_data intc[VT8500_INTC_MAX]; +static u32 active_cnt = 0; + +static void vt8500_irq_mask(struct irq_data *d) +{ +	struct vt8500_irq_data *priv = d->domain->host_data; +	void __iomem *base = priv->base; +	void __iomem *stat_reg = base + VT8500_ICIS + (d->hwirq < 32 ? 0 : 4); +	u8 edge, dctr; +	u32 status; + +	edge = readb(base + VT8500_ICDC + d->hwirq) & VT8500_EDGE; +	if (edge) { +		status = readl(stat_reg); + +		status |= (1 << (d->hwirq & 0x1f)); +		writel(status, stat_reg); +	} else { +		dctr = readb(base + VT8500_ICDC + d->hwirq); +		dctr &= ~VT8500_INT_ENABLE; +		writeb(dctr, base + VT8500_ICDC + d->hwirq); +	} +} + +static void vt8500_irq_unmask(struct irq_data *d) +{ +	struct vt8500_irq_data *priv = d->domain->host_data; +	void __iomem *base = priv->base; +	u8 dctr; + +	dctr = readb(base + VT8500_ICDC + d->hwirq); +	dctr |= VT8500_INT_ENABLE; +	writeb(dctr, base + VT8500_ICDC + d->hwirq); +} + +static int vt8500_irq_set_type(struct irq_data *d, unsigned int flow_type) +{ +	struct vt8500_irq_data *priv = d->domain->host_data; +	void __iomem *base = priv->base; +	u8 dctr; + +	dctr = readb(base + VT8500_ICDC + d->hwirq); +	dctr &= ~VT8500_EDGE; + +	switch (flow_type) { +	case IRQF_TRIGGER_LOW: +		return -EINVAL; +	case IRQF_TRIGGER_HIGH: +		dctr |= VT8500_TRIGGER_HIGH; +		__irq_set_handler_locked(d->irq, handle_level_irq); +		break; +	case IRQF_TRIGGER_FALLING: +		dctr |= VT8500_TRIGGER_FALLING; +		__irq_set_handler_locked(d->irq, handle_edge_irq); +		break; +	case IRQF_TRIGGER_RISING: +		dctr |= VT8500_TRIGGER_RISING; +		__irq_set_handler_locked(d->irq, handle_edge_irq); +		break; +	} +	writeb(dctr, base + VT8500_ICDC + d->hwirq); + +	return 0; +} + +static struct irq_chip vt8500_irq_chip = { +	.name = "vt8500", +	.irq_ack = vt8500_irq_mask, +	.irq_mask = vt8500_irq_mask, +	.irq_unmask = vt8500_irq_unmask, +	.irq_set_type = vt8500_irq_set_type, +}; + +static void __init vt8500_init_irq_hw(void __iomem *base) +{ +	u32 i; + +	/* Enable rotating priority for IRQ */ +	writel(ICPC_ROTATE, base + VT8500_ICPC_IRQ); +	writel(0x00, base + VT8500_ICPC_FIQ); + +	/* Disable all interrupts and route them to IRQ */ +	for (i = 0; i < 64; i++) +		writeb(VT8500_INT_DISABLE | ICDC_IRQ, base + VT8500_ICDC + i); +} + +static int vt8500_irq_map(struct irq_domain *h, unsigned int virq, +							irq_hw_number_t hw) +{ +	irq_set_chip_and_handler(virq, &vt8500_irq_chip, handle_level_irq); +	set_irq_flags(virq, IRQF_VALID); + +	return 0; +} + +static struct irq_domain_ops vt8500_irq_domain_ops = { +	.map = vt8500_irq_map, +	.xlate = irq_domain_xlate_onecell, +}; + +asmlinkage void __exception_irq_entry vt8500_handle_irq(struct pt_regs *regs) +{ +	u32 stat, i; +	int irqnr, virq; +	void __iomem *base; + +	/* Loop through each active controller */ +	for (i=0; i<active_cnt; i++) { +		base = intc[i].base; +		irqnr = readl_relaxed(base) & 0x3F; +		/* +		  Highest Priority register default = 63, so check that this +		  is a real interrupt by checking the status register +		*/ +		if (irqnr == 63) { +			stat = readl_relaxed(base + VT8500_ICIS + 4); +			if (!(stat & BIT(31))) +				continue; +		} + +		virq = irq_find_mapping(intc[i].domain, irqnr); +		handle_IRQ(virq, regs); +	} +} + +int __init vt8500_irq_init(struct device_node *node, struct device_node *parent) +{ +	int irq, i; +	struct device_node *np = node; + +	if (active_cnt == VT8500_INTC_MAX) { +		pr_err("%s: Interrupt controllers > VT8500_INTC_MAX\n", +								__func__); +		goto out; +	} + +	intc[active_cnt].base = of_iomap(np, 0); +	intc[active_cnt].domain = irq_domain_add_linear(node, 64, +			&vt8500_irq_domain_ops,	&intc[active_cnt]); + +	if (!intc[active_cnt].base) { +		pr_err("%s: Unable to map IO memory\n", __func__); +		goto out; +	} + +	if (!intc[active_cnt].domain) { +		pr_err("%s: Unable to add irq domain!\n", __func__); +		goto out; +	} + +	set_handle_irq(vt8500_handle_irq); + +	vt8500_init_irq_hw(intc[active_cnt].base); + +	pr_info("vt8500-irq: Added interrupt controller\n"); + +	active_cnt++; + +	/* check if this is a slaved controller */ +	if (of_irq_count(np) != 0) { +		/* check that we have the correct number of interrupts */ +		if (of_irq_count(np) != 8) { +			pr_err("%s: Incorrect IRQ map for slaved controller\n", +					__func__); +			return -EINVAL; +		} + +		for (i = 0; i < 8; i++) { +			irq = irq_of_parse_and_map(np, i); +			enable_irq(irq); +		} + +		pr_info("vt8500-irq: Enabled slave->parent interrupts\n"); +	} +out: +	return 0; +} + +IRQCHIP_DECLARE(vt8500_irq, "via,vt8500-intc", vt8500_irq_init);  |