aboutsummaryrefslogtreecommitdiff
path: root/arch/powerpc/sysdev
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@g5.osdl.org>2006-10-04 08:16:37 -0700
committerLinus Torvalds <torvalds@g5.osdl.org>2006-10-04 08:16:37 -0700
commit13bbd8d90647132fc295d73b122567eb8987d298 (patch)
tree466ae1f00a5965308ce2e7695d4bfe88d87b9610 /arch/powerpc/sysdev
parent18e6756a6b463e09fd3873592ec6b0579c78103d (diff)
parent9020fc960b8f5fbca0de6e4d11881ddc827aa61d (diff)
Merge git://git.kernel.org/pub/scm/linux/kernel/git/paulus/powerpc
* git://git.kernel.org/pub/scm/linux/kernel/git/paulus/powerpc: (25 commits) [POWERPC] Add support for the mpc832x mds board [POWERPC] Add initial support for the e300c2 core [POWERPC] Add MPC8360EMDS default dts file [POWERPC] Add MPC8360EMDS board support [POWERPC] Add QUICC Engine (QE) infrastructure [POWERPC] Add QE device tree node definition [POWERPC] Don't try to just continue if xmon has no input device [POWERPC] Fix a printk in pseries_mpic_init_IRQ [POWERPC] Get default baud rate in udbg_scc [POWERPC] Fix zImage.coff on oldworld PowerMac [POWERPC] Fix xmon=off and cleanup xmon initialisation [POWERPC] Cleanup include/asm-powerpc/xmon.h [POWERPC] Update swim3 printk after blkdev.h change [POWERPC] Cell interrupt rework POWERPC: mpc82xx merge: board-specific/platform stuff(resend) POWERPC: 8272ads merge to powerpc: common stuff POWERPC: Added devicetree for mpc8272ads board [POWERPC] iSeries has no legacy I/O [POWERPC] implement BEGIN/END_FW_FTR_SECTION [POWERPC] iSeries does not need pcibios_fixup_resources ...
Diffstat (limited to 'arch/powerpc/sysdev')
-rw-r--r--arch/powerpc/sysdev/Makefile1
-rw-r--r--arch/powerpc/sysdev/cpm2_pic.c2
-rw-r--r--arch/powerpc/sysdev/cpm2_pic.h2
-rw-r--r--arch/powerpc/sysdev/fsl_soc.c62
-rw-r--r--arch/powerpc/sysdev/qe_lib/Kconfig30
-rw-r--r--arch/powerpc/sysdev/qe_lib/Makefile8
-rw-r--r--arch/powerpc/sysdev/qe_lib/qe.c353
-rw-r--r--arch/powerpc/sysdev/qe_lib/qe_ic.c555
-rw-r--r--arch/powerpc/sysdev/qe_lib/qe_ic.h106
-rw-r--r--arch/powerpc/sysdev/qe_lib/qe_io.c226
-rw-r--r--arch/powerpc/sysdev/qe_lib/ucc.c251
-rw-r--r--arch/powerpc/sysdev/qe_lib/ucc_fast.c396
-rw-r--r--arch/powerpc/sysdev/qe_lib/ucc_slow.c404
13 files changed, 2387 insertions, 9 deletions
diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile
index f15f4d78aee..91f052d8cce 100644
--- a/arch/powerpc/sysdev/Makefile
+++ b/arch/powerpc/sysdev/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_MMIO_NVRAM) += mmio_nvram.o
obj-$(CONFIG_FSL_SOC) += fsl_soc.o
obj-$(CONFIG_PPC_TODC) += todc.o
obj-$(CONFIG_TSI108_BRIDGE) += tsi108_pci.o tsi108_dev.o
+obj-$(CONFIG_QUICC_ENGINE) += qe_lib/
ifeq ($(CONFIG_PPC_MERGE),y)
obj-$(CONFIG_PPC_I8259) += i8259.o
diff --git a/arch/powerpc/sysdev/cpm2_pic.c b/arch/powerpc/sysdev/cpm2_pic.c
index 51752990f7b..28b01899474 100644
--- a/arch/powerpc/sysdev/cpm2_pic.c
+++ b/arch/powerpc/sysdev/cpm2_pic.c
@@ -147,7 +147,7 @@ static struct irq_chip cpm2_pic = {
.end = cpm2_end_irq,
};
-int cpm2_get_irq(struct pt_regs *regs)
+unsigned int cpm2_get_irq(struct pt_regs *regs)
{
int irq;
unsigned long bits;
diff --git a/arch/powerpc/sysdev/cpm2_pic.h b/arch/powerpc/sysdev/cpm2_pic.h
index d63e45d4df5..3c513e5a688 100644
--- a/arch/powerpc/sysdev/cpm2_pic.h
+++ b/arch/powerpc/sysdev/cpm2_pic.h
@@ -3,7 +3,7 @@
extern intctl_cpm2_t *cpm2_intctl;
-extern int cpm2_get_irq(struct pt_regs *regs);
+extern unsigned int cpm2_get_irq(struct pt_regs *regs);
extern void cpm2_pic_init(struct device_node*);
diff --git a/arch/powerpc/sysdev/fsl_soc.c b/arch/powerpc/sysdev/fsl_soc.c
index 022ed275ea6..7d759f1c26b 100644
--- a/arch/powerpc/sysdev/fsl_soc.c
+++ b/arch/powerpc/sysdev/fsl_soc.c
@@ -37,6 +37,7 @@
#include <asm/cpm2.h>
extern void init_fcc_ioports(struct fs_platform_info*);
+extern void init_scc_ioports(struct fs_uart_platform_info*);
static phys_addr_t immrbase = -1;
phys_addr_t get_immrbase(void)
@@ -566,7 +567,7 @@ static int __init fs_enet_of_init(void)
struct resource r[4];
struct device_node *phy, *mdio;
struct fs_platform_info fs_enet_data;
- const unsigned int *id, *phy_addr;
+ const unsigned int *id, *phy_addr, phy_irq;
const void *mac_addr;
const phandle *ph;
const char *model;
@@ -588,6 +589,7 @@ static int __init fs_enet_of_init(void)
if (ret)
goto err;
r[2].name = fcc_regs_c;
+ fs_enet_data.fcc_regs_c = r[2].start;
r[3].start = r[3].end = irq_of_parse_and_map(np, 0);
r[3].flags = IORESOURCE_IRQ;
@@ -620,6 +622,8 @@ static int __init fs_enet_of_init(void)
phy_addr = get_property(phy, "reg", NULL);
fs_enet_data.phy_addr = *phy_addr;
+ phy_irq = get_property(phy, "interrupts", NULL);
+
id = get_property(np, "device-id", NULL);
fs_enet_data.fs_no = *id;
strcpy(fs_enet_data.fs_type, model);
@@ -637,6 +641,7 @@ static int __init fs_enet_of_init(void)
if (strstr(model, "FCC")) {
int fcc_index = *id - 1;
+ unsigned char* mdio_bb_prop;
fs_enet_data.dpram_offset = (u32)cpm_dpram_addr(0);
fs_enet_data.rx_ring = 32;
@@ -652,14 +657,57 @@ static int __init fs_enet_of_init(void)
(u32)res.start, fs_enet_data.phy_addr);
fs_enet_data.bus_id = (char*)&bus_id[(*id)];
fs_enet_data.init_ioports = init_fcc_ioports;
- }
- of_node_put(phy);
- of_node_put(mdio);
+ mdio_bb_prop = get_property(phy, "bitbang", NULL);
+ if (mdio_bb_prop) {
+ struct platform_device *fs_enet_mdio_bb_dev;
+ struct fs_mii_bb_platform_info fs_enet_mdio_bb_data;
+
+ fs_enet_mdio_bb_dev =
+ platform_device_register_simple("fsl-bb-mdio",
+ i, NULL, 0);
+ memset(&fs_enet_mdio_bb_data, 0,
+ sizeof(struct fs_mii_bb_platform_info));
+ fs_enet_mdio_bb_data.mdio_dat.bit =
+ mdio_bb_prop[0];
+ fs_enet_mdio_bb_data.mdio_dir.bit =
+ mdio_bb_prop[1];
+ fs_enet_mdio_bb_data.mdc_dat.bit =
+ mdio_bb_prop[2];
+ fs_enet_mdio_bb_data.mdio_port =
+ mdio_bb_prop[3];
+ fs_enet_mdio_bb_data.mdc_port =
+ mdio_bb_prop[4];
+ fs_enet_mdio_bb_data.delay =
+ mdio_bb_prop[5];
+
+ fs_enet_mdio_bb_data.irq[0] = phy_irq[0];
+ fs_enet_mdio_bb_data.irq[1] = -1;
+ fs_enet_mdio_bb_data.irq[2] = -1;
+ fs_enet_mdio_bb_data.irq[3] = phy_irq[0];
+ fs_enet_mdio_bb_data.irq[31] = -1;
+
+ fs_enet_mdio_bb_data.mdio_dat.offset =
+ (u32)&cpm2_immr->im_ioport.iop_pdatc;
+ fs_enet_mdio_bb_data.mdio_dir.offset =
+ (u32)&cpm2_immr->im_ioport.iop_pdirc;
+ fs_enet_mdio_bb_data.mdc_dat.offset =
+ (u32)&cpm2_immr->im_ioport.iop_pdatc;
+
+ ret = platform_device_add_data(
+ fs_enet_mdio_bb_dev,
+ &fs_enet_mdio_bb_data,
+ sizeof(struct fs_mii_bb_platform_info));
+ if (ret)
+ goto unreg;
+ }
+
+ of_node_put(phy);
+ of_node_put(mdio);
- ret = platform_device_add_data(fs_enet_dev, &fs_enet_data,
- sizeof(struct
- fs_platform_info));
+ ret = platform_device_add_data(fs_enet_dev, &fs_enet_data,
+ sizeof(struct
+ fs_platform_info));
if (ret)
goto unreg;
}
diff --git a/arch/powerpc/sysdev/qe_lib/Kconfig b/arch/powerpc/sysdev/qe_lib/Kconfig
new file mode 100644
index 00000000000..a725e80befa
--- /dev/null
+++ b/arch/powerpc/sysdev/qe_lib/Kconfig
@@ -0,0 +1,30 @@
+#
+# QE Communication options
+#
+
+menu "QE Options"
+ depends on QUICC_ENGINE
+
+config UCC_SLOW
+ bool "UCC Slow Protocols Support"
+ default n
+ select UCC
+ help
+ This option provides qe_lib support to UCC slow
+ protocols: UART, BISYNC, QMC
+
+config UCC_FAST
+ bool "UCC Fast Protocols Support"
+ default n
+ select UCC
+ select UCC_SLOW
+ help
+ This option provides qe_lib support to UCC fast
+ protocols: HDLC, Ethernet, ATM, transparent
+
+config UCC
+ bool
+ default y if UCC_FAST || UCC_SLOW
+
+endmenu
+
diff --git a/arch/powerpc/sysdev/qe_lib/Makefile b/arch/powerpc/sysdev/qe_lib/Makefile
new file mode 100644
index 00000000000..874fe1a5b1c
--- /dev/null
+++ b/arch/powerpc/sysdev/qe_lib/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for the linux ppc-specific parts of QE
+#
+obj-$(CONFIG_QUICC_ENGINE)+= qe.o qe_ic.o qe_io.o
+
+obj-$(CONFIG_UCC) += ucc.o
+obj-$(CONFIG_UCC_SLOW) += ucc_slow.o
+obj-$(CONFIG_UCC_FAST) += ucc_fast.o
diff --git a/arch/powerpc/sysdev/qe_lib/qe.c b/arch/powerpc/sysdev/qe_lib/qe.c
new file mode 100644
index 00000000000..2bae632d3ad
--- /dev/null
+++ b/arch/powerpc/sysdev/qe_lib/qe.c
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2006 Freescale Semicondutor, Inc. All rights reserved.
+ *
+ * Authors: Shlomi Gridish <gridish@freescale.com>
+ * Li Yang <leoli@freescale.com>
+ * Based on cpm2_common.c from Dan Malek (dmalek@jlc.net)
+ *
+ * Description:
+ * General Purpose functions for the global management of the
+ * QUICC Engine (QE).
+ *
+ * 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/errno.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/param.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/bootmem.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <asm/irq.h>
+#include <asm/page.h>
+#include <asm/pgtable.h>
+#include <asm/immap_qe.h>
+#include <asm/qe.h>
+#include <asm/prom.h>
+#include <asm/rheap.h>
+
+static void qe_snums_init(void);
+static void qe_muram_init(void);
+static int qe_sdma_init(void);
+
+static DEFINE_SPINLOCK(qe_lock);
+
+/* QE snum state */
+enum qe_snum_state {
+ QE_SNUM_STATE_USED,
+ QE_SNUM_STATE_FREE
+};
+
+/* QE snum */
+struct qe_snum {
+ u8 num;
+ enum qe_snum_state state;
+};
+
+/* We allocate this here because it is used almost exclusively for
+ * the communication processor devices.
+ */
+struct qe_immap *qe_immr = NULL;
+EXPORT_SYMBOL(qe_immr);
+
+static struct qe_snum snums[QE_NUM_OF_SNUM]; /* Dynamically allocated SNUMs */
+
+static phys_addr_t qebase = -1;
+
+phys_addr_t get_qe_base(void)
+{
+ struct device_node *qe;
+
+ if (qebase != -1)
+ return qebase;
+
+ qe = of_find_node_by_type(NULL, "qe");
+ if (qe) {
+ unsigned int size;
+ const void *prop = get_property(qe, "reg", &size);
+ qebase = of_translate_address(qe, prop);
+ of_node_put(qe);
+ };
+
+ return qebase;
+}
+
+EXPORT_SYMBOL(get_qe_base);
+
+void qe_reset(void)
+{
+ if (qe_immr == NULL)
+ qe_immr = ioremap(get_qe_base(), QE_IMMAP_SIZE);
+
+ qe_snums_init();
+
+ qe_issue_cmd(QE_RESET, QE_CR_SUBBLOCK_INVALID,
+ QE_CR_PROTOCOL_UNSPECIFIED, 0);
+
+ /* Reclaim the MURAM memory for our use. */
+ qe_muram_init();
+
+ if (qe_sdma_init())
+ panic("sdma init failed!");
+}
+
+int qe_issue_cmd(u32 cmd, u32 device, u8 mcn_protocol, u32 cmd_input)
+{
+ unsigned long flags;
+ u8 mcn_shift = 0, dev_shift = 0;
+
+ spin_lock_irqsave(&qe_lock, flags);
+ if (cmd == QE_RESET) {
+ out_be32(&qe_immr->cp.cecr, (u32) (cmd | QE_CR_FLG));
+ } else {
+ if (cmd == QE_ASSIGN_PAGE) {
+ /* Here device is the SNUM, not sub-block */
+ dev_shift = QE_CR_SNUM_SHIFT;
+ } else if (cmd == QE_ASSIGN_RISC) {
+ /* Here device is the SNUM, and mcnProtocol is
+ * e_QeCmdRiscAssignment value */
+ dev_shift = QE_CR_SNUM_SHIFT;
+ mcn_shift = QE_CR_MCN_RISC_ASSIGN_SHIFT;
+ } else {
+ if (device == QE_CR_SUBBLOCK_USB)
+ mcn_shift = QE_CR_MCN_USB_SHIFT;
+ else
+ mcn_shift = QE_CR_MCN_NORMAL_SHIFT;
+ }
+
+ out_be32(&qe_immr->cp.cecdr,
+ immrbar_virt_to_phys((void *)cmd_input));
+ out_be32(&qe_immr->cp.cecr,
+ (cmd | QE_CR_FLG | ((u32) device << dev_shift) | (u32)
+ mcn_protocol << mcn_shift));
+ }
+
+ /* wait for the QE_CR_FLG to clear */
+ while(in_be32(&qe_immr->cp.cecr) & QE_CR_FLG)
+ cpu_relax();
+ spin_unlock_irqrestore(&qe_lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(qe_issue_cmd);
+
+/* Set a baud rate generator. This needs lots of work. There are
+ * 16 BRGs, which can be connected to the QE channels or output
+ * as clocks. The BRGs are in two different block of internal
+ * memory mapped space.
+ * The baud rate clock is the system clock divided by something.
+ * It was set up long ago during the initial boot phase and is
+ * is given to us.
+ * Baud rate clocks are zero-based in the driver code (as that maps
+ * to port numbers). Documentation uses 1-based numbering.
+ */
+static unsigned int brg_clk = 0;
+
+unsigned int get_brg_clk(void)
+{
+ struct device_node *qe;
+ if (brg_clk)
+ return brg_clk;
+
+ qe = of_find_node_by_type(NULL, "qe");
+ if (qe) {
+ unsigned int size;
+ const u32 *prop = get_property(qe, "brg-frequency", &size);
+ brg_clk = *prop;
+ of_node_put(qe);
+ };
+ return brg_clk;
+}
+
+/* This function is used by UARTS, or anything else that uses a 16x
+ * oversampled clock.
+ */
+void qe_setbrg(u32 brg, u32 rate)
+{
+ volatile u32 *bp;
+ u32 divisor, tempval;
+ int div16 = 0;
+
+ bp = &qe_immr->brg.brgc1;
+ bp += brg;
+
+ divisor = (get_brg_clk() / rate);
+ if (divisor > QE_BRGC_DIVISOR_MAX + 1) {
+ div16 = 1;
+ divisor /= 16;
+ }
+
+ tempval = ((divisor - 1) << QE_BRGC_DIVISOR_SHIFT) | QE_BRGC_ENABLE;
+ if (div16)
+ tempval |= QE_BRGC_DIV16;
+
+ out_be32(bp, tempval);
+}
+
+/* Initialize SNUMs (thread serial numbers) according to
+ * QE Module Control chapter, SNUM table
+ */
+static void qe_snums_init(void)
+{
+ int i;
+ static const u8 snum_init[] = {
+ 0x04, 0x05, 0x0C, 0x0D, 0x14, 0x15, 0x1C, 0x1D,
+ 0x24, 0x25, 0x2C, 0x2D, 0x34, 0x35, 0x88, 0x89,
+ 0x98, 0x99, 0xA8, 0xA9, 0xB8, 0xB9, 0xC8, 0xC9,
+ 0xD8, 0xD9, 0xE8, 0xE9,
+ };
+
+ for (i = 0; i < QE_NUM_OF_SNUM; i++) {
+ snums[i].num = snum_init[i];
+ snums[i].state = QE_SNUM_STATE_FREE;
+ }
+}
+
+int qe_get_snum(void)
+{
+ unsigned long flags;
+ int snum = -EBUSY;
+ int i;
+
+ spin_lock_irqsave(&qe_lock, flags);
+ for (i = 0; i < QE_NUM_OF_SNUM; i++) {
+ if (snums[i].state == QE_SNUM_STATE_FREE) {
+ snums[i].state = QE_SNUM_STATE_USED;
+ snum = snums[i].num;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&qe_lock, flags);
+
+ return snum;
+}
+EXPORT_SYMBOL(qe_get_snum);
+
+void qe_put_snum(u8 snum)
+{
+ int i;
+
+ for (i = 0; i < QE_NUM_OF_SNUM; i++) {
+ if (snums[i].num == snum) {
+ snums[i].state = QE_SNUM_STATE_FREE;
+ break;
+ }
+ }
+}
+EXPORT_SYMBOL(qe_put_snum);
+
+static int qe_sdma_init(void)
+{
+ struct sdma *sdma = &qe_immr->sdma;
+ u32 sdma_buf_offset;
+
+ if (!sdma)
+ return -ENODEV;
+
+ /* allocate 2 internal temporary buffers (512 bytes size each) for
+ * the SDMA */
+ sdma_buf_offset = qe_muram_alloc(512 * 2, 64);
+ if (IS_MURAM_ERR(sdma_buf_offset))
+ return -ENOMEM;
+
+ out_be32(&sdma->sdebcr, sdma_buf_offset & QE_SDEBCR_BA_MASK);
+ out_be32(&sdma->sdmr, (QE_SDMR_GLB_1_MSK | (0x1 >>
+ QE_SDMR_CEN_SHIFT)));
+
+ return 0;
+}
+
+/*
+ * muram_alloc / muram_free bits.
+ */
+static DEFINE_SPINLOCK(qe_muram_lock);
+
+/* 16 blocks should be enough to satisfy all requests
+ * until the memory subsystem goes up... */
+static rh_block_t qe_boot_muram_rh_block[16];
+static rh_info_t qe_muram_info;
+
+static void qe_muram_init(void)
+{
+ struct device_node *np;
+ u32 address;
+ u64 size;
+ unsigned int flags;
+
+ /* initialize the info header */
+ rh_init(&qe_muram_info, 1,
+ sizeof(qe_boot_muram_rh_block) /
+ sizeof(qe_boot_muram_rh_block[0]), qe_boot_muram_rh_block);
+
+ /* Attach the usable muram area */
+ /* XXX: This is a subset of the available muram. It
+ * varies with the processor and the microcode patches activated.
+ */
+ if ((np = of_find_node_by_name(NULL, "data-only")) != NULL) {
+ address = *of_get_address(np, 0, &size, &flags);
+ of_node_put(np);
+ rh_attach_region(&qe_muram_info,
+ (void *)address, (int)size);
+ }
+}
+
+/* This function returns an index into the MURAM area.
+ */
+u32 qe_muram_alloc(u32 size, u32 align)
+{
+ void *start;
+ unsigned long flags;
+
+ spin_lock_irqsave(&qe_muram_lock, flags);
+ start = rh_alloc_align(&qe_muram_info, size, align, "QE");
+ spin_unlock_irqrestore(&qe_muram_lock, flags);
+
+ return (u32) start;
+}
+EXPORT_SYMBOL(qe_muram_alloc);
+
+int qe_muram_free(u32 offset)
+{
+ int ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&qe_muram_lock, flags);
+ ret = rh_free(&qe_muram_info, (void *)offset);
+ spin_unlock_irqrestore(&qe_muram_lock, flags);
+
+ return ret;
+}
+EXPORT_SYMBOL(qe_muram_free);
+
+/* not sure if this is ever needed */
+u32 qe_muram_alloc_fixed(u32 offset, u32 size)
+{
+ void *start;
+ unsigned long flags;
+
+ spin_lock_irqsave(&qe_muram_lock, flags);
+ start = rh_alloc_fixed(&qe_muram_info, (void *)offset, size, "commproc");
+ spin_unlock_irqrestore(&qe_muram_lock, flags);
+
+ return (u32) start;
+}
+EXPORT_SYMBOL(qe_muram_alloc_fixed);
+
+void qe_muram_dump(void)
+{
+ rh_dump(&qe_muram_info);
+}
+EXPORT_SYMBOL(qe_muram_dump);
+
+void *qe_muram_addr(u32 offset)
+{
+ return (void *)&qe_immr->muram[offset];
+}
+EXPORT_SYMBOL(qe_muram_addr);
diff --git a/arch/powerpc/sysdev/qe_lib/qe_ic.c b/arch/powerpc/sysdev/qe_lib/qe_ic.c
new file mode 100644
index 00000000000..c229d07d495
--- /dev/null
+++ b/arch/powerpc/sysdev/qe_lib/qe_ic.c
@@ -0,0 +1,555 @@
+/*
+ * 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);
+
+ 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