v4.19.13 snapshot.
diff --git a/drivers/ps3/Makefile b/drivers/ps3/Makefile
new file mode 100644
index 0000000..50cb1e1
--- /dev/null
+++ b/drivers/ps3/Makefile
@@ -0,0 +1,7 @@
+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 0000000..e34de9a
--- /dev/null
+++ b/drivers/ps3/ps3-lpm.c
@@ -0,0 +1,1253 @@
+/*
+ * PS3 Logical Performance Monitor.
+ *
+ *  Copyright (C) 2007 Sony Computer Entertainment Inc.
+ *  Copyright 2007 Sony Corp.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#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_UP((unsigned long)tb_cache, 128)
+			|| tb_cache_size != _ALIGN_UP(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) {
+			dev_err(sbd_core(), "%s:%u: alloc internal tb_cache "
+				"failed\n", __func__, __LINE__);
+			result = -ENOMEM;
+			goto fail_malloc;
+		}
+		lpm_priv->tb_cache = (void *)_ALIGN_UP(
+			(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 int 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__);
+	return 0;
+}
+
+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 0000000..73e496a
--- /dev/null
+++ b/drivers/ps3/ps3-sys-manager.c
@@ -0,0 +1,768 @@
+/*
+ *  PS3 System Manager.
+ *
+ *  Copyright (C) 2007 Sony Computer Entertainment Inc.
+ *  Copyright 2007 Sony Corp.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#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 0000000..b7f300b
--- /dev/null
+++ b/drivers/ps3/ps3-vuart.c
@@ -0,0 +1,1263 @@
+/*
+ *  PS3 virtual uart
+ *
+ *  Copyright (C) 2006 Sony Computer Entertainment Inc.
+ *  Copyright 2006 Sony Corp.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#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_dbg(&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;
+}
+
+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];
+} static 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) {
+		pr_debug("%s:%d: kzalloc failed.\n", __func__, __LINE__);
+		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 int 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 0;
+	}
+
+	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);
+	return 0;
+}
+
+/**
+ * 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 int 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 0;
+	}
+
+	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);
+	return 0;
+}
+
+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 0000000..e293606
--- /dev/null
+++ b/drivers/ps3/ps3av.c
@@ -0,0 +1,1073 @@
+/*
+ *  PS3 AV backend support.
+ *
+ *  Copyright (C) 2007 Sony Computer Entertainment Inc.
+ *  Copyright 2007 Sony Corp.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#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_dbg(&ps3av->dev->core,
+			"%s: ps3av_vuart_write() failed (result=%d)\n",
+			__func__, 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_dbg(&ps3av->dev->core,
+				"%s: ps3av_vuart_read() failed (result=%d)\n",
+				__func__, res);
+			return res;
+		}
+
+		/* read body */
+		res = ps3av_vuart_read(ps3av->dev, &recv_buf->cid,
+				       recv_buf->size, timeout);
+		if (res < 0) {
+			dev_dbg(&ps3av->dev->core,
+				"%s: ps3av_vuart_read() failed (result=%d)\n",
+				__func__, 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_dbg(&ps3av->dev->core, "%s: reply err (result=%x)\n",
+			__func__, 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;
+			/* fall through */
+		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 0000000..f555fed
--- /dev/null
+++ b/drivers/ps3/ps3av_cmd.c
@@ -0,0 +1,936 @@
+/*
+ * Copyright (C) 2006 Sony Computer Entertainment Inc.
+ * Copyright 2006, 2007 Sony Corporation
+ *
+ * AV backend support for PS3
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#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;
+		/* fall through */
+	case PS3AV_CMD_AUDIO_NUM_OF_CH_6:
+		audio->audio_enable[2] = 1;
+		audio->audio_enable[1] = 1;
+		/* fall through */
+	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 0000000..8c3f5ad
--- /dev/null
+++ b/drivers/ps3/ps3stor_lib.c
@@ -0,0 +1,362 @@
+/*
+ * PS3 Storage Library
+ *
+ * Copyright (C) 2007 Sony Computer Entertainment Inc.
+ * Copyright 2007 Sony Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#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 (!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 0000000..c429ffc
--- /dev/null
+++ b/drivers/ps3/sys-manager-core.c
@@ -0,0 +1,73 @@
+/*
+ *  PS3 System Manager core.
+ *
+ *  Copyright (C) 2007 Sony Computer Entertainment Inc.
+ *  Copyright 2007 Sony Corp.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#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 ? *ops : ps3_sys_manager_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 0000000..23358b7
--- /dev/null
+++ b/drivers/ps3/vuart.h
@@ -0,0 +1,101 @@
+/*
+ *  PS3 virtual uart
+ *
+ *  Copyright (C) 2006 Sony Computer Entertainment Inc.
+ *  Copyright 2006 Sony Corp.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#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