1
linux/arch/powerpc/sysdev/qe_lib/qe_ic.c
Anton Vorontsov cccd21027c [POWERPC] QEIC: Implement pluggable handlers, fix MPIC cascading
set_irq_chained_handler overwrites MPIC's handle_irq function
(handle_fasteoi_irq) thus MPIC never gets eoi event from the
cascaded IRQ. This situation hangs MPIC on MPC8568E.

To solve this problem efficiently, QEIC needs pluggable handlers,
specific to the underlaying interrupt controller.

Patch extends qe_ic_init() function to accept low and high interrupt
handlers. To avoid #ifdefs, stack of interrupt handlers specified in
the header file and functions are marked 'static inline', thus
handlers are compiled-in only if actually used (in the board file).
Another option would be to lookup for parent controller and
automatically detect handlers (will waste text size because of
never used handlers, so this option abolished).

qe_ic_init() also changed in regard to support multiplexed high/low
lines as found in MPC8568E-MDS, plus qe_ic_cascade_muxed_mpic()
handler implemented appropriately.

Signed-off-by: Anton Vorontsov <avorontsov@ru.mvista.com>
Acked-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Kumar Gala <galak@kernel.crashing.org>
2007-10-08 08:38:57 -05:00

514 lines
12 KiB
C

/*
* arch/powerpc/sysdev/qe_lib/qe_ic.c
*
* Copyright (C) 2006 Freescale Semicondutor, Inc. All rights reserved.
*
* Author: Li Yang <leoli@freescale.com>
* Based on code from Shlomi Gridish <gridish@freescale.com>
*
* QUICC ENGINE Interrupt Controller
*
* 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.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/reboot.h>
#include <linux/slab.h>
#include <linux/stddef.h>
#include <linux/sched.h>
#include <linux/signal.h>
#include <linux/sysdev.h>
#include <linux/device.h>
#include <linux/bootmem.h>
#include <linux/spinlock.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/prom.h>
#include <asm/qe_ic.h>
#include "qe_ic.h"
static DEFINE_SPINLOCK(qe_ic_lock);
static struct qe_ic_info qe_ic_info[] = {
[1] = {
.mask = 0x00008000,
.mask_reg = QEIC_CIMR,
.pri_code = 0,
.pri_reg = QEIC_CIPWCC,
},
[2] = {
.mask = 0x00004000,
.mask_reg = QEIC_CIMR,
.pri_code = 1,
.pri_reg = QEIC_CIPWCC,
},
[3] = {
.mask = 0x00002000,
.mask_reg = QEIC_CIMR,
.pri_code = 2,
.pri_reg = QEIC_CIPWCC,
},
[10] = {
.mask = 0x00000040,
.mask_reg = QEIC_CIMR,
.pri_code = 1,
.pri_reg = QEIC_CIPZCC,
},
[11] = {
.mask = 0x00000020,
.mask_reg = QEIC_CIMR,
.pri_code = 2,
.pri_reg = QEIC_CIPZCC,
},
[12] = {
.mask = 0x00000010,
.mask_reg = QEIC_CIMR,
.pri_code = 3,
.pri_reg = QEIC_CIPZCC,
},
[13] = {
.mask = 0x00000008,
.mask_reg = QEIC_CIMR,
.pri_code = 4,
.pri_reg = QEIC_CIPZCC,
},
[14] = {
.mask = 0x00000004,
.mask_reg = QEIC_CIMR,
.pri_code = 5,
.pri_reg = QEIC_CIPZCC,
},
[15] = {
.mask = 0x00000002,
.mask_reg = QEIC_CIMR,
.pri_code = 6,
.pri_reg = QEIC_CIPZCC,
},
[20] = {
.mask = 0x10000000,
.mask_reg = QEIC_CRIMR,
.pri_code = 3,
.pri_reg = QEIC_CIPRTA,
},
[25] = {
.mask = 0x00800000,
.mask_reg = QEIC_CRIMR,
.pri_code = 0,
.pri_reg = QEIC_CIPRTB,
},
[26] = {
.mask = 0x00400000,
.mask_reg = QEIC_CRIMR,
.pri_code = 1,
.pri_reg = QEIC_CIPRTB,
},
[27] = {
.mask = 0x00200000,
.mask_reg = QEIC_CRIMR,
.pri_code = 2,
.pri_reg = QEIC_CIPRTB,
},
[28] = {
.mask = 0x00100000,
.mask_reg = QEIC_CRIMR,
.pri_code = 3,
.pri_reg = QEIC_CIPRTB,
},
[32] = {
.mask = 0x80000000,
.mask_reg = QEIC_CIMR,
.pri_code = 0,
.pri_reg = QEIC_CIPXCC,
},
[33] = {
.mask = 0x40000000,
.mask_reg = QEIC_CIMR,
.pri_code = 1,
.pri_reg = QEIC_CIPXCC,
},
[34] = {
.mask = 0x20000000,
.mask_reg = QEIC_CIMR,
.pri_code = 2,
.pri_reg = QEIC_CIPXCC,
},
[35] = {
.mask = 0x10000000,
.mask_reg = QEIC_CIMR,
.pri_code = 3,
.pri_reg = QEIC_CIPXCC,
},
[36] = {
.mask = 0x08000000,
.mask_reg = QEIC_CIMR,
.pri_code = 4,
.pri_reg = QEIC_CIPXCC,
},
[40] = {
.mask = 0x00800000,
.mask_reg = QEIC_CIMR,
.pri_code = 0,
.pri_reg = QEIC_CIPYCC,
},
[41] = {
.mask = 0x00400000,
.mask_reg = QEIC_CIMR,
.pri_code = 1,
.pri_reg = QEIC_CIPYCC,
},
[42] = {
.mask = 0x00200000,
.mask_reg = QEIC_CIMR,
.pri_code = 2,
.pri_reg = QEIC_CIPYCC,
},
[43] = {
.mask = 0x00100000,
.mask_reg = QEIC_CIMR,
.pri_code = 3,
.pri_reg = QEIC_CIPYCC,
},
};
static inline u32 qe_ic_read(volatile __be32 __iomem * base, unsigned int reg)
{
return in_be32(base + (reg >> 2));
}
static inline void qe_ic_write(volatile __be32 __iomem * base, unsigned int reg,
u32 value)
{
out_be32(base + (reg >> 2), value);
}
static inline struct qe_ic *qe_ic_from_irq(unsigned int virq)
{
return irq_desc[virq].chip_data;
}
#define virq_to_hw(virq) ((unsigned int)irq_map[virq].hwirq)
static void qe_ic_unmask_irq(unsigned int virq)
{
struct qe_ic *qe_ic = qe_ic_from_irq(virq);
unsigned int src = virq_to_hw(virq);
unsigned long flags;
u32 temp;
spin_lock_irqsave(&qe_ic_lock, flags);
temp = qe_ic_read(qe_ic->regs, qe_ic_info[src].mask_reg);
qe_ic_write(qe_ic->regs, qe_ic_info[src].mask_reg,
temp | qe_ic_info[src].mask);
spin_unlock_irqrestore(&qe_ic_lock, flags);
}
static void qe_ic_mask_irq(unsigned int virq)
{
struct qe_ic *qe_ic = qe_ic_from_irq(virq);
unsigned int src = virq_to_hw(virq);
unsigned long flags;
u32 temp;
spin_lock_irqsave(&qe_ic_lock, flags);
temp = qe_ic_read(qe_ic->regs, qe_ic_info[src].mask_reg);
qe_ic_write(qe_ic->regs, qe_ic_info[src].mask_reg,
temp & ~qe_ic_info[src].mask);
/* Flush the above write before enabling interrupts; otherwise,
* spurious interrupts will sometimes happen. To be 100% sure
* that the write has reached the device before interrupts are
* enabled, the mask register would have to be read back; however,
* this is not required for correctness, only to avoid wasting
* time on a large number of spurious interrupts. In testing,
* a sync reduced the observed spurious interrupts to zero.
*/
mb();
spin_unlock_irqrestore(&qe_ic_lock, flags);
}
static struct irq_chip qe_ic_irq_chip = {
.typename = " QEIC ",
.unmask = qe_ic_unmask_irq,
.mask = qe_ic_mask_irq,
.mask_ack = qe_ic_mask_irq,
};
static int qe_ic_host_match(struct irq_host *h, struct device_node *node)
{
/* Exact match, unless qe_ic node is NULL */
return h->of_node == NULL || h->of_node == node;
}
static int qe_ic_host_map(struct irq_host *h, unsigned int virq,
irq_hw_number_t hw)
{
struct qe_ic *qe_ic = h->host_data;
struct irq_chip *chip;
if (qe_ic_info[hw].mask == 0) {
printk(KERN_ERR "Can't map reserved IRQ \n");
return -EINVAL;
}
/* Default chip */
chip = &qe_ic->hc_irq;
set_irq_chip_data(virq, qe_ic);
get_irq_desc(virq)->status |= IRQ_LEVEL;
set_irq_chip_and_handler(virq, chip, handle_level_irq);
return 0;
}
static int qe_ic_host_xlate(struct irq_host *h, struct device_node *ct,
u32 * intspec, unsigned int intsize,
irq_hw_number_t * out_hwirq,
unsigned int *out_flags)
{
*out_hwirq = intspec[0];
if (intsize > 1)
*out_flags = intspec[1];
else
*out_flags = IRQ_TYPE_NONE;
return 0;
}
static struct irq_host_ops qe_ic_host_ops = {
.match = qe_ic_host_match,
.map = qe_ic_host_map,
.xlate = qe_ic_host_xlate,
};
/* Return an interrupt vector or NO_IRQ if no interrupt is pending. */
unsigned int qe_ic_get_low_irq(struct qe_ic *qe_ic)
{
int irq;
BUG_ON(qe_ic == NULL);
/* get the interrupt source vector. */
irq = qe_ic_read(qe_ic->regs, QEIC_CIVEC) >> 26;
if (irq == 0)
return NO_IRQ;
return irq_linear_revmap(qe_ic->irqhost, irq);
}
/* Return an interrupt vector or NO_IRQ if no interrupt is pending. */
unsigned int qe_ic_get_high_irq(struct qe_ic *qe_ic)
{
int irq;
BUG_ON(qe_ic == NULL);
/* get the interrupt source vector. */
irq = qe_ic_read(qe_ic->regs, QEIC_CHIVEC) >> 26;
if (irq == 0)
return NO_IRQ;
return irq_linear_revmap(qe_ic->irqhost, irq);
}
void __init qe_ic_init(struct device_node *node, unsigned int flags,
void (*low_handler)(unsigned int irq, struct irq_desc *desc),
void (*high_handler)(unsigned int irq, struct irq_desc *desc))
{
struct qe_ic *qe_ic;
struct resource res;
u32 temp = 0, ret, high_active = 0;
qe_ic = alloc_bootmem(sizeof(struct qe_ic));
if (qe_ic == NULL)
return;
memset(qe_ic, 0, sizeof(struct qe_ic));
qe_ic->irqhost = irq_alloc_host(of_node_get(node), IRQ_HOST_MAP_LINEAR,
NR_QE_IC_INTS, &qe_ic_host_ops, 0);
if (qe_ic->irqhost == NULL) {
of_node_put(node);
return;
}
ret = of_address_to_resource(node, 0, &res);
if (ret)
return;
qe_ic->regs = ioremap(res.start, res.end - res.start + 1);
qe_ic->irqhost->host_data = qe_ic;
qe_ic->hc_irq = qe_ic_irq_chip;
qe_ic->virq_high = irq_of_parse_and_map(node, 0);
qe_ic->virq_low = irq_of_parse_and_map(node, 1);
if (qe_ic->virq_low == NO_IRQ) {
printk(KERN_ERR "Failed to map QE_IC low IRQ\n");
return;
}
/* default priority scheme is grouped. If spread mode is */
/* required, configure cicr accordingly. */
if (flags & QE_IC_SPREADMODE_GRP_W)
temp |= CICR_GWCC;
if (flags & QE_IC_SPREADMODE_GRP_X)
temp |= CICR_GXCC;
if (flags & QE_IC_SPREADMODE_GRP_Y)
temp |= CICR_GYCC;
if (flags & QE_IC_SPREADMODE_GRP_Z)
temp |= CICR_GZCC;
if (flags & QE_IC_SPREADMODE_GRP_RISCA)
temp |= CICR_GRTA;
if (flags & QE_IC_SPREADMODE_GRP_RISCB)
temp |= CICR_GRTB;
/* choose destination signal for highest priority interrupt */
if (flags & QE_IC_HIGH_SIGNAL) {
temp |= (SIGNAL_HIGH << CICR_HPIT_SHIFT);
high_active = 1;
}
qe_ic_write(qe_ic->regs, QEIC_CICR, temp);
set_irq_data(qe_ic->virq_low, qe_ic);
set_irq_chained_handler(qe_ic->virq_low, low_handler);
if (qe_ic->virq_high != NO_IRQ &&
qe_ic->virq_high != qe_ic->virq_low) {
set_irq_data(qe_ic->virq_high, qe_ic);
set_irq_chained_handler(qe_ic->virq_high, high_handler);
}
}
void qe_ic_set_highest_priority(unsigned int virq, int high)
{
struct qe_ic *qe_ic = qe_ic_from_irq(virq);
unsigned int src = virq_to_hw(virq);
u32 temp = 0;
temp = qe_ic_read(qe_ic->regs, QEIC_CICR);
temp &= ~CICR_HP_MASK;
temp |= src << CICR_HP_SHIFT;
temp &= ~CICR_HPIT_MASK;
temp |= (high ? SIGNAL_HIGH : SIGNAL_LOW) << CICR_HPIT_SHIFT;
qe_ic_write(qe_ic->regs, QEIC_CICR, temp);
}
/* Set Priority level within its group, from 1 to 8 */
int qe_ic_set_priority(unsigned int virq, unsigned int priority)
{
struct qe_ic *qe_ic = qe_ic_from_irq(virq);
unsigned int src = virq_to_hw(virq);
u32 temp;
if (priority > 8 || priority == 0)
return -EINVAL;
if (src > 127)
return -EINVAL;
if (qe_ic_info[src].pri_reg == 0)
return -EINVAL;
temp = qe_ic_read(qe_ic->regs, qe_ic_info[src].pri_reg);
if (priority < 4) {
temp &= ~(0x7 << (32 - priority * 3));
temp |= qe_ic_info[src].pri_code << (32 - priority * 3);
} else {
temp &= ~(0x7 << (24 - priority * 3));
temp |= qe_ic_info[src].pri_code << (24 - priority * 3);
}
qe_ic_write(qe_ic->regs, qe_ic_info[src].pri_reg, temp);
return 0;
}
/* Set a QE priority to use high irq, only priority 1~2 can use high irq */
int qe_ic_set_high_priority(unsigned int virq, unsigned int priority, int high)
{
struct qe_ic *qe_ic = qe_ic_from_irq(virq);
unsigned int src = virq_to_hw(virq);
u32 temp, control_reg = QEIC_CICNR, shift = 0;
if (priority > 2 || priority == 0)
return -EINVAL;
switch (qe_ic_info[src].pri_reg) {
case QEIC_CIPZCC:
shift = CICNR_ZCC1T_SHIFT;
break;
case QEIC_CIPWCC:
shift = CICNR_WCC1T_SHIFT;
break;
case QEIC_CIPYCC:
shift = CICNR_YCC1T_SHIFT;
break;
case QEIC_CIPXCC:
shift = CICNR_XCC1T_SHIFT;
break;
case QEIC_CIPRTA:
shift = CRICR_RTA1T_SHIFT;
control_reg = QEIC_CRICR;
break;
case QEIC_CIPRTB:
shift = CRICR_RTB1T_SHIFT;
control_reg = QEIC_CRICR;
break;
default:
return -EINVAL;
}
shift += (2 - priority) * 2;
temp = qe_ic_read(qe_ic->regs, control_reg);
temp &= ~(SIGNAL_MASK << shift);
temp |= (high ? SIGNAL_HIGH : SIGNAL_LOW) << shift;
qe_ic_write(qe_ic->regs, control_reg, temp);
return 0;
}
static struct sysdev_class qe_ic_sysclass = {
set_kset_name("qe_ic"),
};
static struct sys_device device_qe_ic = {
.id = 0,
.cls = &qe_ic_sysclass,
};
static int __init init_qe_ic_sysfs(void)
{
int rc;
printk(KERN_DEBUG "Registering qe_ic with sysfs...\n");
rc = sysdev_class_register(&qe_ic_sysclass);
if (rc) {
printk(KERN_ERR "Failed registering qe_ic sys class\n");
return -ENODEV;
}
rc = sysdev_register(&device_qe_ic);
if (rc) {
printk(KERN_ERR "Failed registering qe_ic sys device\n");
return -ENODEV;
}
return 0;
}
subsys_initcall(init_qe_ic_sysfs);