| /* | 
 |  * 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> | 
 |  | 
 | #include <asm/arch/sm.h> | 
 |  | 
 | #include "sm.h" | 
 |  | 
 | static void eim_ack_irq(unsigned int irq) | 
 | { | 
 | 	struct at32_sm *sm = get_irq_chip_data(irq); | 
 | 	sm_writel(sm, EIM_ICR, 1 << (irq - sm->eim_first_irq)); | 
 | } | 
 |  | 
 | static void eim_mask_irq(unsigned int irq) | 
 | { | 
 | 	struct at32_sm *sm = get_irq_chip_data(irq); | 
 | 	sm_writel(sm, EIM_IDR, 1 << (irq - sm->eim_first_irq)); | 
 | } | 
 |  | 
 | static void eim_mask_ack_irq(unsigned int irq) | 
 | { | 
 | 	struct at32_sm *sm = get_irq_chip_data(irq); | 
 | 	sm_writel(sm, EIM_ICR, 1 << (irq - sm->eim_first_irq)); | 
 | 	sm_writel(sm, EIM_IDR, 1 << (irq - sm->eim_first_irq)); | 
 | } | 
 |  | 
 | static void eim_unmask_irq(unsigned int irq) | 
 | { | 
 | 	struct at32_sm *sm = get_irq_chip_data(irq); | 
 | 	sm_writel(sm, EIM_IER, 1 << (irq - sm->eim_first_irq)); | 
 | } | 
 |  | 
 | static int eim_set_irq_type(unsigned int irq, unsigned int flow_type) | 
 | { | 
 | 	struct at32_sm *sm = get_irq_chip_data(irq); | 
 | 	unsigned int i = irq - sm->eim_first_irq; | 
 | 	u32 mode, edge, level; | 
 | 	unsigned long flags; | 
 | 	int ret = 0; | 
 |  | 
 | 	flow_type &= IRQ_TYPE_SENSE_MASK; | 
 |  | 
 | 	spin_lock_irqsave(&sm->lock, flags); | 
 |  | 
 | 	mode = sm_readl(sm, EIM_MODE); | 
 | 	edge = sm_readl(sm, EIM_EDGE); | 
 | 	level = sm_readl(sm, EIM_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; | 
 | 	} | 
 |  | 
 | 	sm_writel(sm, EIM_MODE, mode); | 
 | 	sm_writel(sm, EIM_EDGE, edge); | 
 | 	sm_writel(sm, EIM_LEVEL, level); | 
 |  | 
 | 	spin_unlock_irqrestore(&sm->lock, flags); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | struct irq_chip eim_chip = { | 
 | 	.name		= "eim", | 
 | 	.ack		= eim_ack_irq, | 
 | 	.mask		= eim_mask_irq, | 
 | 	.mask_ack	= eim_mask_ack_irq, | 
 | 	.unmask		= eim_unmask_irq, | 
 | 	.set_type	= eim_set_irq_type, | 
 | }; | 
 |  | 
 | static void demux_eim_irq(unsigned int irq, struct irq_desc *desc) | 
 | { | 
 | 	struct at32_sm *sm = desc->handler_data; | 
 | 	struct irq_desc *ext_desc; | 
 | 	unsigned long status, pending; | 
 | 	unsigned int i, ext_irq; | 
 |  | 
 | 	spin_lock(&sm->lock); | 
 |  | 
 | 	status = sm_readl(sm, EIM_ISR); | 
 | 	pending = status & sm_readl(sm, EIM_IMR); | 
 |  | 
 | 	while (pending) { | 
 | 		i = fls(pending) - 1; | 
 | 		pending &= ~(1 << i); | 
 |  | 
 | 		ext_irq = i + sm->eim_first_irq; | 
 | 		ext_desc = irq_desc + ext_irq; | 
 | 		ext_desc->handle_irq(ext_irq, ext_desc); | 
 | 	} | 
 |  | 
 | 	spin_unlock(&sm->lock); | 
 | } | 
 |  | 
 | static int __init eim_init(void) | 
 | { | 
 | 	struct at32_sm *sm = &system_manager; | 
 | 	unsigned int i; | 
 | 	unsigned int nr_irqs; | 
 | 	unsigned int int_irq; | 
 | 	u32 pattern; | 
 |  | 
 | 	/* | 
 | 	 * The EIM is really the same module as SM, so register | 
 | 	 * mapping, etc. has been taken care of already. | 
 | 	 */ | 
 |  | 
 | 	/* | 
 | 	 * Find out how many interrupt lines that are actually | 
 | 	 * implemented in hardware. | 
 | 	 */ | 
 | 	sm_writel(sm, EIM_IDR, ~0UL); | 
 | 	sm_writel(sm, EIM_MODE, ~0UL); | 
 | 	pattern = sm_readl(sm, EIM_MODE); | 
 | 	nr_irqs = fls(pattern); | 
 |  | 
 | 	sm->eim_chip = &eim_chip; | 
 |  | 
 | 	for (i = 0; i < nr_irqs; i++) { | 
 | 		set_irq_chip(sm->eim_first_irq + i, &eim_chip); | 
 | 		set_irq_chip_data(sm->eim_first_irq + i, sm); | 
 | 	} | 
 |  | 
 | 	int_irq = platform_get_irq_byname(sm->pdev, "eim"); | 
 |  | 
 | 	set_irq_chained_handler(int_irq, demux_eim_irq); | 
 | 	set_irq_data(int_irq, sm); | 
 |  | 
 | 	printk("EIM: External Interrupt Module at 0x%p, IRQ %u\n", | 
 | 	       sm->regs, int_irq); | 
 | 	printk("EIM: Handling %u external IRQs, starting with IRQ %u\n", | 
 | 	       nr_irqs, sm->eim_first_irq); | 
 |  | 
 | 	return 0; | 
 | } | 
 | arch_initcall(eim_init); |