new file mode 100644
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PlayStation 2 system commands
+ *
+ * Copyright (C) 2019 Fredrik Noring
+ */
+
+#ifndef __ASM_MACH_PS2_SCMD_H
+#define __ASM_MACH_PS2_SCMD_H
+
+#include <linux/types.h>
+
+#define SCMD_COMMAND 0x1f402016
+#define SCMD_STATUS 0x1f402017
+#define SCMD_SEND 0x1f402017
+#define SCMD_RECV 0x1f402018
+
+#define SCMD_STATUS_EMPTY 0x40 /* Data is unavailable */
+#define SCMD_STATUS_BUSY 0x80 /* Command is processing */
+
+/**
+ * enum scmd_cmd - system commands
+ * @scmd_cmd_power_off: power off the system
+ */
+enum scmd_cmd {
+ scmd_cmd_power_off = 15,
+};
+
+int scmd(enum scmd_cmd cmd,
+ const void *send, size_t send_size,
+ void *recv, size_t recv_size);
+
+#endif /* __ASM_MACH_PS2_SCMD_H */
@@ -2,4 +2,5 @@ obj-y += dmac-irq.o
obj-y += intc-irq.o
obj-y += irq.o
obj-y += memory.o
+obj-y += scmd.o
obj-y += time.o
new file mode 100644
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PlayStation 2 system commands
+ *
+ * Copyright (C) 2019 Fredrik Noring
+ */
+
+#include <linux/build_bug.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/sched/signal.h>
+
+#include <asm/mach-ps2/scmd.h>
+
+/**
+ * completed - poll for condition to happen, or timeout
+ * @condition: function to poll for condition
+ *
+ * Return: %true if condition happened, else %false on timeout
+ */
+static bool completed(bool (*condition)(void))
+{
+ const unsigned long timeout = jiffies + 5*HZ;
+
+ do {
+ if (condition())
+ return true;
+
+ msleep(1);
+ } while (time_is_after_jiffies(timeout));
+
+ return false;
+}
+
+/**
+ * scmd_status - read system command status register
+ *
+ * Return: system command status register value
+ */
+static u8 scmd_status(void)
+{
+ return inb(SCMD_STATUS);
+}
+
+/**
+ * scmd_write - write system command data
+ * @data: pointer to data to write
+ * @size: number of bytes to write
+ */
+static void scmd_write(const u8 *data, size_t size)
+{
+ size_t i;
+
+ for (i = 0; i < size; i++)
+ outb(data[i], SCMD_SEND);
+}
+
+/**
+ * scmd_ready - can the system receive a command or has finished processing?
+ *
+ * Return: %true if the system is ready to receive a command, or has finished
+ * processing a previous command, otherwise %false
+ */
+static bool scmd_ready(void)
+{
+ return (scmd_status() & SCMD_STATUS_BUSY) == 0;
+}
+
+/**
+ * scmd_wait - wait for the system command to become ready
+ *
+ * Return: %true if the system command is ready, else %false on timeout
+ */
+static bool scmd_wait(void)
+{
+ return completed(scmd_ready);
+}
+
+/**
+ * scmd_data - is command data available to be read from the system?
+ *
+ * Return: %true if system data is readable, else %false
+ */
+static bool scmd_data(void)
+{
+ return (scmd_status() & SCMD_STATUS_EMPTY) == 0;
+}
+
+/**
+ * scmd_flush - read and discard all available command data from the system
+ *
+ * Return: %true if something was read, else %false
+ */
+static bool scmd_flush(void)
+{
+ bool flushed;
+
+ for (flushed = false; scmd_data(); flushed = true)
+ inb(SCMD_RECV);
+
+ return flushed;
+}
+
+/**
+ * scmd_read - read command data from the system
+ * @data: pointer to data to read
+ * @size: maximum number of bytes to read
+ *
+ * Return: actual number of bytes read
+ */
+static size_t scmd_read(u8 *data, size_t size)
+{
+ size_t r;
+
+ for (r = 0; r < size && scmd_data(); r++)
+ data[r] = inb(SCMD_RECV);
+
+ return r;
+}
+
+/**
+ * scmd - general system command function
+ * @cmd: system command
+ * @send: pointer to command data to send
+ * @send_size: size in bytes of command data to send
+ * @recv: pointer to command data to receive
+ * @recv_size: exact size in bytes of command data to receive
+ *
+ * Context: sleep
+ * Return: 0 on success, else a negative error number
+ */
+int scmd(enum scmd_cmd cmd,
+ const void *send, size_t send_size,
+ void *recv, size_t recv_size)
+{
+ static DEFINE_MUTEX(scmd_lock);
+ int err = 0;
+ size_t r;
+
+ mutex_lock(&scmd_lock);
+
+ if (!scmd_ready()) {
+ pr_warn("%s: Unexpectedly busy preceding command %d\n",
+ __func__, cmd);
+
+ if (!scmd_wait()) {
+ err = -EBUSY;
+ goto out_err;
+ }
+ }
+ if (scmd_flush())
+ pr_warn("%s: Unexpected data preceding command %d\n",
+ __func__, cmd);
+
+ scmd_write(send, send_size);
+ outb(cmd, SCMD_COMMAND);
+
+ if (!scmd_wait()) {
+ err = -EIO;
+ goto out_err;
+ }
+ r = scmd_read(recv, recv_size);
+ if (r == recv_size && scmd_flush())
+ pr_warn("%s: Unexpected data following command %d\n",
+ __func__, cmd);
+ if (r != recv_size)
+ err = -EIO;
+
+out_err:
+ mutex_unlock(&scmd_lock);
+ return err;
+}
+EXPORT_SYMBOL_GPL(scmd);
+
+MODULE_DESCRIPTION("PlayStation 2 system commands");
+MODULE_AUTHOR("Fredrik Noring");
+MODULE_LICENSE("GPL");
Signed-off-by: Fredrik Noring <noring@nocrew.org> --- arch/mips/include/asm/mach-ps2/scmd.h | 33 +++++ arch/mips/ps2/Makefile | 1 + arch/mips/ps2/scmd.c | 180 ++++++++++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 arch/mips/include/asm/mach-ps2/scmd.h create mode 100644 arch/mips/ps2/scmd.c