| /* | 
 |  * External interrupt handling for AT32AP CPUs | 
 |  * | 
 |  * Copyright (C) 2006 Atmel Corporation | 
 |  * | 
 |  * 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. | 
 |  */ | 
 |  | 
 | #include <linux/errno.h> | 
 | #include <linux/init.h> | 
 | #include <linux/interrupt.h> | 
 | #include <linux/irq.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/random.h> | 
 |  | 
 | #include <asm/io.h> | 
 |  | 
 | /* EIC register offsets */ | 
 | #define EIC_IER					0x0000 | 
 | #define EIC_IDR					0x0004 | 
 | #define EIC_IMR					0x0008 | 
 | #define EIC_ISR					0x000c | 
 | #define EIC_ICR					0x0010 | 
 | #define EIC_MODE				0x0014 | 
 | #define EIC_EDGE				0x0018 | 
 | #define EIC_LEVEL				0x001c | 
 | #define EIC_NMIC				0x0024 | 
 |  | 
 | /* Bitfields in NMIC */ | 
 | #define EIC_NMIC_ENABLE				(1 << 0) | 
 |  | 
 | /* Bit manipulation macros */ | 
 | #define EIC_BIT(name)					\ | 
 | 	(1 << EIC_##name##_OFFSET) | 
 | #define EIC_BF(name,value)				\ | 
 | 	(((value) & ((1 << EIC_##name##_SIZE) - 1))	\ | 
 | 	 << EIC_##name##_OFFSET) | 
 | #define EIC_BFEXT(name,value)				\ | 
 | 	(((value) >> EIC_##name##_OFFSET)		\ | 
 | 	 & ((1 << EIC_##name##_SIZE) - 1)) | 
 | #define EIC_BFINS(name,value,old)			\ | 
 | 	(((old) & ~(((1 << EIC_##name##_SIZE) - 1)	\ | 
 | 		    << EIC_##name##_OFFSET))		\ | 
 | 	 | EIC_BF(name,value)) | 
 |  | 
 | /* Register access macros */ | 
 | #define eic_readl(port,reg)				\ | 
 | 	__raw_readl((port)->regs + EIC_##reg) | 
 | #define eic_writel(port,reg,value)			\ | 
 | 	__raw_writel((value), (port)->regs + EIC_##reg) | 
 |  | 
 | struct eic { | 
 | 	void __iomem *regs; | 
 | 	struct irq_chip *chip; | 
 | 	unsigned int first_irq; | 
 | }; | 
 |  | 
 | static struct eic *nmi_eic; | 
 | static bool nmi_enabled; | 
 |  | 
 | static void eic_ack_irq(unsigned int irq) | 
 | { | 
 | 	struct eic *eic = get_irq_chip_data(irq); | 
 | 	eic_writel(eic, ICR, 1 << (irq - eic->first_irq)); | 
 | } | 
 |  | 
 | static void eic_mask_irq(unsigned int irq) | 
 | { | 
 | 	struct eic *eic = get_irq_chip_data(irq); | 
 | 	eic_writel(eic, IDR, 1 << (irq - eic->first_irq)); | 
 | } | 
 |  | 
 | static void eic_mask_ack_irq(unsigned int irq) | 
 | { | 
 | 	struct eic *eic = get_irq_chip_data(irq); | 
 | 	eic_writel(eic, ICR, 1 << (irq - eic->first_irq)); | 
 | 	eic_writel(eic, IDR, 1 << (irq - eic->first_irq)); | 
 | } | 
 |  | 
 | static void eic_unmask_irq(unsigned int irq) | 
 | { | 
 | 	struct eic *eic = get_irq_chip_data(irq); | 
 | 	eic_writel(eic, IER, 1 << (irq - eic->first_irq)); | 
 | } | 
 |  | 
 | static int eic_set_irq_type(unsigned int irq, unsigned int flow_type) | 
 | { | 
 | 	struct eic *eic = get_irq_chip_data(irq); | 
 | 	struct irq_desc *desc; | 
 | 	unsigned int i = irq - eic->first_irq; | 
 | 	u32 mode, edge, level; | 
 | 	int ret = 0; | 
 |  | 
 | 	flow_type &= IRQ_TYPE_SENSE_MASK; | 
 | 	if (flow_type == IRQ_TYPE_NONE) | 
 | 		flow_type = IRQ_TYPE_LEVEL_LOW; | 
 |  | 
 | 	desc = &irq_desc[irq]; | 
 |  | 
 | 	mode = eic_readl(eic, MODE); | 
 | 	edge = eic_readl(eic, EDGE); | 
 | 	level = eic_readl(eic, LEVEL); | 
 |  | 
 | 	switch (flow_type) { | 
 | 	case IRQ_TYPE_LEVEL_LOW: | 
 | 		mode |= 1 << i; | 
 | 		level &= ~(1 << i); | 
 | 		break; | 
 | 	case IRQ_TYPE_LEVEL_HIGH: | 
 | 		mode |= 1 << i; | 
 | 		level |= 1 << i; | 
 | 		break; | 
 | 	case IRQ_TYPE_EDGE_RISING: | 
 | 		mode &= ~(1 << i); | 
 | 		edge |= 1 << i; | 
 | 		break; | 
 | 	case IRQ_TYPE_EDGE_FALLING: | 
 | 		mode &= ~(1 << i); | 
 | 		edge &= ~(1 << i); | 
 | 		break; | 
 | 	default: | 
 | 		ret = -EINVAL; | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	if (ret == 0) { | 
 | 		eic_writel(eic, MODE, mode); | 
 | 		eic_writel(eic, EDGE, edge); | 
 | 		eic_writel(eic, LEVEL, level); | 
 |  | 
 | 		if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) { | 
 | 			flow_type |= IRQ_LEVEL; | 
 | 			__set_irq_handler_unlocked(irq, handle_level_irq); | 
 | 		} else | 
 | 			__set_irq_handler_unlocked(irq, handle_edge_irq); | 
 | 		desc->status &= ~(IRQ_TYPE_SENSE_MASK | IRQ_LEVEL); | 
 | 		desc->status |= flow_type; | 
 | 	} | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static struct irq_chip eic_chip = { | 
 | 	.name		= "eic", | 
 | 	.ack		= eic_ack_irq, | 
 | 	.mask		= eic_mask_irq, | 
 | 	.mask_ack	= eic_mask_ack_irq, | 
 | 	.unmask		= eic_unmask_irq, | 
 | 	.set_type	= eic_set_irq_type, | 
 | }; | 
 |  | 
 | static void demux_eic_irq(unsigned int irq, struct irq_desc *desc) | 
 | { | 
 | 	struct eic *eic = desc->handler_data; | 
 | 	unsigned long status, pending; | 
 | 	unsigned int i; | 
 |  | 
 | 	status = eic_readl(eic, ISR); | 
 | 	pending = status & eic_readl(eic, IMR); | 
 |  | 
 | 	while (pending) { | 
 | 		i = fls(pending) - 1; | 
 | 		pending &= ~(1 << i); | 
 |  | 
 | 		generic_handle_irq(i + eic->first_irq); | 
 | 	} | 
 | } | 
 |  | 
 | int nmi_enable(void) | 
 | { | 
 | 	nmi_enabled = true; | 
 |  | 
 | 	if (nmi_eic) | 
 | 		eic_writel(nmi_eic, NMIC, EIC_NMIC_ENABLE); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | void nmi_disable(void) | 
 | { | 
 | 	if (nmi_eic) | 
 | 		eic_writel(nmi_eic, NMIC, 0); | 
 |  | 
 | 	nmi_enabled = false; | 
 | } | 
 |  | 
 | static int __init eic_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct eic *eic; | 
 | 	struct resource *regs; | 
 | 	unsigned int i; | 
 | 	unsigned int nr_irqs; | 
 | 	unsigned int int_irq; | 
 | 	int ret; | 
 | 	u32 pattern; | 
 |  | 
 | 	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
 | 	int_irq = platform_get_irq(pdev, 0); | 
 | 	if (!regs || !int_irq) { | 
 | 		dev_dbg(&pdev->dev, "missing regs and/or irq resource\n"); | 
 | 		return -ENXIO; | 
 | 	} | 
 |  | 
 | 	ret = -ENOMEM; | 
 | 	eic = kzalloc(sizeof(struct eic), GFP_KERNEL); | 
 | 	if (!eic) { | 
 | 		dev_dbg(&pdev->dev, "no memory for eic structure\n"); | 
 | 		goto err_kzalloc; | 
 | 	} | 
 |  | 
 | 	eic->first_irq = EIM_IRQ_BASE + 32 * pdev->id; | 
 | 	eic->regs = ioremap(regs->start, regs->end - regs->start + 1); | 
 | 	if (!eic->regs) { | 
 | 		dev_dbg(&pdev->dev, "failed to map regs\n"); | 
 | 		goto err_ioremap; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * Find out how many interrupt lines that are actually | 
 | 	 * implemented in hardware. | 
 | 	 */ | 
 | 	eic_writel(eic, IDR, ~0UL); | 
 | 	eic_writel(eic, MODE, ~0UL); | 
 | 	pattern = eic_readl(eic, MODE); | 
 | 	nr_irqs = fls(pattern); | 
 |  | 
 | 	/* Trigger on low level unless overridden by driver */ | 
 | 	eic_writel(eic, EDGE, 0UL); | 
 | 	eic_writel(eic, LEVEL, 0UL); | 
 |  | 
 | 	eic->chip = &eic_chip; | 
 |  | 
 | 	for (i = 0; i < nr_irqs; i++) { | 
 | 		set_irq_chip_and_handler(eic->first_irq + i, &eic_chip, | 
 | 					 handle_level_irq); | 
 | 		set_irq_chip_data(eic->first_irq + i, eic); | 
 | 	} | 
 |  | 
 | 	set_irq_chained_handler(int_irq, demux_eic_irq); | 
 | 	set_irq_data(int_irq, eic); | 
 |  | 
 | 	if (pdev->id == 0) { | 
 | 		nmi_eic = eic; | 
 | 		if (nmi_enabled) | 
 | 			/* | 
 | 			 * Someone tried to enable NMI before we were | 
 | 			 * ready. Do it now. | 
 | 			 */ | 
 | 			nmi_enable(); | 
 | 	} | 
 |  | 
 | 	dev_info(&pdev->dev, | 
 | 		 "External Interrupt Controller at 0x%p, IRQ %u\n", | 
 | 		 eic->regs, int_irq); | 
 | 	dev_info(&pdev->dev, | 
 | 		 "Handling %u external IRQs, starting with IRQ %u\n", | 
 | 		 nr_irqs, eic->first_irq); | 
 |  | 
 | 	return 0; | 
 |  | 
 | err_ioremap: | 
 | 	kfree(eic); | 
 | err_kzalloc: | 
 | 	return ret; | 
 | } | 
 |  | 
 | static struct platform_driver eic_driver = { | 
 | 	.driver = { | 
 | 		.name = "at32_eic", | 
 | 	}, | 
 | }; | 
 |  | 
 | static int __init eic_init(void) | 
 | { | 
 | 	return platform_driver_probe(&eic_driver, eic_probe); | 
 | } | 
 | arch_initcall(eic_init); |