linux/arch/mips/ralink/irq.c
Andrew Bresticker a669efc4a3 MIPS: Add hook to get C0 performance counter interrupt
The hardware perf event driver and oprofile interpret the global
cp0_perfcount_irq differently: in the hardware perf event driver
it is an offset from MIPS_CPU_IRQ_BASE and in oprofile it is the
actual IRQ number.  This still works most of the time since
MIPS_CPU_IRQ_BASE is usually 0, but is clearly wrong.  Since the
performance counter interrupt may vary from platform to platform
like the C0 timer interrupt, add the optional get_c0_perfcount_int
hook which returns the IRQ number of the performance counter.
The hook should return < 0 if the performance counter interrupt is
shared with the timer.  If the hook is not present, the CPU vector
reported in C0_IntCtl (cp0_perfcount_irq) is used.

Signed-off-by: Andrew Bresticker <abrestic@chromium.org>
Reviewed-by: Qais Yousef <qais.yousef@imgtec.com>
Tested-by: Qais Yousef <qais.yousef@imgtec.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Jason Cooper <jason@lakedaemon.net>
Cc: Andrew Bresticker <abrestic@chromium.org>
Cc: Jeffrey Deans <jeffrey.deans@imgtec.com>
Cc: Markos Chandras <markos.chandras@imgtec.com>
Cc: Paul Burton <paul.burton@imgtec.com>
Cc: Qais Yousef <qais.yousef@imgtec.com>
Cc: Jonas Gorski <jogo@openwrt.org>
Cc: John Crispin <blogic@openwrt.org>
Cc: David Daney <ddaney.cavm@gmail.com>
Cc: linux-mips@linux-mips.org
Cc: linux-kernel@vger.kernel.org
Patchwork: https://patchwork.linux-mips.org/patch/7805/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
2014-11-24 07:44:53 +01:00

192 lines
4.5 KiB
C

/*
* 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.
*
* Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org>
* Copyright (C) 2013 John Crispin <blogic@openwrt.org>
*/
#include <linux/io.h>
#include <linux/bitops.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/irqdomain.h>
#include <linux/interrupt.h>
#include <asm/irq_cpu.h>
#include <asm/mipsregs.h>
#include "common.h"
/* INTC register offsets */
#define INTC_REG_STATUS0 0x00
#define INTC_REG_STATUS1 0x04
#define INTC_REG_TYPE 0x20
#define INTC_REG_RAW_STATUS 0x30
#define INTC_REG_ENABLE 0x34
#define INTC_REG_DISABLE 0x38
#define INTC_INT_GLOBAL BIT(31)
#define RALINK_CPU_IRQ_INTC (MIPS_CPU_IRQ_BASE + 2)
#define RALINK_CPU_IRQ_PCI (MIPS_CPU_IRQ_BASE + 4)
#define RALINK_CPU_IRQ_FE (MIPS_CPU_IRQ_BASE + 5)
#define RALINK_CPU_IRQ_WIFI (MIPS_CPU_IRQ_BASE + 6)
#define RALINK_CPU_IRQ_COUNTER (MIPS_CPU_IRQ_BASE + 7)
/* we have a cascade of 8 irqs */
#define RALINK_INTC_IRQ_BASE 8
/* we have 32 SoC irqs */
#define RALINK_INTC_IRQ_COUNT 32
#define RALINK_INTC_IRQ_PERFC (RALINK_INTC_IRQ_BASE + 9)
static void __iomem *rt_intc_membase;
static int rt_perfcount_irq;
static inline void rt_intc_w32(u32 val, unsigned reg)
{
__raw_writel(val, rt_intc_membase + reg);
}
static inline u32 rt_intc_r32(unsigned reg)
{
return __raw_readl(rt_intc_membase + reg);
}
static void ralink_intc_irq_unmask(struct irq_data *d)
{
rt_intc_w32(BIT(d->hwirq), INTC_REG_ENABLE);
}
static void ralink_intc_irq_mask(struct irq_data *d)
{
rt_intc_w32(BIT(d->hwirq), INTC_REG_DISABLE);
}
static struct irq_chip ralink_intc_irq_chip = {
.name = "INTC",
.irq_unmask = ralink_intc_irq_unmask,
.irq_mask = ralink_intc_irq_mask,
.irq_mask_ack = ralink_intc_irq_mask,
};
int get_c0_perfcount_int(void)
{
return rt_perfcount_irq;
}
unsigned int get_c0_compare_int(void)
{
return CP0_LEGACY_COMPARE_IRQ;
}
static void ralink_intc_irq_handler(unsigned int irq, struct irq_desc *desc)
{
u32 pending = rt_intc_r32(INTC_REG_STATUS0);
if (pending) {
struct irq_domain *domain = irq_get_handler_data(irq);
generic_handle_irq(irq_find_mapping(domain, __ffs(pending)));
} else {
spurious_interrupt();
}
}
asmlinkage void plat_irq_dispatch(void)
{
unsigned long pending;
pending = read_c0_status() & read_c0_cause() & ST0_IM;
if (pending & STATUSF_IP7)
do_IRQ(RALINK_CPU_IRQ_COUNTER);
else if (pending & STATUSF_IP5)
do_IRQ(RALINK_CPU_IRQ_FE);
else if (pending & STATUSF_IP6)
do_IRQ(RALINK_CPU_IRQ_WIFI);
else if (pending & STATUSF_IP4)
do_IRQ(RALINK_CPU_IRQ_PCI);
else if (pending & STATUSF_IP2)
do_IRQ(RALINK_CPU_IRQ_INTC);
else
spurious_interrupt();
}
static int intc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
{
irq_set_chip_and_handler(irq, &ralink_intc_irq_chip, handle_level_irq);
return 0;
}
static const struct irq_domain_ops irq_domain_ops = {
.xlate = irq_domain_xlate_onecell,
.map = intc_map,
};
static int __init intc_of_init(struct device_node *node,
struct device_node *parent)
{
struct resource res;
struct irq_domain *domain;
int irq;
irq = irq_of_parse_and_map(node, 0);
if (!irq)
panic("Failed to get INTC IRQ");
if (of_address_to_resource(node, 0, &res))
panic("Failed to get intc memory range");
if (request_mem_region(res.start, resource_size(&res),
res.name) < 0)
pr_err("Failed to request intc memory");
rt_intc_membase = ioremap_nocache(res.start,
resource_size(&res));
if (!rt_intc_membase)
panic("Failed to remap intc memory");
/* disable all interrupts */
rt_intc_w32(~0, INTC_REG_DISABLE);
/* route all INTC interrupts to MIPS HW0 interrupt */
rt_intc_w32(0, INTC_REG_TYPE);
domain = irq_domain_add_legacy(node, RALINK_INTC_IRQ_COUNT,
RALINK_INTC_IRQ_BASE, 0, &irq_domain_ops, NULL);
if (!domain)
panic("Failed to add irqdomain");
rt_intc_w32(INTC_INT_GLOBAL, INTC_REG_ENABLE);
irq_set_chained_handler(irq, ralink_intc_irq_handler);
irq_set_handler_data(irq, domain);
/* tell the kernel which irq is used for performance monitoring */
rt_perfcount_irq = irq_create_mapping(domain, 9);
return 0;
}
static struct of_device_id __initdata of_irq_ids[] = {
{ .compatible = "mti,cpu-interrupt-controller", .data = mips_cpu_irq_of_init },
{ .compatible = "ralink,rt2880-intc", .data = intc_of_init },
{},
};
void __init arch_init_irq(void)
{
of_irq_init(of_irq_ids);
}