diff mbox series

[058/120] MIPS: PS2: SIF: Handle SIF0 (sub-to-main) RPCs via interrupts

Message ID 4f70dc50d49675c21ef1e835e21ebbb9dc95a256.1567326213.git.noring@nocrew.org (mailing list archive)
State RFC
Headers show
Series Linux for the PlayStation 2 | expand

Commit Message

Fredrik Noring Sept. 1, 2019, 3:59 p.m. UTC
The SIF0 DMA controller asserts an interrupt on RPC completion. The
kernel invokes the corresponding callback in the command table, and then
resets the SIF0 DMA to receive the next command.

Signed-off-by: Fredrik Noring <noring@nocrew.org>
---
 drivers/ps2/sif.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 65 insertions(+)
diff mbox series

Patch

diff --git a/drivers/ps2/sif.c b/drivers/ps2/sif.c
index d077a0a97e02..fac1a5117d1c 100644
--- a/drivers/ps2/sif.c
+++ b/drivers/ps2/sif.c
@@ -164,11 +164,21 @@  static bool sif_smflag_bootend(void)
 	return (sif_read_smflag() & SIF_STATUS_BOOTEND) != 0;
 }
 
+static bool sif0_busy(void)
+{
+	return (inl(DMAC_SIF0_CHCR) & DMAC_CHCR_BUSY) != 0;
+}
+
 static bool sif1_busy(void)
 {
 	return (inl(DMAC_SIF1_CHCR) & DMAC_CHCR_BUSY) != 0;
 }
 
+/*
+ * sif1_ready may be called via cmd_rpc_bind that is a response from
+ * SIF_CMD_RPC_BIND via sif0_dma_handler from IRQ_DMAC_SIF0. Thus we
+ * currently have to busy-wait here if SIF1 is busy.
+ */
 static bool sif1_ready(void)
 {
 	size_t countout = 50000;	/* About 5 s */
@@ -305,6 +315,48 @@  static struct sif_cmd_handler *handler_from_cid(u32 cmd_id)
 	return id < CMD_HANDLER_MAX ? &cmd_handlers[id] : NULL;
 }
 
+static void cmd_call_handler(
+	const struct sif_cmd_header *header, const void *data)
+{
+	const struct sif_cmd_handler *handler = handler_from_cid(header->cmd);
+
+	if (!handler || !handler->cb) {
+		pr_err_once("sif: Invalid command 0x%x ignored\n", header->cmd);
+		return;
+	}
+
+	handler->cb(header, data, handler->arg);
+}
+
+static irqreturn_t sif0_dma_handler(int irq, void *dev_id)
+{
+	const struct sif_cmd_header *header = sif0_buffer;
+	const void *payload = &header[1];
+
+	if (sif0_busy())
+		return IRQ_NONE;
+
+	dma_cache_inv((unsigned long)sif0_buffer, SIF_CMD_PACKET_MAX);
+
+	if (header->data_size)
+		dma_cache_inv((unsigned long)phys_to_virt(header->data_addr),
+				header->data_size);
+
+	if (header->packet_size < sizeof(*header) ||
+	    header->packet_size > SIF_CMD_PACKET_MAX) {
+		pr_err_once("sif: Invalid command header size %u bytes\n",
+			header->packet_size);
+		goto err;
+	}
+
+	cmd_call_handler(header, payload);
+
+err:
+	sif0_reset_dma();	/* Reset DMA for the next incoming packet. */
+
+	return IRQ_HANDLED;
+}
+
 static void cmd_rpc_end(const struct sif_cmd_header *header,
 	const void *data, void *arg)
 {
@@ -530,6 +582,8 @@  EXPORT_SYMBOL_GPL(iop_error_message);
  *
  * 10. Reset the SIF0 (sub-to-main) DMA controller.
  *
+ * 11. Service SIF0 RPCs via interrupts.
+ *
  * Return: 0 on success, otherwise a negative error number
  */
 static int __init sif_init(void)
@@ -584,8 +638,17 @@  static int __init sif_init(void)
 
 	sif0_reset_dma();
 
+	err = request_irq(IRQ_DMAC_SIF0, sif0_dma_handler, 0, "SIF0 DMA", NULL);
+	if (err) {
+		pr_err("sif: Failed to setup SIF0 handler with %d\n", err);
+		goto err_irq_sif0;
+	}
+
 	return 0;
 
+err_irq_sif0:
+	sif_disable_dma();
+
 err_request_commands:
 err_final_subaddr:
 err_iop_reset:
@@ -600,6 +663,8 @@  static void __exit sif_exit(void)
 {
 	sif_disable_dma();
 
+	free_irq(IRQ_DMAC_SIF0, NULL);
+
 	put_dma_buffers();
 }