blob: ad961465b6cb62fa04afa78910a65c59bb5c5a04 [file] [log] [blame]
/*
* arch/ppc/amiga/cia.c - CIA support
*
* Copyright (C) 1996 Roman Zippel
*
* The concept of some functions bases on the original Amiga OS function
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/kernel_stat.h>
#include <linux/init.h>
#include <asm/irq.h>
#include <asm/amigahw.h>
#include <asm/amigaints.h>
struct ciabase {
volatile struct CIA *cia;
u_char icr_mask, icr_data;
u_short int_mask;
int handler_irq, cia_irq, server_irq;
char *name;
} ciaa_base = {
&ciaa, 0, 0, IF_PORTS,
IRQ_AMIGA_AUTO_2, IRQ_AMIGA_CIAA,
IRQ_AMIGA_PORTS,
"CIAA handler"
}, ciab_base = {
&ciab, 0, 0, IF_EXTER,
IRQ_AMIGA_AUTO_6, IRQ_AMIGA_CIAB,
IRQ_AMIGA_EXTER,
"CIAB handler"
};
#define CIA_SET_BASE_ADJUST_IRQ(base, irq) \
do { \
if (irq >= IRQ_AMIGA_CIAB) { \
base = &ciab_base; \
irq -= IRQ_AMIGA_CIAB; \
} else { \
base = &ciaa_base; \
irq -= IRQ_AMIGA_CIAA; \
} \
} while (0)
/*
* Cause or clear CIA interrupts, return old interrupt status.
*/
static unsigned char cia_set_irq_private(struct ciabase *base,
unsigned char mask)
{
u_char old;
old = (base->icr_data |= base->cia->icr);
if (mask & CIA_ICR_SETCLR)
base->icr_data |= mask;
else
base->icr_data &= ~mask;
if (base->icr_data & base->icr_mask)
custom.intreq = IF_SETCLR | base->int_mask;
return old & base->icr_mask;
}
unsigned char cia_set_irq(unsigned int irq, int set)
{
struct ciabase *base;
unsigned char mask;
if (irq >= IRQ_AMIGA_CIAB)
mask = (1 << (irq - IRQ_AMIGA_CIAB));
else
mask = (1 << (irq - IRQ_AMIGA_CIAA));
mask |= (set) ? CIA_ICR_SETCLR : 0;
CIA_SET_BASE_ADJUST_IRQ(base, irq);
return cia_set_irq_private(base, mask);
}
unsigned char cia_get_irq_mask(unsigned int irq)
{
struct ciabase *base;
CIA_SET_BASE_ADJUST_IRQ(base, irq);
return base->cia->icr;
}
/*
* Enable or disable CIA interrupts, return old interrupt mask.
*/
static unsigned char cia_able_irq_private(struct ciabase *base,
unsigned char mask)
{
u_char old;
old = base->icr_mask;
base->icr_data |= base->cia->icr;
base->cia->icr = mask;
if (mask & CIA_ICR_SETCLR)
base->icr_mask |= mask;
else
base->icr_mask &= ~mask;
base->icr_mask &= CIA_ICR_ALL;
if (base->icr_data & base->icr_mask)
custom.intreq = IF_SETCLR | base->int_mask;
return old;
}
unsigned char cia_able_irq(unsigned int irq, int enable)
{
struct ciabase *base;
unsigned char mask;
if (irq >= IRQ_AMIGA_CIAB)
mask = (1 << (irq - IRQ_AMIGA_CIAB));
else
mask = (1 << (irq - IRQ_AMIGA_CIAA));
mask |= (enable) ? CIA_ICR_SETCLR : 0;
CIA_SET_BASE_ADJUST_IRQ(base, irq);
return cia_able_irq_private(base, mask);
}
static void cia_handler(int irq, void *dev_id, struct pt_regs *fp)
{
struct ciabase *base = (struct ciabase *)dev_id;
irq_desc_t *desc;
struct irqaction *action;
int i;
unsigned char ints;
irq = base->cia_irq;
desc = irq_desc + irq;
ints = cia_set_irq_private(base, CIA_ICR_ALL);
custom.intreq = base->int_mask;
for (i = 0; i < CIA_IRQS; i++, irq++) {
if (ints & 1) {
kstat_cpu(0).irqs[irq]++;
action = desc->action;
action->handler(irq, action->dev_id, fp);
}
ints >>= 1;
desc++;
}
amiga_do_irq_list(base->server_irq, fp);
}
void __init cia_init_IRQ(struct ciabase *base)
{
extern struct irqaction amiga_sys_irqaction[AUTO_IRQS];
struct irqaction *action;
/* clear any pending interrupt and turn off all interrupts */
cia_set_irq_private(base, CIA_ICR_ALL);
cia_able_irq_private(base, CIA_ICR_ALL);
/* install CIA handler */
action = &amiga_sys_irqaction[base->handler_irq-IRQ_AMIGA_AUTO];
action->handler = cia_handler;
action->dev_id = base;
action->name = base->name;
setup_irq(base->handler_irq, &amiga_sys_irqaction[base->handler_irq-IRQ_AMIGA_AUTO]);
custom.intena = IF_SETCLR | base->int_mask;
}