blob: d5d1b0f921d408fc5e1ca885a69643c17241286f [file] [log] [blame]
/*
* (C) Copyright 2010 Quantenna Communications Inc.
*
* See file CREDITS for list of people who contributed to this
* project.
*
* 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
*/
/******************************************************************************
* Copyright ARC International (www.arc.com) 2007-2008
*
* Vineetg: Mar 2009
* -use generic irqaction to store IRQ requests
* -device ISRs no longer take pt_regs (rest of the kernel convention)
* -request_irq( ) definition matches declaration in inc/linux/interrupt.h
*
* Vineetg: Mar 2009 (Supporting 2 levels of Interrupts)
* -local_irq_enable shd be cognizant of current IRQ state
* It is lot more involved now and thus re-written in "C"
* -set_irq_regs in common ISR now always done and not dependent
* on CONFIG_PROFILEas it is used by
*
* Vineetg: Jan 2009
* -Cosmetic change to display the registered ISR name for an IRQ
* -free_irq( ) cleaned up to not have special first-node/other node cases
*
* Vineetg: June 17th 2008
* -Added set_irq_regs() to top level ISR for profiling
* -Don't Need __cli just before irq_exit(). Intr already disabled
* -Disabled compile time ARC_IRQ_DBG
*
*****************************************************************************/
/******************************************************************************
* Copyright Codito Technologies (www.codito.com) Oct 01, 2004
*
*
* 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.
*
*****************************************************************************/
/*
* arch/arc/kernel/irq.c
*
*/
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/kernel_stat.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/kallsyms.h>
#include <linux/io.h>
#include <linux/magic.h>
#include <linux/random.h>
#include <linux/sched.h>
#include <asm/system.h>
#include <asm/errno.h>
#include <asm/arcregs.h>
#include <asm/hardware.h>
#include <asm/board/board_config.h>
#include <asm/board/platform.h>
#include <asm/board/mem_check.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE == KERNEL_VERSION(2,6,30)
#include <trace/irq.h>
#else
#include <trace/events/irq.h>
#endif
#include <qtn/ruby_cpumon.h>
//#define ARC_IRQ_DBG
//#define TEST_IRQ_REG
#ifdef ARC_IRQ_DBG
#define ASSERT(expr) BUG_ON(!(expr))
#else
#define ASSERT(expr)
#endif
#ifdef TOPAZ_PLATFORM
#define TIMER_INT_MSK(t) TOPAZ_SYS_CTL_INTR_TIMER_MSK(t)
#else
#define TIMER_INT_MSK(t) RUBY_SYS_CTL_INTR_TIMER_MSK(t)
#endif
enum toggle_irq {
DISABLE,
ENABLE,
};
enum first_isr {
MULTIPLE_ISRS,
FIRST_ISR,
};
enum invert_bit {
NON_INVERT,
INVERT,
};
DEFINE_TRACE(irq_handler_entry);
DEFINE_TRACE(irq_handler_exit);
static void ruby_en_dis_ext_irq(unsigned irq, enum toggle_irq toggle);
static void ruby_en_dis_common_irq(unsigned irq, enum toggle_irq toggle);
/* table for system interrupt handlers, including handlers for extended interrupts. */
struct irq_desc irq_desc[NR_IRQS];
static inline struct irqaction *irq_get_action(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);
return desc->action;
}
/* IRQ status spinlock - enable, disable */
static spinlock_t irq_controller_lock;
extern void smp_ipi_init(void);
static void run_handlers(unsigned irq);
/* Return true if irq is extended. */
static __always_inline int ruby_is_ext_irq(unsigned irq)
{
return (irq > RUBY_MAX_IRQ_VECTOR);
}
void __init arc_irq_init(void)
{
extern int _int_vec_base_lds;
/* set the base for the interrupt vector tabe as defined in Linker File
Interrupts will be enabled in start_kernel
*/
write_new_aux_reg(AUX_INTR_VEC_BASE, &_int_vec_base_lds);
/* vineetg: Jan 28th 2008
Disable all IRQs on CPU side
We will selectively enable them as devices request for IRQ
*/
write_new_aux_reg(AUX_IENABLE, 0);
#ifdef CONFIG_ARCH_ARC_LV2_INTR
{
int level_mask = 0;
/* If any of the peripherals is Level2 Interrupt (high Prio),
set it up that way
*/
#ifdef CONFIG_TIMER_LV2
level_mask |= (1 << TIMER0_INT );
#endif
#ifdef CONFIG_SERIAL_LV2
level_mask |= (1 << VUART_IRQ);
#endif
#ifdef CONFIG_EMAC_LV2
level_mask |= (1 << VMAC_IRQ );
#endif
if (level_mask) {
printk("setup as level-2 interrupt/s \n");
write_new_aux_reg(AUX_IRQ_LEV, level_mask);
}
}
#endif
}
static irqreturn_t uart_irq_demux_handler(int irq, void *dev_id)
{
int i, handled = 0;
u32 rcstatus = readl(IO_ADDRESS(RUBY_SYS_CTL_RESET_CAUSE));
for (i = 0; i < 2; i++) {
u32 rcset = rcstatus & RUBY_RESET_CAUSE_UART(i);
if (rcset && irq_has_action(RUBY_IRQ_UART0 + i)) {
run_handlers(RUBY_IRQ_UART0 + i);
handled = 1;
}
}
return handled ? IRQ_HANDLED : IRQ_NONE;
}
static irqreturn_t timer_irq_demux_handler(int irq, void *dev_id)
{
int i, handled = 0;
u32 rcstatus = readl(IO_ADDRESS(RUBY_SYS_CTL_RESET_CAUSE));
for (i = 0; i < RUBY_NUM_TIMERS; i++) {
if ((rcstatus & TIMER_INT_MSK(i)) && irq_has_action(RUBY_IRQ_TIMER0 + i)) {
run_handlers(RUBY_IRQ_TIMER0 + i);
handled = 1;
}
}
return handled ? IRQ_HANDLED : IRQ_NONE;
}
static irqreturn_t misc_irq_demux_handler(int irq, void *dev_id)
{
bool handled = false;
int i;
uint32_t rcstatus;
rcstatus = readl(IO_ADDRESS(RUBY_SYS_CTL_RESET_CAUSE));
for (i = 0; i < QTN_IRQ_MISC_EXT_IRQ_COUNT; i++) {
const int demux_irq = RUBY_IRQ_MISC_EXT_IRQ_START + i;
const unsigned int rcbit = QTN_IRQ_MISC_RST_CAUSE_START + i;
if ((rcstatus & (1 << rcbit)) && irq_has_action(demux_irq)) {
run_handlers(demux_irq);
handled = true;
}
}
return handled ? IRQ_HANDLED : IRQ_NONE;
}
/* remember desired triggering behavior of each gpio isr registered */
static unsigned long gpio_trig_flags[RUBY_GPIO_IRQ_MAX];
static __always_inline unsigned long read_gpio_inv_status(void)
{
return readl(IO_ADDRESS(RUBY_SYS_CTL_INTR_INV0));
}
static __always_inline void write_gpio_inv_status_bit(int gpio, enum invert_bit invert)
{
if (invert == INVERT) {
writel_or(RUBY_BIT(gpio), IO_ADDRESS(RUBY_SYS_CTL_INTR_INV0));
} else {
writel_and(~RUBY_BIT(gpio), IO_ADDRESS(RUBY_SYS_CTL_INTR_INV0));
}
}
static __always_inline unsigned long read_gpio_input_status(void)
{
return readl(IO_ADDRESS(RUBY_GPIO_INPUT));
}
static void init_gpio_irq(int irq, unsigned long flags)
{
int gpio = irq - RUBY_IRQ_GPIO0;
u32 line_status;
if (gpio >= RUBY_GPIO_IRQ_MAX || gpio < 0) {
panic("error in gpio isr init! irq: %d, flags: 0x%lx\n", irq, flags);
}
gpio_trig_flags[gpio] = flags & IRQF_TRIGGER_MASK;
line_status = read_gpio_input_status() & (1 << gpio);
if (flags & IRQF_TRIGGER_HIGH) {
/* set inversion line off for high level trigger */
write_gpio_inv_status_bit(gpio, NON_INVERT);
}
else if (flags & IRQF_TRIGGER_LOW) {
write_gpio_inv_status_bit(gpio, INVERT);
}
else if (flags & IRQF_TRIGGER_RISING) {
/*
* for rising edge trigger, invert off,
* then enable invert after each rising edge interrupt.
* when the edge falls again, invert off to accept the next rising edge.
* set to the opposite of the input pin for starters, so registering
* the driver doesn't trigger an interrupt on the requested level
* without an edge.
*/
if (line_status) {
write_gpio_inv_status_bit(gpio, INVERT);
} else {
write_gpio_inv_status_bit(gpio, NON_INVERT);
}
}
else if (flags & IRQF_TRIGGER_FALLING) {
if (line_status) {
write_gpio_inv_status_bit(gpio, NON_INVERT);
} else {
write_gpio_inv_status_bit(gpio, INVERT);
}
}
else {
/*
* assume this has been manually set, leave as default or prior
*/
}
}
/**
* gpio demux handler
*
* call handlers based on status of gpio input lines, and line invert level.
* provides software support for edge triggered interrupts for gpio buttons etc.
*/
static irqreturn_t gpio_irq_demux_handler(int irq, void *dev_id)
{
int i;
int handle[RUBY_GPIO_IRQ_MAX];
unsigned long flags, gpio_input_status, gpio_inv_status;
spin_lock_irqsave(&irq_controller_lock, flags);
/*
* determine which handlers to run and manipulate registers
* holding the lock, then run handlers afterwards
*/
gpio_input_status = read_gpio_input_status();
gpio_inv_status = read_gpio_inv_status();
for (i = 0; i < RUBY_GPIO_IRQ_MAX; i++) {
int handle_line = 0;
int ext_irq = RUBY_IRQ_GPIO0 + i;
if (irq_has_action(ext_irq)) {
unsigned long gpio_line_flags = gpio_trig_flags[i];
unsigned long line_high = gpio_input_status & (0x1 << i);
unsigned long inv_on = gpio_inv_status & (0x1 << i);
#if 0
printk("%s irq %d gpio %d input %x inv %x flags %x lhigh %d inv %d\n",
__FUNCTION__, irq, i, gpio_input_status, gpio_inv_status, gpio_line_flags,
line_high != 0, inv_on != 0);
#endif
if (gpio_line_flags & IRQF_TRIGGER_HIGH) {
handle_line = line_high;
} else if (gpio_line_flags & IRQF_TRIGGER_LOW) {
handle_line = !line_high;
} else if (gpio_line_flags & IRQF_TRIGGER_RISING) {
/*
* rising edge trigger. if inverted, dont handle, uninvert.
* if uninverted, handle and invert.
*
* if neither of these cases are true, then either:
* 1) this line didn't cause this interrupt, or
* 2) the input_status register has not updated yet.
* this was observed during test, spurious interrupts
* would occur where input_status is not set appropriately,
* even though an interrupt was caused by this line.
* This just means another interrupt will fire until
* the gpio input status reaches its appropriate state
*/
if (!line_high && inv_on) {
write_gpio_inv_status_bit(i, NON_INVERT);
} else if (line_high && !inv_on) {
handle_line = 1;
write_gpio_inv_status_bit(i, INVERT);
}
} else if (gpio_line_flags & IRQF_TRIGGER_FALLING) {
if (!line_high && inv_on) {
write_gpio_inv_status_bit(i, NON_INVERT);
handle_line = 1;
} else if (line_high && !inv_on) {
write_gpio_inv_status_bit(i, INVERT);
}
} else {
handle_line = (line_high && !inv_on) || (!line_high && inv_on);
}
}
handle[i] = handle_line;
}
spin_unlock_irqrestore(&irq_controller_lock, flags);
for (i = 0; i < RUBY_GPIO_IRQ_MAX; i++) {
if (handle[i]) {
run_handlers(RUBY_IRQ_GPIO0 + i);
}
}
return IRQ_HANDLED;
}
static struct irqaction gpio_irq = {
.name = "GPIO demux",
.flags = 0,
.handler = &gpio_irq_demux_handler,
.dev_id = NULL,
};
static struct irqaction uart_irq = {
.name = "UART demux",
.flags = 0,
.handler = &uart_irq_demux_handler,
.dev_id = NULL,
};
static struct irqaction timer_irq = {
.name = "Timers demux",
.flags = 0,
.handler = &timer_irq_demux_handler,
.dev_id = NULL,
};
static struct irqaction misc_irq = {
.name = "Misc demux",
.flags = 0,
.handler = &misc_irq_demux_handler,
.dev_id = NULL,
};
#ifdef TEST_IRQ_REG
static irqreturn_t test_irq_handler(int irq, void *dev_id)
{
if (printk_ratelimit()) {
printk(KERN_WARNING "%s, %s irq %d\n", __FUNCTION__, (const char*) dev_id, irq);
} else {
unsigned long flags;
spin_lock_irqsave(&irq_controller_lock, flags);
ruby_en_dis_ext_irq(irq, DISABLE);
spin_unlock_irqrestore(&irq_controller_lock, flags);
}
return IRQ_HANDLED;
}
/*
* attempt to register and deregister handlers on every irq
*/
static void test_irq_reg(void)
{
int j, k;
for (j = 0; j < RUBY_IRQ_EXT_VECTORS_NUM; j++) {
char bufj[32];
snprintf(bufj, 32, "test.j.%d", j);
int req2 = request_irq(j, test_irq_handler, IRQF_SHARED, "testj", bufj);
for (k = 0; k < 2; k++) {
char bufk[32];
snprintf(bufk, 32, "test.k.%d", k);
int req3 = request_irq(j, test_irq_handler, IRQF_SHARED, "testk", bufk);
if (!req3) {
disable_irq(j);
disable_irq(j);
enable_irq(j);
enable_irq(j);
free_irq(j, bufk);
}
}
if (!req2) {
free_irq(j, bufj);
} else {
printk(KERN_WARNING "%s could not register %s\n",
__FUNCTION__, bufj);
}
}
}
#endif
/*
*
* Some checking to prevent registration of isrs on gpio lines that are in use
*
*/
static int check_uart1_use(void)
{
int use_uart1 = 0;
if (get_board_config(BOARD_CFG_UART1, &use_uart1) != 0) {
printk(KERN_ERR "get_board_config returned error status for UART1\n");
}
return use_uart1;
}
static struct disallowed_isr {
int irq;
int gpio;
char* use;
int (*checkfunc)(void); /* returns 0 if the line is available */
}
disallowed_isrs[] =
{
{ -1, RUBY_GPIO_UART0_SI, "uart0 rx", NULL },
{ -1, RUBY_GPIO_UART0_SO, "uart0 tx", NULL },
{ -1, RUBY_GPIO_UART1_SI, "uart1 rx", &check_uart1_use },
{ -1, RUBY_GPIO_UART1_SO, "uart1 tx", &check_uart1_use },
{ -1, RUBY_GPIO_I2C_SCL, "i2c scl", NULL },
{ -1, RUBY_GPIO_I2C_SDA, "i2c sda", NULL },
{ RUBY_IRQ_TIMER0, -1, "ruby timer0", NULL },
};
/* returns 0 if this gpio pin may have an isr installed on it */
static int ext_isr_check(int irq)
{
int i;
const struct disallowed_isr *inv;
int gpio = (irq - RUBY_IRQ_GPIO0);
if (!ruby_is_ext_irq(irq)) {
panic("%s: invalid irq: %d\n", __FUNCTION__, irq);
return -EBUSY;
}
for (i = 0; i < ARRAY_SIZE(disallowed_isrs); i++) {
inv = &disallowed_isrs[i];
/* check if pin or irq num matches */
if (inv->irq == irq ||
(inv->irq < 0 &&
inv->gpio == gpio &&
gpio < RUBY_GPIO_IRQ_MAX)) {
/* no checkfunc means always busy */
int used = 1;
if (inv->checkfunc) {
used = inv->checkfunc();
}
if (used) {
printk(KERN_ERR "%s: irq %d in use by %s\n",
__FUNCTION__, irq, inv->use);
return -EBUSY;
}
}
}
return 0;
}
static __always_inline u32 count_bits_set(u32 value)
{
u32 count = 0;
for (; value; value >>= 1) {
count += value & 0x1;
}
return count;
}
/* initialise the irq table */
void __init init_IRQ(void)
{
memset(&irq_desc[0], 0, sizeof(irq_desc));
setup_irq(RUBY_IRQ_UART, &uart_irq);
setup_irq(RUBY_IRQ_GPIO, &gpio_irq);
setup_irq(RUBY_IRQ_TIMER, &timer_irq);
setup_irq(RUBY_IRQ_MISC, &misc_irq);
#ifdef CONFIG_SMP
smp_ipi_init();
#endif
}
/* Map extended IRQ to common IRQ line */
static unsigned int ruby_map_ext_irq(unsigned irq)
{
/*
************************************************
* remap shared irq's at this level - there are
* only 32 physical vectors so we cannot just
* map 64 irq's directly.
* The irq handler is responsible for demuxing
* further - there are only a couple of
* cases as shown below. Otherwise we can
* implement a second level irq scheme by
* registering irq's in the init with demux
* handlers.
*************************************************
*/
if (ruby_is_ext_irq(irq)) {
/* Adjust extended irq to common irq line. */
if (irq <= RUBY_IRQ_GPIO15) {
irq = RUBY_IRQ_GPIO;
} else if (irq <= RUBY_IRQ_UART1) {
irq = RUBY_IRQ_UART;
} else if (irq <= RUBY_IRQ_TIMER3) {
irq = RUBY_IRQ_TIMER;
} else {
irq = RUBY_IRQ_MISC;
}
}
return irq;
}
/* Enable/disable Ruby extended interrupt requests */
static void ruby_en_dis_ext_irq(unsigned irq, enum toggle_irq toggle)
{
if (!ruby_is_ext_irq(irq)) {
printk(KERN_ERR"IRQ %u must be never used with this function.\n", irq);
panic("IRQ handling failure\n");
} else {
unsigned long status = readl(IO_ADDRESS(RUBY_SYS_CTL_LHOST_ORINT_EN));
if (toggle == ENABLE) {
status |= (1 << (irq - RUBY_IRQ_VECTORS_NUM));
} else {
status &= ~(1 << (irq - RUBY_IRQ_VECTORS_NUM));
}
writel(status, IO_ADDRESS(RUBY_SYS_CTL_LHOST_ORINT_EN));
}
}
/* Enable/disable Ruby common interrupt requests */
static void ruby_en_dis_common_irq(unsigned irq, enum toggle_irq toggle)
{
if (ruby_is_ext_irq(irq)) {
printk(KERN_ERR"IRQ %u must be never used with this function.\n", irq);
panic("IRQ handling failure\n");
} else if (irq >= RUBY_IRQ_WATCHDOG) {
/* If higher than timer, this is external irq to cpu and needs additional reg set up. */
unsigned long status = readl(IO_ADDRESS(RUBY_SYS_CTL_LHOST_INT_EN));
if (toggle == ENABLE) {
status |= (1 << (irq - RUBY_IRQ_WATCHDOG));
} else {
status &= ~(1 << (irq - RUBY_IRQ_WATCHDOG));
}
writel(status, IO_ADDRESS(RUBY_SYS_CTL_LHOST_INT_EN));
}
}
/* Check that setup request is correct. */
static int ruby_setup_check_1(unsigned irq, unsigned mapped_irq, struct irqaction *node)
{
int ret = 0;
if (ruby_is_ext_irq(irq)) {
ret = ext_isr_check(irq);
}
if (node->flags & IRQF_TRIGGER_MASK) {
if (mapped_irq == RUBY_IRQ_GPIO) {
if (count_bits_set(node->flags & IRQF_TRIGGER_MASK) > 1) {
printk(KERN_ERR"IRQ %d (0x%x) does not support multiple IRQF_TRIGGER_* flags\n",
irq, (unsigned)node->flags);
ret = -EINVAL;
}
} else {
printk(KERN_ERR"IRQ %d (0x%x) does not support IRQF_TRIGGER_* flags (fixme)\n",
irq, (unsigned)node->flags);
ret = -EINVAL;
}
}
return ret;
}
/* Check that setup request is correct. */
static int ruby_setup_check_2(unsigned irq, unsigned mapped_irq, struct irqaction *node)
{
int ret = 0;
if (!(irq_get_action(irq)->flags & IRQF_SHARED) || !(node->flags & IRQF_SHARED)) {
/* Enforce condition that all (if more than one) ISRs belong to the same
* vector must be shared.
*/
printk(KERN_ERR"%s: %s incompatible with IRQ %d from %s\n",
__FUNCTION__, node->name, irq, irq_get_action(irq)->name);
ret = -EBUSY;
}
return ret;
}
/* Enable IRQ during setup */
static void ruby_setup_enable_irq(unsigned irq)
{
if (ruby_is_ext_irq(irq)) {
/* enable extended interrupt. */
ruby_en_dis_ext_irq(irq, ENABLE);
} else {
/* Enable this IRQ in CPU AUX_IENABLE Reg */
unmask_interrupt((1 << irq));
/* Enable IRQ on Ruby interrupt controller */
ruby_en_dis_common_irq(irq, ENABLE);
}
}
/* Link node during setup */
static void ruby_setup_link_irq(enum first_isr is_first, unsigned irq, struct irqaction *node)
{
if (is_first == FIRST_ISR) {
/* Add ISR to the head */
irq_to_desc(irq)->action = node;
} else {
/* Add the ISR to link-list of ISRs per IRQ */
struct irqaction *curr = irq_get_action(irq);
while (curr->next) {
curr = curr->next;
}
curr->next = node;
}
}
/* setup_irq:
* Typically used by architecure special interrupts for
* registering handler to IRQ line
*/
int setup_irq(unsigned irq, struct irqaction *node)
{
int ret = 0;
unsigned mapped_irq = ruby_map_ext_irq(irq);
unsigned long flags;
enum first_isr is_first;
#ifdef ARC_IRQ_DBG
printk(KERN_INFO"---IRQ Request (%d) ISR\n", irq);
#endif
if (node->flags & IRQF_SAMPLE_RANDOM)
rand_initialize_irq(irq);
spin_lock_irqsave(&irq_controller_lock, flags);
ret = ruby_setup_check_1(irq, mapped_irq, node);
if (ret) {
goto error;
}
if (!irq_has_action(irq)) {
is_first = FIRST_ISR;
} else {
is_first = MULTIPLE_ISRS;
/* additional checks for multiple isrs */
ret = ruby_setup_check_2(irq, mapped_irq, node);
if (ret) {
goto error;
}
}
/* additional pin settings for gpio irqs */
if (ruby_is_ext_irq(irq) && mapped_irq == RUBY_IRQ_GPIO) {
init_gpio_irq(irq, node->flags);
}
ruby_setup_link_irq(is_first, irq, node);
ruby_setup_enable_irq(irq);
error:
spin_unlock_irqrestore(&irq_controller_lock, flags);
return ret;
}
/* request_irq:
* Exported to device drivers / modules to assign handler to IRQ line
*/
int request_irq(unsigned irq,
irqreturn_t (*handler)(int, void *),
unsigned long flags, const char *name, void *dev_id)
{
struct irqaction *node;
int retval;
if (irq >= NR_IRQS) {
printk("%s: Unknown IRQ %d\n", __FUNCTION__, irq);
return -ENXIO;
}
node = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!node)
return -ENOMEM;
node->handler = handler;
node->flags = flags;
node->dev_id = dev_id;
node->name = name;
node->next = NULL;
/* insert the new irq registered into the irq list */
retval = setup_irq(irq, node);
if (retval)
kfree(node);
return retval;
}
EXPORT_SYMBOL(request_irq);
/* free an irq node for the irq list */
void free_irq(unsigned irq, void *dev_id)
{
unsigned long flags;
struct irqaction *tmp = NULL, **node;
if (irq >= NR_IRQS) {
printk(KERN_ERR "%s: Unknown IRQ %d\n", __FUNCTION__, irq);
return;
}
spin_lock_irqsave(&irq_controller_lock, flags); /* delete atomically */
/* Traverse through linked-list of ISRs. */
node = &irq_to_desc(irq)->action;
while (*node) {
if ((*node)->dev_id == dev_id) {
tmp = *node;
(*node) = (*node)->next;
kfree(tmp);
} else {
node = &((*node)->next);
}
}
/* Disable IRQ if found in linked-list. */
if (tmp) {
/* Disable if last ISR deleted. */
if (!irq_has_action(irq)) {
/* If it is extended irq - disable it. */
if (ruby_is_ext_irq(irq)) {
ruby_en_dis_ext_irq(irq, DISABLE);
} else {
/* Disable this IRQ in CPU AUX_IENABLE Reg */
mask_interrupt((1 << irq));
/* Disable IRQ on Ruby interrupt controller */
ruby_en_dis_common_irq(irq, DISABLE);
}
}
}
spin_unlock_irqrestore(&irq_controller_lock, flags);
if (!tmp) {
printk(KERN_ERR"%s: tried to remove invalid interrupt", __FUNCTION__);
}
}
EXPORT_SYMBOL(free_irq);
#if defined(CONFIG_PCI_MSI)
static inline void clear_msi_request(void)
{
writel(RUBY_PCIE_MSI_CLEAR, RUBY_PCIE_MSI_STATUS);
}
#endif
struct ruby_cpumon_data {
uint64_t sleep_cycles;
uint64_t awake_cycles;
uint32_t last_cyc;
int last_was_asleep;
};
static __sram_data struct ruby_cpumon_data ruby_cpumon;
static __always_inline uint32_t ruby_cpumon_get_clock(void)
{
return read_new_aux_reg(ARC_REG_TIMER1_CNT);
}
void ruby_cpumon_get_cycles(uint64_t *sleep, uint64_t *awake)
{
uint32_t cyc;
unsigned long flags;
struct ruby_cpumon_data *cd = &ruby_cpumon;
local_irq_save(flags);
WARN_ON_ONCE(cd->last_was_asleep);
cyc = ruby_cpumon_get_clock();
cd->awake_cycles += cyc - cd->last_cyc;
cd->last_cyc = cyc;
if (sleep) {
*sleep = cd->sleep_cycles;
}
if (awake) {
*awake = cd->awake_cycles;
}
local_irq_restore(flags);
}
EXPORT_SYMBOL(ruby_cpumon_get_cycles);
static int ruby_cpumon_proc_read(char *page, char **start, off_t offset, int count, int *eof, void *data)
{
char *p = page;
uint64_t awake;
uint64_t sleep;
if (offset > 0) {
*eof = 1;
return 0;
}
ruby_cpumon_get_cycles(&sleep, &awake);
p += sprintf(p, "up_msecs %u sleep %Lu awake %Lu\n", jiffies_to_msecs(jiffies), sleep, awake);
return p - page;
}
static int __init ruby_cpumon_register_proc(void)
{
struct ruby_cpumon_data *cd = &ruby_cpumon;
create_proc_read_entry("ruby_cpumon", 0, NULL, ruby_cpumon_proc_read, NULL);
memset(cd, 0, sizeof(*cd));
cd->last_cyc = ruby_cpumon_get_clock();
return 0;
}
late_initcall(ruby_cpumon_register_proc);
void arch_idle(void)
{
uint32_t sleep_start;
unsigned long flags;
struct ruby_cpumon_data *cd = &ruby_cpumon;
local_irq_save(flags);
sleep_start = ruby_cpumon_get_clock();
cd->awake_cycles += sleep_start - cd->last_cyc;
cd->last_cyc = sleep_start;
cd->last_was_asleep = 1;
local_irq_restore_and_sleep(flags);
}
static inline void ruby_cpumon_interrupt(void)
{
struct ruby_cpumon_data *cd = &ruby_cpumon;
if (unlikely(cd->last_was_asleep)) {
uint32_t awake_start = ruby_cpumon_get_clock();
cd->sleep_cycles += awake_start - cd->last_cyc;
cd->last_cyc = awake_start;
cd->last_was_asleep = 0;
}
}
static void __sram_text run_handlers(unsigned irq)
{
struct irqaction *node;
ASSERT((irq < NR_IRQS) && irq_has_action(irq));
#if defined(CONFIG_PCI_MSI)
if (irq == RUBY_IRQ_MSI) {
clear_msi_request();
}
#endif
/* call all the ISR's in the list for that interrupt source */
node = irq_get_action(irq);
while (node) {
kstat_cpu(smp_processor_id()).irqs[irq]++;
trace_irq_handler_entry(irq, 0);
node->handler(irq, node->dev_id);
trace_irq_handler_exit(irq, 0, 0);
if (node->flags & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
node = node->next;
}
#ifdef ARC_IRQ_DBG
if (!irq_has_action(irq))
printk(KERN_ERR "Spurious interrupt : irq no %u on cpu %u", irq,
smp_processor_id());
#endif
}
/* handle the irq */
void __sram_text process_interrupt(unsigned irq, struct pt_regs *fp)
{
struct pt_regs *old = set_irq_regs(fp);
irq_enter();
ruby_cpumon_interrupt();
run_handlers(irq);
irq_exit();
check_stack_consistency(__FUNCTION__);
set_irq_regs(old);
return;
}
/* IRQ Autodetect not required for ARC
* However the stubs still need to be exported for IDE et all
*/
unsigned long probe_irq_on(void)
{
return 0;
}
EXPORT_SYMBOL(probe_irq_on);
int probe_irq_off(unsigned long irqs)
{
return 0;
}
EXPORT_SYMBOL(probe_irq_off);
/* FIXME: implement if necessary */
void init_irq_proc(void)
{
// for implementing /proc/irq/xxx
}
int show_interrupts(struct seq_file *p, void *v)
{
int i = *(loff_t *) v, j;
if (i == 0) { // First line, first CPU
seq_printf(p,"\t");
for_each_online_cpu(j) {
seq_printf(p,"CPU%-8d",j);
}
seq_putc(p,'\n');
#ifdef TEST_IRQ_REG
test_irq_reg();
#endif
}
if (i < NR_IRQS) {
int irq = i;
const struct irqaction *node = irq_get_action(irq);
while (node) {
seq_printf(p,"%u:\t",i);
if (strlen(node->name) < 8) {
for_each_online_cpu(j) {
seq_printf(p,"%s\t\t\t%u\n",
node->name, kstat_cpu(j).irqs[i]);
}
} else {
for_each_online_cpu(j) {
seq_printf(p,"%s\t\t%u\n",
node->name, kstat_cpu(j).irqs[i]);
}
}
node = node->next;
}
}
return 0;
}
/**
* disable_irq - disable an irq and wait for completion
* @irq: Interrupt to disable
*
* Disable the selected interrupt line. We do this lazily.
*
* This function may be called from IRQ context.
*/
void disable_irq(unsigned irq)
{
unsigned long flags;
spin_lock_irqsave(&irq_controller_lock, flags);
if ((irq < NR_IRQS) && irq_has_action(irq)) {
if (!irq_to_desc(irq)->depth++) {
if (ruby_is_ext_irq(irq)) {
ruby_en_dis_ext_irq(irq, DISABLE);
} else {
ruby_en_dis_common_irq(irq, DISABLE);
}
}
}
spin_unlock_irqrestore(&irq_controller_lock, flags);
}
EXPORT_SYMBOL(disable_irq);
/**
* enable_irq - enable interrupt handling on an irq
* @irq: Interrupt to enable
*
* Re-enables the processing of interrupts on this IRQ line.
* Note that this may call the interrupt handler, so you may
* get unexpected results if you hold IRQs disabled.
*
* This function may be called from IRQ context.
*/
void enable_irq(unsigned irq)
{
unsigned long flags;
spin_lock_irqsave(&irq_controller_lock, flags);
if ((irq < NR_IRQS) && irq_has_action(irq)) {
if (irq_to_desc(irq)->depth) {
if (!--irq_to_desc(irq)->depth) {
if (ruby_is_ext_irq(irq)) {
ruby_en_dis_ext_irq(irq, ENABLE);
} else {
ruby_en_dis_common_irq(irq, ENABLE);
}
}
} else {
printk(KERN_ERR"Unbalanced IRQ action %d %s\n", irq, __FUNCTION__);
}
}
spin_unlock_irqrestore(&irq_controller_lock, flags);
}
EXPORT_SYMBOL(enable_irq);
#ifdef CONFIG_SMP
int get_hw_config_num_irq()
{
uint32_t val = read_new_aux_reg(ARC_REG_VECBASE_BCR);
switch(val & 0x03)
{
case 0: return 16;
case 1: return 32;
case 2: return 8;
default: return 0;
}
return 0;
}
#endif
/* Enable interrupts.
* 1. Explicitly called to re-enable interrupts
* 2. Implicitly called from spin_unlock_irq, write_unlock_irq etc
* which maybe in hard ISR itself
*
* Semantics of this function change depending on where it is called from:
*
* -If called from hard-ISR, it must not invert interrupt priorities
* e.g. suppose TIMER is high priority (Level 2) IRQ
* Time hard-ISR, timer_interrupt( ) calls spin_unlock_irq several times.
* Here local_irq_enable( ) shd not re-enable lower priority interrupts
* -If called from soft-ISR, it must re-enable all interrupts
* soft ISR are low prioity jobs which can be very slow, thus all IRQs
* must be enabled while they run.
* Now hardware context wise we may still be in L2 ISR (not done rtie)
* still we must re-enable both L1 and L2 IRQs
* Another twist is prev scenario with flow being
* L1 ISR ==> interrupted by L2 ISR ==> L2 soft ISR
* here we must not re-enable Ll as prev Ll Interrupt's h/w context will get
* over-written (this is deficiency in ARC700 Interrupt mechanism)
*/
#ifdef CONFIG_ARCH_ARC_LV2_INTR // Complex version for 2 levels of Intr
void __sram_text local_irq_enable(void) {
unsigned long flags;
local_save_flags(flags);
/* Allow both L1 and L2 at the onset */
flags |= (STATUS_E1_MASK | STATUS_E2_MASK);
/* Called from hard ISR (between irq_enter and irq_exit) */
if (in_irq()) {
/* If in L2 ISR, don't re-enable any further IRQs as this can cause
* IRQ priorities to get upside down.
* L1 can be taken while in L2 hard ISR which is wron in theory ans
* can also cause the dreaded L1-L2-L1 scenario
*/
if (flags & STATUS_A2_MASK) {
flags &= ~(STATUS_E1_MASK | STATUS_E2_MASK);
}
/* Even if in L1 ISR, allowe Higher prio L2 IRQs */
else if (flags & STATUS_A1_MASK) {
flags &= ~(STATUS_E1_MASK);
}
}
/* called from soft IRQ, ideally we want to re-enable all levels */
else if (in_softirq()) {
/* However if this is case of L1 interrupted by L2,
* re-enabling both may cause whaco L1-L2-L1 scenario
* because ARC700 allows level 1 to interrupt an active L2 ISR
* Thus we disable both
* However some code, executing in soft ISR wants some IRQs to be
* enabled so we re-enable L2 only
*
* How do we determine L1 intr by L2
* -A2 is set (means in L2 ISR)
* -E1 is set in this ISR's pt_regs->status32 which is
* saved copy of status32_l2 when l2 ISR happened
*/
struct pt_regs *pt = get_irq_regs();
if ((flags & STATUS_A2_MASK) && pt &&
(pt->status32 & STATUS_A1_MASK ) ) {
//flags &= ~(STATUS_E1_MASK | STATUS_E2_MASK);
flags &= ~(STATUS_E1_MASK);
}
}
local_irq_restore(flags);
}
#else /* ! CONFIG_ARCH_ARC_LV2_INTR */
/* Simpler version for only 1 level of interrupt
* Here we only Worry about Level 1 Bits
*/
void __sram_text local_irq_enable(void) {
unsigned long flags;
local_save_flags(flags);
flags |= (STATUS_E1_MASK | STATUS_E2_MASK);
/* If called from hard ISR (between irq_enter and irq_exit)
* don't allow Level 1. In Soft ISR we allow further Level 1s
*/
if (in_irq()) {
flags &= ~(STATUS_E1_MASK | STATUS_E2_MASK);
}
local_irq_restore(flags);
}
#endif
EXPORT_SYMBOL(local_irq_enable);