blob: 935afd030c11ea095af2cf16d7da9b914c3039e6 [file] [log] [blame]
/*
* linux/arch/arm/mach-zx297510/irq.c
*
* Copyright (C) 2013 ZTE-TSP
*
* 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.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/types.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <mach/iomap.h>
#include <mach/irqs.h>
#include <mach/debug.h>
#include <mach/pcu.h>
#include <asm/irq.h>
#include <asm/setup.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/cp15.h>
#include <asm/mach/arch.h>
#include <asm/mach/irq.h>
#include <asm/mach/map.h>
#include <mach/board.h>
#if NEW_LINUX_FRAME
#include <linux/irqchip/arm-gic.h>
#else
#include <asm/hardware/gic.h>
#endif
#include <linux/irqdomain.h>
#define A9_INT_MODE_BASE (A9_CRM_BASE + 0x50)
#define INT_HIGHLEVEL (0x0) /* 00: high level */
#define INT_LOWLEVEL (0x1) /* 01: low level */
#define INT_POSEDGE (0x2) /* 10: raise edge */
#define INT_NEGEDGE (0x3) /* 11: fall edge */
extern int gic_set_type(struct irq_data *d, unsigned int type);
extern unsigned int gic_get_irq_nr(irq_hw_number_t hwirq);
static int init_irq_flag = 0;
/*
* GIC only support IRQ_TYPE_LEVEL_HIGH and IRQ_TYPE_EDGE_RISING. In order to support
* full interrupt trigger mode(high,low,falling,rising), zx29 treats all level interrupt
* as high level interrupt and Edge interrupt is treated as rising interrupt . It is only
* for SPIs. External int need additional configuration in PCU
*/
static int zx29_int_set_type(struct irq_data *d, unsigned int type)
{
unsigned int data_tmp=0;
unsigned int srctype=0;
unsigned int reg_index=0,pos_index=0;
unsigned int hwirq = d->hwirq;
switch (type) {
case IRQ_TYPE_LEVEL_HIGH:
srctype = INT_HIGHLEVEL;
break;
case IRQ_TYPE_EDGE_RISING:
srctype = INT_POSEDGE;
break;
case IRQ_TYPE_LEVEL_LOW:
srctype = INT_LOWLEVEL;
break;
case IRQ_TYPE_EDGE_FALLING:
srctype = INT_NEGEDGE;
break;
default:
return -EINVAL;
}
reg_index=(hwirq-32)/16;
pos_index=((hwirq-32)%16)*2;
data_tmp=ioread32(A9_INT_MODE_BASE+reg_index*4);
data_tmp &= ~(3<<pos_index);
data_tmp |= srctype<<pos_index;
iowrite32(data_tmp,A9_INT_MODE_BASE+reg_index*4);
return 0;
}
/*
* Enable/disable power management wakeup mode, which is
* disabled by default. Enables and disables must match,
* just as they match for non-wakeup mode support.
*
* Wakeup mode lets this IRQ wake the system from sleep
* states like "suspend to RAM".
*/
static int zx29_int_set_wake(struct irq_data *d, unsigned int on)
{
return pcu_set_irq_wake(d->hwirq, on);
}
bool irq_type_valid(unsigned int int_type)
{
return ((int_type==IRQ_TYPE_LEVEL_HIGH) || (int_type==IRQ_TYPE_LEVEL_LOW) || \
(int_type==IRQ_TYPE_EDGE_RISING) || (int_type==IRQ_TYPE_EDGE_FALLING));
}
/*
* set default interrupt trigger type
*/
static void int_set_type_default(unsigned int line)
{
unsigned int int_type=0;
unsigned int irq=0;
struct irq_data *d;
if(line<GIC_PPI_START)
return;
irq = gic_get_irq_nr((unsigned long)line);
d = irq_get_irq_data(irq);
/*set the interrupt mode --raise edge,fall edge, high level, low level*/
switch ( line )
{
case WDT_INT:
case AP_TIMER0_INT:
case GSM_SYS0_INT:
case GSM_SYS1_INT:
case AP_TIMER4_INT:
{
int_type = IRQ_TYPE_EDGE_RISING;
break;
}
default:
{
int_type = IRQ_TYPE_LEVEL_HIGH;
break;
}
}
gic_set_type(d,int_type);
}
/*
* Initialize the interrupt controller unit.
*/
void __init zx29_init_irq(void)
{
unsigned int i;
gic_arch_extn.irq_set_type = zx29_int_set_type;
gic_arch_extn.irq_set_wake = zx29_int_set_wake;
gic_init(0, GIC_PPI_START, GIC_DIST_BASE, GIC_CPU_BASE);
for (i = GIC_PPI_START; i < NR_IRQS; i++) {
int_set_type_default(i);
}
init_irq_flag = 1;
pr_info("[GIC]ZTE-TSP zx297520v2 GIC initialized.\n");
}
int get_init_irq_flag(void)
{
return init_irq_flag;
}
/* --------------------------------------------------------------------
* extint_8in1
* -------------------------------------------------------------------- */
static void ext8in1_irq_lock(struct irq_data *data)
{
}
static void ext8in1_irq_sync_unlock(struct irq_data *data)
{
}
static void ext8in1_irq_mask(struct irq_data *data)
{
}
static void ext8in1_irq_unmask(struct irq_data *data)
{
}
static struct irq_chip ext8in1_irq_chip =
{
.name = "ext8in1",
.irq_bus_lock = ext8in1_irq_lock,
.irq_bus_sync_unlock = ext8in1_irq_sync_unlock,
.irq_mask = ext8in1_irq_mask,
.irq_unmask = ext8in1_irq_unmask,
.irq_set_wake = zx29_int_set_wake,
};
static irqreturn_t ext8in1_irq_thread(int irq, void *irq_data)
{
u32 line;
u32 nextLine;
line = pcu_get_8in1_int_source();
while (1)
{
pcu_clr_irq_pending(line + EX8IN1_INT_BASE);
handle_nested_irq(line + EX8IN1_INT_BASE);
nextLine = pcu_get_8in1_int_source();
/*
* no new interrupt
* here is not absolutely right. the same interrupt maybe come again
* during ex8in1Isr() execution, or there is really no interrupt comes.
* in both situation, we can get same line number. we do nothing to the
* case. if there is really a new interrupt, it will trigger a new
* arm interrupt.
*/
if (line == nextLine)
break;
else
line = nextLine;
}
// enable_irq(irq);
return IRQ_HANDLED;
}
static irqreturn_t ext8in1_irq(int irq, void *dev_id)
{
// disable_irq_nosync(irq);
return IRQ_WAKE_THREAD;
}
static int __init zx29_ext_int_init(void)
{
int ret;
int cur_irq;
for (cur_irq = EX8IN1_INT_BASE; cur_irq <= EX8IN1_INT_END; cur_irq++)
{
irq_set_chip_and_handler(cur_irq, &ext8in1_irq_chip, handle_simple_irq);
irq_set_nested_thread(cur_irq, 1);
set_irq_flags(cur_irq, IRQF_VALID);
}
ret = request_threaded_irq(EX8IN1_INT, ext8in1_irq, ext8in1_irq_thread, IRQF_ONESHOT,
"ext8in1", NULL);
if(ret<0)
{
pr_info("[GIC]ZTE-TSP zx297520v2 EXTINT_8IN1 initialized failed.\n");
}
return ret;
}
subsys_initcall(zx29_ext_int_init);