summaryrefslogtreecommitdiffstats
path: root/drivers/ps3
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
commit2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch)
tree848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/ps3
parentInitial commit. (diff)
downloadlinux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz
linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/ps3')
-rw-r--r--drivers/ps3/Makefile8
-rw-r--r--drivers/ps3/ps3-lpm.c1238
-rw-r--r--drivers/ps3/ps3-sys-manager.c756
-rw-r--r--drivers/ps3/ps3-vuart.c1248
-rw-r--r--drivers/ps3/ps3av.c1061
-rw-r--r--drivers/ps3/ps3av_cmd.c924
-rw-r--r--drivers/ps3/ps3stor_lib.c350
-rw-r--r--drivers/ps3/sys-manager-core.c61
-rw-r--r--drivers/ps3/vuart.h89
9 files changed, 5735 insertions, 0 deletions
diff --git a/drivers/ps3/Makefile b/drivers/ps3/Makefile
new file mode 100644
index 000000000..c4d1f467f
--- /dev/null
+++ b/drivers/ps3/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_PS3_VUART) += ps3-vuart.o
+obj-$(CONFIG_PS3_PS3AV) += ps3av_mod.o
+ps3av_mod-y := ps3av.o ps3av_cmd.o
+obj-$(CONFIG_PPC_PS3) += sys-manager-core.o
+obj-$(CONFIG_PS3_SYS_MANAGER) += ps3-sys-manager.o
+obj-$(CONFIG_PS3_STORAGE) += ps3stor_lib.o
+obj-$(CONFIG_PS3_LPM) += ps3-lpm.o
diff --git a/drivers/ps3/ps3-lpm.c b/drivers/ps3/ps3-lpm.c
new file mode 100644
index 000000000..65512b6cc
--- /dev/null
+++ b/drivers/ps3/ps3-lpm.c
@@ -0,0 +1,1238 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PS3 Logical Performance Monitor.
+ *
+ * Copyright (C) 2007 Sony Computer Entertainment Inc.
+ * Copyright 2007 Sony Corp.
+ */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/uaccess.h>
+#include <asm/smp.h>
+#include <asm/time.h>
+#include <asm/ps3.h>
+#include <asm/lv1call.h>
+#include <asm/cell-pmu.h>
+
+
+/* BOOKMARK tag macros */
+#define PS3_PM_BOOKMARK_START 0x8000000000000000ULL
+#define PS3_PM_BOOKMARK_STOP 0x4000000000000000ULL
+#define PS3_PM_BOOKMARK_TAG_KERNEL 0x1000000000000000ULL
+#define PS3_PM_BOOKMARK_TAG_USER 0x3000000000000000ULL
+#define PS3_PM_BOOKMARK_TAG_MASK_HI 0xF000000000000000ULL
+#define PS3_PM_BOOKMARK_TAG_MASK_LO 0x0F00000000000000ULL
+
+/* CBE PM CONTROL register macros */
+#define PS3_PM_CONTROL_PPU_TH0_BOOKMARK 0x00001000
+#define PS3_PM_CONTROL_PPU_TH1_BOOKMARK 0x00000800
+#define PS3_PM_CONTROL_PPU_COUNT_MODE_MASK 0x000C0000
+#define PS3_PM_CONTROL_PPU_COUNT_MODE_PROBLEM 0x00080000
+#define PS3_WRITE_PM_MASK 0xFFFFFFFFFFFFFFFFULL
+
+/* CBE PM START STOP register macros */
+#define PS3_PM_START_STOP_PPU_TH0_BOOKMARK_START 0x02000000
+#define PS3_PM_START_STOP_PPU_TH1_BOOKMARK_START 0x01000000
+#define PS3_PM_START_STOP_PPU_TH0_BOOKMARK_STOP 0x00020000
+#define PS3_PM_START_STOP_PPU_TH1_BOOKMARK_STOP 0x00010000
+#define PS3_PM_START_STOP_START_MASK 0xFF000000
+#define PS3_PM_START_STOP_STOP_MASK 0x00FF0000
+
+/* CBE PM COUNTER register macres */
+#define PS3_PM_COUNTER_MASK_HI 0xFFFFFFFF00000000ULL
+#define PS3_PM_COUNTER_MASK_LO 0x00000000FFFFFFFFULL
+
+/* BASE SIGNAL GROUP NUMBER macros */
+#define PM_ISLAND2_BASE_SIGNAL_GROUP_NUMBER 0
+#define PM_ISLAND2_SIGNAL_GROUP_NUMBER1 6
+#define PM_ISLAND2_SIGNAL_GROUP_NUMBER2 7
+#define PM_ISLAND3_BASE_SIGNAL_GROUP_NUMBER 7
+#define PM_ISLAND4_BASE_SIGNAL_GROUP_NUMBER 15
+#define PM_SPU_TRIGGER_SIGNAL_GROUP_NUMBER 17
+#define PM_SPU_EVENT_SIGNAL_GROUP_NUMBER 18
+#define PM_ISLAND5_BASE_SIGNAL_GROUP_NUMBER 18
+#define PM_ISLAND6_BASE_SIGNAL_GROUP_NUMBER 24
+#define PM_ISLAND7_BASE_SIGNAL_GROUP_NUMBER 49
+#define PM_ISLAND8_BASE_SIGNAL_GROUP_NUMBER 52
+#define PM_SIG_GROUP_SPU 41
+#define PM_SIG_GROUP_SPU_TRIGGER 42
+#define PM_SIG_GROUP_SPU_EVENT 43
+#define PM_SIG_GROUP_MFC_MAX 60
+
+/**
+ * struct ps3_lpm_shadow_regs - Performance monitor shadow registers.
+ *
+ * @pm_control: Shadow of the processor's pm_control register.
+ * @pm_start_stop: Shadow of the processor's pm_start_stop register.
+ * @group_control: Shadow of the processor's group_control register.
+ * @debug_bus_control: Shadow of the processor's debug_bus_control register.
+ *
+ * The logical performance monitor provides a write-only interface to
+ * these processor registers. These shadow variables cache the processor
+ * register values for reading.
+ *
+ * The initial value of the shadow registers at lpm creation is
+ * PS3_LPM_SHADOW_REG_INIT.
+ */
+
+struct ps3_lpm_shadow_regs {
+ u64 pm_control;
+ u64 pm_start_stop;
+ u64 group_control;
+ u64 debug_bus_control;
+};
+
+#define PS3_LPM_SHADOW_REG_INIT 0xFFFFFFFF00000000ULL
+
+/**
+ * struct ps3_lpm_priv - Private lpm device data.
+ *
+ * @open: An atomic variable indicating the lpm driver has been opened.
+ * @rights: The lpm rigths granted by the system policy module. A logical
+ * OR of enum ps3_lpm_rights.
+ * @node_id: The node id of a BE processor whose performance monitor this
+ * lpar has the right to use.
+ * @pu_id: The lv1 id of the logical PU.
+ * @lpm_id: The lv1 id of this lpm instance.
+ * @outlet_id: The outlet created by lv1 for this lpm instance.
+ * @tb_count: The number of bytes of data held in the lv1 trace buffer.
+ * @tb_cache: Kernel buffer to receive the data from the lv1 trace buffer.
+ * Must be 128 byte aligned.
+ * @tb_cache_size: Size of the kernel @tb_cache buffer. Must be 128 byte
+ * aligned.
+ * @tb_cache_internal: An unaligned buffer allocated by this driver to be
+ * used for the trace buffer cache when ps3_lpm_open() is called with a
+ * NULL tb_cache argument. Otherwise unused.
+ * @shadow: Processor register shadow of type struct ps3_lpm_shadow_regs.
+ * @sbd: The struct ps3_system_bus_device attached to this driver.
+ *
+ * The trace buffer is a buffer allocated and used internally to the lv1
+ * hypervisor to collect trace data. The trace buffer cache is a guest
+ * buffer that accepts the trace data from the trace buffer.
+ */
+
+struct ps3_lpm_priv {
+ atomic_t open;
+ u64 rights;
+ u64 node_id;
+ u64 pu_id;
+ u64 lpm_id;
+ u64 outlet_id;
+ u64 tb_count;
+ void *tb_cache;
+ u64 tb_cache_size;
+ void *tb_cache_internal;
+ struct ps3_lpm_shadow_regs shadow;
+ struct ps3_system_bus_device *sbd;
+};
+
+enum {
+ PS3_LPM_DEFAULT_TB_CACHE_SIZE = 0x4000,
+};
+
+/**
+ * lpm_priv - Static instance of the lpm data.
+ *
+ * Since the exported routines don't support the notion of a device
+ * instance we need to hold the instance in this static variable
+ * and then only allow at most one instance at a time to be created.
+ */
+
+static struct ps3_lpm_priv *lpm_priv;
+
+static struct device *sbd_core(void)
+{
+ BUG_ON(!lpm_priv || !lpm_priv->sbd);
+ return &lpm_priv->sbd->core;
+}
+
+/**
+ * use_start_stop_bookmark - Enable the PPU bookmark trace.
+ *
+ * And it enables PPU bookmark triggers ONLY if the other triggers are not set.
+ * The start/stop bookmarks are inserted at ps3_enable_pm() and ps3_disable_pm()
+ * to start/stop LPM.
+ *
+ * Used to get good quality of the performance counter.
+ */
+
+enum {use_start_stop_bookmark = 1,};
+
+void ps3_set_bookmark(u64 bookmark)
+{
+ /*
+ * As per the PPE book IV, to avoid bookmark loss there must
+ * not be a traced branch within 10 cycles of setting the
+ * SPRN_BKMK register. The actual text is unclear if 'within'
+ * includes cycles before the call.
+ */
+
+ asm volatile("nop;nop;nop;nop;nop;nop;nop;nop;nop;");
+ mtspr(SPRN_BKMK, bookmark);
+ asm volatile("nop;nop;nop;nop;nop;nop;nop;nop;nop;");
+}
+EXPORT_SYMBOL_GPL(ps3_set_bookmark);
+
+void ps3_set_pm_bookmark(u64 tag, u64 incident, u64 th_id)
+{
+ u64 bookmark;
+
+ bookmark = (get_tb() & 0x00000000FFFFFFFFULL) |
+ PS3_PM_BOOKMARK_TAG_KERNEL;
+ bookmark = ((tag << 56) & PS3_PM_BOOKMARK_TAG_MASK_LO) |
+ (incident << 48) | (th_id << 32) | bookmark;
+ ps3_set_bookmark(bookmark);
+}
+EXPORT_SYMBOL_GPL(ps3_set_pm_bookmark);
+
+/**
+ * ps3_read_phys_ctr - Read physical counter registers.
+ *
+ * Each physical counter can act as one 32 bit counter or as two 16 bit
+ * counters.
+ */
+
+u32 ps3_read_phys_ctr(u32 cpu, u32 phys_ctr)
+{
+ int result;
+ u64 counter0415;
+ u64 counter2637;
+
+ if (phys_ctr >= NR_PHYS_CTRS) {
+ dev_dbg(sbd_core(), "%s:%u: phys_ctr too big: %u\n", __func__,
+ __LINE__, phys_ctr);
+ return 0;
+ }
+
+ result = lv1_set_lpm_counter(lpm_priv->lpm_id, 0, 0, 0, 0, &counter0415,
+ &counter2637);
+ if (result) {
+ dev_err(sbd_core(), "%s:%u: lv1_set_lpm_counter failed: "
+ "phys_ctr %u, %s\n", __func__, __LINE__, phys_ctr,
+ ps3_result(result));
+ return 0;
+ }
+
+ switch (phys_ctr) {
+ case 0:
+ return counter0415 >> 32;
+ case 1:
+ return counter0415 & PS3_PM_COUNTER_MASK_LO;
+ case 2:
+ return counter2637 >> 32;
+ case 3:
+ return counter2637 & PS3_PM_COUNTER_MASK_LO;
+ default:
+ BUG();
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ps3_read_phys_ctr);
+
+/**
+ * ps3_write_phys_ctr - Write physical counter registers.
+ *
+ * Each physical counter can act as one 32 bit counter or as two 16 bit
+ * counters.
+ */
+
+void ps3_write_phys_ctr(u32 cpu, u32 phys_ctr, u32 val)
+{
+ u64 counter0415;
+ u64 counter0415_mask;
+ u64 counter2637;
+ u64 counter2637_mask;
+ int result;
+
+ if (phys_ctr >= NR_PHYS_CTRS) {
+ dev_dbg(sbd_core(), "%s:%u: phys_ctr too big: %u\n", __func__,
+ __LINE__, phys_ctr);
+ return;
+ }
+
+ switch (phys_ctr) {
+ case 0:
+ counter0415 = (u64)val << 32;
+ counter0415_mask = PS3_PM_COUNTER_MASK_HI;
+ counter2637 = 0x0;
+ counter2637_mask = 0x0;
+ break;
+ case 1:
+ counter0415 = (u64)val;
+ counter0415_mask = PS3_PM_COUNTER_MASK_LO;
+ counter2637 = 0x0;
+ counter2637_mask = 0x0;
+ break;
+ case 2:
+ counter0415 = 0x0;
+ counter0415_mask = 0x0;
+ counter2637 = (u64)val << 32;
+ counter2637_mask = PS3_PM_COUNTER_MASK_HI;
+ break;
+ case 3:
+ counter0415 = 0x0;
+ counter0415_mask = 0x0;
+ counter2637 = (u64)val;
+ counter2637_mask = PS3_PM_COUNTER_MASK_LO;
+ break;
+ default:
+ BUG();
+ }
+
+ result = lv1_set_lpm_counter(lpm_priv->lpm_id,
+ counter0415, counter0415_mask,
+ counter2637, counter2637_mask,
+ &counter0415, &counter2637);
+ if (result)
+ dev_err(sbd_core(), "%s:%u: lv1_set_lpm_counter failed: "
+ "phys_ctr %u, val %u, %s\n", __func__, __LINE__,
+ phys_ctr, val, ps3_result(result));
+}
+EXPORT_SYMBOL_GPL(ps3_write_phys_ctr);
+
+/**
+ * ps3_read_ctr - Read counter.
+ *
+ * Read 16 or 32 bits depending on the current size of the counter.
+ * Counters 4, 5, 6 & 7 are always 16 bit.
+ */
+
+u32 ps3_read_ctr(u32 cpu, u32 ctr)
+{
+ u32 val;
+ u32 phys_ctr = ctr & (NR_PHYS_CTRS - 1);
+
+ val = ps3_read_phys_ctr(cpu, phys_ctr);
+
+ if (ps3_get_ctr_size(cpu, phys_ctr) == 16)
+ val = (ctr < NR_PHYS_CTRS) ? (val >> 16) : (val & 0xffff);
+
+ return val;
+}
+EXPORT_SYMBOL_GPL(ps3_read_ctr);
+
+/**
+ * ps3_write_ctr - Write counter.
+ *
+ * Write 16 or 32 bits depending on the current size of the counter.
+ * Counters 4, 5, 6 & 7 are always 16 bit.
+ */
+
+void ps3_write_ctr(u32 cpu, u32 ctr, u32 val)
+{
+ u32 phys_ctr;
+ u32 phys_val;
+
+ phys_ctr = ctr & (NR_PHYS_CTRS - 1);
+
+ if (ps3_get_ctr_size(cpu, phys_ctr) == 16) {
+ phys_val = ps3_read_phys_ctr(cpu, phys_ctr);
+
+ if (ctr < NR_PHYS_CTRS)
+ val = (val << 16) | (phys_val & 0xffff);
+ else
+ val = (val & 0xffff) | (phys_val & 0xffff0000);
+ }
+
+ ps3_write_phys_ctr(cpu, phys_ctr, val);
+}
+EXPORT_SYMBOL_GPL(ps3_write_ctr);
+
+/**
+ * ps3_read_pm07_control - Read counter control registers.
+ *
+ * Each logical counter has a corresponding control register.
+ */
+
+u32 ps3_read_pm07_control(u32 cpu, u32 ctr)
+{
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ps3_read_pm07_control);
+
+/**
+ * ps3_write_pm07_control - Write counter control registers.
+ *
+ * Each logical counter has a corresponding control register.
+ */
+
+void ps3_write_pm07_control(u32 cpu, u32 ctr, u32 val)
+{
+ int result;
+ static const u64 mask = 0xFFFFFFFFFFFFFFFFULL;
+ u64 old_value;
+
+ if (ctr >= NR_CTRS) {
+ dev_dbg(sbd_core(), "%s:%u: ctr too big: %u\n", __func__,
+ __LINE__, ctr);
+ return;
+ }
+
+ result = lv1_set_lpm_counter_control(lpm_priv->lpm_id, ctr, val, mask,
+ &old_value);
+ if (result)
+ dev_err(sbd_core(), "%s:%u: lv1_set_lpm_counter_control "
+ "failed: ctr %u, %s\n", __func__, __LINE__, ctr,
+ ps3_result(result));
+}
+EXPORT_SYMBOL_GPL(ps3_write_pm07_control);
+
+/**
+ * ps3_read_pm - Read Other LPM control registers.
+ */
+
+u32 ps3_read_pm(u32 cpu, enum pm_reg_name reg)
+{
+ int result = 0;
+ u64 val = 0;
+
+ switch (reg) {
+ case pm_control:
+ return lpm_priv->shadow.pm_control;
+ case trace_address:
+ return CBE_PM_TRACE_BUF_EMPTY;
+ case pm_start_stop:
+ return lpm_priv->shadow.pm_start_stop;
+ case pm_interval:
+ result = lv1_set_lpm_interval(lpm_priv->lpm_id, 0, 0, &val);
+ if (result) {
+ val = 0;
+ dev_dbg(sbd_core(), "%s:%u: lv1 set_interval failed: "
+ "reg %u, %s\n", __func__, __LINE__, reg,
+ ps3_result(result));
+ }
+ return (u32)val;
+ case group_control:
+ return lpm_priv->shadow.group_control;
+ case debug_bus_control:
+ return lpm_priv->shadow.debug_bus_control;
+ case pm_status:
+ result = lv1_get_lpm_interrupt_status(lpm_priv->lpm_id,
+ &val);
+ if (result) {
+ val = 0;
+ dev_dbg(sbd_core(), "%s:%u: lv1 get_lpm_status failed: "
+ "reg %u, %s\n", __func__, __LINE__, reg,
+ ps3_result(result));
+ }
+ return (u32)val;
+ case ext_tr_timer:
+ return 0;
+ default:
+ dev_dbg(sbd_core(), "%s:%u: unknown reg: %d\n", __func__,
+ __LINE__, reg);
+ BUG();
+ break;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ps3_read_pm);
+
+/**
+ * ps3_write_pm - Write Other LPM control registers.
+ */
+
+void ps3_write_pm(u32 cpu, enum pm_reg_name reg, u32 val)
+{
+ int result = 0;
+ u64 dummy;
+
+ switch (reg) {
+ case group_control:
+ if (val != lpm_priv->shadow.group_control)
+ result = lv1_set_lpm_group_control(lpm_priv->lpm_id,
+ val,
+ PS3_WRITE_PM_MASK,
+ &dummy);
+ lpm_priv->shadow.group_control = val;
+ break;
+ case debug_bus_control:
+ if (val != lpm_priv->shadow.debug_bus_control)
+ result = lv1_set_lpm_debug_bus_control(lpm_priv->lpm_id,
+ val,
+ PS3_WRITE_PM_MASK,
+ &dummy);
+ lpm_priv->shadow.debug_bus_control = val;
+ break;
+ case pm_control:
+ if (use_start_stop_bookmark)
+ val |= (PS3_PM_CONTROL_PPU_TH0_BOOKMARK |
+ PS3_PM_CONTROL_PPU_TH1_BOOKMARK);
+ if (val != lpm_priv->shadow.pm_control)
+ result = lv1_set_lpm_general_control(lpm_priv->lpm_id,
+ val,
+ PS3_WRITE_PM_MASK,
+ 0, 0, &dummy,
+ &dummy);
+ lpm_priv->shadow.pm_control = val;
+ break;
+ case pm_interval:
+ result = lv1_set_lpm_interval(lpm_priv->lpm_id, val,
+ PS3_WRITE_PM_MASK, &dummy);
+ break;
+ case pm_start_stop:
+ if (val != lpm_priv->shadow.pm_start_stop)
+ result = lv1_set_lpm_trigger_control(lpm_priv->lpm_id,
+ val,
+ PS3_WRITE_PM_MASK,
+ &dummy);
+ lpm_priv->shadow.pm_start_stop = val;
+ break;
+ case trace_address:
+ case ext_tr_timer:
+ case pm_status:
+ break;
+ default:
+ dev_dbg(sbd_core(), "%s:%u: unknown reg: %d\n", __func__,
+ __LINE__, reg);
+ BUG();
+ break;
+ }
+
+ if (result)
+ dev_err(sbd_core(), "%s:%u: lv1 set_control failed: "
+ "reg %u, %s\n", __func__, __LINE__, reg,
+ ps3_result(result));
+}
+EXPORT_SYMBOL_GPL(ps3_write_pm);
+
+/**
+ * ps3_get_ctr_size - Get the size of a physical counter.
+ *
+ * Returns either 16 or 32.
+ */
+
+u32 ps3_get_ctr_size(u32 cpu, u32 phys_ctr)
+{
+ u32 pm_ctrl;
+
+ if (phys_ctr >= NR_PHYS_CTRS) {
+ dev_dbg(sbd_core(), "%s:%u: phys_ctr too big: %u\n", __func__,
+ __LINE__, phys_ctr);
+ return 0;
+ }
+
+ pm_ctrl = ps3_read_pm(cpu, pm_control);
+ return (pm_ctrl & CBE_PM_16BIT_CTR(phys_ctr)) ? 16 : 32;
+}
+EXPORT_SYMBOL_GPL(ps3_get_ctr_size);
+
+/**
+ * ps3_set_ctr_size - Set the size of a physical counter to 16 or 32 bits.
+ */
+
+void ps3_set_ctr_size(u32 cpu, u32 phys_ctr, u32 ctr_size)
+{
+ u32 pm_ctrl;
+
+ if (phys_ctr >= NR_PHYS_CTRS) {
+ dev_dbg(sbd_core(), "%s:%u: phys_ctr too big: %u\n", __func__,
+ __LINE__, phys_ctr);
+ return;
+ }
+
+ pm_ctrl = ps3_read_pm(cpu, pm_control);
+
+ switch (ctr_size) {
+ case 16:
+ pm_ctrl |= CBE_PM_16BIT_CTR(phys_ctr);
+ ps3_write_pm(cpu, pm_control, pm_ctrl);
+ break;
+
+ case 32:
+ pm_ctrl &= ~CBE_PM_16BIT_CTR(phys_ctr);
+ ps3_write_pm(cpu, pm_control, pm_ctrl);
+ break;
+ default:
+ BUG();
+ }
+}
+EXPORT_SYMBOL_GPL(ps3_set_ctr_size);
+
+static u64 pm_translate_signal_group_number_on_island2(u64 subgroup)
+{
+
+ if (subgroup == 2)
+ subgroup = 3;
+
+ if (subgroup <= 6)
+ return PM_ISLAND2_BASE_SIGNAL_GROUP_NUMBER + subgroup;
+ else if (subgroup == 7)
+ return PM_ISLAND2_SIGNAL_GROUP_NUMBER1;
+ else
+ return PM_ISLAND2_SIGNAL_GROUP_NUMBER2;
+}
+
+static u64 pm_translate_signal_group_number_on_island3(u64 subgroup)
+{
+
+ switch (subgroup) {
+ case 2:
+ case 3:
+ case 4:
+ subgroup += 2;
+ break;
+ case 5:
+ subgroup = 8;
+ break;
+ default:
+ break;
+ }
+ return PM_ISLAND3_BASE_SIGNAL_GROUP_NUMBER + subgroup;
+}
+
+static u64 pm_translate_signal_group_number_on_island4(u64 subgroup)
+{
+ return PM_ISLAND4_BASE_SIGNAL_GROUP_NUMBER + subgroup;
+}
+
+static u64 pm_translate_signal_group_number_on_island5(u64 subgroup)
+{
+
+ switch (subgroup) {
+ case 3:
+ subgroup = 4;
+ break;
+ case 4:
+ subgroup = 6;
+ break;
+ default:
+ break;
+ }
+ return PM_ISLAND5_BASE_SIGNAL_GROUP_NUMBER + subgroup;
+}
+
+static u64 pm_translate_signal_group_number_on_island6(u64 subgroup,
+ u64 subsubgroup)
+{
+ switch (subgroup) {
+ case 3:
+ case 4:
+ case 5:
+ subgroup += 1;
+ break;
+ default:
+ break;
+ }
+
+ switch (subsubgroup) {
+ case 4:
+ case 5:
+ case 6:
+ subsubgroup += 2;
+ break;
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ subsubgroup += 4;
+ break;
+ case 11:
+ case 12:
+ case 13:
+ subsubgroup += 5;
+ break;
+ default:
+ break;
+ }
+
+ if (subgroup <= 5)
+ return (PM_ISLAND6_BASE_SIGNAL_GROUP_NUMBER + subgroup);
+ else
+ return (PM_ISLAND6_BASE_SIGNAL_GROUP_NUMBER + subgroup
+ + subsubgroup - 1);
+}
+
+static u64 pm_translate_signal_group_number_on_island7(u64 subgroup)
+{
+ return PM_ISLAND7_BASE_SIGNAL_GROUP_NUMBER + subgroup;
+}
+
+static u64 pm_translate_signal_group_number_on_island8(u64 subgroup)
+{
+ return PM_ISLAND8_BASE_SIGNAL_GROUP_NUMBER + subgroup;
+}
+
+static u64 pm_signal_group_to_ps3_lv1_signal_group(u64 group)
+{
+ u64 island;
+ u64 subgroup;
+ u64 subsubgroup;
+
+ subgroup = 0;
+ subsubgroup = 0;
+ island = 0;
+ if (group < 1000) {
+ if (group < 100) {
+ if (20 <= group && group < 30) {
+ island = 2;
+ subgroup = group - 20;
+ } else if (30 <= group && group < 40) {
+ island = 3;
+ subgroup = group - 30;
+ } else if (40 <= group && group < 50) {
+ island = 4;
+ subgroup = group - 40;
+ } else if (50 <= group && group < 60) {
+ island = 5;
+ subgroup = group - 50;
+ } else if (60 <= group && group < 70) {
+ island = 6;
+ subgroup = group - 60;
+ } else if (70 <= group && group < 80) {
+ island = 7;
+ subgroup = group - 70;
+ } else if (80 <= group && group < 90) {
+ island = 8;
+ subgroup = group - 80;
+ }
+ } else if (200 <= group && group < 300) {
+ island = 2;
+ subgroup = group - 200;
+ } else if (600 <= group && group < 700) {
+ island = 6;
+ subgroup = 5;
+ subsubgroup = group - 650;
+ }
+ } else if (6000 <= group && group < 7000) {
+ island = 6;
+ subgroup = 5;
+ subsubgroup = group - 6500;
+ }
+
+ switch (island) {
+ case 2:
+ return pm_translate_signal_group_number_on_island2(subgroup);
+ case 3:
+ return pm_translate_signal_group_number_on_island3(subgroup);
+ case 4:
+ return pm_translate_signal_group_number_on_island4(subgroup);
+ case 5:
+ return pm_translate_signal_group_number_on_island5(subgroup);
+ case 6:
+ return pm_translate_signal_group_number_on_island6(subgroup,
+ subsubgroup);
+ case 7:
+ return pm_translate_signal_group_number_on_island7(subgroup);
+ case 8:
+ return pm_translate_signal_group_number_on_island8(subgroup);
+ default:
+ dev_dbg(sbd_core(), "%s:%u: island not found: %llu\n", __func__,
+ __LINE__, group);
+ BUG();
+ break;
+ }
+ return 0;
+}
+
+static u64 pm_bus_word_to_ps3_lv1_bus_word(u8 word)
+{
+
+ switch (word) {
+ case 1:
+ return 0xF000;
+ case 2:
+ return 0x0F00;
+ case 4:
+ return 0x00F0;
+ case 8:
+ default:
+ return 0x000F;
+ }
+}
+
+static int __ps3_set_signal(u64 lv1_signal_group, u64 bus_select,
+ u64 signal_select, u64 attr1, u64 attr2, u64 attr3)
+{
+ int ret;
+
+ ret = lv1_set_lpm_signal(lpm_priv->lpm_id, lv1_signal_group, bus_select,
+ signal_select, attr1, attr2, attr3);
+ if (ret)
+ dev_err(sbd_core(),
+ "%s:%u: error:%d 0x%llx 0x%llx 0x%llx 0x%llx 0x%llx 0x%llx\n",
+ __func__, __LINE__, ret, lv1_signal_group, bus_select,
+ signal_select, attr1, attr2, attr3);
+
+ return ret;
+}
+
+int ps3_set_signal(u64 signal_group, u8 signal_bit, u16 sub_unit,
+ u8 bus_word)
+{
+ int ret;
+ u64 lv1_signal_group;
+ u64 bus_select;
+ u64 signal_select;
+ u64 attr1, attr2, attr3;
+
+ if (signal_group == 0)
+ return __ps3_set_signal(0, 0, 0, 0, 0, 0);
+
+ lv1_signal_group =
+ pm_signal_group_to_ps3_lv1_signal_group(signal_group);
+ bus_select = pm_bus_word_to_ps3_lv1_bus_word(bus_word);
+
+ switch (signal_group) {
+ case PM_SIG_GROUP_SPU_TRIGGER:
+ signal_select = 1;
+ signal_select = signal_select << (63 - signal_bit);
+ break;
+ case PM_SIG_GROUP_SPU_EVENT:
+ signal_select = 1;
+ signal_select = (signal_select << (63 - signal_bit)) | 0x3;
+ break;
+ default:
+ signal_select = 0;
+ break;
+ }
+
+ /*
+ * 0: physical object.
+ * 1: logical object.
+ * This parameter is only used for the PPE and SPE signals.
+ */
+ attr1 = 1;
+
+ /*
+ * This parameter is used to specify the target physical/logical
+ * PPE/SPE object.
+ */
+ if (PM_SIG_GROUP_SPU <= signal_group &&
+ signal_group < PM_SIG_GROUP_MFC_MAX)
+ attr2 = sub_unit;
+ else
+ attr2 = lpm_priv->pu_id;
+
+ /*
+ * This parameter is only used for setting the SPE signal.
+ */
+ attr3 = 0;
+
+ ret = __ps3_set_signal(lv1_signal_group, bus_select, signal_select,
+ attr1, attr2, attr3);
+ if (ret)
+ dev_err(sbd_core(), "%s:%u: __ps3_set_signal failed: %d\n",
+ __func__, __LINE__, ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ps3_set_signal);
+
+u32 ps3_get_hw_thread_id(int cpu)
+{
+ return get_hard_smp_processor_id(cpu);
+}
+EXPORT_SYMBOL_GPL(ps3_get_hw_thread_id);
+
+/**
+ * ps3_enable_pm - Enable the entire performance monitoring unit.
+ *
+ * When we enable the LPM, all pending writes to counters get committed.
+ */
+
+void ps3_enable_pm(u32 cpu)
+{
+ int result;
+ u64 tmp;
+ int insert_bookmark = 0;
+
+ lpm_priv->tb_count = 0;
+
+ if (use_start_stop_bookmark) {
+ if (!(lpm_priv->shadow.pm_start_stop &
+ (PS3_PM_START_STOP_START_MASK
+ | PS3_PM_START_STOP_STOP_MASK))) {
+ result = lv1_set_lpm_trigger_control(lpm_priv->lpm_id,
+ (PS3_PM_START_STOP_PPU_TH0_BOOKMARK_START |
+ PS3_PM_START_STOP_PPU_TH1_BOOKMARK_START |
+ PS3_PM_START_STOP_PPU_TH0_BOOKMARK_STOP |
+ PS3_PM_START_STOP_PPU_TH1_BOOKMARK_STOP),
+ 0xFFFFFFFFFFFFFFFFULL, &tmp);
+
+ if (result)
+ dev_err(sbd_core(), "%s:%u: "
+ "lv1_set_lpm_trigger_control failed: "
+ "%s\n", __func__, __LINE__,
+ ps3_result(result));
+
+ insert_bookmark = !result;
+ }
+ }
+
+ result = lv1_start_lpm(lpm_priv->lpm_id);
+
+ if (result)
+ dev_err(sbd_core(), "%s:%u: lv1_start_lpm failed: %s\n",
+ __func__, __LINE__, ps3_result(result));
+
+ if (use_start_stop_bookmark && !result && insert_bookmark)
+ ps3_set_bookmark(get_tb() | PS3_PM_BOOKMARK_START);
+}
+EXPORT_SYMBOL_GPL(ps3_enable_pm);
+
+/**
+ * ps3_disable_pm - Disable the entire performance monitoring unit.
+ */
+
+void ps3_disable_pm(u32 cpu)
+{
+ int result;
+ u64 tmp;
+
+ ps3_set_bookmark(get_tb() | PS3_PM_BOOKMARK_STOP);
+
+ result = lv1_stop_lpm(lpm_priv->lpm_id, &tmp);
+
+ if (result) {
+ if (result != LV1_WRONG_STATE)
+ dev_err(sbd_core(), "%s:%u: lv1_stop_lpm failed: %s\n",
+ __func__, __LINE__, ps3_result(result));
+ return;
+ }
+
+ lpm_priv->tb_count = tmp;
+
+ dev_dbg(sbd_core(), "%s:%u: tb_count %llu (%llxh)\n", __func__, __LINE__,
+ lpm_priv->tb_count, lpm_priv->tb_count);
+}
+EXPORT_SYMBOL_GPL(ps3_disable_pm);
+
+/**
+ * ps3_lpm_copy_tb - Copy data from the trace buffer to a kernel buffer.
+ * @offset: Offset in bytes from the start of the trace buffer.
+ * @buf: Copy destination.
+ * @count: Maximum count of bytes to copy.
+ * @bytes_copied: Pointer to a variable that will receive the number of
+ * bytes copied to @buf.
+ *
+ * On error @buf will contain any successfully copied trace buffer data
+ * and bytes_copied will be set to the number of bytes successfully copied.
+ */
+
+int ps3_lpm_copy_tb(unsigned long offset, void *buf, unsigned long count,
+ unsigned long *bytes_copied)
+{
+ int result;
+
+ *bytes_copied = 0;
+
+ if (!lpm_priv->tb_cache)
+ return -EPERM;
+
+ if (offset >= lpm_priv->tb_count)
+ return 0;
+
+ count = min_t(u64, count, lpm_priv->tb_count - offset);
+
+ while (*bytes_copied < count) {
+ const unsigned long request = count - *bytes_copied;
+ u64 tmp;
+
+ result = lv1_copy_lpm_trace_buffer(lpm_priv->lpm_id, offset,
+ request, &tmp);
+ if (result) {
+ dev_dbg(sbd_core(), "%s:%u: 0x%lx bytes at 0x%lx\n",
+ __func__, __LINE__, request, offset);
+
+ dev_err(sbd_core(), "%s:%u: lv1_copy_lpm_trace_buffer "
+ "failed: %s\n", __func__, __LINE__,
+ ps3_result(result));
+ return result == LV1_WRONG_STATE ? -EBUSY : -EINVAL;
+ }
+
+ memcpy(buf, lpm_priv->tb_cache, tmp);
+ buf += tmp;
+ *bytes_copied += tmp;
+ offset += tmp;
+ }
+ dev_dbg(sbd_core(), "%s:%u: copied %lxh bytes\n", __func__, __LINE__,
+ *bytes_copied);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ps3_lpm_copy_tb);
+
+/**
+ * ps3_lpm_copy_tb_to_user - Copy data from the trace buffer to a user buffer.
+ * @offset: Offset in bytes from the start of the trace buffer.
+ * @buf: A __user copy destination.
+ * @count: Maximum count of bytes to copy.
+ * @bytes_copied: Pointer to a variable that will receive the number of
+ * bytes copied to @buf.
+ *
+ * On error @buf will contain any successfully copied trace buffer data
+ * and bytes_copied will be set to the number of bytes successfully copied.
+ */
+
+int ps3_lpm_copy_tb_to_user(unsigned long offset, void __user *buf,
+ unsigned long count, unsigned long *bytes_copied)
+{
+ int result;
+
+ *bytes_copied = 0;
+
+ if (!lpm_priv->tb_cache)
+ return -EPERM;
+
+ if (offset >= lpm_priv->tb_count)
+ return 0;
+
+ count = min_t(u64, count, lpm_priv->tb_count - offset);
+
+ while (*bytes_copied < count) {
+ const unsigned long request = count - *bytes_copied;
+ u64 tmp;
+
+ result = lv1_copy_lpm_trace_buffer(lpm_priv->lpm_id, offset,
+ request, &tmp);
+ if (result) {
+ dev_dbg(sbd_core(), "%s:%u: 0x%lx bytes at 0x%lx\n",
+ __func__, __LINE__, request, offset);
+ dev_err(sbd_core(), "%s:%u: lv1_copy_lpm_trace_buffer "
+ "failed: %s\n", __func__, __LINE__,
+ ps3_result(result));
+ return result == LV1_WRONG_STATE ? -EBUSY : -EINVAL;
+ }
+
+ result = copy_to_user(buf, lpm_priv->tb_cache, tmp);
+
+ if (result) {
+ dev_dbg(sbd_core(), "%s:%u: 0x%llx bytes at 0x%p\n",
+ __func__, __LINE__, tmp, buf);
+ dev_err(sbd_core(), "%s:%u: copy_to_user failed: %d\n",
+ __func__, __LINE__, result);
+ return -EFAULT;
+ }
+
+ buf += tmp;
+ *bytes_copied += tmp;
+ offset += tmp;
+ }
+ dev_dbg(sbd_core(), "%s:%u: copied %lxh bytes\n", __func__, __LINE__,
+ *bytes_copied);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ps3_lpm_copy_tb_to_user);
+
+/**
+ * ps3_get_and_clear_pm_interrupts -
+ *
+ * Clearing interrupts for the entire performance monitoring unit.
+ * Reading pm_status clears the interrupt bits.
+ */
+
+u32 ps3_get_and_clear_pm_interrupts(u32 cpu)
+{
+ return ps3_read_pm(cpu, pm_status);
+}
+EXPORT_SYMBOL_GPL(ps3_get_and_clear_pm_interrupts);
+
+/**
+ * ps3_enable_pm_interrupts -
+ *
+ * Enabling interrupts for the entire performance monitoring unit.
+ * Enables the interrupt bits in the pm_status register.
+ */
+
+void ps3_enable_pm_interrupts(u32 cpu, u32 thread, u32 mask)
+{
+ if (mask)
+ ps3_write_pm(cpu, pm_status, mask);
+}
+EXPORT_SYMBOL_GPL(ps3_enable_pm_interrupts);
+
+/**
+ * ps3_enable_pm_interrupts -
+ *
+ * Disabling interrupts for the entire performance monitoring unit.
+ */
+
+void ps3_disable_pm_interrupts(u32 cpu)
+{
+ ps3_get_and_clear_pm_interrupts(cpu);
+ ps3_write_pm(cpu, pm_status, 0);
+}
+EXPORT_SYMBOL_GPL(ps3_disable_pm_interrupts);
+
+/**
+ * ps3_lpm_open - Open the logical performance monitor device.
+ * @tb_type: Specifies the type of trace buffer lv1 should use for this lpm
+ * instance, specified by one of enum ps3_lpm_tb_type.
+ * @tb_cache: Optional user supplied buffer to use as the trace buffer cache.
+ * If NULL, the driver will allocate and manage an internal buffer.
+ * Unused when when @tb_type is PS3_LPM_TB_TYPE_NONE.
+ * @tb_cache_size: The size in bytes of the user supplied @tb_cache buffer.
+ * Unused when @tb_cache is NULL or @tb_type is PS3_LPM_TB_TYPE_NONE.
+ */
+
+int ps3_lpm_open(enum ps3_lpm_tb_type tb_type, void *tb_cache,
+ u64 tb_cache_size)
+{
+ int result;
+ u64 tb_size;
+
+ BUG_ON(!lpm_priv);
+ BUG_ON(tb_type != PS3_LPM_TB_TYPE_NONE
+ && tb_type != PS3_LPM_TB_TYPE_INTERNAL);
+
+ if (tb_type == PS3_LPM_TB_TYPE_NONE && tb_cache)
+ dev_dbg(sbd_core(), "%s:%u: bad in vals\n", __func__, __LINE__);
+
+ if (!atomic_add_unless(&lpm_priv->open, 1, 1)) {
+ dev_dbg(sbd_core(), "%s:%u: busy\n", __func__, __LINE__);
+ return -EBUSY;
+ }
+
+ /* Note tb_cache needs 128 byte alignment. */
+
+ if (tb_type == PS3_LPM_TB_TYPE_NONE) {
+ lpm_priv->tb_cache_size = 0;
+ lpm_priv->tb_cache_internal = NULL;
+ lpm_priv->tb_cache = NULL;
+ } else if (tb_cache) {
+ if (tb_cache != (void *)ALIGN((unsigned long)tb_cache, 128)
+ || tb_cache_size != ALIGN(tb_cache_size, 128)) {
+ dev_err(sbd_core(), "%s:%u: unaligned tb_cache\n",
+ __func__, __LINE__);
+ result = -EINVAL;
+ goto fail_align;
+ }
+ lpm_priv->tb_cache_size = tb_cache_size;
+ lpm_priv->tb_cache_internal = NULL;
+ lpm_priv->tb_cache = tb_cache;
+ } else {
+ lpm_priv->tb_cache_size = PS3_LPM_DEFAULT_TB_CACHE_SIZE;
+ lpm_priv->tb_cache_internal = kzalloc(
+ lpm_priv->tb_cache_size + 127, GFP_KERNEL);
+ if (!lpm_priv->tb_cache_internal) {
+ result = -ENOMEM;
+ goto fail_malloc;
+ }
+ lpm_priv->tb_cache = (void *)ALIGN(
+ (unsigned long)lpm_priv->tb_cache_internal, 128);
+ }
+
+ result = lv1_construct_lpm(lpm_priv->node_id, tb_type, 0, 0,
+ ps3_mm_phys_to_lpar(__pa(lpm_priv->tb_cache)),
+ lpm_priv->tb_cache_size, &lpm_priv->lpm_id,
+ &lpm_priv->outlet_id, &tb_size);
+
+ if (result) {
+ dev_err(sbd_core(), "%s:%u: lv1_construct_lpm failed: %s\n",
+ __func__, __LINE__, ps3_result(result));
+ result = -EINVAL;
+ goto fail_construct;
+ }
+
+ lpm_priv->shadow.pm_control = PS3_LPM_SHADOW_REG_INIT;
+ lpm_priv->shadow.pm_start_stop = PS3_LPM_SHADOW_REG_INIT;
+ lpm_priv->shadow.group_control = PS3_LPM_SHADOW_REG_INIT;
+ lpm_priv->shadow.debug_bus_control = PS3_LPM_SHADOW_REG_INIT;
+
+ dev_dbg(sbd_core(), "%s:%u: lpm_id 0x%llx, outlet_id 0x%llx, "
+ "tb_size 0x%llx\n", __func__, __LINE__, lpm_priv->lpm_id,
+ lpm_priv->outlet_id, tb_size);
+
+ return 0;
+
+fail_construct:
+ kfree(lpm_priv->tb_cache_internal);
+ lpm_priv->tb_cache_internal = NULL;
+fail_malloc:
+fail_align:
+ atomic_dec(&lpm_priv->open);
+ return result;
+}
+EXPORT_SYMBOL_GPL(ps3_lpm_open);
+
+/**
+ * ps3_lpm_close - Close the lpm device.
+ *
+ */
+
+int ps3_lpm_close(void)
+{
+ dev_dbg(sbd_core(), "%s:%u\n", __func__, __LINE__);
+
+ lv1_destruct_lpm(lpm_priv->lpm_id);
+ lpm_priv->lpm_id = 0;
+
+ kfree(lpm_priv->tb_cache_internal);
+ lpm_priv->tb_cache_internal = NULL;
+
+ atomic_dec(&lpm_priv->open);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ps3_lpm_close);
+
+static int ps3_lpm_probe(struct ps3_system_bus_device *dev)
+{
+ dev_dbg(&dev->core, " -> %s:%u\n", __func__, __LINE__);
+
+ if (lpm_priv) {
+ dev_info(&dev->core, "%s:%u: called twice\n",
+ __func__, __LINE__);
+ return -EBUSY;
+ }
+
+ lpm_priv = kzalloc(sizeof(*lpm_priv), GFP_KERNEL);
+
+ if (!lpm_priv)
+ return -ENOMEM;
+
+ lpm_priv->sbd = dev;
+ lpm_priv->node_id = dev->lpm.node_id;
+ lpm_priv->pu_id = dev->lpm.pu_id;
+ lpm_priv->rights = dev->lpm.rights;
+
+ dev_info(&dev->core, " <- %s:%u:\n", __func__, __LINE__);
+
+ return 0;
+}
+
+static void ps3_lpm_remove(struct ps3_system_bus_device *dev)
+{
+ dev_dbg(&dev->core, " -> %s:%u:\n", __func__, __LINE__);
+
+ ps3_lpm_close();
+
+ kfree(lpm_priv);
+ lpm_priv = NULL;
+
+ dev_info(&dev->core, " <- %s:%u:\n", __func__, __LINE__);
+}
+
+static struct ps3_system_bus_driver ps3_lpm_driver = {
+ .match_id = PS3_MATCH_ID_LPM,
+ .core.name = "ps3-lpm",
+ .core.owner = THIS_MODULE,
+ .probe = ps3_lpm_probe,
+ .remove = ps3_lpm_remove,
+ .shutdown = ps3_lpm_remove,
+};
+
+static int __init ps3_lpm_init(void)
+{
+ pr_debug("%s:%d:\n", __func__, __LINE__);
+ return ps3_system_bus_driver_register(&ps3_lpm_driver);
+}
+
+static void __exit ps3_lpm_exit(void)
+{
+ pr_debug("%s:%d:\n", __func__, __LINE__);
+ ps3_system_bus_driver_unregister(&ps3_lpm_driver);
+}
+
+module_init(ps3_lpm_init);
+module_exit(ps3_lpm_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PS3 Logical Performance Monitor Driver");
+MODULE_AUTHOR("Sony Corporation");
+MODULE_ALIAS(PS3_MODULE_ALIAS_LPM);
diff --git a/drivers/ps3/ps3-sys-manager.c b/drivers/ps3/ps3-sys-manager.c
new file mode 100644
index 000000000..ad8ef59de
--- /dev/null
+++ b/drivers/ps3/ps3-sys-manager.c
@@ -0,0 +1,756 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PS3 System Manager.
+ *
+ * Copyright (C) 2007 Sony Computer Entertainment Inc.
+ * Copyright 2007 Sony Corp.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/reboot.h>
+#include <linux/sched/signal.h>
+
+#include <asm/firmware.h>
+#include <asm/lv1call.h>
+#include <asm/ps3.h>
+
+#include "vuart.h"
+
+/**
+ * ps3_sys_manager - PS3 system manager driver.
+ *
+ * The system manager provides an asynchronous system event notification
+ * mechanism for reporting events like thermal alert and button presses to
+ * guests. It also provides support to control system shutdown and startup.
+ *
+ * The actual system manager is implemented as an application running in the
+ * system policy module in lpar_1. Guests communicate with the system manager
+ * through port 2 of the vuart using a simple packet message protocol.
+ * Messages are comprised of a fixed field header followed by a message
+ * specific payload.
+ */
+
+/**
+ * struct ps3_sys_manager_header - System manager message header.
+ * @version: Header version, currently 1.
+ * @size: Header size in bytes, currently 16.
+ * @payload_size: Message payload size in bytes.
+ * @service_id: Message type, one of enum ps3_sys_manager_service_id.
+ * @request_tag: Unique number to identify reply.
+ */
+
+struct ps3_sys_manager_header {
+ /* version 1 */
+ u8 version;
+ u8 size;
+ u16 reserved_1;
+ u32 payload_size;
+ u16 service_id;
+ u16 reserved_2;
+ u32 request_tag;
+};
+
+#define dump_sm_header(_h) _dump_sm_header(_h, __func__, __LINE__)
+static void __maybe_unused _dump_sm_header(
+ const struct ps3_sys_manager_header *h, const char *func, int line)
+{
+ pr_debug("%s:%d: version: %xh\n", func, line, h->version);
+ pr_debug("%s:%d: size: %xh\n", func, line, h->size);
+ pr_debug("%s:%d: payload_size: %xh\n", func, line, h->payload_size);
+ pr_debug("%s:%d: service_id: %xh\n", func, line, h->service_id);
+ pr_debug("%s:%d: request_tag: %xh\n", func, line, h->request_tag);
+}
+
+/**
+ * @PS3_SM_RX_MSG_LEN_MIN - Shortest received message length.
+ * @PS3_SM_RX_MSG_LEN_MAX - Longest received message length.
+ *
+ * Currently all messages received from the system manager are either
+ * (16 bytes header + 8 bytes payload = 24 bytes) or (16 bytes header
+ * + 16 bytes payload = 32 bytes). This knowledge is used to simplify
+ * the logic.
+ */
+
+enum {
+ PS3_SM_RX_MSG_LEN_MIN = 24,
+ PS3_SM_RX_MSG_LEN_MAX = 32,
+};
+
+/**
+ * enum ps3_sys_manager_service_id - Message header service_id.
+ * @PS3_SM_SERVICE_ID_REQUEST: guest --> sys_manager.
+ * @PS3_SM_SERVICE_ID_REQUEST_ERROR: guest <-- sys_manager.
+ * @PS3_SM_SERVICE_ID_COMMAND: guest <-- sys_manager.
+ * @PS3_SM_SERVICE_ID_RESPONSE: guest --> sys_manager.
+ * @PS3_SM_SERVICE_ID_SET_ATTR: guest --> sys_manager.
+ * @PS3_SM_SERVICE_ID_EXTERN_EVENT: guest <-- sys_manager.
+ * @PS3_SM_SERVICE_ID_SET_NEXT_OP: guest --> sys_manager.
+ *
+ * PS3_SM_SERVICE_ID_REQUEST_ERROR is returned for invalid data values in a
+ * a PS3_SM_SERVICE_ID_REQUEST message. It also seems to be returned when
+ * a REQUEST message is sent at the wrong time.
+ */
+
+enum ps3_sys_manager_service_id {
+ /* version 1 */
+ PS3_SM_SERVICE_ID_REQUEST = 1,
+ PS3_SM_SERVICE_ID_RESPONSE = 2,
+ PS3_SM_SERVICE_ID_COMMAND = 3,
+ PS3_SM_SERVICE_ID_EXTERN_EVENT = 4,
+ PS3_SM_SERVICE_ID_SET_NEXT_OP = 5,
+ PS3_SM_SERVICE_ID_REQUEST_ERROR = 6,
+ PS3_SM_SERVICE_ID_SET_ATTR = 8,
+};
+
+/**
+ * enum ps3_sys_manager_attr - Notification attribute (bit position mask).
+ * @PS3_SM_ATTR_POWER: Power button.
+ * @PS3_SM_ATTR_RESET: Reset button, not available on retail console.
+ * @PS3_SM_ATTR_THERMAL: System thermal alert.
+ * @PS3_SM_ATTR_CONTROLLER: Remote controller event.
+ * @PS3_SM_ATTR_ALL: Logical OR of all.
+ *
+ * The guest tells the system manager which events it is interested in receiving
+ * notice of by sending the system manager a logical OR of notification
+ * attributes via the ps3_sys_manager_send_attr() routine.
+ */
+
+enum ps3_sys_manager_attr {
+ /* version 1 */
+ PS3_SM_ATTR_POWER = 1,
+ PS3_SM_ATTR_RESET = 2,
+ PS3_SM_ATTR_THERMAL = 4,
+ PS3_SM_ATTR_CONTROLLER = 8, /* bogus? */
+ PS3_SM_ATTR_ALL = 0x0f,
+};
+
+/**
+ * enum ps3_sys_manager_event - External event type, reported by system manager.
+ * @PS3_SM_EVENT_POWER_PRESSED: payload.value =
+ * enum ps3_sys_manager_button_event.
+ * @PS3_SM_EVENT_POWER_RELEASED: payload.value = time pressed in millisec.
+ * @PS3_SM_EVENT_RESET_PRESSED: payload.value =
+ * enum ps3_sys_manager_button_event.
+ * @PS3_SM_EVENT_RESET_RELEASED: payload.value = time pressed in millisec.
+ * @PS3_SM_EVENT_THERMAL_ALERT: payload.value = thermal zone id.
+ * @PS3_SM_EVENT_THERMAL_CLEARED: payload.value = thermal zone id.
+ */
+
+enum ps3_sys_manager_event {
+ /* version 1 */
+ PS3_SM_EVENT_POWER_PRESSED = 3,
+ PS3_SM_EVENT_POWER_RELEASED = 4,
+ PS3_SM_EVENT_RESET_PRESSED = 5,
+ PS3_SM_EVENT_RESET_RELEASED = 6,
+ PS3_SM_EVENT_THERMAL_ALERT = 7,
+ PS3_SM_EVENT_THERMAL_CLEARED = 8,
+ /* no info on controller events */
+};
+
+/**
+ * enum ps3_sys_manager_button_event - Button event payload values.
+ * @PS3_SM_BUTTON_EVENT_HARD: Hardware generated event.
+ * @PS3_SM_BUTTON_EVENT_SOFT: Software generated event.
+ */
+
+enum ps3_sys_manager_button_event {
+ PS3_SM_BUTTON_EVENT_HARD = 0,
+ PS3_SM_BUTTON_EVENT_SOFT = 1,
+};
+
+/**
+ * enum ps3_sys_manager_next_op - Operation to perform after lpar is destroyed.
+ */
+
+enum ps3_sys_manager_next_op {
+ /* version 3 */
+ PS3_SM_NEXT_OP_SYS_SHUTDOWN = 1,
+ PS3_SM_NEXT_OP_SYS_REBOOT = 2,
+ PS3_SM_NEXT_OP_LPAR_REBOOT = 0x82,
+};
+
+/**
+ * enum ps3_sys_manager_wake_source - Next-op wakeup source (bit position mask).
+ * @PS3_SM_WAKE_DEFAULT: Disk insert, power button, eject button.
+ * @PS3_SM_WAKE_W_O_L: Ether or wireless LAN.
+ * @PS3_SM_WAKE_P_O_R: Power on reset.
+ *
+ * Additional wakeup sources when specifying PS3_SM_NEXT_OP_SYS_SHUTDOWN.
+ * The system will always wake from the PS3_SM_WAKE_DEFAULT sources.
+ * Sources listed here are the only ones available to guests in the
+ * other-os lpar.
+ */
+
+enum ps3_sys_manager_wake_source {
+ /* version 3 */
+ PS3_SM_WAKE_DEFAULT = 0,
+ PS3_SM_WAKE_W_O_L = 0x00000400,
+ PS3_SM_WAKE_P_O_R = 0x80000000,
+};
+
+/**
+ * user_wake_sources - User specified wakeup sources.
+ *
+ * Logical OR of enum ps3_sys_manager_wake_source types.
+ */
+
+static u32 user_wake_sources = PS3_SM_WAKE_DEFAULT;
+
+/**
+ * enum ps3_sys_manager_cmd - Command from system manager to guest.
+ *
+ * The guest completes the actions needed, then acks or naks the command via
+ * ps3_sys_manager_send_response(). In the case of @PS3_SM_CMD_SHUTDOWN,
+ * the guest must be fully prepared for a system poweroff prior to acking the
+ * command.
+ */
+
+enum ps3_sys_manager_cmd {
+ /* version 1 */
+ PS3_SM_CMD_SHUTDOWN = 1, /* shutdown guest OS */
+};
+
+/**
+ * ps3_sm_force_power_off - Poweroff helper.
+ *
+ * A global variable used to force a poweroff when the power button has
+ * been pressed irrespective of how init handles the ctrl_alt_del signal.
+ *
+ */
+
+static unsigned int ps3_sm_force_power_off;
+
+/**
+ * ps3_sys_manager_write - Helper to write a two part message to the vuart.
+ *
+ */
+
+static int ps3_sys_manager_write(struct ps3_system_bus_device *dev,
+ const struct ps3_sys_manager_header *header, const void *payload)
+{
+ int result;
+
+ BUG_ON(header->version != 1);
+ BUG_ON(header->size != 16);
+ BUG_ON(header->payload_size != 8 && header->payload_size != 16);
+ BUG_ON(header->service_id > 8);
+
+ result = ps3_vuart_write(dev, header,
+ sizeof(struct ps3_sys_manager_header));
+
+ if (!result)
+ result = ps3_vuart_write(dev, payload, header->payload_size);
+
+ return result;
+}
+
+/**
+ * ps3_sys_manager_send_attr - Send a 'set attribute' to the system manager.
+ *
+ */
+
+static int ps3_sys_manager_send_attr(struct ps3_system_bus_device *dev,
+ enum ps3_sys_manager_attr attr)
+{
+ struct ps3_sys_manager_header header;
+ struct {
+ u8 version;
+ u8 reserved_1[3];
+ u32 attribute;
+ } payload;
+
+ BUILD_BUG_ON(sizeof(payload) != 8);
+
+ dev_dbg(&dev->core, "%s:%d: %xh\n", __func__, __LINE__, attr);
+
+ memset(&header, 0, sizeof(header));
+ header.version = 1;
+ header.size = 16;
+ header.payload_size = 16;
+ header.service_id = PS3_SM_SERVICE_ID_SET_ATTR;
+
+ memset(&payload, 0, sizeof(payload));
+ payload.version = 1;
+ payload.attribute = attr;
+
+ return ps3_sys_manager_write(dev, &header, &payload);
+}
+
+/**
+ * ps3_sys_manager_send_next_op - Send a 'set next op' to the system manager.
+ *
+ * Tell the system manager what to do after this lpar is destroyed.
+ */
+
+static int ps3_sys_manager_send_next_op(struct ps3_system_bus_device *dev,
+ enum ps3_sys_manager_next_op op,
+ enum ps3_sys_manager_wake_source wake_source)
+{
+ struct ps3_sys_manager_header header;
+ struct {
+ u8 version;
+ u8 type;
+ u8 gos_id;
+ u8 reserved_1;
+ u32 wake_source;
+ u8 reserved_2[8];
+ } payload;
+
+ BUILD_BUG_ON(sizeof(payload) != 16);
+
+ dev_dbg(&dev->core, "%s:%d: (%xh)\n", __func__, __LINE__, op);
+
+ memset(&header, 0, sizeof(header));
+ header.version = 1;
+ header.size = 16;
+ header.payload_size = 16;
+ header.service_id = PS3_SM_SERVICE_ID_SET_NEXT_OP;
+
+ memset(&payload, 0, sizeof(payload));
+ payload.version = 3;
+ payload.type = op;
+ payload.gos_id = 3; /* other os */
+ payload.wake_source = wake_source;
+
+ return ps3_sys_manager_write(dev, &header, &payload);
+}
+
+/**
+ * ps3_sys_manager_send_request_shutdown - Send 'request' to the system manager.
+ *
+ * The guest sends this message to request an operation or action of the system
+ * manager. The reply is a command message from the system manager. In the
+ * command handler the guest performs the requested operation. The result of
+ * the command is then communicated back to the system manager with a response
+ * message.
+ *
+ * Currently, the only supported request is the 'shutdown self' request.
+ */
+
+static int ps3_sys_manager_send_request_shutdown(
+ struct ps3_system_bus_device *dev)
+{
+ struct ps3_sys_manager_header header;
+ struct {
+ u8 version;
+ u8 type;
+ u8 gos_id;
+ u8 reserved_1[13];
+ } payload;
+
+ BUILD_BUG_ON(sizeof(payload) != 16);
+
+ dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
+
+ memset(&header, 0, sizeof(header));
+ header.version = 1;
+ header.size = 16;
+ header.payload_size = 16;
+ header.service_id = PS3_SM_SERVICE_ID_REQUEST;
+
+ memset(&payload, 0, sizeof(payload));
+ payload.version = 1;
+ payload.type = 1; /* shutdown */
+ payload.gos_id = 0; /* self */
+
+ return ps3_sys_manager_write(dev, &header, &payload);
+}
+
+/**
+ * ps3_sys_manager_send_response - Send a 'response' to the system manager.
+ * @status: zero = success, others fail.
+ *
+ * The guest sends this message to the system manager to acnowledge success or
+ * failure of a command sent by the system manager.
+ */
+
+static int ps3_sys_manager_send_response(struct ps3_system_bus_device *dev,
+ u64 status)
+{
+ struct ps3_sys_manager_header header;
+ struct {
+ u8 version;
+ u8 reserved_1[3];
+ u8 status;
+ u8 reserved_2[11];
+ } payload;
+
+ BUILD_BUG_ON(sizeof(payload) != 16);
+
+ dev_dbg(&dev->core, "%s:%d: (%s)\n", __func__, __LINE__,
+ (status ? "nak" : "ack"));
+
+ memset(&header, 0, sizeof(header));
+ header.version = 1;
+ header.size = 16;
+ header.payload_size = 16;
+ header.service_id = PS3_SM_SERVICE_ID_RESPONSE;
+
+ memset(&payload, 0, sizeof(payload));
+ payload.version = 1;
+ payload.status = status;
+
+ return ps3_sys_manager_write(dev, &header, &payload);
+}
+
+/**
+ * ps3_sys_manager_handle_event - Second stage event msg handler.
+ *
+ */
+
+static int ps3_sys_manager_handle_event(struct ps3_system_bus_device *dev)
+{
+ int result;
+ struct {
+ u8 version;
+ u8 type;
+ u8 reserved_1[2];
+ u32 value;
+ u8 reserved_2[8];
+ } event;
+
+ BUILD_BUG_ON(sizeof(event) != 16);
+
+ result = ps3_vuart_read(dev, &event, sizeof(event));
+ BUG_ON(result && "need to retry here");
+
+ if (event.version != 1) {
+ dev_dbg(&dev->core, "%s:%d: unsupported event version (%u)\n",
+ __func__, __LINE__, event.version);
+ return -EIO;
+ }
+
+ switch (event.type) {
+ case PS3_SM_EVENT_POWER_PRESSED:
+ dev_dbg(&dev->core, "%s:%d: POWER_PRESSED (%s)\n",
+ __func__, __LINE__,
+ (event.value == PS3_SM_BUTTON_EVENT_SOFT ? "soft"
+ : "hard"));
+ ps3_sm_force_power_off = 1;
+ /*
+ * A memory barrier is use here to sync memory since
+ * ps3_sys_manager_final_restart() could be called on
+ * another cpu.
+ */
+ wmb();
+ kill_cad_pid(SIGINT, 1); /* ctrl_alt_del */
+ break;
+ case PS3_SM_EVENT_POWER_RELEASED:
+ dev_dbg(&dev->core, "%s:%d: POWER_RELEASED (%u ms)\n",
+ __func__, __LINE__, event.value);
+ break;
+ case PS3_SM_EVENT_RESET_PRESSED:
+ dev_dbg(&dev->core, "%s:%d: RESET_PRESSED (%s)\n",
+ __func__, __LINE__,
+ (event.value == PS3_SM_BUTTON_EVENT_SOFT ? "soft"
+ : "hard"));
+ ps3_sm_force_power_off = 0;
+ /*
+ * A memory barrier is use here to sync memory since
+ * ps3_sys_manager_final_restart() could be called on
+ * another cpu.
+ */
+ wmb();
+ kill_cad_pid(SIGINT, 1); /* ctrl_alt_del */
+ break;
+ case PS3_SM_EVENT_RESET_RELEASED:
+ dev_dbg(&dev->core, "%s:%d: RESET_RELEASED (%u ms)\n",
+ __func__, __LINE__, event.value);
+ break;
+ case PS3_SM_EVENT_THERMAL_ALERT:
+ dev_dbg(&dev->core, "%s:%d: THERMAL_ALERT (zone %u)\n",
+ __func__, __LINE__, event.value);
+ pr_info("PS3 Thermal Alert Zone %u\n", event.value);
+ break;
+ case PS3_SM_EVENT_THERMAL_CLEARED:
+ dev_dbg(&dev->core, "%s:%d: THERMAL_CLEARED (zone %u)\n",
+ __func__, __LINE__, event.value);
+ break;
+ default:
+ dev_dbg(&dev->core, "%s:%d: unknown event (%u)\n",
+ __func__, __LINE__, event.type);
+ return -EIO;
+ }
+
+ return 0;
+}
+/**
+ * ps3_sys_manager_handle_cmd - Second stage command msg handler.
+ *
+ * The system manager sends this in reply to a 'request' message from the guest.
+ */
+
+static int ps3_sys_manager_handle_cmd(struct ps3_system_bus_device *dev)
+{
+ int result;
+ struct {
+ u8 version;
+ u8 type;
+ u8 reserved_1[14];
+ } cmd;
+
+ BUILD_BUG_ON(sizeof(cmd) != 16);
+
+ dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
+
+ result = ps3_vuart_read(dev, &cmd, sizeof(cmd));
+ BUG_ON(result && "need to retry here");
+
+ if (result)
+ return result;
+
+ if (cmd.version != 1) {
+ dev_dbg(&dev->core, "%s:%d: unsupported cmd version (%u)\n",
+ __func__, __LINE__, cmd.version);
+ return -EIO;
+ }
+
+ if (cmd.type != PS3_SM_CMD_SHUTDOWN) {
+ dev_dbg(&dev->core, "%s:%d: unknown cmd (%u)\n",
+ __func__, __LINE__, cmd.type);
+ return -EIO;
+ }
+
+ ps3_sys_manager_send_response(dev, 0);
+ return 0;
+}
+
+/**
+ * ps3_sys_manager_handle_msg - First stage msg handler.
+ *
+ * Can be called directly to manually poll vuart and pump message handler.
+ */
+
+static int ps3_sys_manager_handle_msg(struct ps3_system_bus_device *dev)
+{
+ int result;
+ struct ps3_sys_manager_header header;
+
+ result = ps3_vuart_read(dev, &header,
+ sizeof(struct ps3_sys_manager_header));
+
+ if (result)
+ return result;
+
+ if (header.version != 1) {
+ dev_dbg(&dev->core, "%s:%d: unsupported header version (%u)\n",
+ __func__, __LINE__, header.version);
+ dump_sm_header(&header);
+ goto fail_header;
+ }
+
+ BUILD_BUG_ON(sizeof(header) != 16);
+
+ if (header.size != 16 || (header.payload_size != 8
+ && header.payload_size != 16)) {
+ dump_sm_header(&header);
+ BUG();
+ }
+
+ switch (header.service_id) {
+ case PS3_SM_SERVICE_ID_EXTERN_EVENT:
+ dev_dbg(&dev->core, "%s:%d: EVENT\n", __func__, __LINE__);
+ return ps3_sys_manager_handle_event(dev);
+ case PS3_SM_SERVICE_ID_COMMAND:
+ dev_dbg(&dev->core, "%s:%d: COMMAND\n", __func__, __LINE__);
+ return ps3_sys_manager_handle_cmd(dev);
+ case PS3_SM_SERVICE_ID_REQUEST_ERROR:
+ dev_dbg(&dev->core, "%s:%d: REQUEST_ERROR\n", __func__,
+ __LINE__);
+ dump_sm_header(&header);
+ break;
+ default:
+ dev_dbg(&dev->core, "%s:%d: unknown service_id (%u)\n",
+ __func__, __LINE__, header.service_id);
+ break;
+ }
+ goto fail_id;
+
+fail_header:
+ ps3_vuart_clear_rx_bytes(dev, 0);
+ return -EIO;
+fail_id:
+ ps3_vuart_clear_rx_bytes(dev, header.payload_size);
+ return -EIO;
+}
+
+static void ps3_sys_manager_fin(struct ps3_system_bus_device *dev)
+{
+ ps3_sys_manager_send_request_shutdown(dev);
+
+ pr_emerg("System Halted, OK to turn off power\n");
+
+ while (ps3_sys_manager_handle_msg(dev)) {
+ /* pause until next DEC interrupt */
+ lv1_pause(0);
+ }
+
+ while (1) {
+ /* pause, ignoring DEC interrupt */
+ lv1_pause(1);
+ }
+}
+
+/**
+ * ps3_sys_manager_final_power_off - The final platform machine_power_off routine.
+ *
+ * This routine never returns. The routine disables asynchronous vuart reads
+ * then spins calling ps3_sys_manager_handle_msg() to receive and acknowledge
+ * the shutdown command sent from the system manager. Soon after the
+ * acknowledgement is sent the lpar is destroyed by the HV. This routine
+ * should only be called from ps3_power_off() through
+ * ps3_sys_manager_ops.power_off.
+ */
+
+static void ps3_sys_manager_final_power_off(struct ps3_system_bus_device *dev)
+{
+ BUG_ON(!dev);
+
+ dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
+
+ ps3_vuart_cancel_async(dev);
+
+ ps3_sys_manager_send_next_op(dev, PS3_SM_NEXT_OP_SYS_SHUTDOWN,
+ user_wake_sources);
+
+ ps3_sys_manager_fin(dev);
+}
+
+/**
+ * ps3_sys_manager_final_restart - The final platform machine_restart routine.
+ *
+ * This routine never returns. The routine disables asynchronous vuart reads
+ * then spins calling ps3_sys_manager_handle_msg() to receive and acknowledge
+ * the shutdown command sent from the system manager. Soon after the
+ * acknowledgement is sent the lpar is destroyed by the HV. This routine
+ * should only be called from ps3_restart() through ps3_sys_manager_ops.restart.
+ */
+
+static void ps3_sys_manager_final_restart(struct ps3_system_bus_device *dev)
+{
+ BUG_ON(!dev);
+
+ dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
+
+ /* Check if we got here via a power button event. */
+
+ if (ps3_sm_force_power_off) {
+ dev_dbg(&dev->core, "%s:%d: forcing poweroff\n",
+ __func__, __LINE__);
+ ps3_sys_manager_final_power_off(dev);
+ }
+
+ ps3_vuart_cancel_async(dev);
+
+ ps3_sys_manager_send_attr(dev, 0);
+ ps3_sys_manager_send_next_op(dev, PS3_SM_NEXT_OP_SYS_REBOOT,
+ user_wake_sources);
+
+ ps3_sys_manager_fin(dev);
+}
+
+/**
+ * ps3_sys_manager_get_wol - Get wake-on-lan setting.
+ */
+
+int ps3_sys_manager_get_wol(void)
+{
+ pr_debug("%s:%d\n", __func__, __LINE__);
+
+ return (user_wake_sources & PS3_SM_WAKE_W_O_L) != 0;
+}
+EXPORT_SYMBOL_GPL(ps3_sys_manager_get_wol);
+
+/**
+ * ps3_sys_manager_set_wol - Set wake-on-lan setting.
+ */
+
+void ps3_sys_manager_set_wol(int state)
+{
+ static DEFINE_MUTEX(mutex);
+
+ mutex_lock(&mutex);
+
+ pr_debug("%s:%d: %d\n", __func__, __LINE__, state);
+
+ if (state)
+ user_wake_sources |= PS3_SM_WAKE_W_O_L;
+ else
+ user_wake_sources &= ~PS3_SM_WAKE_W_O_L;
+ mutex_unlock(&mutex);
+}
+EXPORT_SYMBOL_GPL(ps3_sys_manager_set_wol);
+
+/**
+ * ps3_sys_manager_work - Asynchronous read handler.
+ *
+ * Signaled when PS3_SM_RX_MSG_LEN_MIN bytes arrive at the vuart port.
+ */
+
+static void ps3_sys_manager_work(struct ps3_system_bus_device *dev)
+{
+ ps3_sys_manager_handle_msg(dev);
+ ps3_vuart_read_async(dev, PS3_SM_RX_MSG_LEN_MIN);
+}
+
+static int ps3_sys_manager_probe(struct ps3_system_bus_device *dev)
+{
+ int result;
+ struct ps3_sys_manager_ops ops;
+
+ dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
+
+ ops.power_off = ps3_sys_manager_final_power_off;
+ ops.restart = ps3_sys_manager_final_restart;
+ ops.dev = dev;
+
+ /* ps3_sys_manager_register_ops copies ops. */
+
+ ps3_sys_manager_register_ops(&ops);
+
+ result = ps3_sys_manager_send_attr(dev, PS3_SM_ATTR_ALL);
+ BUG_ON(result);
+
+ result = ps3_vuart_read_async(dev, PS3_SM_RX_MSG_LEN_MIN);
+ BUG_ON(result);
+
+ return result;
+}
+
+static int ps3_sys_manager_remove(struct ps3_system_bus_device *dev)
+{
+ dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
+ return 0;
+}
+
+static void ps3_sys_manager_shutdown(struct ps3_system_bus_device *dev)
+{
+ dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
+}
+
+static struct ps3_vuart_port_driver ps3_sys_manager = {
+ .core.match_id = PS3_MATCH_ID_SYSTEM_MANAGER,
+ .core.core.name = "ps3_sys_manager",
+ .probe = ps3_sys_manager_probe,
+ .remove = ps3_sys_manager_remove,
+ .shutdown = ps3_sys_manager_shutdown,
+ .work = ps3_sys_manager_work,
+};
+
+static int __init ps3_sys_manager_init(void)
+{
+ if (!firmware_has_feature(FW_FEATURE_PS3_LV1))
+ return -ENODEV;
+
+ return ps3_vuart_port_driver_register(&ps3_sys_manager);
+}
+
+module_init(ps3_sys_manager_init);
+/* Module remove not supported. */
+
+MODULE_AUTHOR("Sony Corporation");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PS3 System Manager");
+MODULE_ALIAS(PS3_MODULE_ALIAS_SYSTEM_MANAGER);
diff --git a/drivers/ps3/ps3-vuart.c b/drivers/ps3/ps3-vuart.c
new file mode 100644
index 000000000..6328abd51
--- /dev/null
+++ b/drivers/ps3/ps3-vuart.c
@@ -0,0 +1,1248 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PS3 virtual uart
+ *
+ * Copyright (C) 2006 Sony Computer Entertainment Inc.
+ * Copyright 2006 Sony Corp.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/bitops.h>
+#include <asm/ps3.h>
+
+#include <asm/firmware.h>
+#include <asm/lv1call.h>
+
+#include "vuart.h"
+
+MODULE_AUTHOR("Sony Corporation");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PS3 vuart");
+
+/**
+ * vuart - An inter-partition data link service.
+ * port 0: PS3 AV Settings.
+ * port 2: PS3 System Manager.
+ *
+ * The vuart provides a bi-directional byte stream data link between logical
+ * partitions. Its primary role is as a communications link between the guest
+ * OS and the system policy module. The current HV does not support any
+ * connections other than those listed.
+ */
+
+enum {PORT_COUNT = 3,};
+
+enum vuart_param {
+ PARAM_TX_TRIGGER = 0,
+ PARAM_RX_TRIGGER = 1,
+ PARAM_INTERRUPT_MASK = 2,
+ PARAM_RX_BUF_SIZE = 3, /* read only */
+ PARAM_RX_BYTES = 4, /* read only */
+ PARAM_TX_BUF_SIZE = 5, /* read only */
+ PARAM_TX_BYTES = 6, /* read only */
+ PARAM_INTERRUPT_STATUS = 7, /* read only */
+};
+
+enum vuart_interrupt_bit {
+ INTERRUPT_BIT_TX = 0,
+ INTERRUPT_BIT_RX = 1,
+ INTERRUPT_BIT_DISCONNECT = 2,
+};
+
+enum vuart_interrupt_mask {
+ INTERRUPT_MASK_TX = 1,
+ INTERRUPT_MASK_RX = 2,
+ INTERRUPT_MASK_DISCONNECT = 4,
+};
+
+/**
+ * struct ps3_vuart_port_priv - private vuart device data.
+ */
+
+struct ps3_vuart_port_priv {
+ u64 interrupt_mask;
+
+ struct {
+ spinlock_t lock;
+ struct list_head head;
+ } tx_list;
+ struct {
+ struct ps3_vuart_work work;
+ unsigned long bytes_held;
+ spinlock_t lock;
+ struct list_head head;
+ } rx_list;
+ struct ps3_vuart_stats stats;
+};
+
+static struct ps3_vuart_port_priv *to_port_priv(
+ struct ps3_system_bus_device *dev)
+{
+ BUG_ON(!dev);
+ BUG_ON(!dev->driver_priv);
+ return (struct ps3_vuart_port_priv *)dev->driver_priv;
+}
+
+/**
+ * struct ports_bmp - bitmap indicating ports needing service.
+ *
+ * A 256 bit read only bitmap indicating ports needing service. Do not write
+ * to these bits. Must not cross a page boundary.
+ */
+
+struct ports_bmp {
+ u64 status;
+ u64 unused[3];
+} __attribute__((aligned(32)));
+
+#define dump_ports_bmp(_b) _dump_ports_bmp(_b, __func__, __LINE__)
+static void __maybe_unused _dump_ports_bmp(
+ const struct ports_bmp *bmp, const char *func, int line)
+{
+ pr_debug("%s:%d: ports_bmp: %016llxh\n", func, line, bmp->status);
+}
+
+#define dump_port_params(_b) _dump_port_params(_b, __func__, __LINE__)
+static void __maybe_unused _dump_port_params(unsigned int port_number,
+ const char *func, int line)
+{
+#if defined(DEBUG)
+ static const char *strings[] = {
+ "tx_trigger ",
+ "rx_trigger ",
+ "interrupt_mask ",
+ "rx_buf_size ",
+ "rx_bytes ",
+ "tx_buf_size ",
+ "tx_bytes ",
+ "interrupt_status",
+ };
+ int result;
+ unsigned int i;
+ u64 value;
+
+ for (i = 0; i < ARRAY_SIZE(strings); i++) {
+ result = lv1_get_virtual_uart_param(port_number, i, &value);
+
+ if (result) {
+ pr_debug("%s:%d: port_%u: %s failed: %s\n", func, line,
+ port_number, strings[i], ps3_result(result));
+ continue;
+ }
+ pr_debug("%s:%d: port_%u: %s = %lxh\n",
+ func, line, port_number, strings[i], value);
+ }
+#endif
+}
+
+int ps3_vuart_get_triggers(struct ps3_system_bus_device *dev,
+ struct vuart_triggers *trig)
+{
+ int result;
+ u64 size;
+ u64 val;
+ u64 tx;
+
+ result = lv1_get_virtual_uart_param(dev->port_number,
+ PARAM_TX_TRIGGER, &tx);
+ trig->tx = tx;
+
+ if (result) {
+ dev_dbg(&dev->core, "%s:%d: tx_trigger failed: %s\n",
+ __func__, __LINE__, ps3_result(result));
+ return result;
+ }
+
+ result = lv1_get_virtual_uart_param(dev->port_number,
+ PARAM_RX_BUF_SIZE, &size);
+
+ if (result) {
+ dev_dbg(&dev->core, "%s:%d: tx_buf_size failed: %s\n",
+ __func__, __LINE__, ps3_result(result));
+ return result;
+ }
+
+ result = lv1_get_virtual_uart_param(dev->port_number,
+ PARAM_RX_TRIGGER, &val);
+
+ if (result) {
+ dev_dbg(&dev->core, "%s:%d: rx_trigger failed: %s\n",
+ __func__, __LINE__, ps3_result(result));
+ return result;
+ }
+
+ trig->rx = size - val;
+
+ dev_dbg(&dev->core, "%s:%d: tx %lxh, rx %lxh\n", __func__, __LINE__,
+ trig->tx, trig->rx);
+
+ return result;
+}
+
+int ps3_vuart_set_triggers(struct ps3_system_bus_device *dev, unsigned int tx,
+ unsigned int rx)
+{
+ int result;
+ u64 size;
+
+ result = lv1_set_virtual_uart_param(dev->port_number,
+ PARAM_TX_TRIGGER, tx);
+
+ if (result) {
+ dev_dbg(&dev->core, "%s:%d: tx_trigger failed: %s\n",
+ __func__, __LINE__, ps3_result(result));
+ return result;
+ }
+
+ result = lv1_get_virtual_uart_param(dev->port_number,
+ PARAM_RX_BUF_SIZE, &size);
+
+ if (result) {
+ dev_dbg(&dev->core, "%s:%d: tx_buf_size failed: %s\n",
+ __func__, __LINE__, ps3_result(result));
+ return result;
+ }
+
+ result = lv1_set_virtual_uart_param(dev->port_number,
+ PARAM_RX_TRIGGER, size - rx);
+
+ if (result) {
+ dev_dbg(&dev->core, "%s:%d: rx_trigger failed: %s\n",
+ __func__, __LINE__, ps3_result(result));
+ return result;
+ }
+
+ dev_dbg(&dev->core, "%s:%d: tx %xh, rx %xh\n", __func__, __LINE__,
+ tx, rx);
+
+ return result;
+}
+
+static int ps3_vuart_get_rx_bytes_waiting(struct ps3_system_bus_device *dev,
+ u64 *bytes_waiting)
+{
+ int result;
+
+ result = lv1_get_virtual_uart_param(dev->port_number,
+ PARAM_RX_BYTES, bytes_waiting);
+
+ if (result)
+ dev_dbg(&dev->core, "%s:%d: rx_bytes failed: %s\n",
+ __func__, __LINE__, ps3_result(result));
+
+ dev_dbg(&dev->core, "%s:%d: %llxh\n", __func__, __LINE__,
+ *bytes_waiting);
+ return result;
+}
+
+/**
+ * ps3_vuart_set_interrupt_mask - Enable/disable the port interrupt sources.
+ * @dev: The struct ps3_system_bus_device instance.
+ * @bmp: Logical OR of enum vuart_interrupt_mask values. A zero bit disables.
+ */
+
+static int ps3_vuart_set_interrupt_mask(struct ps3_system_bus_device *dev,
+ unsigned long mask)
+{
+ int result;
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+
+ dev_dbg(&dev->core, "%s:%d: %lxh\n", __func__, __LINE__, mask);
+
+ priv->interrupt_mask = mask;
+
+ result = lv1_set_virtual_uart_param(dev->port_number,
+ PARAM_INTERRUPT_MASK, priv->interrupt_mask);
+
+ if (result)
+ dev_dbg(&dev->core, "%s:%d: interrupt_mask failed: %s\n",
+ __func__, __LINE__, ps3_result(result));
+
+ return result;
+}
+
+static int ps3_vuart_get_interrupt_status(struct ps3_system_bus_device *dev,
+ unsigned long *status)
+{
+ int result;
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+ u64 tmp;
+
+ result = lv1_get_virtual_uart_param(dev->port_number,
+ PARAM_INTERRUPT_STATUS, &tmp);
+
+ if (result)
+ dev_dbg(&dev->core, "%s:%d: interrupt_status failed: %s\n",
+ __func__, __LINE__, ps3_result(result));
+
+ *status = tmp & priv->interrupt_mask;
+
+ dev_dbg(&dev->core, "%s:%d: m %llxh, s %llxh, m&s %lxh\n",
+ __func__, __LINE__, priv->interrupt_mask, tmp, *status);
+
+ return result;
+}
+
+int ps3_vuart_enable_interrupt_tx(struct ps3_system_bus_device *dev)
+{
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+
+ return (priv->interrupt_mask & INTERRUPT_MASK_TX) ? 0
+ : ps3_vuart_set_interrupt_mask(dev, priv->interrupt_mask
+ | INTERRUPT_MASK_TX);
+}
+
+int ps3_vuart_enable_interrupt_rx(struct ps3_system_bus_device *dev)
+{
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+
+ return (priv->interrupt_mask & INTERRUPT_MASK_RX) ? 0
+ : ps3_vuart_set_interrupt_mask(dev, priv->interrupt_mask
+ | INTERRUPT_MASK_RX);
+}
+
+int ps3_vuart_enable_interrupt_disconnect(struct ps3_system_bus_device *dev)
+{
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+
+ return (priv->interrupt_mask & INTERRUPT_MASK_DISCONNECT) ? 0
+ : ps3_vuart_set_interrupt_mask(dev, priv->interrupt_mask
+ | INTERRUPT_MASK_DISCONNECT);
+}
+
+int ps3_vuart_disable_interrupt_tx(struct ps3_system_bus_device *dev)
+{
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+
+ return (priv->interrupt_mask & INTERRUPT_MASK_TX)
+ ? ps3_vuart_set_interrupt_mask(dev, priv->interrupt_mask
+ & ~INTERRUPT_MASK_TX) : 0;
+}
+
+int ps3_vuart_disable_interrupt_rx(struct ps3_system_bus_device *dev)
+{
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+
+ return (priv->interrupt_mask & INTERRUPT_MASK_RX)
+ ? ps3_vuart_set_interrupt_mask(dev, priv->interrupt_mask
+ & ~INTERRUPT_MASK_RX) : 0;
+}
+
+int ps3_vuart_disable_interrupt_disconnect(struct ps3_system_bus_device *dev)
+{
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+
+ return (priv->interrupt_mask & INTERRUPT_MASK_DISCONNECT)
+ ? ps3_vuart_set_interrupt_mask(dev, priv->interrupt_mask
+ & ~INTERRUPT_MASK_DISCONNECT) : 0;
+}
+
+/**
+ * ps3_vuart_raw_write - Low level write helper.
+ * @dev: The struct ps3_system_bus_device instance.
+ *
+ * Do not call ps3_vuart_raw_write directly, use ps3_vuart_write.
+ */
+
+static int ps3_vuart_raw_write(struct ps3_system_bus_device *dev,
+ const void *buf, unsigned int bytes, u64 *bytes_written)
+{
+ int result;
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+
+ result = lv1_write_virtual_uart(dev->port_number,
+ ps3_mm_phys_to_lpar(__pa(buf)), bytes, bytes_written);
+
+ if (result) {
+ dev_warn(&dev->core, "%s:%d: lv1_write_virtual_uart failed: "
+ "%s\n", __func__, __LINE__, ps3_result(result));
+ return result;
+ }
+
+ priv->stats.bytes_written += *bytes_written;
+
+ dev_dbg(&dev->core, "%s:%d: wrote %llxh/%xh=>%lxh\n", __func__, __LINE__,
+ *bytes_written, bytes, priv->stats.bytes_written);
+
+ return result;
+}
+
+/**
+ * ps3_vuart_raw_read - Low level read helper.
+ * @dev: The struct ps3_system_bus_device instance.
+ *
+ * Do not call ps3_vuart_raw_read directly, use ps3_vuart_read.
+ */
+
+static int ps3_vuart_raw_read(struct ps3_system_bus_device *dev, void *buf,
+ unsigned int bytes, u64 *bytes_read)
+{
+ int result;
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+
+ dev_dbg(&dev->core, "%s:%d: %xh\n", __func__, __LINE__, bytes);
+
+ result = lv1_read_virtual_uart(dev->port_number,
+ ps3_mm_phys_to_lpar(__pa(buf)), bytes, bytes_read);
+
+ if (result) {
+ dev_dbg(&dev->core, "%s:%d: lv1_read_virtual_uart failed: %s\n",
+ __func__, __LINE__, ps3_result(result));
+ return result;
+ }
+
+ priv->stats.bytes_read += *bytes_read;
+
+ dev_dbg(&dev->core, "%s:%d: read %llxh/%xh=>%lxh\n", __func__, __LINE__,
+ *bytes_read, bytes, priv->stats.bytes_read);
+
+ return result;
+}
+
+/**
+ * ps3_vuart_clear_rx_bytes - Discard bytes received.
+ * @dev: The struct ps3_system_bus_device instance.
+ * @bytes: Max byte count to discard, zero = all pending.
+ *
+ * Used to clear pending rx interrupt source. Will not block.
+ */
+
+void ps3_vuart_clear_rx_bytes(struct ps3_system_bus_device *dev,
+ unsigned int bytes)
+{
+ int result;
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+ u64 bytes_waiting;
+ void *tmp;
+
+ result = ps3_vuart_get_rx_bytes_waiting(dev, &bytes_waiting);
+
+ BUG_ON(result);
+
+ bytes = bytes ? min(bytes, (unsigned int)bytes_waiting) : bytes_waiting;
+
+ dev_dbg(&dev->core, "%s:%d: %u\n", __func__, __LINE__, bytes);
+
+ if (!bytes)
+ return;
+
+ /* Add some extra space for recently arrived data. */
+
+ bytes += 128;
+
+ tmp = kmalloc(bytes, GFP_KERNEL);
+
+ if (!tmp)
+ return;
+
+ ps3_vuart_raw_read(dev, tmp, bytes, &bytes_waiting);
+
+ kfree(tmp);
+
+ /* Don't include these bytes in the stats. */
+
+ priv->stats.bytes_read -= bytes_waiting;
+}
+EXPORT_SYMBOL_GPL(ps3_vuart_clear_rx_bytes);
+
+/**
+ * struct list_buffer - An element for a port device fifo buffer list.
+ */
+
+struct list_buffer {
+ struct list_head link;
+ const unsigned char *head;
+ const unsigned char *tail;
+ unsigned long dbg_number;
+ unsigned char data[];
+};
+
+/**
+ * ps3_vuart_write - the entry point for writing data to a port
+ * @dev: The struct ps3_system_bus_device instance.
+ *
+ * If the port is idle on entry as much of the incoming data is written to
+ * the port as the port will accept. Otherwise a list buffer is created
+ * and any remaning incoming data is copied to that buffer. The buffer is
+ * then enqueued for transmision via the transmit interrupt.
+ */
+
+int ps3_vuart_write(struct ps3_system_bus_device *dev, const void *buf,
+ unsigned int bytes)
+{
+ static unsigned long dbg_number;
+ int result;
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+ unsigned long flags;
+ struct list_buffer *lb;
+
+ dev_dbg(&dev->core, "%s:%d: %u(%xh) bytes\n", __func__, __LINE__,
+ bytes, bytes);
+
+ spin_lock_irqsave(&priv->tx_list.lock, flags);
+
+ if (list_empty(&priv->tx_list.head)) {
+ u64 bytes_written;
+
+ result = ps3_vuart_raw_write(dev, buf, bytes, &bytes_written);
+
+ spin_unlock_irqrestore(&priv->tx_list.lock, flags);
+
+ if (result) {
+ dev_dbg(&dev->core,
+ "%s:%d: ps3_vuart_raw_write failed\n",
+ __func__, __LINE__);
+ return result;
+ }
+
+ if (bytes_written == bytes) {
+ dev_dbg(&dev->core, "%s:%d: wrote %xh bytes\n",
+ __func__, __LINE__, bytes);
+ return 0;
+ }
+
+ bytes -= bytes_written;
+ buf += bytes_written;
+ } else
+ spin_unlock_irqrestore(&priv->tx_list.lock, flags);
+
+ lb = kmalloc(sizeof(struct list_buffer) + bytes, GFP_KERNEL);
+
+ if (!lb)
+ return -ENOMEM;
+
+ memcpy(lb->data, buf, bytes);
+ lb->head = lb->data;
+ lb->tail = lb->data + bytes;
+ lb->dbg_number = ++dbg_number;
+
+ spin_lock_irqsave(&priv->tx_list.lock, flags);
+ list_add_tail(&lb->link, &priv->tx_list.head);
+ ps3_vuart_enable_interrupt_tx(dev);
+ spin_unlock_irqrestore(&priv->tx_list.lock, flags);
+
+ dev_dbg(&dev->core, "%s:%d: queued buf_%lu, %xh bytes\n",
+ __func__, __LINE__, lb->dbg_number, bytes);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ps3_vuart_write);
+
+/**
+ * ps3_vuart_queue_rx_bytes - Queue waiting bytes into the buffer list.
+ * @dev: The struct ps3_system_bus_device instance.
+ * @bytes_queued: Number of bytes queued to the buffer list.
+ *
+ * Must be called with priv->rx_list.lock held.
+ */
+
+static int ps3_vuart_queue_rx_bytes(struct ps3_system_bus_device *dev,
+ u64 *bytes_queued)
+{
+ static unsigned long dbg_number;
+ int result;
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+ struct list_buffer *lb;
+ u64 bytes;
+
+ *bytes_queued = 0;
+
+ result = ps3_vuart_get_rx_bytes_waiting(dev, &bytes);
+ BUG_ON(result);
+
+ if (result)
+ return -EIO;
+
+ if (!bytes)
+ return 0;
+
+ /* Add some extra space for recently arrived data. */
+
+ bytes += 128;
+
+ lb = kmalloc(sizeof(struct list_buffer) + bytes, GFP_ATOMIC);
+
+ if (!lb)
+ return -ENOMEM;
+
+ ps3_vuart_raw_read(dev, lb->data, bytes, &bytes);
+
+ lb->head = lb->data;
+ lb->tail = lb->data + bytes;
+ lb->dbg_number = ++dbg_number;
+
+ list_add_tail(&lb->link, &priv->rx_list.head);
+ priv->rx_list.bytes_held += bytes;
+
+ dev_dbg(&dev->core, "%s:%d: buf_%lu: queued %llxh bytes\n",
+ __func__, __LINE__, lb->dbg_number, bytes);
+
+ *bytes_queued = bytes;
+
+ return 0;
+}
+
+/**
+ * ps3_vuart_read - The entry point for reading data from a port.
+ *
+ * Queue data waiting at the port, and if enough bytes to satisfy the request
+ * are held in the buffer list those bytes are dequeued and copied to the
+ * caller's buffer. Emptied list buffers are retiered. If the request cannot
+ * be statified by bytes held in the list buffers -EAGAIN is returned.
+ */
+
+int ps3_vuart_read(struct ps3_system_bus_device *dev, void *buf,
+ unsigned int bytes)
+{
+ int result;
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+ unsigned long flags;
+ struct list_buffer *lb, *n;
+ unsigned long bytes_read;
+
+ dev_dbg(&dev->core, "%s:%d: %u(%xh) bytes\n", __func__, __LINE__,
+ bytes, bytes);
+
+ spin_lock_irqsave(&priv->rx_list.lock, flags);
+
+ /* Queue rx bytes here for polled reads. */
+
+ while (priv->rx_list.bytes_held < bytes) {
+ u64 tmp;
+
+ result = ps3_vuart_queue_rx_bytes(dev, &tmp);
+ if (result || !tmp) {
+ dev_dbg(&dev->core, "%s:%d: starved for %lxh bytes\n",
+ __func__, __LINE__,
+ bytes - priv->rx_list.bytes_held);
+ spin_unlock_irqrestore(&priv->rx_list.lock, flags);
+ return -EAGAIN;
+ }
+ }
+
+ list_for_each_entry_safe(lb, n, &priv->rx_list.head, link) {
+ bytes_read = min((unsigned int)(lb->tail - lb->head), bytes);
+
+ memcpy(buf, lb->head, bytes_read);
+ buf += bytes_read;
+ bytes -= bytes_read;
+ priv->rx_list.bytes_held -= bytes_read;
+
+ if (bytes_read < lb->tail - lb->head) {
+ lb->head += bytes_read;
+ dev_dbg(&dev->core, "%s:%d: buf_%lu: dequeued %lxh "
+ "bytes\n", __func__, __LINE__, lb->dbg_number,
+ bytes_read);
+ spin_unlock_irqrestore(&priv->rx_list.lock, flags);
+ return 0;
+ }
+
+ dev_dbg(&dev->core, "%s:%d: buf_%lu: free, dequeued %lxh "
+ "bytes\n", __func__, __LINE__, lb->dbg_number,
+ bytes_read);
+
+ list_del(&lb->link);
+ kfree(lb);
+ }
+
+ spin_unlock_irqrestore(&priv->rx_list.lock, flags);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ps3_vuart_read);
+
+/**
+ * ps3_vuart_work - Asynchronous read handler.
+ */
+
+static void ps3_vuart_work(struct work_struct *work)
+{
+ struct ps3_system_bus_device *dev =
+ ps3_vuart_work_to_system_bus_dev(work);
+ struct ps3_vuart_port_driver *drv =
+ ps3_system_bus_dev_to_vuart_drv(dev);
+
+ BUG_ON(!drv);
+ drv->work(dev);
+}
+
+int ps3_vuart_read_async(struct ps3_system_bus_device *dev, unsigned int bytes)
+{
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+ unsigned long flags;
+
+ if (priv->rx_list.work.trigger) {
+ dev_dbg(&dev->core, "%s:%d: warning, multiple calls\n",
+ __func__, __LINE__);
+ return -EAGAIN;
+ }
+
+ BUG_ON(!bytes);
+
+ spin_lock_irqsave(&priv->rx_list.lock, flags);
+ if (priv->rx_list.bytes_held >= bytes) {
+ dev_dbg(&dev->core, "%s:%d: schedule_work %xh bytes\n",
+ __func__, __LINE__, bytes);
+ schedule_work(&priv->rx_list.work.work);
+ spin_unlock_irqrestore(&priv->rx_list.lock, flags);
+ return 0;
+ }
+
+ priv->rx_list.work.trigger = bytes;
+ spin_unlock_irqrestore(&priv->rx_list.lock, flags);
+
+ dev_dbg(&dev->core, "%s:%d: waiting for %u(%xh) bytes\n", __func__,
+ __LINE__, bytes, bytes);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ps3_vuart_read_async);
+
+void ps3_vuart_cancel_async(struct ps3_system_bus_device *dev)
+{
+ to_port_priv(dev)->rx_list.work.trigger = 0;
+}
+EXPORT_SYMBOL_GPL(ps3_vuart_cancel_async);
+
+/**
+ * ps3_vuart_handle_interrupt_tx - third stage transmit interrupt handler
+ *
+ * Services the transmit interrupt for the port. Writes as much data from the
+ * buffer list as the port will accept. Retires any emptied list buffers and
+ * adjusts the final list buffer state for a partial write.
+ */
+
+static int ps3_vuart_handle_interrupt_tx(struct ps3_system_bus_device *dev)
+{
+ int result = 0;
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+ unsigned long flags;
+ struct list_buffer *lb, *n;
+ unsigned long bytes_total = 0;
+
+ dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
+
+ spin_lock_irqsave(&priv->tx_list.lock, flags);
+
+ list_for_each_entry_safe(lb, n, &priv->tx_list.head, link) {
+
+ u64 bytes_written;
+
+ result = ps3_vuart_raw_write(dev, lb->head, lb->tail - lb->head,
+ &bytes_written);
+
+ if (result) {
+ dev_dbg(&dev->core,
+ "%s:%d: ps3_vuart_raw_write failed\n",
+ __func__, __LINE__);
+ break;
+ }
+
+ bytes_total += bytes_written;
+
+ if (bytes_written < lb->tail - lb->head) {
+ lb->head += bytes_written;
+ dev_dbg(&dev->core,
+ "%s:%d cleared buf_%lu, %llxh bytes\n",
+ __func__, __LINE__, lb->dbg_number,
+ bytes_written);
+ goto port_full;
+ }
+
+ dev_dbg(&dev->core, "%s:%d free buf_%lu\n", __func__, __LINE__,
+ lb->dbg_number);
+
+ list_del(&lb->link);
+ kfree(lb);
+ }
+
+ ps3_vuart_disable_interrupt_tx(dev);
+port_full:
+ spin_unlock_irqrestore(&priv->tx_list.lock, flags);
+ dev_dbg(&dev->core, "%s:%d wrote %lxh bytes total\n",
+ __func__, __LINE__, bytes_total);
+ return result;
+}
+
+/**
+ * ps3_vuart_handle_interrupt_rx - third stage receive interrupt handler
+ *
+ * Services the receive interrupt for the port. Creates a list buffer and
+ * copies all waiting port data to that buffer and enqueues the buffer in the
+ * buffer list. Buffer list data is dequeued via ps3_vuart_read.
+ */
+
+static int ps3_vuart_handle_interrupt_rx(struct ps3_system_bus_device *dev)
+{
+ int result;
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+ unsigned long flags;
+ u64 bytes;
+
+ dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
+
+ spin_lock_irqsave(&priv->rx_list.lock, flags);
+ result = ps3_vuart_queue_rx_bytes(dev, &bytes);
+
+ if (result) {
+ spin_unlock_irqrestore(&priv->rx_list.lock, flags);
+ return result;
+ }
+
+ if (priv->rx_list.work.trigger && priv->rx_list.bytes_held
+ >= priv->rx_list.work.trigger) {
+ dev_dbg(&dev->core, "%s:%d: schedule_work %lxh bytes\n",
+ __func__, __LINE__, priv->rx_list.work.trigger);
+ priv->rx_list.work.trigger = 0;
+ schedule_work(&priv->rx_list.work.work);
+ }
+
+ spin_unlock_irqrestore(&priv->rx_list.lock, flags);
+ return result;
+}
+
+static int ps3_vuart_handle_interrupt_disconnect(
+ struct ps3_system_bus_device *dev)
+{
+ dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
+ BUG_ON("no support");
+ return -1;
+}
+
+/**
+ * ps3_vuart_handle_port_interrupt - second stage interrupt handler
+ *
+ * Services any pending interrupt types for the port. Passes control to the
+ * third stage type specific interrupt handler. Returns control to the first
+ * stage handler after one iteration.
+ */
+
+static int ps3_vuart_handle_port_interrupt(struct ps3_system_bus_device *dev)
+{
+ int result;
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+ unsigned long status;
+
+ result = ps3_vuart_get_interrupt_status(dev, &status);
+
+ if (result)
+ return result;
+
+ dev_dbg(&dev->core, "%s:%d: status: %lxh\n", __func__, __LINE__,
+ status);
+
+ if (status & INTERRUPT_MASK_DISCONNECT) {
+ priv->stats.disconnect_interrupts++;
+ result = ps3_vuart_handle_interrupt_disconnect(dev);
+ if (result)
+ ps3_vuart_disable_interrupt_disconnect(dev);
+ }
+
+ if (status & INTERRUPT_MASK_TX) {
+ priv->stats.tx_interrupts++;
+ result = ps3_vuart_handle_interrupt_tx(dev);
+ if (result)
+ ps3_vuart_disable_interrupt_tx(dev);
+ }
+
+ if (status & INTERRUPT_MASK_RX) {
+ priv->stats.rx_interrupts++;
+ result = ps3_vuart_handle_interrupt_rx(dev);
+ if (result)
+ ps3_vuart_disable_interrupt_rx(dev);
+ }
+
+ return 0;
+}
+
+static struct vuart_bus_priv {
+ struct ports_bmp *bmp;
+ unsigned int virq;
+ struct mutex probe_mutex;
+ int use_count;
+ struct ps3_system_bus_device *devices[PORT_COUNT];
+} vuart_bus_priv;
+
+/**
+ * ps3_vuart_irq_handler - first stage interrupt handler
+ *
+ * Loops finding any interrupting port and its associated instance data.
+ * Passes control to the second stage port specific interrupt handler. Loops
+ * until all outstanding interrupts are serviced.
+ */
+
+static irqreturn_t ps3_vuart_irq_handler(int irq, void *_private)
+{
+ struct vuart_bus_priv *bus_priv = _private;
+
+ BUG_ON(!bus_priv);
+
+ while (1) {
+ unsigned int port;
+
+ dump_ports_bmp(bus_priv->bmp);
+
+ port = (BITS_PER_LONG - 1) - __ilog2(bus_priv->bmp->status);
+
+ if (port == BITS_PER_LONG)
+ break;
+
+ BUG_ON(port >= PORT_COUNT);
+ BUG_ON(!bus_priv->devices[port]);
+
+ ps3_vuart_handle_port_interrupt(bus_priv->devices[port]);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int ps3_vuart_bus_interrupt_get(void)
+{
+ int result;
+
+ pr_debug(" -> %s:%d\n", __func__, __LINE__);
+
+ vuart_bus_priv.use_count++;
+
+ BUG_ON(vuart_bus_priv.use_count > 2);
+
+ if (vuart_bus_priv.use_count != 1)
+ return 0;
+
+ BUG_ON(vuart_bus_priv.bmp);
+
+ vuart_bus_priv.bmp = kzalloc(sizeof(struct ports_bmp), GFP_KERNEL);
+
+ if (!vuart_bus_priv.bmp) {
+ result = -ENOMEM;
+ goto fail_bmp_malloc;
+ }
+
+ result = ps3_vuart_irq_setup(PS3_BINDING_CPU_ANY, vuart_bus_priv.bmp,
+ &vuart_bus_priv.virq);
+
+ if (result) {
+ pr_debug("%s:%d: ps3_vuart_irq_setup failed (%d)\n",
+ __func__, __LINE__, result);
+ result = -EPERM;
+ goto fail_alloc_irq;
+ }
+
+ result = request_irq(vuart_bus_priv.virq, ps3_vuart_irq_handler,
+ 0, "vuart", &vuart_bus_priv);
+
+ if (result) {
+ pr_debug("%s:%d: request_irq failed (%d)\n",
+ __func__, __LINE__, result);
+ goto fail_request_irq;
+ }
+
+ pr_debug(" <- %s:%d: ok\n", __func__, __LINE__);
+ return result;
+
+fail_request_irq:
+ ps3_vuart_irq_destroy(vuart_bus_priv.virq);
+ vuart_bus_priv.virq = 0;
+fail_alloc_irq:
+ kfree(vuart_bus_priv.bmp);
+ vuart_bus_priv.bmp = NULL;
+fail_bmp_malloc:
+ vuart_bus_priv.use_count--;
+ pr_debug(" <- %s:%d: failed\n", __func__, __LINE__);
+ return result;
+}
+
+static int ps3_vuart_bus_interrupt_put(void)
+{
+ pr_debug(" -> %s:%d\n", __func__, __LINE__);
+
+ vuart_bus_priv.use_count--;
+
+ BUG_ON(vuart_bus_priv.use_count < 0);
+
+ if (vuart_bus_priv.use_count != 0)
+ return 0;
+
+ free_irq(vuart_bus_priv.virq, &vuart_bus_priv);
+
+ ps3_vuart_irq_destroy(vuart_bus_priv.virq);
+ vuart_bus_priv.virq = 0;
+
+ kfree(vuart_bus_priv.bmp);
+ vuart_bus_priv.bmp = NULL;
+
+ pr_debug(" <- %s:%d\n", __func__, __LINE__);
+ return 0;
+}
+
+static int ps3_vuart_probe(struct ps3_system_bus_device *dev)
+{
+ int result;
+ struct ps3_vuart_port_driver *drv;
+ struct ps3_vuart_port_priv *priv = NULL;
+
+ dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
+
+ drv = ps3_system_bus_dev_to_vuart_drv(dev);
+ BUG_ON(!drv);
+
+ dev_dbg(&dev->core, "%s:%d: (%s)\n", __func__, __LINE__,
+ drv->core.core.name);
+
+ if (dev->port_number >= PORT_COUNT) {
+ BUG();
+ return -EINVAL;
+ }
+
+ mutex_lock(&vuart_bus_priv.probe_mutex);
+
+ result = ps3_vuart_bus_interrupt_get();
+
+ if (result)
+ goto fail_setup_interrupt;
+
+ if (vuart_bus_priv.devices[dev->port_number]) {
+ dev_dbg(&dev->core, "%s:%d: port busy (%d)\n", __func__,
+ __LINE__, dev->port_number);
+ result = -EBUSY;
+ goto fail_busy;
+ }
+
+ vuart_bus_priv.devices[dev->port_number] = dev;
+
+ /* Setup dev->driver_priv. */
+
+ dev->driver_priv = kzalloc(sizeof(struct ps3_vuart_port_priv),
+ GFP_KERNEL);
+
+ if (!dev->driver_priv) {
+ result = -ENOMEM;
+ goto fail_dev_malloc;
+ }
+
+ priv = to_port_priv(dev);
+
+ INIT_LIST_HEAD(&priv->tx_list.head);
+ spin_lock_init(&priv->tx_list.lock);
+
+ INIT_LIST_HEAD(&priv->rx_list.head);
+ spin_lock_init(&priv->rx_list.lock);
+
+ INIT_WORK(&priv->rx_list.work.work, ps3_vuart_work);
+ priv->rx_list.work.trigger = 0;
+ priv->rx_list.work.dev = dev;
+
+ /* clear stale pending interrupts */
+
+ ps3_vuart_clear_rx_bytes(dev, 0);
+
+ ps3_vuart_set_interrupt_mask(dev, INTERRUPT_MASK_RX);
+
+ ps3_vuart_set_triggers(dev, 1, 1);
+
+ if (drv->probe)
+ result = drv->probe(dev);
+ else {
+ result = 0;
+ dev_info(&dev->core, "%s:%d: no probe method\n", __func__,
+ __LINE__);
+ }
+
+ if (result) {
+ dev_dbg(&dev->core, "%s:%d: drv->probe failed\n",
+ __func__, __LINE__);
+ goto fail_probe;
+ }
+
+ mutex_unlock(&vuart_bus_priv.probe_mutex);
+
+ return result;
+
+fail_probe:
+ ps3_vuart_set_interrupt_mask(dev, 0);
+ kfree(dev->driver_priv);
+ dev->driver_priv = NULL;
+fail_dev_malloc:
+ vuart_bus_priv.devices[dev->port_number] = NULL;
+fail_busy:
+ ps3_vuart_bus_interrupt_put();
+fail_setup_interrupt:
+ mutex_unlock(&vuart_bus_priv.probe_mutex);
+ dev_dbg(&dev->core, "%s:%d: failed\n", __func__, __LINE__);
+ return result;
+}
+
+/**
+ * ps3_vuart_cleanup - common cleanup helper.
+ * @dev: The struct ps3_system_bus_device instance.
+ *
+ * Cleans interrupts and HV resources. Must be called with
+ * vuart_bus_priv.probe_mutex held. Used by ps3_vuart_remove and
+ * ps3_vuart_shutdown. After this call, polled reading will still work.
+ */
+
+static int ps3_vuart_cleanup(struct ps3_system_bus_device *dev)
+{
+ dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
+
+ ps3_vuart_cancel_async(dev);
+ ps3_vuart_set_interrupt_mask(dev, 0);
+ ps3_vuart_bus_interrupt_put();
+ return 0;
+}
+
+/**
+ * ps3_vuart_remove - Completely clean the device instance.
+ * @dev: The struct ps3_system_bus_device instance.
+ *
+ * Cleans all memory, interrupts and HV resources. After this call the
+ * device can no longer be used.
+ */
+
+static void ps3_vuart_remove(struct ps3_system_bus_device *dev)
+{
+ struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+ struct ps3_vuart_port_driver *drv;
+
+ BUG_ON(!dev);
+
+ mutex_lock(&vuart_bus_priv.probe_mutex);
+
+ dev_dbg(&dev->core, " -> %s:%d: match_id %d\n", __func__, __LINE__,
+ dev->match_id);
+
+ if (!dev->core.driver) {
+ dev_dbg(&dev->core, "%s:%d: no driver bound\n", __func__,
+ __LINE__);
+ mutex_unlock(&vuart_bus_priv.probe_mutex);
+ return;
+ }
+
+ drv = ps3_system_bus_dev_to_vuart_drv(dev);
+
+ BUG_ON(!drv);
+
+ if (drv->remove) {
+ drv->remove(dev);
+ } else {
+ dev_dbg(&dev->core, "%s:%d: no remove method\n", __func__,
+ __LINE__);
+ BUG();
+ }
+
+ ps3_vuart_cleanup(dev);
+
+ vuart_bus_priv.devices[dev->port_number] = NULL;
+ kfree(priv);
+ priv = NULL;
+
+ dev_dbg(&dev->core, " <- %s:%d\n", __func__, __LINE__);
+ mutex_unlock(&vuart_bus_priv.probe_mutex);
+}
+
+/**
+ * ps3_vuart_shutdown - Cleans interrupts and HV resources.
+ * @dev: The struct ps3_system_bus_device instance.
+ *
+ * Cleans interrupts and HV resources. After this call the
+ * device can still be used in polling mode. This behavior required
+ * by sys-manager to be able to complete the device power operation
+ * sequence.
+ */
+
+static void ps3_vuart_shutdown(struct ps3_system_bus_device *dev)
+{
+ struct ps3_vuart_port_driver *drv;
+
+ BUG_ON(!dev);
+
+ mutex_lock(&vuart_bus_priv.probe_mutex);
+
+ dev_dbg(&dev->core, " -> %s:%d: match_id %d\n", __func__, __LINE__,
+ dev->match_id);
+
+ if (!dev->core.driver) {
+ dev_dbg(&dev->core, "%s:%d: no driver bound\n", __func__,
+ __LINE__);
+ mutex_unlock(&vuart_bus_priv.probe_mutex);
+ return;
+ }
+
+ drv = ps3_system_bus_dev_to_vuart_drv(dev);
+
+ BUG_ON(!drv);
+
+ if (drv->shutdown)
+ drv->shutdown(dev);
+ else if (drv->remove) {
+ dev_dbg(&dev->core, "%s:%d: no shutdown, calling remove\n",
+ __func__, __LINE__);
+ drv->remove(dev);
+ } else {
+ dev_dbg(&dev->core, "%s:%d: no shutdown method\n", __func__,
+ __LINE__);
+ BUG();
+ }
+
+ ps3_vuart_cleanup(dev);
+
+ dev_dbg(&dev->core, " <- %s:%d\n", __func__, __LINE__);
+
+ mutex_unlock(&vuart_bus_priv.probe_mutex);
+}
+
+static int __init ps3_vuart_bus_init(void)
+{
+ pr_debug("%s:%d:\n", __func__, __LINE__);
+
+ if (!firmware_has_feature(FW_FEATURE_PS3_LV1))
+ return -ENODEV;
+
+ mutex_init(&vuart_bus_priv.probe_mutex);
+
+ return 0;
+}
+
+static void __exit ps3_vuart_bus_exit(void)
+{
+ pr_debug("%s:%d:\n", __func__, __LINE__);
+}
+
+core_initcall(ps3_vuart_bus_init);
+module_exit(ps3_vuart_bus_exit);
+
+/**
+ * ps3_vuart_port_driver_register - Add a vuart port device driver.
+ */
+
+int ps3_vuart_port_driver_register(struct ps3_vuart_port_driver *drv)
+{
+ int result;
+
+ pr_debug("%s:%d: (%s)\n", __func__, __LINE__, drv->core.core.name);
+
+ BUG_ON(!drv->core.match_id);
+ BUG_ON(!drv->core.core.name);
+
+ drv->core.probe = ps3_vuart_probe;
+ drv->core.remove = ps3_vuart_remove;
+ drv->core.shutdown = ps3_vuart_shutdown;
+
+ result = ps3_system_bus_driver_register(&drv->core);
+ return result;
+}
+EXPORT_SYMBOL_GPL(ps3_vuart_port_driver_register);
+
+/**
+ * ps3_vuart_port_driver_unregister - Remove a vuart port device driver.
+ */
+
+void ps3_vuart_port_driver_unregister(struct ps3_vuart_port_driver *drv)
+{
+ pr_debug("%s:%d: (%s)\n", __func__, __LINE__, drv->core.core.name);
+ ps3_system_bus_driver_unregister(&drv->core);
+}
+EXPORT_SYMBOL_GPL(ps3_vuart_port_driver_unregister);
diff --git a/drivers/ps3/ps3av.c b/drivers/ps3/ps3av.c
new file mode 100644
index 000000000..516e6d14d
--- /dev/null
+++ b/drivers/ps3/ps3av.c
@@ -0,0 +1,1061 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PS3 AV backend support.
+ *
+ * Copyright (C) 2007 Sony Computer Entertainment Inc.
+ * Copyright 2007 Sony Corp.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/notifier.h>
+#include <linux/ioctl.h>
+#include <linux/fb.h>
+#include <linux/slab.h>
+
+#include <asm/firmware.h>
+#include <asm/ps3av.h>
+#include <asm/ps3.h>
+
+#include "vuart.h"
+
+#define BUFSIZE 4096 /* vuart buf size */
+#define PS3AV_BUF_SIZE 512 /* max packet size */
+
+static int safe_mode;
+
+static int timeout = 5000; /* in msec ( 5 sec ) */
+module_param(timeout, int, 0644);
+
+static struct ps3av {
+ struct mutex mutex;
+ struct work_struct work;
+ struct completion done;
+ int open_count;
+ struct ps3_system_bus_device *dev;
+
+ int region;
+ struct ps3av_pkt_av_get_hw_conf av_hw_conf;
+ u32 av_port[PS3AV_AV_PORT_MAX + PS3AV_OPT_PORT_MAX];
+ u32 opt_port[PS3AV_OPT_PORT_MAX];
+ u32 head[PS3AV_HEAD_MAX];
+ u32 audio_port;
+ int ps3av_mode;
+ int ps3av_mode_old;
+ union {
+ struct ps3av_reply_hdr reply_hdr;
+ u8 raw[PS3AV_BUF_SIZE];
+ } recv_buf;
+} *ps3av;
+
+/* color space */
+#define YUV444 PS3AV_CMD_VIDEO_CS_YUV444_8
+#define RGB8 PS3AV_CMD_VIDEO_CS_RGB_8
+/* format */
+#define XRGB PS3AV_CMD_VIDEO_FMT_X8R8G8B8
+/* aspect */
+#define A_N PS3AV_CMD_AV_ASPECT_4_3
+#define A_W PS3AV_CMD_AV_ASPECT_16_9
+static const struct avset_video_mode {
+ u32 cs;
+ u32 fmt;
+ u32 vid;
+ u32 aspect;
+ u32 x;
+ u32 y;
+} video_mode_table[] = {
+ { 0, }, /* auto */
+ {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_480I, A_N, 720, 480},
+ {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_480P, A_N, 720, 480},
+ {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_720P_60HZ, A_W, 1280, 720},
+ {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_1080I_60HZ, A_W, 1920, 1080},
+ {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_1080P_60HZ, A_W, 1920, 1080},
+ {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_576I, A_N, 720, 576},
+ {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_576P, A_N, 720, 576},
+ {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_720P_50HZ, A_W, 1280, 720},
+ {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_1080I_50HZ, A_W, 1920, 1080},
+ {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_1080P_50HZ, A_W, 1920, 1080},
+ { RGB8, XRGB, PS3AV_CMD_VIDEO_VID_WXGA, A_W, 1280, 768},
+ { RGB8, XRGB, PS3AV_CMD_VIDEO_VID_SXGA, A_N, 1280, 1024},
+ { RGB8, XRGB, PS3AV_CMD_VIDEO_VID_WUXGA, A_W, 1920, 1200},
+};
+
+/* supported CIDs */
+static u32 cmd_table[] = {
+ /* init */
+ PS3AV_CID_AV_INIT,
+ PS3AV_CID_AV_FIN,
+ PS3AV_CID_VIDEO_INIT,
+ PS3AV_CID_AUDIO_INIT,
+
+ /* set */
+ PS3AV_CID_AV_ENABLE_EVENT,
+ PS3AV_CID_AV_DISABLE_EVENT,
+
+ PS3AV_CID_AV_VIDEO_CS,
+ PS3AV_CID_AV_VIDEO_MUTE,
+ PS3AV_CID_AV_VIDEO_DISABLE_SIG,
+ PS3AV_CID_AV_AUDIO_PARAM,
+ PS3AV_CID_AV_AUDIO_MUTE,
+ PS3AV_CID_AV_HDMI_MODE,
+ PS3AV_CID_AV_TV_MUTE,
+
+ PS3AV_CID_VIDEO_MODE,
+ PS3AV_CID_VIDEO_FORMAT,
+ PS3AV_CID_VIDEO_PITCH,
+
+ PS3AV_CID_AUDIO_MODE,
+ PS3AV_CID_AUDIO_MUTE,
+ PS3AV_CID_AUDIO_ACTIVE,
+ PS3AV_CID_AUDIO_INACTIVE,
+ PS3AV_CID_AVB_PARAM,
+
+ /* get */
+ PS3AV_CID_AV_GET_HW_CONF,
+ PS3AV_CID_AV_GET_MONITOR_INFO,
+
+ /* event */
+ PS3AV_CID_EVENT_UNPLUGGED,
+ PS3AV_CID_EVENT_PLUGGED,
+ PS3AV_CID_EVENT_HDCP_DONE,
+ PS3AV_CID_EVENT_HDCP_FAIL,
+ PS3AV_CID_EVENT_HDCP_AUTH,
+ PS3AV_CID_EVENT_HDCP_ERROR,
+
+ 0
+};
+
+#define PS3AV_EVENT_CMD_MASK 0x10000000
+#define PS3AV_EVENT_ID_MASK 0x0000ffff
+#define PS3AV_CID_MASK 0xffffffff
+#define PS3AV_REPLY_BIT 0x80000000
+
+#define ps3av_event_get_port_id(cid) ((cid >> 16) & 0xff)
+
+static u32 *ps3av_search_cmd_table(u32 cid, u32 mask)
+{
+ u32 *table;
+ int i;
+
+ table = cmd_table;
+ for (i = 0;; table++, i++) {
+ if ((*table & mask) == (cid & mask))
+ break;
+ if (*table == 0)
+ return NULL;
+ }
+ return table;
+}
+
+static int ps3av_parse_event_packet(const struct ps3av_reply_hdr *hdr)
+{
+ u32 *table;
+
+ if (hdr->cid & PS3AV_EVENT_CMD_MASK) {
+ table = ps3av_search_cmd_table(hdr->cid, PS3AV_EVENT_CMD_MASK);
+ if (table)
+ dev_dbg(&ps3av->dev->core,
+ "recv event packet cid:%08x port:0x%x size:%d\n",
+ hdr->cid, ps3av_event_get_port_id(hdr->cid),
+ hdr->size);
+ else
+ printk(KERN_ERR
+ "%s: failed event packet, cid:%08x size:%d\n",
+ __func__, hdr->cid, hdr->size);
+ return 1; /* receive event packet */
+ }
+ return 0;
+}
+
+
+#define POLLING_INTERVAL 25 /* in msec */
+
+static int ps3av_vuart_write(struct ps3_system_bus_device *dev,
+ const void *buf, unsigned long size)
+{
+ int error;
+ dev_dbg(&dev->core, " -> %s:%d\n", __func__, __LINE__);
+ error = ps3_vuart_write(dev, buf, size);
+ dev_dbg(&dev->core, " <- %s:%d\n", __func__, __LINE__);
+ return error ? error : size;
+}
+
+static int ps3av_vuart_read(struct ps3_system_bus_device *dev, void *buf,
+ unsigned long size, int timeout)
+{
+ int error;
+ int loopcnt = 0;
+
+ dev_dbg(&dev->core, " -> %s:%d\n", __func__, __LINE__);
+ timeout = (timeout + POLLING_INTERVAL - 1) / POLLING_INTERVAL;
+ while (loopcnt++ <= timeout) {
+ error = ps3_vuart_read(dev, buf, size);
+ if (!error)
+ return size;
+ if (error != -EAGAIN) {
+ printk(KERN_ERR "%s: ps3_vuart_read failed %d\n",
+ __func__, error);
+ return error;
+ }
+ msleep(POLLING_INTERVAL);
+ }
+ return -EWOULDBLOCK;
+}
+
+static int ps3av_send_cmd_pkt(const struct ps3av_send_hdr *send_buf,
+ struct ps3av_reply_hdr *recv_buf, int write_len,
+ int read_len)
+{
+ int res;
+ u32 cmd;
+ int event;
+
+ if (!ps3av)
+ return -ENODEV;
+
+ /* send pkt */
+ res = ps3av_vuart_write(ps3av->dev, send_buf, write_len);
+ if (res < 0) {
+ dev_warn(&ps3av->dev->core,
+ "%s:%d: ps3av_vuart_write() failed: %s\n", __func__,
+ __LINE__, ps3_result(res));
+ return res;
+ }
+
+ /* recv pkt */
+ cmd = send_buf->cid;
+ do {
+ /* read header */
+ res = ps3av_vuart_read(ps3av->dev, recv_buf, PS3AV_HDR_SIZE,
+ timeout);
+ if (res != PS3AV_HDR_SIZE) {
+ dev_warn(&ps3av->dev->core,
+ "%s:%d: ps3av_vuart_read() failed: %s\n", __func__,
+ __LINE__, ps3_result(res));
+ return res;
+ }
+
+ /* read body */
+ res = ps3av_vuart_read(ps3av->dev, &recv_buf->cid,
+ recv_buf->size, timeout);
+ if (res < 0) {
+ dev_warn(&ps3av->dev->core,
+ "%s:%d: ps3av_vuart_read() failed: %s\n", __func__,
+ __LINE__, ps3_result(res));
+ return res;
+ }
+ res += PS3AV_HDR_SIZE; /* total len */
+ event = ps3av_parse_event_packet(recv_buf);
+ /* ret > 0 event packet */
+ } while (event);
+
+ if ((cmd | PS3AV_REPLY_BIT) != recv_buf->cid) {
+ dev_warn(&ps3av->dev->core, "%s:%d: reply err: %x\n", __func__,
+ __LINE__, recv_buf->cid);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ps3av_process_reply_packet(struct ps3av_send_hdr *cmd_buf,
+ const struct ps3av_reply_hdr *recv_buf,
+ int user_buf_size)
+{
+ int return_len;
+
+ if (recv_buf->version != PS3AV_VERSION) {
+ dev_dbg(&ps3av->dev->core, "reply_packet invalid version:%x\n",
+ recv_buf->version);
+ return -EFAULT;
+ }
+ return_len = recv_buf->size + PS3AV_HDR_SIZE;
+ if (return_len > user_buf_size)
+ return_len = user_buf_size;
+ memcpy(cmd_buf, recv_buf, return_len);
+ return 0; /* success */
+}
+
+void ps3av_set_hdr(u32 cid, u16 size, struct ps3av_send_hdr *hdr)
+{
+ hdr->version = PS3AV_VERSION;
+ hdr->size = size - PS3AV_HDR_SIZE;
+ hdr->cid = cid;
+}
+
+int ps3av_do_pkt(u32 cid, u16 send_len, size_t usr_buf_size,
+ struct ps3av_send_hdr *buf)
+{
+ int res = 0;
+ u32 *table;
+
+ BUG_ON(!ps3av);
+
+ mutex_lock(&ps3av->mutex);
+
+ table = ps3av_search_cmd_table(cid, PS3AV_CID_MASK);
+ BUG_ON(!table);
+ BUG_ON(send_len < PS3AV_HDR_SIZE);
+ BUG_ON(usr_buf_size < send_len);
+ BUG_ON(usr_buf_size > PS3AV_BUF_SIZE);
+
+ /* create header */
+ ps3av_set_hdr(cid, send_len, buf);
+
+ /* send packet via vuart */
+ res = ps3av_send_cmd_pkt(buf, &ps3av->recv_buf.reply_hdr, send_len,
+ usr_buf_size);
+ if (res < 0) {
+ printk(KERN_ERR
+ "%s: ps3av_send_cmd_pkt() failed (result=%d)\n",
+ __func__, res);
+ goto err;
+ }
+
+ /* process reply packet */
+ res = ps3av_process_reply_packet(buf, &ps3av->recv_buf.reply_hdr,
+ usr_buf_size);
+ if (res < 0) {
+ printk(KERN_ERR "%s: put_return_status() failed (result=%d)\n",
+ __func__, res);
+ goto err;
+ }
+
+ mutex_unlock(&ps3av->mutex);
+ return 0;
+
+err:
+ mutex_unlock(&ps3av->mutex);
+ printk(KERN_ERR "%s: failed cid:%x res:%d\n", __func__, cid, res);
+ return res;
+}
+
+static int ps3av_set_av_video_mute(u32 mute)
+{
+ int i, num_of_av_port, res;
+
+ num_of_av_port = ps3av->av_hw_conf.num_of_hdmi +
+ ps3av->av_hw_conf.num_of_avmulti;
+ /* video mute on */
+ for (i = 0; i < num_of_av_port; i++) {
+ res = ps3av_cmd_av_video_mute(1, &ps3av->av_port[i], mute);
+ if (res < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int ps3av_set_video_disable_sig(void)
+{
+ int i, num_of_hdmi_port, num_of_av_port, res;
+
+ num_of_hdmi_port = ps3av->av_hw_conf.num_of_hdmi;
+ num_of_av_port = ps3av->av_hw_conf.num_of_hdmi +
+ ps3av->av_hw_conf.num_of_avmulti;
+
+ /* tv mute */
+ for (i = 0; i < num_of_hdmi_port; i++) {
+ res = ps3av_cmd_av_tv_mute(ps3av->av_port[i],
+ PS3AV_CMD_MUTE_ON);
+ if (res < 0)
+ return -1;
+ }
+ msleep(100);
+
+ /* video mute on */
+ for (i = 0; i < num_of_av_port; i++) {
+ res = ps3av_cmd_av_video_disable_sig(ps3av->av_port[i]);
+ if (res < 0)
+ return -1;
+ if (i < num_of_hdmi_port) {
+ res = ps3av_cmd_av_tv_mute(ps3av->av_port[i],
+ PS3AV_CMD_MUTE_OFF);
+ if (res < 0)
+ return -1;
+ }
+ }
+ msleep(300);
+
+ return 0;
+}
+
+static int ps3av_set_audio_mute(u32 mute)
+{
+ int i, num_of_av_port, num_of_opt_port, res;
+
+ num_of_av_port = ps3av->av_hw_conf.num_of_hdmi +
+ ps3av->av_hw_conf.num_of_avmulti;
+ num_of_opt_port = ps3av->av_hw_conf.num_of_spdif;
+
+ for (i = 0; i < num_of_av_port; i++) {
+ res = ps3av_cmd_av_audio_mute(1, &ps3av->av_port[i], mute);
+ if (res < 0)
+ return -1;
+ }
+ for (i = 0; i < num_of_opt_port; i++) {
+ res = ps3av_cmd_audio_mute(1, &ps3av->opt_port[i], mute);
+ if (res < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+int ps3av_set_audio_mode(u32 ch, u32 fs, u32 word_bits, u32 format, u32 source)
+{
+ struct ps3av_pkt_avb_param avb_param;
+ int i, num_of_audio, vid, res;
+ struct ps3av_pkt_audio_mode audio_mode;
+ u32 len = 0;
+
+ num_of_audio = ps3av->av_hw_conf.num_of_hdmi +
+ ps3av->av_hw_conf.num_of_avmulti +
+ ps3av->av_hw_conf.num_of_spdif;
+
+ avb_param.num_of_video_pkt = 0;
+ avb_param.num_of_audio_pkt = PS3AV_AVB_NUM_AUDIO; /* always 0 */
+ avb_param.num_of_av_video_pkt = 0;
+ avb_param.num_of_av_audio_pkt = ps3av->av_hw_conf.num_of_hdmi;
+
+ vid = video_mode_table[ps3av->ps3av_mode].vid;
+
+ /* audio mute */
+ ps3av_set_audio_mute(PS3AV_CMD_MUTE_ON);
+
+ /* audio inactive */
+ res = ps3av_cmd_audio_active(0, ps3av->audio_port);
+ if (res < 0)
+ dev_dbg(&ps3av->dev->core,
+ "ps3av_cmd_audio_active OFF failed\n");
+
+ /* audio_pkt */
+ for (i = 0; i < num_of_audio; i++) {
+ ps3av_cmd_set_audio_mode(&audio_mode, ps3av->av_port[i], ch,
+ fs, word_bits, format, source);
+ if (i < ps3av->av_hw_conf.num_of_hdmi) {
+ /* hdmi only */
+ len += ps3av_cmd_set_av_audio_param(&avb_param.buf[len],
+ ps3av->av_port[i],
+ &audio_mode, vid);
+ }
+ /* audio_mode pkt should be sent separately */
+ res = ps3av_cmd_audio_mode(&audio_mode);
+ if (res < 0)
+ dev_dbg(&ps3av->dev->core,
+ "ps3av_cmd_audio_mode failed, port:%x\n", i);
+ }
+
+ /* send command using avb pkt */
+ len += offsetof(struct ps3av_pkt_avb_param, buf);
+ res = ps3av_cmd_avb_param(&avb_param, len);
+ if (res < 0)
+ dev_dbg(&ps3av->dev->core, "ps3av_cmd_avb_param failed\n");
+
+ /* audio mute */
+ ps3av_set_audio_mute(PS3AV_CMD_MUTE_OFF);
+
+ /* audio active */
+ res = ps3av_cmd_audio_active(1, ps3av->audio_port);
+ if (res < 0)
+ dev_dbg(&ps3av->dev->core,
+ "ps3av_cmd_audio_active ON failed\n");
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ps3av_set_audio_mode);
+
+static int ps3av_set_videomode(void)
+{
+ /* av video mute */
+ ps3av_set_av_video_mute(PS3AV_CMD_MUTE_ON);
+
+ /* wake up ps3avd to do the actual video mode setting */
+ schedule_work(&ps3av->work);
+
+ return 0;
+}
+
+static void ps3av_set_videomode_packet(u32 id)
+{
+ struct ps3av_pkt_avb_param avb_param;
+ unsigned int i;
+ u32 len = 0, av_video_cs;
+ const struct avset_video_mode *video_mode;
+ int res;
+
+ video_mode = &video_mode_table[id & PS3AV_MODE_MASK];
+
+ avb_param.num_of_video_pkt = PS3AV_AVB_NUM_VIDEO; /* num of head */
+ avb_param.num_of_audio_pkt = 0;
+ avb_param.num_of_av_video_pkt = ps3av->av_hw_conf.num_of_hdmi +
+ ps3av->av_hw_conf.num_of_avmulti;
+ avb_param.num_of_av_audio_pkt = 0;
+
+ /* video_pkt */
+ for (i = 0; i < avb_param.num_of_video_pkt; i++)
+ len += ps3av_cmd_set_video_mode(&avb_param.buf[len],
+ ps3av->head[i], video_mode->vid,
+ video_mode->fmt, id);
+ /* av_video_pkt */
+ for (i = 0; i < avb_param.num_of_av_video_pkt; i++) {
+ if (id & PS3AV_MODE_DVI || id & PS3AV_MODE_RGB)
+ av_video_cs = RGB8;
+ else
+ av_video_cs = video_mode->cs;
+#ifndef PS3AV_HDMI_YUV
+ if (ps3av->av_port[i] == PS3AV_CMD_AVPORT_HDMI_0 ||
+ ps3av->av_port[i] == PS3AV_CMD_AVPORT_HDMI_1)
+ av_video_cs = RGB8; /* use RGB for HDMI */
+#endif
+ len += ps3av_cmd_set_av_video_cs(&avb_param.buf[len],
+ ps3av->av_port[i],
+ video_mode->vid, av_video_cs,
+ video_mode->aspect, id);
+ }
+ /* send command using avb pkt */
+ len += offsetof(struct ps3av_pkt_avb_param, buf);
+ res = ps3av_cmd_avb_param(&avb_param, len);
+ if (res == PS3AV_STATUS_NO_SYNC_HEAD)
+ printk(KERN_WARNING
+ "%s: Command failed. Please try your request again.\n",
+ __func__);
+ else if (res)
+ dev_dbg(&ps3av->dev->core, "ps3av_cmd_avb_param failed\n");
+}
+
+static void ps3av_set_videomode_cont(u32 id, u32 old_id)
+{
+ static int vesa;
+ int res;
+
+ /* video signal off */
+ ps3av_set_video_disable_sig();
+
+ /*
+ * AV backend needs non-VESA mode setting at least one time
+ * when VESA mode is used.
+ */
+ if (vesa == 0 && (id & PS3AV_MODE_MASK) >= PS3AV_MODE_WXGA) {
+ /* vesa mode */
+ ps3av_set_videomode_packet(PS3AV_MODE_480P);
+ }
+ vesa = 1;
+
+ /* Retail PS3 product doesn't support this */
+ if (id & PS3AV_MODE_HDCP_OFF) {
+ res = ps3av_cmd_av_hdmi_mode(PS3AV_CMD_AV_HDMI_HDCP_OFF);
+ if (res == PS3AV_STATUS_UNSUPPORTED_HDMI_MODE)
+ dev_dbg(&ps3av->dev->core, "Not supported\n");
+ else if (res)
+ dev_dbg(&ps3av->dev->core,
+ "ps3av_cmd_av_hdmi_mode failed\n");
+ } else if (old_id & PS3AV_MODE_HDCP_OFF) {
+ res = ps3av_cmd_av_hdmi_mode(PS3AV_CMD_AV_HDMI_MODE_NORMAL);
+ if (res < 0 && res != PS3AV_STATUS_UNSUPPORTED_HDMI_MODE)
+ dev_dbg(&ps3av->dev->core,
+ "ps3av_cmd_av_hdmi_mode failed\n");
+ }
+
+ ps3av_set_videomode_packet(id);
+
+ msleep(1500);
+ /* av video mute */
+ ps3av_set_av_video_mute(PS3AV_CMD_MUTE_OFF);
+}
+
+static void ps3avd(struct work_struct *work)
+{
+ ps3av_set_videomode_cont(ps3av->ps3av_mode, ps3av->ps3av_mode_old);
+ complete(&ps3av->done);
+}
+
+#define SHIFT_50 0
+#define SHIFT_60 4
+#define SHIFT_VESA 8
+
+static const struct {
+ unsigned mask:19;
+ unsigned id:4;
+} ps3av_preferred_modes[] = {
+ { PS3AV_RESBIT_WUXGA << SHIFT_VESA, PS3AV_MODE_WUXGA },
+ { PS3AV_RESBIT_1920x1080P << SHIFT_60, PS3AV_MODE_1080P60 },
+ { PS3AV_RESBIT_1920x1080P << SHIFT_50, PS3AV_MODE_1080P50 },
+ { PS3AV_RESBIT_1920x1080I << SHIFT_60, PS3AV_MODE_1080I60 },
+ { PS3AV_RESBIT_1920x1080I << SHIFT_50, PS3AV_MODE_1080I50 },
+ { PS3AV_RESBIT_SXGA << SHIFT_VESA, PS3AV_MODE_SXGA },
+ { PS3AV_RESBIT_WXGA << SHIFT_VESA, PS3AV_MODE_WXGA },
+ { PS3AV_RESBIT_1280x720P << SHIFT_60, PS3AV_MODE_720P60 },
+ { PS3AV_RESBIT_1280x720P << SHIFT_50, PS3AV_MODE_720P50 },
+ { PS3AV_RESBIT_720x480P << SHIFT_60, PS3AV_MODE_480P },
+ { PS3AV_RESBIT_720x576P << SHIFT_50, PS3AV_MODE_576P },
+};
+
+static enum ps3av_mode_num ps3av_resbit2id(u32 res_50, u32 res_60,
+ u32 res_vesa)
+{
+ unsigned int i;
+ u32 res_all;
+
+ /*
+ * We mask off the resolution bits we care about and combine the
+ * results in one bitfield, so make sure there's no overlap
+ */
+ BUILD_BUG_ON(PS3AV_RES_MASK_50 << SHIFT_50 &
+ PS3AV_RES_MASK_60 << SHIFT_60);
+ BUILD_BUG_ON(PS3AV_RES_MASK_50 << SHIFT_50 &
+ PS3AV_RES_MASK_VESA << SHIFT_VESA);
+ BUILD_BUG_ON(PS3AV_RES_MASK_60 << SHIFT_60 &
+ PS3AV_RES_MASK_VESA << SHIFT_VESA);
+ res_all = (res_50 & PS3AV_RES_MASK_50) << SHIFT_50 |
+ (res_60 & PS3AV_RES_MASK_60) << SHIFT_60 |
+ (res_vesa & PS3AV_RES_MASK_VESA) << SHIFT_VESA;
+
+ if (!res_all)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(ps3av_preferred_modes); i++)
+ if (res_all & ps3av_preferred_modes[i].mask)
+ return ps3av_preferred_modes[i].id;
+
+ return 0;
+}
+
+static enum ps3av_mode_num ps3av_hdmi_get_id(struct ps3av_info_monitor *info)
+{
+ enum ps3av_mode_num id;
+
+ if (safe_mode)
+ return PS3AV_DEFAULT_HDMI_MODE_ID_REG_60;
+
+ /* check native resolution */
+ id = ps3av_resbit2id(info->res_50.native, info->res_60.native,
+ info->res_vesa.native);
+ if (id) {
+ pr_debug("%s: Using native mode %d\n", __func__, id);
+ return id;
+ }
+
+ /* check supported resolutions */
+ id = ps3av_resbit2id(info->res_50.res_bits, info->res_60.res_bits,
+ info->res_vesa.res_bits);
+ if (id) {
+ pr_debug("%s: Using supported mode %d\n", __func__, id);
+ return id;
+ }
+
+ if (ps3av->region & PS3AV_REGION_60)
+ id = PS3AV_DEFAULT_HDMI_MODE_ID_REG_60;
+ else
+ id = PS3AV_DEFAULT_HDMI_MODE_ID_REG_50;
+ pr_debug("%s: Using default mode %d\n", __func__, id);
+ return id;
+}
+
+static void ps3av_monitor_info_dump(
+ const struct ps3av_pkt_av_get_monitor_info *monitor_info)
+{
+ const struct ps3av_info_monitor *info = &monitor_info->info;
+ const struct ps3av_info_audio *audio = info->audio;
+ char id[sizeof(info->monitor_id)*3+1];
+ int i;
+
+ pr_debug("Monitor Info: size %u\n", monitor_info->send_hdr.size);
+
+ pr_debug("avport: %02x\n", info->avport);
+ for (i = 0; i < sizeof(info->monitor_id); i++)
+ sprintf(&id[i*3], " %02x", info->monitor_id[i]);
+ pr_debug("monitor_id: %s\n", id);
+ pr_debug("monitor_type: %02x\n", info->monitor_type);
+ pr_debug("monitor_name: %.*s\n", (int)sizeof(info->monitor_name),
+ info->monitor_name);
+
+ /* resolution */
+ pr_debug("resolution_60: bits: %08x native: %08x\n",
+ info->res_60.res_bits, info->res_60.native);
+ pr_debug("resolution_50: bits: %08x native: %08x\n",
+ info->res_50.res_bits, info->res_50.native);
+ pr_debug("resolution_other: bits: %08x native: %08x\n",
+ info->res_other.res_bits, info->res_other.native);
+ pr_debug("resolution_vesa: bits: %08x native: %08x\n",
+ info->res_vesa.res_bits, info->res_vesa.native);
+
+ /* color space */
+ pr_debug("color space rgb: %02x\n", info->cs.rgb);
+ pr_debug("color space yuv444: %02x\n", info->cs.yuv444);
+ pr_debug("color space yuv422: %02x\n", info->cs.yuv422);
+
+ /* color info */
+ pr_debug("color info red: X %04x Y %04x\n", info->color.red_x,
+ info->color.red_y);
+ pr_debug("color info green: X %04x Y %04x\n", info->color.green_x,
+ info->color.green_y);
+ pr_debug("color info blue: X %04x Y %04x\n", info->color.blue_x,
+ info->color.blue_y);
+ pr_debug("color info white: X %04x Y %04x\n", info->color.white_x,
+ info->color.white_y);
+ pr_debug("color info gamma: %08x\n", info->color.gamma);
+
+ /* other info */
+ pr_debug("supported_AI: %02x\n", info->supported_ai);
+ pr_debug("speaker_info: %02x\n", info->speaker_info);
+ pr_debug("num of audio: %02x\n", info->num_of_audio_block);
+
+ /* audio block */
+ for (i = 0; i < info->num_of_audio_block; i++) {
+ pr_debug(
+ "audio[%d] type: %02x max_ch: %02x fs: %02x sbit: %02x\n",
+ i, audio->type, audio->max_num_of_ch, audio->fs,
+ audio->sbit);
+ audio++;
+ }
+}
+
+static const struct ps3av_monitor_quirk {
+ const char *monitor_name;
+ u32 clear_60;
+} ps3av_monitor_quirks[] = {
+ {
+ .monitor_name = "DELL 2007WFP",
+ .clear_60 = PS3AV_RESBIT_1920x1080I
+ }, {
+ .monitor_name = "L226WTQ",
+ .clear_60 = PS3AV_RESBIT_1920x1080I |
+ PS3AV_RESBIT_1920x1080P
+ }, {
+ .monitor_name = "SyncMaster",
+ .clear_60 = PS3AV_RESBIT_1920x1080I
+ }
+};
+
+static void ps3av_fixup_monitor_info(struct ps3av_info_monitor *info)
+{
+ unsigned int i;
+ const struct ps3av_monitor_quirk *quirk;
+
+ for (i = 0; i < ARRAY_SIZE(ps3av_monitor_quirks); i++) {
+ quirk = &ps3av_monitor_quirks[i];
+ if (!strncmp(info->monitor_name, quirk->monitor_name,
+ sizeof(info->monitor_name))) {
+ pr_info("%s: Applying quirk for %s\n", __func__,
+ quirk->monitor_name);
+ info->res_60.res_bits &= ~quirk->clear_60;
+ info->res_60.native &= ~quirk->clear_60;
+ break;
+ }
+ }
+}
+
+static int ps3av_auto_videomode(struct ps3av_pkt_av_get_hw_conf *av_hw_conf)
+{
+ int i, res, id = 0, dvi = 0, rgb = 0;
+ struct ps3av_pkt_av_get_monitor_info monitor_info;
+ struct ps3av_info_monitor *info;
+
+ /* get mode id for hdmi */
+ for (i = 0; i < av_hw_conf->num_of_hdmi && !id; i++) {
+ res = ps3av_cmd_video_get_monitor_info(&monitor_info,
+ PS3AV_CMD_AVPORT_HDMI_0 +
+ i);
+ if (res < 0)
+ return -1;
+
+ ps3av_monitor_info_dump(&monitor_info);
+
+ info = &monitor_info.info;
+ ps3av_fixup_monitor_info(info);
+
+ switch (info->monitor_type) {
+ case PS3AV_MONITOR_TYPE_DVI:
+ dvi = PS3AV_MODE_DVI;
+ fallthrough;
+ case PS3AV_MONITOR_TYPE_HDMI:
+ id = ps3av_hdmi_get_id(info);
+ break;
+ }
+ }
+
+ if (!id) {
+ /* no HDMI interface or HDMI is off */
+ if (ps3av->region & PS3AV_REGION_60)
+ id = PS3AV_DEFAULT_AVMULTI_MODE_ID_REG_60;
+ else
+ id = PS3AV_DEFAULT_AVMULTI_MODE_ID_REG_50;
+ if (ps3av->region & PS3AV_REGION_RGB)
+ rgb = PS3AV_MODE_RGB;
+ pr_debug("%s: Using avmulti mode %d\n", __func__, id);
+ }
+
+ return id | dvi | rgb;
+}
+
+static int ps3av_get_hw_conf(struct ps3av *ps3av)
+{
+ int i, j, k, res;
+ const struct ps3av_pkt_av_get_hw_conf *hw_conf;
+
+ /* get av_hw_conf */
+ res = ps3av_cmd_av_get_hw_conf(&ps3av->av_hw_conf);
+ if (res < 0)
+ return -1;
+
+ hw_conf = &ps3av->av_hw_conf;
+ pr_debug("av_h_conf: num of hdmi: %u\n", hw_conf->num_of_hdmi);
+ pr_debug("av_h_conf: num of avmulti: %u\n", hw_conf->num_of_avmulti);
+ pr_debug("av_h_conf: num of spdif: %u\n", hw_conf->num_of_spdif);
+
+ for (i = 0; i < PS3AV_HEAD_MAX; i++)
+ ps3av->head[i] = PS3AV_CMD_VIDEO_HEAD_A + i;
+ for (i = 0; i < PS3AV_OPT_PORT_MAX; i++)
+ ps3av->opt_port[i] = PS3AV_CMD_AVPORT_SPDIF_0 + i;
+ for (i = 0; i < hw_conf->num_of_hdmi; i++)
+ ps3av->av_port[i] = PS3AV_CMD_AVPORT_HDMI_0 + i;
+ for (j = 0; j < hw_conf->num_of_avmulti; j++)
+ ps3av->av_port[i + j] = PS3AV_CMD_AVPORT_AVMULTI_0 + j;
+ for (k = 0; k < hw_conf->num_of_spdif; k++)
+ ps3av->av_port[i + j + k] = PS3AV_CMD_AVPORT_SPDIF_0 + k;
+
+ /* set all audio port */
+ ps3av->audio_port = PS3AV_CMD_AUDIO_PORT_HDMI_0
+ | PS3AV_CMD_AUDIO_PORT_HDMI_1
+ | PS3AV_CMD_AUDIO_PORT_AVMULTI_0
+ | PS3AV_CMD_AUDIO_PORT_SPDIF_0 | PS3AV_CMD_AUDIO_PORT_SPDIF_1;
+
+ return 0;
+}
+
+/* set mode using id */
+int ps3av_set_video_mode(int id)
+{
+ int size;
+ u32 option;
+
+ size = ARRAY_SIZE(video_mode_table);
+ if ((id & PS3AV_MODE_MASK) > size - 1 || id < 0) {
+ dev_dbg(&ps3av->dev->core, "%s: error id :%d\n", __func__, id);
+ return -EINVAL;
+ }
+
+ /* auto mode */
+ option = id & ~PS3AV_MODE_MASK;
+ if ((id & PS3AV_MODE_MASK) == PS3AV_MODE_AUTO) {
+ id = ps3av_auto_videomode(&ps3av->av_hw_conf);
+ if (id < 1) {
+ printk(KERN_ERR "%s: invalid id :%d\n", __func__, id);
+ return -EINVAL;
+ }
+ id |= option;
+ }
+
+ /* set videomode */
+ wait_for_completion(&ps3av->done);
+ ps3av->ps3av_mode_old = ps3av->ps3av_mode;
+ ps3av->ps3av_mode = id;
+ if (ps3av_set_videomode())
+ ps3av->ps3av_mode = ps3av->ps3av_mode_old;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ps3av_set_video_mode);
+
+int ps3av_get_auto_mode(void)
+{
+ return ps3av_auto_videomode(&ps3av->av_hw_conf);
+}
+EXPORT_SYMBOL_GPL(ps3av_get_auto_mode);
+
+int ps3av_get_mode(void)
+{
+ return ps3av ? ps3av->ps3av_mode : 0;
+}
+EXPORT_SYMBOL_GPL(ps3av_get_mode);
+
+/* get resolution by video_mode */
+int ps3av_video_mode2res(u32 id, u32 *xres, u32 *yres)
+{
+ int size;
+
+ id = id & PS3AV_MODE_MASK;
+ size = ARRAY_SIZE(video_mode_table);
+ if (id > size - 1 || id < 0) {
+ printk(KERN_ERR "%s: invalid mode %d\n", __func__, id);
+ return -EINVAL;
+ }
+ *xres = video_mode_table[id].x;
+ *yres = video_mode_table[id].y;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ps3av_video_mode2res);
+
+/* mute */
+int ps3av_video_mute(int mute)
+{
+ return ps3av_set_av_video_mute(mute ? PS3AV_CMD_MUTE_ON
+ : PS3AV_CMD_MUTE_OFF);
+}
+EXPORT_SYMBOL_GPL(ps3av_video_mute);
+
+/* mute analog output only */
+int ps3av_audio_mute_analog(int mute)
+{
+ int i, res;
+
+ for (i = 0; i < ps3av->av_hw_conf.num_of_avmulti; i++) {
+ res = ps3av_cmd_av_audio_mute(1,
+ &ps3av->av_port[i + ps3av->av_hw_conf.num_of_hdmi],
+ mute);
+ if (res < 0)
+ return -1;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ps3av_audio_mute_analog);
+
+int ps3av_audio_mute(int mute)
+{
+ return ps3av_set_audio_mute(mute ? PS3AV_CMD_MUTE_ON
+ : PS3AV_CMD_MUTE_OFF);
+}
+EXPORT_SYMBOL_GPL(ps3av_audio_mute);
+
+static int ps3av_probe(struct ps3_system_bus_device *dev)
+{
+ int res;
+ int id;
+
+ dev_dbg(&dev->core, " -> %s:%d\n", __func__, __LINE__);
+ dev_dbg(&dev->core, " timeout=%d\n", timeout);
+
+ if (ps3av) {
+ dev_err(&dev->core, "Only one ps3av device is supported\n");
+ return -EBUSY;
+ }
+
+ ps3av = kzalloc(sizeof(*ps3av), GFP_KERNEL);
+ if (!ps3av)
+ return -ENOMEM;
+
+ mutex_init(&ps3av->mutex);
+ ps3av->ps3av_mode = PS3AV_MODE_AUTO;
+ ps3av->dev = dev;
+
+ INIT_WORK(&ps3av->work, ps3avd);
+ init_completion(&ps3av->done);
+ complete(&ps3av->done);
+
+ switch (ps3_os_area_get_av_multi_out()) {
+ case PS3_PARAM_AV_MULTI_OUT_NTSC:
+ ps3av->region = PS3AV_REGION_60;
+ break;
+ case PS3_PARAM_AV_MULTI_OUT_PAL_YCBCR:
+ case PS3_PARAM_AV_MULTI_OUT_SECAM:
+ ps3av->region = PS3AV_REGION_50;
+ break;
+ case PS3_PARAM_AV_MULTI_OUT_PAL_RGB:
+ ps3av->region = PS3AV_REGION_50 | PS3AV_REGION_RGB;
+ break;
+ default:
+ ps3av->region = PS3AV_REGION_60;
+ break;
+ }
+
+ /* init avsetting modules */
+ res = ps3av_cmd_init();
+ if (res < 0)
+ printk(KERN_ERR "%s: ps3av_cmd_init failed %d\n", __func__,
+ res);
+
+ ps3av_get_hw_conf(ps3av);
+
+#ifdef CONFIG_FB
+ if (fb_mode_option && !strcmp(fb_mode_option, "safe"))
+ safe_mode = 1;
+#endif /* CONFIG_FB */
+ id = ps3av_auto_videomode(&ps3av->av_hw_conf);
+ if (id < 0) {
+ printk(KERN_ERR "%s: invalid id :%d\n", __func__, id);
+ res = -EINVAL;
+ goto fail;
+ }
+
+ safe_mode = 0;
+
+ mutex_lock(&ps3av->mutex);
+ ps3av->ps3av_mode = id;
+ mutex_unlock(&ps3av->mutex);
+
+ dev_dbg(&dev->core, " <- %s:%d\n", __func__, __LINE__);
+
+ return 0;
+
+fail:
+ kfree(ps3av);
+ ps3av = NULL;
+ return res;
+}
+
+static int ps3av_remove(struct ps3_system_bus_device *dev)
+{
+ dev_dbg(&dev->core, " -> %s:%d\n", __func__, __LINE__);
+ if (ps3av) {
+ ps3av_cmd_fin();
+ flush_work(&ps3av->work);
+ kfree(ps3av);
+ ps3av = NULL;
+ }
+
+ dev_dbg(&dev->core, " <- %s:%d\n", __func__, __LINE__);
+ return 0;
+}
+
+static void ps3av_shutdown(struct ps3_system_bus_device *dev)
+{
+ dev_dbg(&dev->core, " -> %s:%d\n", __func__, __LINE__);
+ ps3av_remove(dev);
+ dev_dbg(&dev->core, " <- %s:%d\n", __func__, __LINE__);
+}
+
+static struct ps3_vuart_port_driver ps3av_driver = {
+ .core.match_id = PS3_MATCH_ID_AV_SETTINGS,
+ .core.core.name = "ps3_av",
+ .probe = ps3av_probe,
+ .remove = ps3av_remove,
+ .shutdown = ps3av_shutdown,
+};
+
+static int __init ps3av_module_init(void)
+{
+ int error;
+
+ if (!firmware_has_feature(FW_FEATURE_PS3_LV1))
+ return -ENODEV;
+
+ pr_debug(" -> %s:%d\n", __func__, __LINE__);
+
+ error = ps3_vuart_port_driver_register(&ps3av_driver);
+ if (error) {
+ printk(KERN_ERR
+ "%s: ps3_vuart_port_driver_register failed %d\n",
+ __func__, error);
+ return error;
+ }
+
+ pr_debug(" <- %s:%d\n", __func__, __LINE__);
+ return error;
+}
+
+static void __exit ps3av_module_exit(void)
+{
+ pr_debug(" -> %s:%d\n", __func__, __LINE__);
+ ps3_vuart_port_driver_unregister(&ps3av_driver);
+ pr_debug(" <- %s:%d\n", __func__, __LINE__);
+}
+
+subsys_initcall(ps3av_module_init);
+module_exit(ps3av_module_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PS3 AV Settings Driver");
+MODULE_AUTHOR("Sony Computer Entertainment Inc.");
+MODULE_ALIAS(PS3_MODULE_ALIAS_AV_SETTINGS);
diff --git a/drivers/ps3/ps3av_cmd.c b/drivers/ps3/ps3av_cmd.c
new file mode 100644
index 000000000..c22206652
--- /dev/null
+++ b/drivers/ps3/ps3av_cmd.c
@@ -0,0 +1,924 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2006 Sony Computer Entertainment Inc.
+ * Copyright 2006, 2007 Sony Corporation
+ *
+ * AV backend support for PS3
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+
+#include <asm/ps3av.h>
+#include <asm/ps3.h>
+#include <asm/ps3gpu.h>
+
+#include "vuart.h"
+
+static const struct video_fmt {
+ u32 format;
+ u32 order;
+} ps3av_video_fmt_table[] = {
+ { PS3AV_CMD_VIDEO_FORMAT_ARGB_8BIT, PS3AV_CMD_VIDEO_ORDER_RGB },
+ { PS3AV_CMD_VIDEO_FORMAT_ARGB_8BIT, PS3AV_CMD_VIDEO_ORDER_BGR },
+};
+
+static const struct {
+ int cs;
+ u32 av;
+ u32 bl;
+} ps3av_cs_video2av_table[] = {
+ {
+ .cs = PS3AV_CMD_VIDEO_CS_RGB_8,
+ .av = PS3AV_CMD_AV_CS_RGB_8,
+ .bl = PS3AV_CMD_AV_CS_8
+ }, {
+ .cs = PS3AV_CMD_VIDEO_CS_RGB_10,
+ .av = PS3AV_CMD_AV_CS_RGB_8,
+ .bl = PS3AV_CMD_AV_CS_8
+ }, {
+ .cs = PS3AV_CMD_VIDEO_CS_RGB_12,
+ .av = PS3AV_CMD_AV_CS_RGB_8,
+ .bl = PS3AV_CMD_AV_CS_8
+ }, {
+ .cs = PS3AV_CMD_VIDEO_CS_YUV444_8,
+ .av = PS3AV_CMD_AV_CS_YUV444_8,
+ .bl = PS3AV_CMD_AV_CS_8
+ }, {
+ .cs = PS3AV_CMD_VIDEO_CS_YUV444_10,
+ .av = PS3AV_CMD_AV_CS_YUV444_8,
+ .bl = PS3AV_CMD_AV_CS_10
+ }, {
+ .cs = PS3AV_CMD_VIDEO_CS_YUV444_12,
+ .av = PS3AV_CMD_AV_CS_YUV444_8,
+ .bl = PS3AV_CMD_AV_CS_10
+ }, {
+ .cs = PS3AV_CMD_VIDEO_CS_YUV422_8,
+ .av = PS3AV_CMD_AV_CS_YUV422_8,
+ .bl = PS3AV_CMD_AV_CS_10
+ }, {
+ .cs = PS3AV_CMD_VIDEO_CS_YUV422_10,
+ .av = PS3AV_CMD_AV_CS_YUV422_8,
+ .bl = PS3AV_CMD_AV_CS_10
+ }, {
+ .cs = PS3AV_CMD_VIDEO_CS_YUV422_12,
+ .av = PS3AV_CMD_AV_CS_YUV422_8,
+ .bl = PS3AV_CMD_AV_CS_12
+ }, {
+ .cs = PS3AV_CMD_VIDEO_CS_XVYCC_8,
+ .av = PS3AV_CMD_AV_CS_XVYCC_8,
+ .bl = PS3AV_CMD_AV_CS_12
+ }, {
+ .cs = PS3AV_CMD_VIDEO_CS_XVYCC_10,
+ .av = PS3AV_CMD_AV_CS_XVYCC_8,
+ .bl = PS3AV_CMD_AV_CS_12
+ }, {
+ .cs = PS3AV_CMD_VIDEO_CS_XVYCC_12,
+ .av = PS3AV_CMD_AV_CS_XVYCC_8,
+ .bl = PS3AV_CMD_AV_CS_12
+ }
+};
+
+static u32 ps3av_cs_video2av(int cs)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(ps3av_cs_video2av_table); i++)
+ if (ps3av_cs_video2av_table[i].cs == cs)
+ return ps3av_cs_video2av_table[i].av;
+
+ return PS3AV_CMD_AV_CS_RGB_8;
+}
+
+static u32 ps3av_cs_video2av_bitlen(int cs)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(ps3av_cs_video2av_table); i++)
+ if (ps3av_cs_video2av_table[i].cs == cs)
+ return ps3av_cs_video2av_table[i].bl;
+
+ return PS3AV_CMD_AV_CS_8;
+}
+
+static const struct {
+ int vid;
+ u32 av;
+} ps3av_vid_video2av_table[] = {
+ { PS3AV_CMD_VIDEO_VID_480I, PS3AV_CMD_AV_VID_480I },
+ { PS3AV_CMD_VIDEO_VID_480P, PS3AV_CMD_AV_VID_480P },
+ { PS3AV_CMD_VIDEO_VID_576I, PS3AV_CMD_AV_VID_576I },
+ { PS3AV_CMD_VIDEO_VID_576P, PS3AV_CMD_AV_VID_576P },
+ { PS3AV_CMD_VIDEO_VID_1080I_60HZ, PS3AV_CMD_AV_VID_1080I_60HZ },
+ { PS3AV_CMD_VIDEO_VID_720P_60HZ, PS3AV_CMD_AV_VID_720P_60HZ },
+ { PS3AV_CMD_VIDEO_VID_1080P_60HZ, PS3AV_CMD_AV_VID_1080P_60HZ },
+ { PS3AV_CMD_VIDEO_VID_1080I_50HZ, PS3AV_CMD_AV_VID_1080I_50HZ },
+ { PS3AV_CMD_VIDEO_VID_720P_50HZ, PS3AV_CMD_AV_VID_720P_50HZ },
+ { PS3AV_CMD_VIDEO_VID_1080P_50HZ, PS3AV_CMD_AV_VID_1080P_50HZ },
+ { PS3AV_CMD_VIDEO_VID_WXGA, PS3AV_CMD_AV_VID_WXGA },
+ { PS3AV_CMD_VIDEO_VID_SXGA, PS3AV_CMD_AV_VID_SXGA },
+ { PS3AV_CMD_VIDEO_VID_WUXGA, PS3AV_CMD_AV_VID_WUXGA }
+};
+
+static u32 ps3av_vid_video2av(int vid)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(ps3av_vid_video2av_table); i++)
+ if (ps3av_vid_video2av_table[i].vid == vid)
+ return ps3av_vid_video2av_table[i].av;
+
+ return PS3AV_CMD_AV_VID_480P;
+}
+
+static int ps3av_hdmi_range(void)
+{
+ if (ps3_compare_firmware_version(1, 8, 0) < 0)
+ return 0;
+ else
+ return 1; /* supported */
+}
+
+int ps3av_cmd_init(void)
+{
+ int res;
+ struct ps3av_pkt_av_init av_init;
+ struct ps3av_pkt_video_init video_init;
+ struct ps3av_pkt_audio_init audio_init;
+
+ /* video init */
+ memset(&video_init, 0, sizeof(video_init));
+
+ res = ps3av_do_pkt(PS3AV_CID_VIDEO_INIT, sizeof(video_init.send_hdr),
+ sizeof(video_init), &video_init.send_hdr);
+ if (res < 0)
+ return res;
+
+ res = get_status(&video_init);
+ if (res) {
+ printk(KERN_ERR "PS3AV_CID_VIDEO_INIT: failed %x\n", res);
+ return res;
+ }
+
+ /* audio init */
+ memset(&audio_init, 0, sizeof(audio_init));
+
+ res = ps3av_do_pkt(PS3AV_CID_AUDIO_INIT, sizeof(audio_init.send_hdr),
+ sizeof(audio_init), &audio_init.send_hdr);
+ if (res < 0)
+ return res;
+
+ res = get_status(&audio_init);
+ if (res) {
+ printk(KERN_ERR "PS3AV_CID_AUDIO_INIT: failed %x\n", res);
+ return res;
+ }
+
+ /* av init */
+ memset(&av_init, 0, sizeof(av_init));
+ av_init.event_bit = 0;
+
+ res = ps3av_do_pkt(PS3AV_CID_AV_INIT, sizeof(av_init), sizeof(av_init),
+ &av_init.send_hdr);
+ if (res < 0)
+ return res;
+
+ res = get_status(&av_init);
+ if (res)
+ printk(KERN_ERR "PS3AV_CID_AV_INIT: failed %x\n", res);
+
+ return res;
+}
+
+int ps3av_cmd_fin(void)
+{
+ int res;
+ struct ps3av_pkt_av_fin av_fin;
+
+ memset(&av_fin, 0, sizeof(av_fin));
+
+ res = ps3av_do_pkt(PS3AV_CID_AV_FIN, sizeof(av_fin.send_hdr),
+ sizeof(av_fin), &av_fin.send_hdr);
+ if (res < 0)
+ return res;
+
+ res = get_status(&av_fin);
+ if (res)
+ printk(KERN_ERR "PS3AV_CID_AV_FIN: failed %x\n", res);
+
+ return res;
+}
+
+int ps3av_cmd_av_video_mute(int num_of_port, u32 *port, u32 mute)
+{
+ int i, send_len, res;
+ struct ps3av_pkt_av_video_mute av_video_mute;
+
+ if (num_of_port > PS3AV_MUTE_PORT_MAX)
+ return -EINVAL;
+
+ memset(&av_video_mute, 0, sizeof(av_video_mute));
+ for (i = 0; i < num_of_port; i++) {
+ av_video_mute.mute[i].avport = port[i];
+ av_video_mute.mute[i].mute = mute;
+ }
+
+ send_len = sizeof(av_video_mute.send_hdr) +
+ sizeof(struct ps3av_av_mute) * num_of_port;
+ res = ps3av_do_pkt(PS3AV_CID_AV_VIDEO_MUTE, send_len,
+ sizeof(av_video_mute), &av_video_mute.send_hdr);
+ if (res < 0)
+ return res;
+
+ res = get_status(&av_video_mute);
+ if (res)
+ printk(KERN_ERR "PS3AV_CID_AV_VIDEO_MUTE: failed %x\n", res);
+
+ return res;
+}
+
+int ps3av_cmd_av_video_disable_sig(u32 port)
+{
+ int res;
+ struct ps3av_pkt_av_video_disable_sig av_video_sig;
+
+ memset(&av_video_sig, 0, sizeof(av_video_sig));
+ av_video_sig.avport = port;
+
+ res = ps3av_do_pkt(PS3AV_CID_AV_VIDEO_DISABLE_SIG,
+ sizeof(av_video_sig), sizeof(av_video_sig),
+ &av_video_sig.send_hdr);
+ if (res < 0)
+ return res;
+
+ res = get_status(&av_video_sig);
+ if (res)
+ printk(KERN_ERR
+ "PS3AV_CID_AV_VIDEO_DISABLE_SIG: failed %x port:%x\n",
+ res, port);
+
+ return res;
+}
+
+int ps3av_cmd_av_tv_mute(u32 avport, u32 mute)
+{
+ int res;
+ struct ps3av_pkt_av_tv_mute tv_mute;
+
+ memset(&tv_mute, 0, sizeof(tv_mute));
+ tv_mute.avport = avport;
+ tv_mute.mute = mute;
+
+ res = ps3av_do_pkt(PS3AV_CID_AV_TV_MUTE, sizeof(tv_mute),
+ sizeof(tv_mute), &tv_mute.send_hdr);
+ if (res < 0)
+ return res;
+
+ res = get_status(&tv_mute);
+ if (res)
+ printk(KERN_ERR "PS3AV_CID_AV_TV_MUTE: failed %x port:%x\n",
+ res, avport);
+
+ return res;
+}
+
+int ps3av_cmd_enable_event(void)
+{
+ int res;
+ struct ps3av_pkt_av_event av_event;
+
+ memset(&av_event, 0, sizeof(av_event));
+ av_event.event_bit = PS3AV_CMD_EVENT_BIT_UNPLUGGED |
+ PS3AV_CMD_EVENT_BIT_PLUGGED | PS3AV_CMD_EVENT_BIT_HDCP_DONE;
+
+ res = ps3av_do_pkt(PS3AV_CID_AV_ENABLE_EVENT, sizeof(av_event),
+ sizeof(av_event), &av_event.send_hdr);
+ if (res < 0)
+ return res;
+
+ res = get_status(&av_event);
+ if (res)
+ printk(KERN_ERR "PS3AV_CID_AV_ENABLE_EVENT: failed %x\n", res);
+
+ return res;
+}
+
+int ps3av_cmd_av_hdmi_mode(u8 mode)
+{
+ int res;
+ struct ps3av_pkt_av_hdmi_mode hdmi_mode;
+
+ memset(&hdmi_mode, 0, sizeof(hdmi_mode));
+ hdmi_mode.mode = mode;
+
+ res = ps3av_do_pkt(PS3AV_CID_AV_HDMI_MODE, sizeof(hdmi_mode),
+ sizeof(hdmi_mode), &hdmi_mode.send_hdr);
+ if (res < 0)
+ return res;
+
+ res = get_status(&hdmi_mode);
+ if (res && res != PS3AV_STATUS_UNSUPPORTED_HDMI_MODE)
+ printk(KERN_ERR "PS3AV_CID_AV_HDMI_MODE: failed %x\n", res);
+
+ return res;
+}
+
+u32 ps3av_cmd_set_av_video_cs(void *p, u32 avport, int video_vid, int cs_out,
+ int aspect, u32 id)
+{
+ struct ps3av_pkt_av_video_cs *av_video_cs;
+
+ av_video_cs = (struct ps3av_pkt_av_video_cs *)p;
+ if (video_vid == -1)
+ video_vid = PS3AV_CMD_VIDEO_VID_720P_60HZ;
+ if (cs_out == -1)
+ cs_out = PS3AV_CMD_VIDEO_CS_YUV444_8;
+ if (aspect == -1)
+ aspect = 0;
+
+ memset(av_video_cs, 0, sizeof(*av_video_cs));
+ ps3av_set_hdr(PS3AV_CID_AV_VIDEO_CS, sizeof(*av_video_cs),
+ &av_video_cs->send_hdr);
+ av_video_cs->avport = avport;
+ /* should be same as video_mode.resolution */
+ av_video_cs->av_vid = ps3av_vid_video2av(video_vid);
+ av_video_cs->av_cs_out = ps3av_cs_video2av(cs_out);
+ /* should be same as video_mode.video_cs_out */
+ av_video_cs->av_cs_in = ps3av_cs_video2av(PS3AV_CMD_VIDEO_CS_RGB_8);
+ av_video_cs->bitlen_out = ps3av_cs_video2av_bitlen(cs_out);
+ if ((id & PS3AV_MODE_WHITE) && ps3av_hdmi_range())
+ av_video_cs->super_white = PS3AV_CMD_AV_SUPER_WHITE_ON;
+ else /* default off */
+ av_video_cs->super_white = PS3AV_CMD_AV_SUPER_WHITE_OFF;
+ av_video_cs->aspect = aspect;
+ if (id & PS3AV_MODE_DITHER) {
+ av_video_cs->dither = PS3AV_CMD_AV_DITHER_ON
+ | PS3AV_CMD_AV_DITHER_8BIT;
+ } else {
+ /* default off */
+ av_video_cs->dither = PS3AV_CMD_AV_DITHER_OFF;
+ }
+
+ return sizeof(*av_video_cs);
+}
+
+u32 ps3av_cmd_set_video_mode(void *p, u32 head, int video_vid, int video_fmt,
+ u32 id)
+{
+ struct ps3av_pkt_video_mode *video_mode;
+ u32 x, y;
+
+ video_mode = (struct ps3av_pkt_video_mode *)p;
+ if (video_vid == -1)
+ video_vid = PS3AV_CMD_VIDEO_VID_720P_60HZ;
+ if (video_fmt == -1)
+ video_fmt = PS3AV_CMD_VIDEO_FMT_X8R8G8B8;
+
+ if (ps3av_video_mode2res(id, &x, &y))
+ return 0;
+
+ /* video mode */
+ memset(video_mode, 0, sizeof(*video_mode));
+ ps3av_set_hdr(PS3AV_CID_VIDEO_MODE, sizeof(*video_mode),
+ &video_mode->send_hdr);
+ video_mode->video_head = head;
+ if (video_vid == PS3AV_CMD_VIDEO_VID_480I
+ && head == PS3AV_CMD_VIDEO_HEAD_B)
+ video_mode->video_vid = PS3AV_CMD_VIDEO_VID_480I_A;
+ else
+ video_mode->video_vid = video_vid;
+ video_mode->width = (u16) x;
+ video_mode->height = (u16) y;
+ video_mode->pitch = video_mode->width * 4; /* line_length */
+ video_mode->video_out_format = PS3AV_CMD_VIDEO_OUT_FORMAT_RGB_12BIT;
+ video_mode->video_format = ps3av_video_fmt_table[video_fmt].format;
+ if ((id & PS3AV_MODE_COLOR) && ps3av_hdmi_range())
+ video_mode->video_cl_cnv = PS3AV_CMD_VIDEO_CL_CNV_DISABLE_LUT;
+ else /* default enable */
+ video_mode->video_cl_cnv = PS3AV_CMD_VIDEO_CL_CNV_ENABLE_LUT;
+ video_mode->video_order = ps3av_video_fmt_table[video_fmt].order;
+
+ pr_debug("%s: video_mode:vid:%x width:%d height:%d pitch:%d out_format:%d format:%x order:%x\n",
+ __func__, video_vid, video_mode->width, video_mode->height,
+ video_mode->pitch, video_mode->video_out_format,
+ video_mode->video_format, video_mode->video_order);
+ return sizeof(*video_mode);
+}
+
+int ps3av_cmd_video_format_black(u32 head, u32 video_fmt, u32 mute)
+{
+ int res;
+ struct ps3av_pkt_video_format video_format;
+
+ memset(&video_format, 0, sizeof(video_format));
+ video_format.video_head = head;
+ if (mute != PS3AV_CMD_MUTE_OFF)
+ video_format.video_format = PS3AV_CMD_VIDEO_FORMAT_BLACK;
+ else
+ video_format.video_format =
+ ps3av_video_fmt_table[video_fmt].format;
+ video_format.video_order = ps3av_video_fmt_table[video_fmt].order;
+
+ res = ps3av_do_pkt(PS3AV_CID_VIDEO_FORMAT, sizeof(video_format),
+ sizeof(video_format), &video_format.send_hdr);
+ if (res < 0)
+ return res;
+
+ res = get_status(&video_format);
+ if (res)
+ printk(KERN_ERR "PS3AV_CID_VIDEO_FORMAT: failed %x\n", res);
+
+ return res;
+}
+
+
+int ps3av_cmd_av_audio_mute(int num_of_port, u32 *port, u32 mute)
+{
+ int i, res;
+ struct ps3av_pkt_av_audio_mute av_audio_mute;
+
+ if (num_of_port > PS3AV_MUTE_PORT_MAX)
+ return -EINVAL;
+
+ /* audio mute */
+ memset(&av_audio_mute, 0, sizeof(av_audio_mute));
+ for (i = 0; i < num_of_port; i++) {
+ av_audio_mute.mute[i].avport = port[i];
+ av_audio_mute.mute[i].mute = mute;
+ }
+
+ res = ps3av_do_pkt(PS3AV_CID_AV_AUDIO_MUTE,
+ sizeof(av_audio_mute.send_hdr) +
+ sizeof(struct ps3av_av_mute) * num_of_port,
+ sizeof(av_audio_mute), &av_audio_mute.send_hdr);
+ if (res < 0)
+ return res;
+
+ res = get_status(&av_audio_mute);
+ if (res)
+ printk(KERN_ERR "PS3AV_CID_AV_AUDIO_MUTE: failed %x\n", res);
+
+ return res;
+}
+
+static const struct {
+ u32 fs;
+ u8 mclk;
+} ps3av_cnv_mclk_table[] = {
+ { PS3AV_CMD_AUDIO_FS_44K, PS3AV_CMD_AV_MCLK_512 },
+ { PS3AV_CMD_AUDIO_FS_48K, PS3AV_CMD_AV_MCLK_512 },
+ { PS3AV_CMD_AUDIO_FS_88K, PS3AV_CMD_AV_MCLK_256 },
+ { PS3AV_CMD_AUDIO_FS_96K, PS3AV_CMD_AV_MCLK_256 },
+ { PS3AV_CMD_AUDIO_FS_176K, PS3AV_CMD_AV_MCLK_128 },
+ { PS3AV_CMD_AUDIO_FS_192K, PS3AV_CMD_AV_MCLK_128 }
+};
+
+static u8 ps3av_cnv_mclk(u32 fs)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(ps3av_cnv_mclk_table); i++)
+ if (ps3av_cnv_mclk_table[i].fs == fs)
+ return ps3av_cnv_mclk_table[i].mclk;
+
+ printk(KERN_ERR "%s failed, fs:%x\n", __func__, fs);
+ return 0;
+}
+
+#define BASE PS3AV_CMD_AUDIO_FS_44K
+
+static const u32 ps3av_ns_table[][5] = {
+ /* D1, D2, D3, D4, D5 */
+ [PS3AV_CMD_AUDIO_FS_44K-BASE] = { 6272, 6272, 17836, 17836, 8918 },
+ [PS3AV_CMD_AUDIO_FS_48K-BASE] = { 6144, 6144, 11648, 11648, 5824 },
+ [PS3AV_CMD_AUDIO_FS_88K-BASE] = { 12544, 12544, 35672, 35672, 17836 },
+ [PS3AV_CMD_AUDIO_FS_96K-BASE] = { 12288, 12288, 23296, 23296, 11648 },
+ [PS3AV_CMD_AUDIO_FS_176K-BASE] = { 25088, 25088, 71344, 71344, 35672 },
+ [PS3AV_CMD_AUDIO_FS_192K-BASE] = { 24576, 24576, 46592, 46592, 23296 }
+};
+
+static void ps3av_cnv_ns(u8 *ns, u32 fs, u32 video_vid)
+{
+ u32 av_vid, ns_val;
+ int d;
+
+ d = ns_val = 0;
+ av_vid = ps3av_vid_video2av(video_vid);
+ switch (av_vid) {
+ case PS3AV_CMD_AV_VID_480I:
+ case PS3AV_CMD_AV_VID_576I:
+ d = 0;
+ break;
+ case PS3AV_CMD_AV_VID_480P:
+ case PS3AV_CMD_AV_VID_576P:
+ d = 1;
+ break;
+ case PS3AV_CMD_AV_VID_1080I_60HZ:
+ case PS3AV_CMD_AV_VID_1080I_50HZ:
+ d = 2;
+ break;
+ case PS3AV_CMD_AV_VID_720P_60HZ:
+ case PS3AV_CMD_AV_VID_720P_50HZ:
+ d = 3;
+ break;
+ case PS3AV_CMD_AV_VID_1080P_60HZ:
+ case PS3AV_CMD_AV_VID_1080P_50HZ:
+ case PS3AV_CMD_AV_VID_WXGA:
+ case PS3AV_CMD_AV_VID_SXGA:
+ case PS3AV_CMD_AV_VID_WUXGA:
+ d = 4;
+ break;
+ default:
+ printk(KERN_ERR "%s failed, vid:%x\n", __func__, video_vid);
+ break;
+ }
+
+ if (fs < PS3AV_CMD_AUDIO_FS_44K || fs > PS3AV_CMD_AUDIO_FS_192K)
+ printk(KERN_ERR "%s failed, fs:%x\n", __func__, fs);
+ else
+ ns_val = ps3av_ns_table[PS3AV_CMD_AUDIO_FS_44K-BASE][d];
+
+ *ns++ = ns_val & 0x000000FF;
+ *ns++ = (ns_val & 0x0000FF00) >> 8;
+ *ns = (ns_val & 0x00FF0000) >> 16;
+}
+
+#undef BASE
+
+static u8 ps3av_cnv_enable(u32 source, const u8 *enable)
+{
+ u8 ret = 0;
+
+ if (source == PS3AV_CMD_AUDIO_SOURCE_SPDIF) {
+ ret = 0x03;
+ } else if (source == PS3AV_CMD_AUDIO_SOURCE_SERIAL) {
+ ret = ((enable[0] << 4) + (enable[1] << 5) + (enable[2] << 6) +
+ (enable[3] << 7)) | 0x01;
+ } else
+ printk(KERN_ERR "%s failed, source:%x\n", __func__, source);
+ return ret;
+}
+
+static u8 ps3av_cnv_fifomap(const u8 *map)
+{
+ u8 ret = 0;
+
+ ret = map[0] + (map[1] << 2) + (map[2] << 4) + (map[3] << 6);
+ return ret;
+}
+
+static u8 ps3av_cnv_inputlen(u32 word_bits)
+{
+ u8 ret = 0;
+
+ switch (word_bits) {
+ case PS3AV_CMD_AUDIO_WORD_BITS_16:
+ ret = PS3AV_CMD_AV_INPUTLEN_16;
+ break;
+ case PS3AV_CMD_AUDIO_WORD_BITS_20:
+ ret = PS3AV_CMD_AV_INPUTLEN_20;
+ break;
+ case PS3AV_CMD_AUDIO_WORD_BITS_24:
+ ret = PS3AV_CMD_AV_INPUTLEN_24;
+ break;
+ default:
+ printk(KERN_ERR "%s failed, word_bits:%x\n", __func__,
+ word_bits);
+ break;
+ }
+ return ret;
+}
+
+static u8 ps3av_cnv_layout(u32 num_of_ch)
+{
+ if (num_of_ch > PS3AV_CMD_AUDIO_NUM_OF_CH_8) {
+ printk(KERN_ERR "%s failed, num_of_ch:%x\n", __func__,
+ num_of_ch);
+ return 0;
+ }
+
+ return num_of_ch == PS3AV_CMD_AUDIO_NUM_OF_CH_2 ? 0x0 : 0x1;
+}
+
+static void ps3av_cnv_info(struct ps3av_audio_info_frame *info,
+ const struct ps3av_pkt_audio_mode *mode)
+{
+ info->pb1.cc = mode->audio_num_of_ch + 1; /* CH2:0x01 --- CH8:0x07 */
+ info->pb1.ct = 0;
+ info->pb2.sf = 0;
+ info->pb2.ss = 0;
+
+ info->pb3 = 0; /* check mode->audio_format ?? */
+ info->pb4 = mode->audio_layout;
+ info->pb5.dm = mode->audio_downmix;
+ info->pb5.lsv = mode->audio_downmix_level;
+}
+
+static void ps3av_cnv_chstat(u8 *chstat, const u8 *cs_info)
+{
+ memcpy(chstat, cs_info, 5);
+}
+
+u32 ps3av_cmd_set_av_audio_param(void *p, u32 port,
+ const struct ps3av_pkt_audio_mode *audio_mode,
+ u32 video_vid)
+{
+ struct ps3av_pkt_av_audio_param *param;
+
+ param = (struct ps3av_pkt_av_audio_param *)p;
+
+ memset(param, 0, sizeof(*param));
+ ps3av_set_hdr(PS3AV_CID_AV_AUDIO_PARAM, sizeof(*param),
+ &param->send_hdr);
+
+ param->avport = port;
+ param->mclk = ps3av_cnv_mclk(audio_mode->audio_fs) | 0x80;
+ ps3av_cnv_ns(param->ns, audio_mode->audio_fs, video_vid);
+ param->enable = ps3av_cnv_enable(audio_mode->audio_source,
+ audio_mode->audio_enable);
+ param->swaplr = 0x09;
+ param->fifomap = ps3av_cnv_fifomap(audio_mode->audio_map);
+ param->inputctrl = 0x49;
+ param->inputlen = ps3av_cnv_inputlen(audio_mode->audio_word_bits);
+ param->layout = ps3av_cnv_layout(audio_mode->audio_num_of_ch);
+ ps3av_cnv_info(&param->info, audio_mode);
+ ps3av_cnv_chstat(param->chstat, audio_mode->audio_cs_info);
+
+ return sizeof(*param);
+}
+
+/* default cs val */
+u8 ps3av_mode_cs_info[] = {
+ 0x00, 0x09, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00
+};
+EXPORT_SYMBOL_GPL(ps3av_mode_cs_info);
+
+#define CS_44 0x00
+#define CS_48 0x02
+#define CS_88 0x08
+#define CS_96 0x0a
+#define CS_176 0x0c
+#define CS_192 0x0e
+#define CS_MASK 0x0f
+#define CS_BIT 0x40
+
+void ps3av_cmd_set_audio_mode(struct ps3av_pkt_audio_mode *audio, u32 avport,
+ u32 ch, u32 fs, u32 word_bits, u32 format,
+ u32 source)
+{
+ int spdif_through;
+ int i;
+
+ if (!(ch | fs | format | word_bits | source)) {
+ ch = PS3AV_CMD_AUDIO_NUM_OF_CH_2;
+ fs = PS3AV_CMD_AUDIO_FS_48K;
+ word_bits = PS3AV_CMD_AUDIO_WORD_BITS_16;
+ format = PS3AV_CMD_AUDIO_FORMAT_PCM;
+ source = PS3AV_CMD_AUDIO_SOURCE_SERIAL;
+ }
+
+ /* audio mode */
+ memset(audio, 0, sizeof(*audio));
+ ps3av_set_hdr(PS3AV_CID_AUDIO_MODE, sizeof(*audio), &audio->send_hdr);
+
+ audio->avport = (u8) avport;
+ audio->mask = 0x0FFF; /* XXX set all */
+ audio->audio_num_of_ch = ch;
+ audio->audio_fs = fs;
+ audio->audio_word_bits = word_bits;
+ audio->audio_format = format;
+ audio->audio_source = source;
+
+ switch (ch) {
+ case PS3AV_CMD_AUDIO_NUM_OF_CH_8:
+ audio->audio_enable[3] = 1;
+ fallthrough;
+ case PS3AV_CMD_AUDIO_NUM_OF_CH_6:
+ audio->audio_enable[2] = 1;
+ audio->audio_enable[1] = 1;
+ fallthrough;
+ case PS3AV_CMD_AUDIO_NUM_OF_CH_2:
+ default:
+ audio->audio_enable[0] = 1;
+ }
+
+ /* audio swap L/R */
+ for (i = 0; i < 4; i++)
+ audio->audio_swap[i] = PS3AV_CMD_AUDIO_SWAP_0; /* no swap */
+
+ /* audio serial input mapping */
+ audio->audio_map[0] = PS3AV_CMD_AUDIO_MAP_OUTPUT_0;
+ audio->audio_map[1] = PS3AV_CMD_AUDIO_MAP_OUTPUT_1;
+ audio->audio_map[2] = PS3AV_CMD_AUDIO_MAP_OUTPUT_2;
+ audio->audio_map[3] = PS3AV_CMD_AUDIO_MAP_OUTPUT_3;
+
+ /* audio speaker layout */
+ if (avport == PS3AV_CMD_AVPORT_HDMI_0 ||
+ avport == PS3AV_CMD_AVPORT_HDMI_1) {
+ switch (ch) {
+ case PS3AV_CMD_AUDIO_NUM_OF_CH_8:
+ audio->audio_layout = PS3AV_CMD_AUDIO_LAYOUT_8CH;
+ break;
+ case PS3AV_CMD_AUDIO_NUM_OF_CH_6:
+ audio->audio_layout = PS3AV_CMD_AUDIO_LAYOUT_6CH;
+ break;
+ case PS3AV_CMD_AUDIO_NUM_OF_CH_2:
+ default:
+ audio->audio_layout = PS3AV_CMD_AUDIO_LAYOUT_2CH;
+ break;
+ }
+ } else {
+ audio->audio_layout = PS3AV_CMD_AUDIO_LAYOUT_2CH;
+ }
+
+ /* audio downmix permission */
+ audio->audio_downmix = PS3AV_CMD_AUDIO_DOWNMIX_PERMITTED;
+ /* audio downmix level shift (0:0dB to 15:15dB) */
+ audio->audio_downmix_level = 0; /* 0dB */
+
+ /* set ch status */
+ for (i = 0; i < 8; i++)
+ audio->audio_cs_info[i] = ps3av_mode_cs_info[i];
+
+ switch (fs) {
+ case PS3AV_CMD_AUDIO_FS_44K:
+ audio->audio_cs_info[3] &= ~CS_MASK;
+ audio->audio_cs_info[3] |= CS_44;
+ break;
+ case PS3AV_CMD_AUDIO_FS_88K:
+ audio->audio_cs_info[3] &= ~CS_MASK;
+ audio->audio_cs_info[3] |= CS_88;
+ break;
+ case PS3AV_CMD_AUDIO_FS_96K:
+ audio->audio_cs_info[3] &= ~CS_MASK;
+ audio->audio_cs_info[3] |= CS_96;
+ break;
+ case PS3AV_CMD_AUDIO_FS_176K:
+ audio->audio_cs_info[3] &= ~CS_MASK;
+ audio->audio_cs_info[3] |= CS_176;
+ break;
+ case PS3AV_CMD_AUDIO_FS_192K:
+ audio->audio_cs_info[3] &= ~CS_MASK;
+ audio->audio_cs_info[3] |= CS_192;
+ break;
+ default:
+ break;
+ }
+
+ /* non-audio bit */
+ spdif_through = audio->audio_cs_info[0] & 0x02;
+
+ /* pass through setting */
+ if (spdif_through &&
+ (avport == PS3AV_CMD_AVPORT_SPDIF_0 ||
+ avport == PS3AV_CMD_AVPORT_SPDIF_1 ||
+ avport == PS3AV_CMD_AVPORT_HDMI_0 ||
+ avport == PS3AV_CMD_AVPORT_HDMI_1)) {
+ audio->audio_word_bits = PS3AV_CMD_AUDIO_WORD_BITS_16;
+ audio->audio_format = PS3AV_CMD_AUDIO_FORMAT_BITSTREAM;
+ }
+}
+
+int ps3av_cmd_audio_mode(struct ps3av_pkt_audio_mode *audio_mode)
+{
+ int res;
+
+ res = ps3av_do_pkt(PS3AV_CID_AUDIO_MODE, sizeof(*audio_mode),
+ sizeof(*audio_mode), &audio_mode->send_hdr);
+ if (res < 0)
+ return res;
+
+ res = get_status(audio_mode);
+ if (res)
+ printk(KERN_ERR "PS3AV_CID_AUDIO_MODE: failed %x\n", res);
+
+ return res;
+}
+
+int ps3av_cmd_audio_mute(int num_of_port, u32 *port, u32 mute)
+{
+ int i, res;
+ struct ps3av_pkt_audio_mute audio_mute;
+
+ if (num_of_port > PS3AV_OPT_PORT_MAX)
+ return -EINVAL;
+
+ /* audio mute */
+ memset(&audio_mute, 0, sizeof(audio_mute));
+ for (i = 0; i < num_of_port; i++) {
+ audio_mute.mute[i].avport = port[i];
+ audio_mute.mute[i].mute = mute;
+ }
+
+ res = ps3av_do_pkt(PS3AV_CID_AUDIO_MUTE,
+ sizeof(audio_mute.send_hdr) +
+ sizeof(struct ps3av_audio_mute) * num_of_port,
+ sizeof(audio_mute), &audio_mute.send_hdr);
+ if (res < 0)
+ return res;
+
+ res = get_status(&audio_mute);
+ if (res)
+ printk(KERN_ERR "PS3AV_CID_AUDIO_MUTE: failed %x\n", res);
+
+ return res;
+}
+
+int ps3av_cmd_audio_active(int active, u32 port)
+{
+ int res;
+ struct ps3av_pkt_audio_active audio_active;
+ u32 cid;
+
+ /* audio active */
+ memset(&audio_active, 0, sizeof(audio_active));
+ audio_active.audio_port = port;
+ cid = active ? PS3AV_CID_AUDIO_ACTIVE : PS3AV_CID_AUDIO_INACTIVE;
+
+ res = ps3av_do_pkt(cid, sizeof(audio_active), sizeof(audio_active),
+ &audio_active.send_hdr);
+ if (res < 0)
+ return res;
+
+ res = get_status(&audio_active);
+ if (res)
+ printk(KERN_ERR "PS3AV_CID_AUDIO_ACTIVE:%x failed %x\n", cid,
+ res);
+
+ return res;
+}
+
+int ps3av_cmd_avb_param(struct ps3av_pkt_avb_param *avb, u32 send_len)
+{
+ int res;
+
+ mutex_lock(&ps3_gpu_mutex);
+
+ /* avb packet */
+ res = ps3av_do_pkt(PS3AV_CID_AVB_PARAM, send_len, sizeof(*avb),
+ &avb->send_hdr);
+ if (res < 0)
+ goto out;
+
+ res = get_status(avb);
+ if (res)
+ pr_debug("%s: PS3AV_CID_AVB_PARAM: failed %x\n", __func__,
+ res);
+
+ out:
+ mutex_unlock(&ps3_gpu_mutex);
+ return res;
+}
+
+int ps3av_cmd_av_get_hw_conf(struct ps3av_pkt_av_get_hw_conf *hw_conf)
+{
+ int res;
+
+ memset(hw_conf, 0, sizeof(*hw_conf));
+
+ res = ps3av_do_pkt(PS3AV_CID_AV_GET_HW_CONF, sizeof(hw_conf->send_hdr),
+ sizeof(*hw_conf), &hw_conf->send_hdr);
+ if (res < 0)
+ return res;
+
+ res = get_status(hw_conf);
+ if (res)
+ printk(KERN_ERR "PS3AV_CID_AV_GET_HW_CONF: failed %x\n", res);
+
+ return res;
+}
+
+int ps3av_cmd_video_get_monitor_info(struct ps3av_pkt_av_get_monitor_info *info,
+ u32 avport)
+{
+ int res;
+
+ memset(info, 0, sizeof(*info));
+ info->avport = avport;
+
+ res = ps3av_do_pkt(PS3AV_CID_AV_GET_MONITOR_INFO,
+ sizeof(info->send_hdr) + sizeof(info->avport) +
+ sizeof(info->reserved),
+ sizeof(*info), &info->send_hdr);
+ if (res < 0)
+ return res;
+
+ res = get_status(info);
+ if (res)
+ printk(KERN_ERR "PS3AV_CID_AV_GET_MONITOR_INFO: failed %x\n",
+ res);
+
+ return res;
+}
+
+#define PS3AV_AV_LAYOUT_0 (PS3AV_CMD_AV_LAYOUT_32 \
+ | PS3AV_CMD_AV_LAYOUT_44 \
+ | PS3AV_CMD_AV_LAYOUT_48)
+
+#define PS3AV_AV_LAYOUT_1 (PS3AV_AV_LAYOUT_0 \
+ | PS3AV_CMD_AV_LAYOUT_88 \
+ | PS3AV_CMD_AV_LAYOUT_96 \
+ | PS3AV_CMD_AV_LAYOUT_176 \
+ | PS3AV_CMD_AV_LAYOUT_192)
+
diff --git a/drivers/ps3/ps3stor_lib.c b/drivers/ps3/ps3stor_lib.c
new file mode 100644
index 000000000..a12a1ad9b
--- /dev/null
+++ b/drivers/ps3/ps3stor_lib.c
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PS3 Storage Library
+ *
+ * Copyright (C) 2007 Sony Computer Entertainment Inc.
+ * Copyright 2007 Sony Corp.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+
+#include <asm/lv1call.h>
+#include <asm/ps3stor.h>
+
+/*
+ * A workaround for flash memory I/O errors when the internal hard disk
+ * has not been formatted for OtherOS use. Delay disk close until flash
+ * memory is closed.
+ */
+
+static struct ps3_flash_workaround {
+ int flash_open;
+ int disk_open;
+ struct ps3_system_bus_device *disk_sbd;
+} ps3_flash_workaround;
+
+static int ps3stor_open_hv_device(struct ps3_system_bus_device *sbd)
+{
+ int error = ps3_open_hv_device(sbd);
+
+ if (error)
+ return error;
+
+ if (sbd->match_id == PS3_MATCH_ID_STOR_FLASH)
+ ps3_flash_workaround.flash_open = 1;
+
+ if (sbd->match_id == PS3_MATCH_ID_STOR_DISK)
+ ps3_flash_workaround.disk_open = 1;
+
+ return 0;
+}
+
+static int ps3stor_close_hv_device(struct ps3_system_bus_device *sbd)
+{
+ int error;
+
+ if (sbd->match_id == PS3_MATCH_ID_STOR_DISK
+ && ps3_flash_workaround.disk_open
+ && ps3_flash_workaround.flash_open) {
+ ps3_flash_workaround.disk_sbd = sbd;
+ return 0;
+ }
+
+ error = ps3_close_hv_device(sbd);
+
+ if (error)
+ return error;
+
+ if (sbd->match_id == PS3_MATCH_ID_STOR_DISK)
+ ps3_flash_workaround.disk_open = 0;
+
+ if (sbd->match_id == PS3_MATCH_ID_STOR_FLASH) {
+ ps3_flash_workaround.flash_open = 0;
+
+ if (ps3_flash_workaround.disk_sbd) {
+ ps3_close_hv_device(ps3_flash_workaround.disk_sbd);
+ ps3_flash_workaround.disk_open = 0;
+ ps3_flash_workaround.disk_sbd = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static int ps3stor_probe_access(struct ps3_storage_device *dev)
+{
+ int res, error;
+ unsigned int i;
+ unsigned long n;
+
+ if (dev->sbd.match_id == PS3_MATCH_ID_STOR_ROM) {
+ /* special case: CD-ROM is assumed always accessible */
+ dev->accessible_regions = 1;
+ return 0;
+ }
+
+ error = -EPERM;
+ for (i = 0; i < dev->num_regions; i++) {
+ dev_dbg(&dev->sbd.core,
+ "%s:%u: checking accessibility of region %u\n",
+ __func__, __LINE__, i);
+
+ dev->region_idx = i;
+ res = ps3stor_read_write_sectors(dev, dev->bounce_lpar, 0, 1,
+ 0);
+ if (res) {
+ dev_dbg(&dev->sbd.core, "%s:%u: read failed, "
+ "region %u is not accessible\n", __func__,
+ __LINE__, i);
+ continue;
+ }
+
+ dev_dbg(&dev->sbd.core, "%s:%u: region %u is accessible\n",
+ __func__, __LINE__, i);
+ set_bit(i, &dev->accessible_regions);
+
+ /* We can access at least one region */
+ error = 0;
+ }
+ if (error)
+ return error;
+
+ n = hweight_long(dev->accessible_regions);
+ if (n > 1)
+ dev_info(&dev->sbd.core,
+ "%s:%u: %lu accessible regions found. Only the first "
+ "one will be used\n",
+ __func__, __LINE__, n);
+ dev->region_idx = __ffs(dev->accessible_regions);
+ dev_info(&dev->sbd.core,
+ "First accessible region has index %u start %llu size %llu\n",
+ dev->region_idx, dev->regions[dev->region_idx].start,
+ dev->regions[dev->region_idx].size);
+
+ return 0;
+}
+
+
+/**
+ * ps3stor_setup - Setup a storage device before use
+ * @dev: Pointer to a struct ps3_storage_device
+ * @handler: Pointer to an interrupt handler
+ *
+ * Returns 0 for success, or an error code
+ */
+int ps3stor_setup(struct ps3_storage_device *dev, irq_handler_t handler)
+{
+ int error, res, alignment;
+ enum ps3_dma_page_size page_size;
+
+ error = ps3stor_open_hv_device(&dev->sbd);
+ if (error) {
+ dev_err(&dev->sbd.core,
+ "%s:%u: ps3_open_hv_device failed %d\n", __func__,
+ __LINE__, error);
+ goto fail;
+ }
+
+ error = ps3_sb_event_receive_port_setup(&dev->sbd, PS3_BINDING_CPU_ANY,
+ &dev->irq);
+ if (error) {
+ dev_err(&dev->sbd.core,
+ "%s:%u: ps3_sb_event_receive_port_setup failed %d\n",
+ __func__, __LINE__, error);
+ goto fail_close_device;
+ }
+
+ error = request_irq(dev->irq, handler, 0,
+ dev->sbd.core.driver->name, dev);
+ if (error) {
+ dev_err(&dev->sbd.core, "%s:%u: request_irq failed %d\n",
+ __func__, __LINE__, error);
+ goto fail_sb_event_receive_port_destroy;
+ }
+
+ alignment = min(__ffs(dev->bounce_size),
+ __ffs((unsigned long)dev->bounce_buf));
+ if (alignment < 12) {
+ dev_err(&dev->sbd.core,
+ "%s:%u: bounce buffer not aligned (%lx at 0x%p)\n",
+ __func__, __LINE__, dev->bounce_size, dev->bounce_buf);
+ error = -EINVAL;
+ goto fail_free_irq;
+ } else if (alignment < 16)
+ page_size = PS3_DMA_4K;
+ else
+ page_size = PS3_DMA_64K;
+ dev->sbd.d_region = &dev->dma_region;
+ ps3_dma_region_init(&dev->sbd, &dev->dma_region, page_size,
+ PS3_DMA_OTHER, dev->bounce_buf, dev->bounce_size);
+ res = ps3_dma_region_create(&dev->dma_region);
+ if (res) {
+ dev_err(&dev->sbd.core, "%s:%u: cannot create DMA region\n",
+ __func__, __LINE__);
+ error = -ENOMEM;
+ goto fail_free_irq;
+ }
+
+ dev->bounce_lpar = ps3_mm_phys_to_lpar(__pa(dev->bounce_buf));
+ dev->bounce_dma = dma_map_single(&dev->sbd.core, dev->bounce_buf,
+ dev->bounce_size, DMA_BIDIRECTIONAL);
+ if (dma_mapping_error(&dev->sbd.core, dev->bounce_dma)) {
+ dev_err(&dev->sbd.core, "%s:%u: map DMA region failed\n",
+ __func__, __LINE__);
+ error = -ENODEV;
+ goto fail_free_dma;
+ }
+
+ error = ps3stor_probe_access(dev);
+ if (error) {
+ dev_err(&dev->sbd.core, "%s:%u: No accessible regions found\n",
+ __func__, __LINE__);
+ goto fail_unmap_dma;
+ }
+ return 0;
+
+fail_unmap_dma:
+ dma_unmap_single(&dev->sbd.core, dev->bounce_dma, dev->bounce_size,
+ DMA_BIDIRECTIONAL);
+fail_free_dma:
+ ps3_dma_region_free(&dev->dma_region);
+fail_free_irq:
+ free_irq(dev->irq, dev);
+fail_sb_event_receive_port_destroy:
+ ps3_sb_event_receive_port_destroy(&dev->sbd, dev->irq);
+fail_close_device:
+ ps3stor_close_hv_device(&dev->sbd);
+fail:
+ return error;
+}
+EXPORT_SYMBOL_GPL(ps3stor_setup);
+
+
+/**
+ * ps3stor_teardown - Tear down a storage device after use
+ * @dev: Pointer to a struct ps3_storage_device
+ */
+void ps3stor_teardown(struct ps3_storage_device *dev)
+{
+ int error;
+
+ dma_unmap_single(&dev->sbd.core, dev->bounce_dma, dev->bounce_size,
+ DMA_BIDIRECTIONAL);
+ ps3_dma_region_free(&dev->dma_region);
+
+ free_irq(dev->irq, dev);
+
+ error = ps3_sb_event_receive_port_destroy(&dev->sbd, dev->irq);
+ if (error)
+ dev_err(&dev->sbd.core,
+ "%s:%u: destroy event receive port failed %d\n",
+ __func__, __LINE__, error);
+
+ error = ps3stor_close_hv_device(&dev->sbd);
+ if (error)
+ dev_err(&dev->sbd.core,
+ "%s:%u: ps3_close_hv_device failed %d\n", __func__,
+ __LINE__, error);
+}
+EXPORT_SYMBOL_GPL(ps3stor_teardown);
+
+
+/**
+ * ps3stor_read_write_sectors - read/write from/to a storage device
+ * @dev: Pointer to a struct ps3_storage_device
+ * @lpar: HV logical partition address
+ * @start_sector: First sector to read/write
+ * @sectors: Number of sectors to read/write
+ * @write: Flag indicating write (non-zero) or read (zero)
+ *
+ * Returns 0 for success, -1 in case of failure to submit the command, or
+ * an LV1 status value in case of other errors
+ */
+u64 ps3stor_read_write_sectors(struct ps3_storage_device *dev, u64 lpar,
+ u64 start_sector, u64 sectors, int write)
+{
+ unsigned int region_id = dev->regions[dev->region_idx].id;
+ const char *op = write ? "write" : "read";
+ int res;
+
+ dev_dbg(&dev->sbd.core, "%s:%u: %s %llu sectors starting at %llu\n",
+ __func__, __LINE__, op, sectors, start_sector);
+
+ init_completion(&dev->done);
+ res = write ? lv1_storage_write(dev->sbd.dev_id, region_id,
+ start_sector, sectors, 0, lpar,
+ &dev->tag)
+ : lv1_storage_read(dev->sbd.dev_id, region_id,
+ start_sector, sectors, 0, lpar,
+ &dev->tag);
+ if (res) {
+ dev_dbg(&dev->sbd.core, "%s:%u: %s failed %d\n", __func__,
+ __LINE__, op, res);
+ return -1;
+ }
+
+ wait_for_completion(&dev->done);
+ if (dev->lv1_status) {
+ dev_dbg(&dev->sbd.core, "%s:%u: %s failed 0x%llx\n", __func__,
+ __LINE__, op, dev->lv1_status);
+ return dev->lv1_status;
+ }
+
+ dev_dbg(&dev->sbd.core, "%s:%u: %s completed\n", __func__, __LINE__,
+ op);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ps3stor_read_write_sectors);
+
+
+/**
+ * ps3stor_send_command - send a device command to a storage device
+ * @dev: Pointer to a struct ps3_storage_device
+ * @cmd: Command number
+ * @arg1: First command argument
+ * @arg2: Second command argument
+ * @arg3: Third command argument
+ * @arg4: Fourth command argument
+ *
+ * Returns 0 for success, -1 in case of failure to submit the command, or
+ * an LV1 status value in case of other errors
+ */
+u64 ps3stor_send_command(struct ps3_storage_device *dev, u64 cmd, u64 arg1,
+ u64 arg2, u64 arg3, u64 arg4)
+{
+ int res;
+
+ dev_dbg(&dev->sbd.core, "%s:%u: send device command 0x%llx\n", __func__,
+ __LINE__, cmd);
+
+ init_completion(&dev->done);
+
+ res = lv1_storage_send_device_command(dev->sbd.dev_id, cmd, arg1,
+ arg2, arg3, arg4, &dev->tag);
+ if (res) {
+ dev_err(&dev->sbd.core,
+ "%s:%u: send_device_command 0x%llx failed %d\n",
+ __func__, __LINE__, cmd, res);
+ return -1;
+ }
+
+ wait_for_completion(&dev->done);
+ if (dev->lv1_status) {
+ dev_dbg(&dev->sbd.core, "%s:%u: command 0x%llx failed 0x%llx\n",
+ __func__, __LINE__, cmd, dev->lv1_status);
+ return dev->lv1_status;
+ }
+
+ dev_dbg(&dev->sbd.core, "%s:%u: command 0x%llx completed\n", __func__,
+ __LINE__, cmd);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ps3stor_send_command);
+
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("PS3 Storage Bus Library");
+MODULE_AUTHOR("Sony Corporation");
diff --git a/drivers/ps3/sys-manager-core.c b/drivers/ps3/sys-manager-core.c
new file mode 100644
index 000000000..e061b7d06
--- /dev/null
+++ b/drivers/ps3/sys-manager-core.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PS3 System Manager core.
+ *
+ * Copyright (C) 2007 Sony Computer Entertainment Inc.
+ * Copyright 2007 Sony Corp.
+ */
+
+#include <linux/kernel.h>
+#include <linux/export.h>
+#include <asm/lv1call.h>
+#include <asm/ps3.h>
+
+/**
+ * Staticly linked routines that allow late binding of a loaded sys-manager
+ * module.
+ */
+
+static struct ps3_sys_manager_ops ps3_sys_manager_ops;
+
+/**
+ * ps3_register_sys_manager_ops - Bind ps3_sys_manager_ops to a module.
+ * @ops: struct ps3_sys_manager_ops.
+ *
+ * To be called from ps3_sys_manager_probe() and ps3_sys_manager_remove() to
+ * register call back ops for power control. Copies data to the static
+ * variable ps3_sys_manager_ops.
+ */
+
+void ps3_sys_manager_register_ops(const struct ps3_sys_manager_ops *ops)
+{
+ BUG_ON(!ops);
+ BUG_ON(!ops->dev);
+ ps3_sys_manager_ops = *ops;
+}
+EXPORT_SYMBOL_GPL(ps3_sys_manager_register_ops);
+
+void __noreturn ps3_sys_manager_power_off(void)
+{
+ if (ps3_sys_manager_ops.power_off)
+ ps3_sys_manager_ops.power_off(ps3_sys_manager_ops.dev);
+
+ ps3_sys_manager_halt();
+}
+
+void __noreturn ps3_sys_manager_restart(void)
+{
+ if (ps3_sys_manager_ops.restart)
+ ps3_sys_manager_ops.restart(ps3_sys_manager_ops.dev);
+
+ ps3_sys_manager_halt();
+}
+
+void __noreturn ps3_sys_manager_halt(void)
+{
+ pr_emerg("System Halted, OK to turn off power\n");
+ local_irq_disable();
+ while (1)
+ lv1_pause(1);
+}
+
diff --git a/drivers/ps3/vuart.h b/drivers/ps3/vuart.h
new file mode 100644
index 000000000..64d34efef
--- /dev/null
+++ b/drivers/ps3/vuart.h
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * PS3 virtual uart
+ *
+ * Copyright (C) 2006 Sony Computer Entertainment Inc.
+ * Copyright 2006 Sony Corp.
+ */
+
+#if !defined(_PS3_VUART_H)
+#define _PS3_VUART_H
+
+#include <asm/ps3.h>
+
+struct ps3_vuart_stats {
+ unsigned long bytes_written;
+ unsigned long bytes_read;
+ unsigned long tx_interrupts;
+ unsigned long rx_interrupts;
+ unsigned long disconnect_interrupts;
+};
+
+struct ps3_vuart_work {
+ struct work_struct work;
+ unsigned long trigger;
+ struct ps3_system_bus_device *dev; /* to convert work to device */
+};
+
+/**
+ * struct ps3_vuart_port_driver - a driver for a device on a vuart port
+ */
+
+struct ps3_vuart_port_driver {
+ struct ps3_system_bus_driver core;
+ int (*probe)(struct ps3_system_bus_device *);
+ int (*remove)(struct ps3_system_bus_device *);
+ void (*shutdown)(struct ps3_system_bus_device *);
+ void (*work)(struct ps3_system_bus_device *);
+ /* int (*tx_event)(struct ps3_system_bus_device *dev); */
+ /* int (*rx_event)(struct ps3_system_bus_device *dev); */
+ /* int (*disconnect_event)(struct ps3_system_bus_device *dev); */
+ /* int (*suspend)(struct ps3_system_bus_device *, pm_message_t); */
+ /* int (*resume)(struct ps3_system_bus_device *); */
+};
+
+int ps3_vuart_port_driver_register(struct ps3_vuart_port_driver *drv);
+void ps3_vuart_port_driver_unregister(struct ps3_vuart_port_driver *drv);
+
+static inline struct ps3_vuart_port_driver *
+ ps3_system_bus_dev_to_vuart_drv(struct ps3_system_bus_device *_dev)
+{
+ struct ps3_system_bus_driver *sbd =
+ ps3_system_bus_dev_to_system_bus_drv(_dev);
+ BUG_ON(!sbd);
+ return container_of(sbd, struct ps3_vuart_port_driver, core);
+}
+static inline struct ps3_system_bus_device *ps3_vuart_work_to_system_bus_dev(
+ struct work_struct *_work)
+{
+ struct ps3_vuart_work *vw = container_of(_work, struct ps3_vuart_work,
+ work);
+ return vw->dev;
+}
+
+int ps3_vuart_write(struct ps3_system_bus_device *dev, const void *buf,
+ unsigned int bytes);
+int ps3_vuart_read(struct ps3_system_bus_device *dev, void *buf,
+ unsigned int bytes);
+int ps3_vuart_read_async(struct ps3_system_bus_device *dev, unsigned int bytes);
+void ps3_vuart_cancel_async(struct ps3_system_bus_device *dev);
+void ps3_vuart_clear_rx_bytes(struct ps3_system_bus_device *dev,
+ unsigned int bytes);
+
+struct vuart_triggers {
+ unsigned long rx;
+ unsigned long tx;
+};
+
+int ps3_vuart_get_triggers(struct ps3_system_bus_device *dev,
+ struct vuart_triggers *trig);
+int ps3_vuart_set_triggers(struct ps3_system_bus_device *dev, unsigned int tx,
+ unsigned int rx);
+int ps3_vuart_enable_interrupt_tx(struct ps3_system_bus_device *dev);
+int ps3_vuart_disable_interrupt_tx(struct ps3_system_bus_device *dev);
+int ps3_vuart_enable_interrupt_rx(struct ps3_system_bus_device *dev);
+int ps3_vuart_disable_interrupt_rx(struct ps3_system_bus_device *dev);
+int ps3_vuart_enable_interrupt_disconnect(struct ps3_system_bus_device *dev);
+int ps3_vuart_disable_interrupt_disconnect(struct ps3_system_bus_device *dev);
+
+#endif