qe_lib: Add QE interrupt controller driver
PATCH 2/9 qe_lib: Add QE interrupt controller driver
The QEIC is updated to use genirq and Ben's interrupt mapping.
Signed-off-by Li Yang <[email protected]>
Signed-off-by Shlomi Gridish <[email protected]>
Signed-off-by Kim Phillips <[email protected]>
---
arch/powerpc/sysdev/qe_lib/qe_ic.c | 554 ++++++++++++++++++++++++++++++++++++ arch/powerpc/sysdev/qe_lib/qe_ic.h | 105 +++++++ include/asm-powerpc/qe_ic.h | 89 ++++++ 3 files changed, 748 insertions(+), 0 deletions(-)
arch/powerpc/sysdev/qe_lib/qe_ic.c new file +554 -0
@@ -0,0 +1,554 @@ +/* +* Copyright (C) 2006 Freescale Semicondutor, Inc. All rights reserved. +* +* Author: Li Yang <[email protected]> +* Based on code from Shlomi Gridish <[email protected]> +* +* Description: +* 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 u32 __iomem * base,unsigned int reg) +{ +return in_be32 (base + (reg >>2 )); +} + +static inline void qe_ic_write (volatile u32 __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); + +spin_unlock_irqrestore (&qe_ic_lock, flags); +} + +static void qe_ic_mask_irq_and_ack (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); + +/* There is nothing to do for ack here, ack is handled in ISR */ + +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_and_ack, +}; + +static int qe_ic_host_match (struct irq_host *h,struct device_node *node) +{ +struct qe_ic *qe_ic = h->host_data; + +/* Exact match, unless qe_ic node is NULL */ +return qe_ic->of_node ==NULL || qe_ic->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,struct pt_regs *regs) +{ +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,struct pt_regs *regs) +{ +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); +} + +/* FIXME: We mask all the QE Low interrupts while handling. We should +* let other interrupt come in, but BAD interrupts are generated */ +void fastcall qe_ic_cascade_low(unsigned int irq, struct irq_desc *desc, +struct pt_regs *regs) +{ +struct qe_ic *qe_ic = desc->handler_data; +struct irq_chip *chip = irq_desc[irq].chip; + +unsigned int cascade_irq =qe_ic_get_low_irq (qe_ic, regs); + + chip->mask_ack (irq); +if (cascade_irq !=NO_IRQ ) +generic_handle_irq (cascade_irq, regs); + chip->unmask (irq); +} + +/* FIXME: We mask all the QE High interrupts while handling. We should +* let other interrupt come in, but BAD interrupts are generated */ +void fastcall qe_ic_cascade_high(unsigned int irq, struct irq_desc *desc, +struct pt_regs *regs) +{ +struct qe_ic *qe_ic = desc->handler_data; +struct irq_chip *chip = irq_desc[irq].chip; + +unsigned int cascade_irq =qe_ic_get_high_irq (qe_ic, regs); + + chip->mask_ack (irq); +if (cascade_irq !=NO_IRQ ) +generic_handle_irq (cascade_irq, regs); + chip->unmask (irq); +} + +void __init qe_ic_init (struct device_node *node,unsigned int flags) +{ +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->of_node = node ?of_node_get (node) :NULL ; + + qe_ic->irqhost =irq_alloc_host (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, qe_ic_cascade_low); + +if (qe_ic->virq_high !=NO_IRQ ) { +set_irq_data (qe_ic->virq_high, qe_ic); +set_irq_chained_handler (qe_ic->virq_high, qe_ic_cascade_high); + } + +printk ("QEIC (%d IRQ sources) at %p\n" ,NR_QE_IC_INTS , qe_ic->regs); +} + +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);
arch/powerpc/sysdev/qe_lib/qe_ic.h new file +105 -0
105 lines not on this page · raw message
include/asm-powerpc/qe_ic.h new file +89 -0
89 lines not on this page · raw message