18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci *  linux/arch/m68k/amiga/cia.c - CIA support
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci *  Copyright (C) 1996 Roman Zippel
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci *  The concept of some functions bases on the original Amiga OS function
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
98c2ecf20Sopenharmony_ci * License.  See the file COPYING in the main directory of this archive
108c2ecf20Sopenharmony_ci * for more details.
118c2ecf20Sopenharmony_ci */
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include <linux/types.h>
148c2ecf20Sopenharmony_ci#include <linux/kernel.h>
158c2ecf20Sopenharmony_ci#include <linux/sched.h>
168c2ecf20Sopenharmony_ci#include <linux/errno.h>
178c2ecf20Sopenharmony_ci#include <linux/kernel_stat.h>
188c2ecf20Sopenharmony_ci#include <linux/init.h>
198c2ecf20Sopenharmony_ci#include <linux/seq_file.h>
208c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
218c2ecf20Sopenharmony_ci#include <linux/irq.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#include <asm/irq.h>
248c2ecf20Sopenharmony_ci#include <asm/amigahw.h>
258c2ecf20Sopenharmony_ci#include <asm/amigaints.h>
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_cistruct ciabase {
288c2ecf20Sopenharmony_ci	volatile struct CIA *cia;
298c2ecf20Sopenharmony_ci	unsigned char icr_mask, icr_data;
308c2ecf20Sopenharmony_ci	unsigned short int_mask;
318c2ecf20Sopenharmony_ci	int handler_irq, cia_irq, server_irq;
328c2ecf20Sopenharmony_ci	char *name;
338c2ecf20Sopenharmony_ci} ciaa_base = {
348c2ecf20Sopenharmony_ci	.cia		= &ciaa,
358c2ecf20Sopenharmony_ci	.int_mask	= IF_PORTS,
368c2ecf20Sopenharmony_ci	.handler_irq	= IRQ_AMIGA_PORTS,
378c2ecf20Sopenharmony_ci	.cia_irq	= IRQ_AMIGA_CIAA,
388c2ecf20Sopenharmony_ci	.name		= "CIAA"
398c2ecf20Sopenharmony_ci}, ciab_base = {
408c2ecf20Sopenharmony_ci	.cia		= &ciab,
418c2ecf20Sopenharmony_ci	.int_mask	= IF_EXTER,
428c2ecf20Sopenharmony_ci	.handler_irq	= IRQ_AMIGA_EXTER,
438c2ecf20Sopenharmony_ci	.cia_irq	= IRQ_AMIGA_CIAB,
448c2ecf20Sopenharmony_ci	.name		= "CIAB"
458c2ecf20Sopenharmony_ci};
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci/*
488c2ecf20Sopenharmony_ci *  Cause or clear CIA interrupts, return old interrupt status.
498c2ecf20Sopenharmony_ci */
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ciunsigned char cia_set_irq(struct ciabase *base, unsigned char mask)
528c2ecf20Sopenharmony_ci{
538c2ecf20Sopenharmony_ci	unsigned char old;
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	old = (base->icr_data |= base->cia->icr);
568c2ecf20Sopenharmony_ci	if (mask & CIA_ICR_SETCLR)
578c2ecf20Sopenharmony_ci		base->icr_data |= mask;
588c2ecf20Sopenharmony_ci	else
598c2ecf20Sopenharmony_ci		base->icr_data &= ~mask;
608c2ecf20Sopenharmony_ci	if (base->icr_data & base->icr_mask)
618c2ecf20Sopenharmony_ci		amiga_custom.intreq = IF_SETCLR | base->int_mask;
628c2ecf20Sopenharmony_ci	return old & base->icr_mask;
638c2ecf20Sopenharmony_ci}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci/*
668c2ecf20Sopenharmony_ci *  Enable or disable CIA interrupts, return old interrupt mask,
678c2ecf20Sopenharmony_ci */
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ciunsigned char cia_able_irq(struct ciabase *base, unsigned char mask)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	unsigned char old;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	old = base->icr_mask;
748c2ecf20Sopenharmony_ci	base->icr_data |= base->cia->icr;
758c2ecf20Sopenharmony_ci	base->cia->icr = mask;
768c2ecf20Sopenharmony_ci	if (mask & CIA_ICR_SETCLR)
778c2ecf20Sopenharmony_ci		base->icr_mask |= mask;
788c2ecf20Sopenharmony_ci	else
798c2ecf20Sopenharmony_ci		base->icr_mask &= ~mask;
808c2ecf20Sopenharmony_ci	base->icr_mask &= CIA_ICR_ALL;
818c2ecf20Sopenharmony_ci	if (base->icr_data & base->icr_mask)
828c2ecf20Sopenharmony_ci		amiga_custom.intreq = IF_SETCLR | base->int_mask;
838c2ecf20Sopenharmony_ci	return old;
848c2ecf20Sopenharmony_ci}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_cistatic irqreturn_t cia_handler(int irq, void *dev_id)
878c2ecf20Sopenharmony_ci{
888c2ecf20Sopenharmony_ci	struct ciabase *base = dev_id;
898c2ecf20Sopenharmony_ci	int mach_irq;
908c2ecf20Sopenharmony_ci	unsigned char ints;
918c2ecf20Sopenharmony_ci	unsigned long flags;
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	/* Interrupts get disabled while the timer irq flag is cleared and
948c2ecf20Sopenharmony_ci	 * the timer interrupt serviced.
958c2ecf20Sopenharmony_ci	 */
968c2ecf20Sopenharmony_ci	mach_irq = base->cia_irq;
978c2ecf20Sopenharmony_ci	local_irq_save(flags);
988c2ecf20Sopenharmony_ci	ints = cia_set_irq(base, CIA_ICR_ALL);
998c2ecf20Sopenharmony_ci	amiga_custom.intreq = base->int_mask;
1008c2ecf20Sopenharmony_ci	if (ints & 1)
1018c2ecf20Sopenharmony_ci		generic_handle_irq(mach_irq);
1028c2ecf20Sopenharmony_ci	local_irq_restore(flags);
1038c2ecf20Sopenharmony_ci	mach_irq++, ints >>= 1;
1048c2ecf20Sopenharmony_ci	for (; ints; mach_irq++, ints >>= 1) {
1058c2ecf20Sopenharmony_ci		if (ints & 1)
1068c2ecf20Sopenharmony_ci			generic_handle_irq(mach_irq);
1078c2ecf20Sopenharmony_ci	}
1088c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1098c2ecf20Sopenharmony_ci}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_cistatic void cia_irq_enable(struct irq_data *data)
1128c2ecf20Sopenharmony_ci{
1138c2ecf20Sopenharmony_ci	unsigned int irq = data->irq;
1148c2ecf20Sopenharmony_ci	unsigned char mask;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	if (irq >= IRQ_AMIGA_CIAB) {
1178c2ecf20Sopenharmony_ci		mask = 1 << (irq - IRQ_AMIGA_CIAB);
1188c2ecf20Sopenharmony_ci		cia_set_irq(&ciab_base, mask);
1198c2ecf20Sopenharmony_ci		cia_able_irq(&ciab_base, CIA_ICR_SETCLR | mask);
1208c2ecf20Sopenharmony_ci	} else {
1218c2ecf20Sopenharmony_ci		mask = 1 << (irq - IRQ_AMIGA_CIAA);
1228c2ecf20Sopenharmony_ci		cia_set_irq(&ciaa_base, mask);
1238c2ecf20Sopenharmony_ci		cia_able_irq(&ciaa_base, CIA_ICR_SETCLR | mask);
1248c2ecf20Sopenharmony_ci	}
1258c2ecf20Sopenharmony_ci}
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_cistatic void cia_irq_disable(struct irq_data *data)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	unsigned int irq = data->irq;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	if (irq >= IRQ_AMIGA_CIAB)
1328c2ecf20Sopenharmony_ci		cia_able_irq(&ciab_base, 1 << (irq - IRQ_AMIGA_CIAB));
1338c2ecf20Sopenharmony_ci	else
1348c2ecf20Sopenharmony_ci		cia_able_irq(&ciaa_base, 1 << (irq - IRQ_AMIGA_CIAA));
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistatic struct irq_chip cia_irq_chip = {
1388c2ecf20Sopenharmony_ci	.name		= "cia",
1398c2ecf20Sopenharmony_ci	.irq_enable	= cia_irq_enable,
1408c2ecf20Sopenharmony_ci	.irq_disable	= cia_irq_disable,
1418c2ecf20Sopenharmony_ci};
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci/*
1448c2ecf20Sopenharmony_ci * Override auto irq 2 & 6 and use them as general chain
1458c2ecf20Sopenharmony_ci * for external interrupts, we link the CIA interrupt sources
1468c2ecf20Sopenharmony_ci * into this chain.
1478c2ecf20Sopenharmony_ci */
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_cistatic void auto_irq_enable(struct irq_data *data)
1508c2ecf20Sopenharmony_ci{
1518c2ecf20Sopenharmony_ci	switch (data->irq) {
1528c2ecf20Sopenharmony_ci	case IRQ_AUTO_2:
1538c2ecf20Sopenharmony_ci		amiga_custom.intena = IF_SETCLR | IF_PORTS;
1548c2ecf20Sopenharmony_ci		break;
1558c2ecf20Sopenharmony_ci	case IRQ_AUTO_6:
1568c2ecf20Sopenharmony_ci		amiga_custom.intena = IF_SETCLR | IF_EXTER;
1578c2ecf20Sopenharmony_ci		break;
1588c2ecf20Sopenharmony_ci	}
1598c2ecf20Sopenharmony_ci}
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_cistatic void auto_irq_disable(struct irq_data *data)
1628c2ecf20Sopenharmony_ci{
1638c2ecf20Sopenharmony_ci	switch (data->irq) {
1648c2ecf20Sopenharmony_ci	case IRQ_AUTO_2:
1658c2ecf20Sopenharmony_ci		amiga_custom.intena = IF_PORTS;
1668c2ecf20Sopenharmony_ci		break;
1678c2ecf20Sopenharmony_ci	case IRQ_AUTO_6:
1688c2ecf20Sopenharmony_ci		amiga_custom.intena = IF_EXTER;
1698c2ecf20Sopenharmony_ci		break;
1708c2ecf20Sopenharmony_ci	}
1718c2ecf20Sopenharmony_ci}
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_cistatic struct irq_chip auto_irq_chip = {
1748c2ecf20Sopenharmony_ci	.name		= "auto",
1758c2ecf20Sopenharmony_ci	.irq_enable	= auto_irq_enable,
1768c2ecf20Sopenharmony_ci	.irq_disable	= auto_irq_disable,
1778c2ecf20Sopenharmony_ci};
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_civoid __init cia_init_IRQ(struct ciabase *base)
1808c2ecf20Sopenharmony_ci{
1818c2ecf20Sopenharmony_ci	m68k_setup_irq_controller(&cia_irq_chip, handle_simple_irq,
1828c2ecf20Sopenharmony_ci				  base->cia_irq, CIA_IRQS);
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	/* clear any pending interrupt and turn off all interrupts */
1858c2ecf20Sopenharmony_ci	cia_set_irq(base, CIA_ICR_ALL);
1868c2ecf20Sopenharmony_ci	cia_able_irq(base, CIA_ICR_ALL);
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	/* override auto int and install CIA handler */
1898c2ecf20Sopenharmony_ci	m68k_setup_irq_controller(&auto_irq_chip, handle_simple_irq,
1908c2ecf20Sopenharmony_ci				  base->handler_irq, 1);
1918c2ecf20Sopenharmony_ci	m68k_irq_startup_irq(base->handler_irq);
1928c2ecf20Sopenharmony_ci	if (request_irq(base->handler_irq, cia_handler, IRQF_SHARED,
1938c2ecf20Sopenharmony_ci			base->name, base))
1948c2ecf20Sopenharmony_ci		pr_err("Couldn't register %s interrupt\n", base->name);
1958c2ecf20Sopenharmony_ci}
196