diff --git a/arch/mips/include/asm/mach-ps2/irq.h b/arch/mips/include/asm/mach-ps2/irq.h
index 64d3fbf4789e..57b3e539ad92 100644
--- a/arch/mips/include/asm/mach-ps2/irq.h
+++ b/arch/mips/include/asm/mach-ps2/irq.h
@@ -13,7 +13,7 @@
 #define INTC_STAT	0x1000f000	/* Flags are cleared by writing 1 */
 #define INTC_MASK	0x1000f010	/* Bits are reversed by writing 1 */
 
-#define NR_IRQS		56
+#define NR_IRQS		128
 
 /*
  * The interrupt controller (INTC) arbitrates interrupts from peripheral
@@ -71,6 +71,53 @@
 #define IRQ_C0_DMAC	51
 #define IRQ_C0_IRQ7	55
 
+/* Input/output processor (IOP) */
+#define IOP_IRQ_BASE	64
+#define IRQ_IOP_VBLANK	64
+#define IRQ_IOP_SBUS	65
+#define IRQ_IOP_CDVD	66
+#define IRQ_IOP_DMA	67
+#define IRQ_IOP_RTC0	68
+#define IRQ_IOP_RTC1	69
+#define IRQ_IOP_RTC2	70
+#define IRQ_IOP_SIO0	71
+#define IRQ_IOP_SIO1	72
+#define IRQ_IOP_SPU	73
+#define IRQ_IOP_PIO	74
+#define IRQ_IOP_EVBLANK	75
+#define IRQ_IOP_DVD	76
+#define IRQ_IOP_DEV9	77
+#define IRQ_IOP_RTC3	78
+#define IRQ_IOP_RTC4	79
+#define IRQ_IOP_RTC5	80
+#define IRQ_IOP_SIO2	81
+#define IRQ_IOP_HTR0	82
+#define IRQ_IOP_HTR1	83
+#define IRQ_IOP_HTR2	84
+#define IRQ_IOP_HTR3	85
+#define IRQ_IOP_USB	86
+#define IRQ_IOP_EXTR	87
+#define IRQ_IOP_ILINK	88
+#define IRQ_IOP_ILNKDMA	89
+
+#define IRQ_IOP_DMAC_MDEC_IN	96	/* Ch 0 */
+#define IRQ_IOP_DMAC_MDEC_OUT	97	/* Ch 1 */
+#define IRQ_IOP_DMAC_SIF2	98	/* Ch 2 */
+#define IRQ_IOP_DMAC_CDVD	99	/* Ch 3 */
+#define IRQ_IOP_DMAC_SPU	100	/* Ch 4 */
+#define IRQ_IOP_DMAC_PIO	101	/* Ch 5 */
+#define IRQ_IOP_DMAC_GPU_OTC	102	/* Ch 6 */
+#define IRQ_IOP_DMAC_BE		103	/* Bus error */
+#define IRQ_IOP_DMAC_SPU2	104	/* Ch 7 */
+#define IRQ_IOP_DMAC_DEV9	105	/* Ch 8 */
+#define IRQ_IOP_DMAC_SIF0	106	/* Ch 9 */
+#define IRQ_IOP_DMAC_SIF1	107	/* Ch 10 */
+#define IRQ_IOP_DMAC_SIO2_IN	108	/* Ch 11 */
+#define IRQ_IOP_DMAC_SIO2_OUT	109	/* Ch 12 */
+
+#define IRQ_IOP_SW1	126	/* R3000A Software Interrupt 1 */
+#define IRQ_IOP_SW2	127	/* R3000A Software Interrupt 2 */
+
 int __init intc_irq_init(void);
 int __init dmac_irq_init(void);
 
diff --git a/drivers/ps2/Makefile b/drivers/ps2/Makefile
index 02a5756cb059..dd00eff52ef3 100644
--- a/drivers/ps2/Makefile
+++ b/drivers/ps2/Makefile
@@ -1,4 +1,5 @@
 obj-m				+= iop-heap.o
+obj-m				+= iop-irq.o
 obj-m				+= iop-memory.o
 obj-m				+= iop-module.o
 obj-m				+= iop-registers.o
diff --git a/drivers/ps2/iop-irq.c b/drivers/ps2/iop-irq.c
new file mode 100644
index 000000000000..859a3f64dd00
--- /dev/null
+++ b/drivers/ps2/iop-irq.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PlayStation 2 input/output processor (IOP) IRQs
+ *
+ * Copyright (C) 2019 Fredrik Noring
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/types.h>
+
+#include <asm/mach-ps2/iop-module.h>
+#include <asm/mach-ps2/irq.h>
+#include <asm/mach-ps2/sif.h>
+
+/**
+ * enum iop_irq_relay_rpc_ops - IOP IRQ relay RPC operations
+ * @rpo_request_irq: request IRQ mapping
+ * @rpo_release_irq: release IRQ mapping
+ * @rpo_remap_irq: remap existing IRQ mapping
+ */
+enum iop_irq_relay_rpc_ops {
+	rpo_request_irq = 1,
+	rpo_release_irq = 2,
+	rpo_remap_irq   = 3,
+};
+
+/**
+ * struct iop_rpc_relay_map - IOP IRQ relay mapping
+ * @u8 iop: IOP IRQ map source
+ * @u8 map: main IRQ map target
+ * @u8 rpc: %true for RPC relay, %false for SMFLAG relay
+ */
+struct iop_rpc_relay_map {
+	u8 iop;
+	u8 map;
+	u8 rpc;
+};
+
+/**
+ * struct iop_rpc_relay_release - IOP IRQ relay to release
+ * @iop: IOP IRQ to release mapping for
+ */
+struct iop_rpc_relay_release {
+	u8 iop;
+};
+
+static struct sif_rpc_client iop_irq_rpc;
+
+static unsigned int iop_irq_startup(struct irq_data *data)
+{
+	static bool irq_relay = false;
+
+	const struct iop_rpc_relay_map arg = {
+		.iop = data->irq - IOP_IRQ_BASE,
+		.map = data->irq,
+		.rpc = true,	/* FIXME: Also implement SMFLAG relay */
+	};
+	s32 status;
+	int err;
+
+	BUG_ON(in_irq());
+
+	if (!irq_relay) {
+		int id;
+
+		/*
+		 * The main reason for requesting the IOP IRQ relay module here
+		 * instead of in irqrelay_init() is that now the console may be
+		 * visible to print messages if there are problems.
+		 */
+		id = iop_module_request("irqrelay", 0x0100, NULL);
+		if (id < 0)
+			return id;
+
+		err = sif_rpc_bind(&iop_irq_rpc, SIF_SID_IRQ_RELAY);
+		if (err < 0) {
+			pr_err("%s: sif_rpc_bind failed with %d\n",
+				__func__, err);
+			return err;
+		}
+
+		irq_relay = true;
+	}
+
+	err = sif_rpc(&iop_irq_rpc, rpo_request_irq,
+		&arg, sizeof(arg),
+		&status, sizeof(status));
+
+	pr_debug("%s: err %d status %d\n", __func__, err, status);
+
+	return err < 0 ? err : status;
+}
+
+static void iop_irq_shutdown(struct irq_data *data)
+{
+	const struct iop_rpc_relay_release arg = {
+		.iop = data->irq - IOP_IRQ_BASE,
+	};
+	s32 status;
+	int err;
+
+	BUG_ON(in_irq());
+
+	err = sif_rpc(&iop_irq_rpc, rpo_release_irq,
+		&arg, sizeof(arg),
+		&status, sizeof(status));
+
+	pr_debug("%s: err %d status %d\n", __func__, err, status);
+}
+
+#define IOP_IRQ_TYPE(irq_, name_)					\
+	{								\
+		.irq = irq_,						\
+		.irq_chip = {						\
+			.name = name_,					\
+			.irq_startup = iop_irq_startup,			\
+			.irq_shutdown = iop_irq_shutdown,		\
+		}							\
+	}
+
+static struct {
+	unsigned int irq;
+	struct irq_chip irq_chip;
+} iop_irqs[] = {
+	IOP_IRQ_TYPE(IRQ_IOP_VBLANK,        "IOP VBLANK"),
+	IOP_IRQ_TYPE(IRQ_IOP_SBUS,          "IOP SBUS"),
+	IOP_IRQ_TYPE(IRQ_IOP_CDVD,          "IOP CDVD"),
+	IOP_IRQ_TYPE(IRQ_IOP_DMA,           "IOP DMA"),
+	IOP_IRQ_TYPE(IRQ_IOP_RTC0,          "IOP RTC0"),
+	IOP_IRQ_TYPE(IRQ_IOP_RTC1,          "IOP RTC1"),
+	IOP_IRQ_TYPE(IRQ_IOP_RTC2,          "IOP RTC2"),
+	IOP_IRQ_TYPE(IRQ_IOP_SIO0,          "IOP SIO0"),
+	IOP_IRQ_TYPE(IRQ_IOP_SIO1,          "IOP SIO1"),
+	IOP_IRQ_TYPE(IRQ_IOP_SPU,           "IOP SPU"),
+	IOP_IRQ_TYPE(IRQ_IOP_PIO,           "IOP PIO"),
+	IOP_IRQ_TYPE(IRQ_IOP_EVBLANK,       "IOP EVBLANK"),
+	IOP_IRQ_TYPE(IRQ_IOP_DVD,           "IOP DVD"),
+	IOP_IRQ_TYPE(IRQ_IOP_DEV9,          "IOP DEV9"),
+	IOP_IRQ_TYPE(IRQ_IOP_RTC3,          "IOP RTC3"),
+	IOP_IRQ_TYPE(IRQ_IOP_RTC4,          "IOP RTC4"),
+	IOP_IRQ_TYPE(IRQ_IOP_RTC5,          "IOP RTC5"),
+	IOP_IRQ_TYPE(IRQ_IOP_SIO2,          "IOP SIO2"),
+	IOP_IRQ_TYPE(IRQ_IOP_HTR0,          "IOP HTR0"),
+	IOP_IRQ_TYPE(IRQ_IOP_HTR1,          "IOP HTR1"),
+	IOP_IRQ_TYPE(IRQ_IOP_HTR2,          "IOP HTR2"),
+	IOP_IRQ_TYPE(IRQ_IOP_HTR3,          "IOP HTR3"),
+	IOP_IRQ_TYPE(IRQ_IOP_USB,           "IOP USB"),
+	IOP_IRQ_TYPE(IRQ_IOP_EXTR,          "IOP EXTR"),
+	IOP_IRQ_TYPE(IRQ_IOP_ILINK,         "IOP iLink"),
+	IOP_IRQ_TYPE(IRQ_IOP_ILNKDMA,       "IOP ILink DMA"),
+	IOP_IRQ_TYPE(IRQ_IOP_DMAC_MDEC_IN,  "IOP DMAC MDEC IN"),
+	IOP_IRQ_TYPE(IRQ_IOP_DMAC_MDEC_OUT, "IOP DMAC MDEC OUT"),
+	IOP_IRQ_TYPE(IRQ_IOP_DMAC_SIF2,     "IOP DMAC SIF2"),
+	IOP_IRQ_TYPE(IRQ_IOP_DMAC_CDVD,     "IOP DMAC CDVD"),
+	IOP_IRQ_TYPE(IRQ_IOP_DMAC_SPU,      "IOP DMAC SPU"),
+	IOP_IRQ_TYPE(IRQ_IOP_DMAC_PIO,      "IOP DMAC PIO"),
+	IOP_IRQ_TYPE(IRQ_IOP_DMAC_GPU_OTC,  "IOP DMAC GPU OTC"),
+	IOP_IRQ_TYPE(IRQ_IOP_DMAC_BE,       "IOP DMAC BE"),
+	IOP_IRQ_TYPE(IRQ_IOP_DMAC_SPU2,     "IOP DMAC SPU2"),
+	IOP_IRQ_TYPE(IRQ_IOP_DMAC_DEV9,     "IOP DMAC DEV9"),
+	IOP_IRQ_TYPE(IRQ_IOP_DMAC_SIF0,     "IOP DMAC SIF0"),
+	IOP_IRQ_TYPE(IRQ_IOP_DMAC_SIF1,     "IOP DMAC SIF1"),
+	IOP_IRQ_TYPE(IRQ_IOP_DMAC_SIO2_IN,  "IOP DMAC SIO2 IN"),
+	IOP_IRQ_TYPE(IRQ_IOP_DMAC_SIO2_OUT, "IOP DMAC SIO2 OUT"),
+	IOP_IRQ_TYPE(IRQ_IOP_SW1,           "IOP SW1"),
+	IOP_IRQ_TYPE(IRQ_IOP_SW2,           "IOP SW2"),
+};
+
+static int __init iop_irq_init(void)
+{
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(iop_irqs); i++)
+		irq_set_chip_and_handler(iop_irqs[i].irq,
+			&iop_irqs[i].irq_chip, handle_level_irq);
+
+	return 0;
+}
+// FIXME: subsys_initcall(iop_irq_init);
+module_init(iop_irq_init);
+
+MODULE_DESCRIPTION("PlayStation 2 input/output processor (IOP) IRQs");
+MODULE_AUTHOR("Fredrik Noring");
+MODULE_LICENSE("GPL");
