diff options
Diffstat (limited to 'drivers/ps3')
| -rw-r--r-- | drivers/ps3/Makefile | 10 | ||||
| -rw-r--r-- | drivers/ps3/ps3-lpm.c | 1253 | ||||
| -rw-r--r-- | drivers/ps3/ps3-sys-manager.c (renamed from drivers/ps3/sys-manager.c) | 411 | ||||
| -rw-r--r-- | drivers/ps3/ps3-vuart.c | 1269 | ||||
| -rw-r--r-- | drivers/ps3/ps3av.c | 879 | ||||
| -rw-r--r-- | drivers/ps3/ps3av_cmd.c | 174 | ||||
| -rw-r--r-- | drivers/ps3/ps3stor_lib.c | 362 | ||||
| -rw-r--r-- | drivers/ps3/sys-manager-core.c | 73 | ||||
| -rw-r--r-- | drivers/ps3/vuart.c | 1118 | ||||
| -rw-r--r-- | drivers/ps3/vuart.h | 71 |
10 files changed, 3804 insertions, 1816 deletions
diff --git a/drivers/ps3/Makefile b/drivers/ps3/Makefile index e251d1c1171..50cb1e1b4a1 100644 --- a/drivers/ps3/Makefile +++ b/drivers/ps3/Makefile @@ -1,3 +1,7 @@ -obj-$(CONFIG_PS3_VUART) += vuart.o -obj-$(CONFIG_PS3_PS3AV) += ps3av.o ps3av_cmd.o -obj-$(CONFIG_PS3_SYS_MANAGER) += sys-manager.o +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 00000000000..b139b7792e9 --- /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 prosessor 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_inteval 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/sys-manager.c b/drivers/ps3/ps3-sys-manager.c index 3aa2b0dcc36..f2ab435954f 100644 --- a/drivers/ps3/sys-manager.c +++ b/drivers/ps3/ps3-sys-manager.c @@ -24,18 +24,15 @@ #include <linux/reboot.h> #include <asm/firmware.h> +#include <asm/lv1call.h> #include <asm/ps3.h> #include "vuart.h" -MODULE_AUTHOR("Sony Corporation"); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("PS3 System Manager"); - /** * ps3_sys_manager - PS3 system manager driver. * - * The system manager provides an asyncronous system event notification + * 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. * @@ -49,9 +46,10 @@ MODULE_DESCRIPTION("PS3 System Manager"); /** * struct ps3_sys_manager_header - System manager message header. * @version: Header version, currently 1. - * @size: Header size in bytes, curently 16. + * @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 { @@ -61,29 +59,49 @@ struct ps3_sys_manager_header { u16 reserved_1; u32 payload_size; u16 service_id; - u16 reserved_2[3]; + 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 - System manager received message length. + * @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 the same length - * (16 bytes header + 16 bytes payload = 32 bytes). This knowlege is used to - * simplify the logic. + * 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 = 32, + 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_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: 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 { @@ -93,6 +111,7 @@ enum ps3_sys_manager_service_id { 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, }; @@ -100,7 +119,7 @@ enum ps3_sys_manager_service_id { * 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: Sytem thermal alert. + * @PS3_SM_ATTR_THERMAL: System thermal alert. * @PS3_SM_ATTR_CONTROLLER: Remote controller event. * @PS3_SM_ATTR_ALL: Logical OR of all. * @@ -120,9 +139,11 @@ enum ps3_sys_manager_attr { /** * enum ps3_sys_manager_event - External event type, reported by system manager. - * @PS3_SM_EVENT_POWER_PRESSED: payload.value not used. + * @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 not used. + * @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. @@ -140,6 +161,17 @@ enum ps3_sys_manager_event { }; /** + * 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. */ @@ -152,25 +184,32 @@ enum ps3_sys_manager_next_op { /** * enum ps3_sys_manager_wake_source - Next-op wakeup source (bit position mask). - * @PS3_SM_WAKE_DEFAULT: Disk insert, power button, eject button, IR - * controller, and bluetooth controller. - * @PS3_SM_WAKE_RTC: - * @PS3_SM_WAKE_RTC_ERROR: + * @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. - * System will always wake from the PS3_SM_WAKE_DEFAULT sources. + * 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_RTC = 0x00000040, - PS3_SM_WAKE_RTC_ERROR = 0x00000080, - PS3_SM_WAKE_P_O_R = 0x10000000, + 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 @@ -185,11 +224,21 @@ enum ps3_sys_manager_cmd { }; /** + * 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_vuart_port_device *dev, +static int ps3_sys_manager_write(struct ps3_system_bus_device *dev, const struct ps3_sys_manager_header *header, const void *payload) { int result; @@ -213,15 +262,10 @@ static int ps3_sys_manager_write(struct ps3_vuart_port_device *dev, * */ -static int ps3_sys_manager_send_attr(struct ps3_vuart_port_device *dev, +static int ps3_sys_manager_send_attr(struct ps3_system_bus_device *dev, enum ps3_sys_manager_attr attr) { - static const struct ps3_sys_manager_header header = { - .version = 1, - .size = 16, - .payload_size = 16, - .service_id = PS3_SM_SERVICE_ID_SET_ATTR, - }; + struct ps3_sys_manager_header header; struct { u8 version; u8 reserved_1[3]; @@ -232,6 +276,12 @@ static int ps3_sys_manager_send_attr(struct ps3_vuart_port_device *dev, 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; @@ -245,16 +295,11 @@ static int ps3_sys_manager_send_attr(struct ps3_vuart_port_device *dev, * Tell the system manager what to do after this lpar is destroyed. */ -static int ps3_sys_manager_send_next_op(struct ps3_vuart_port_device *dev, +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) { - static const struct ps3_sys_manager_header header = { - .version = 1, - .size = 16, - .payload_size = 16, - .service_id = PS3_SM_SERVICE_ID_SET_NEXT_OP, - }; + struct ps3_sys_manager_header header; struct { u8 version; u8 type; @@ -268,6 +313,12 @@ static int ps3_sys_manager_send_next_op(struct ps3_vuart_port_device *dev, 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; @@ -286,32 +337,35 @@ static int ps3_sys_manager_send_next_op(struct ps3_vuart_port_device *dev, * the command is then communicated back to the system manager with a response * message. * - * Currently, the only supported request it the 'shutdown self' request. + * Currently, the only supported request is the 'shutdown self' request. */ -static int ps3_sys_manager_send_request_shutdown(struct ps3_vuart_port_device *dev) +static int ps3_sys_manager_send_request_shutdown( + struct ps3_system_bus_device *dev) { - static const struct ps3_sys_manager_header header = { - .version = 1, - .size = 16, - .payload_size = 16, - .service_id = PS3_SM_SERVICE_ID_REQUEST, - }; + struct ps3_sys_manager_header header; struct { u8 version; u8 type; u8 gos_id; u8 reserved_1[13]; - } static const payload = { - .version = 1, - .type = 1, /* shutdown */ - .gos_id = 0, /* self */ - }; + } 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); } @@ -323,15 +377,10 @@ static int ps3_sys_manager_send_request_shutdown(struct ps3_vuart_port_device *d * failure of a command sent by the system manager. */ -static int ps3_sys_manager_send_response(struct ps3_vuart_port_device *dev, +static int ps3_sys_manager_send_response(struct ps3_system_bus_device *dev, u64 status) { - static const struct ps3_sys_manager_header header = { - .version = 1, - .size = 16, - .payload_size = 16, - .service_id = PS3_SM_SERVICE_ID_RESPONSE, - }; + struct ps3_sys_manager_header header; struct { u8 version; u8 reserved_1[3]; @@ -344,6 +393,12 @@ static int ps3_sys_manager_send_response(struct ps3_vuart_port_device *dev, 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; @@ -356,7 +411,7 @@ static int ps3_sys_manager_send_response(struct ps3_vuart_port_device *dev, * */ -static int ps3_sys_manager_handle_event(struct ps3_vuart_port_device *dev) +static int ps3_sys_manager_handle_event(struct ps3_system_bus_device *dev) { int result; struct { @@ -370,7 +425,7 @@ static int ps3_sys_manager_handle_event(struct ps3_vuart_port_device *dev) BUILD_BUG_ON(sizeof(event) != 16); result = ps3_vuart_read(dev, &event, sizeof(event)); - BUG_ON(result); + BUG_ON(result && "need to retry here"); if (event.version != 1) { dev_dbg(&dev->core, "%s:%d: unsupported event version (%u)\n", @@ -380,18 +435,45 @@ static int ps3_sys_manager_handle_event(struct ps3_vuart_port_device *dev) switch (event.type) { case PS3_SM_EVENT_POWER_PRESSED: - dev_dbg(&dev->core, "%s:%d: POWER_PRESSED\n", - __func__, __LINE__); + 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); - kill_cad_pid(SIGINT, 1); + 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); - printk(KERN_INFO "PS3 Thermal Alert Zone %u\n", 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", @@ -411,7 +493,7 @@ static int ps3_sys_manager_handle_event(struct ps3_vuart_port_device *dev) * The system manager sends this in reply to a 'request' message from the guest. */ -static int ps3_sys_manager_handle_cmd(struct ps3_vuart_port_device *dev) +static int ps3_sys_manager_handle_cmd(struct ps3_system_bus_device *dev) { int result; struct { @@ -425,8 +507,9 @@ static int ps3_sys_manager_handle_cmd(struct ps3_vuart_port_device *dev) 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) + if (result) return result; if (cmd.version != 1) { @@ -448,9 +531,10 @@ static int ps3_sys_manager_handle_cmd(struct ps3_vuart_port_device *dev) /** * 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_vuart_port_device *dev) +static int ps3_sys_manager_handle_msg(struct ps3_system_bus_device *dev) { int result; struct ps3_sys_manager_header header; @@ -458,18 +542,23 @@ static int ps3_sys_manager_handle_msg(struct ps3_vuart_port_device *dev) result = ps3_vuart_read(dev, &header, sizeof(struct ps3_sys_manager_header)); - if(result) + 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); - BUG_ON(header.size != 16); - BUG_ON(header.payload_size != 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: @@ -478,6 +567,11 @@ static int ps3_sys_manager_handle_msg(struct ps3_vuart_port_device *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); @@ -493,110 +587,167 @@ fail_id: return -EIO; } -/** - * ps3_sys_manager_work - Asyncronous read handler. - * - * Signaled when a complete message arrives at the vuart port. - */ - -static void ps3_sys_manager_work(struct work_struct *work) +static void ps3_sys_manager_fin(struct ps3_system_bus_device *dev) { - struct ps3_vuart_port_device *dev = ps3_vuart_work_to_port_device(work); + ps3_sys_manager_send_request_shutdown(dev); - ps3_sys_manager_handle_msg(dev); - ps3_vuart_read_async(dev, ps3_sys_manager_work, PS3_SM_RX_MSG_LEN); -} + 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); + } -struct { - struct ps3_vuart_port_device *dev; -} static drv_priv; + while (1) { + /* pause, ignoring DEC interrupt */ + lv1_pause(1); + } +} /** - * ps3_sys_manager_restart - The final platform machine_restart routine. + * ps3_sys_manager_final_power_off - The final platform machine_power_off routine. * - * This routine never returns. The routine disables asyncronous vuart reads + * 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(). + * should only be called from ps3_power_off() through + * ps3_sys_manager_ops.power_off. */ -void ps3_sys_manager_restart(void) +static void ps3_sys_manager_final_power_off(struct ps3_system_bus_device *dev) { - struct ps3_vuart_port_device *dev = drv_priv.dev; - - BUG_ON(!drv_priv.dev); + BUG_ON(!dev); dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__); ps3_vuart_cancel_async(dev); - ps3_sys_manager_send_attr(dev, 0); - ps3_sys_manager_send_next_op(dev, PS3_SM_NEXT_OP_LPAR_REBOOT, - PS3_SM_WAKE_DEFAULT); - ps3_sys_manager_send_request_shutdown(dev); - - printk(KERN_EMERG "System Halted, OK to turn off power\n"); + ps3_sys_manager_send_next_op(dev, PS3_SM_NEXT_OP_SYS_SHUTDOWN, + user_wake_sources); - while(1) - ps3_sys_manager_handle_msg(dev); + ps3_sys_manager_fin(dev); } /** - * ps3_sys_manager_power_off - The final platform machine_power_off routine. + * ps3_sys_manager_final_restart - The final platform machine_restart routine. * - * This routine never returns. The routine disables asyncronous vuart reads + * 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(). + * should only be called from ps3_restart() through ps3_sys_manager_ops.restart. */ -void ps3_sys_manager_power_off(void) +static void ps3_sys_manager_final_restart(struct ps3_system_bus_device *dev) { - struct ps3_vuart_port_device *dev = drv_priv.dev; - - BUG_ON(!drv_priv.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_next_op(dev, PS3_SM_NEXT_OP_SYS_SHUTDOWN, - PS3_SM_WAKE_DEFAULT); - ps3_sys_manager_send_request_shutdown(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); +} - printk(KERN_EMERG "System Halted, OK to turn off power\n"); +/** + * ps3_sys_manager_get_wol - Get wake-on-lan setting. + */ + +int ps3_sys_manager_get_wol(void) +{ + pr_debug("%s:%d\n", __func__, __LINE__); - while(1) - ps3_sys_manager_handle_msg(dev); + return (user_wake_sources & PS3_SM_WAKE_W_O_L) != 0; } +EXPORT_SYMBOL_GPL(ps3_sys_manager_get_wol); -static int ps3_sys_manager_probe(struct ps3_vuart_port_device *dev) +/** + * 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__); - BUG_ON(drv_priv.dev); - drv_priv.dev = dev; + 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_sys_manager_work, - PS3_SM_RX_MSG_LEN); + 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 = { - .match_id = PS3_MATCH_ID_SYSTEM_MANAGER, - .core = { - .name = "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) @@ -608,3 +759,9 @@ static int __init ps3_sys_manager_init(void) } 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 00000000000..bc1e5139ba2 --- /dev/null +++ b/drivers/ps3/ps3-vuart.c @@ -0,0 +1,1269 @@ +/* + * 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 +} + +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 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 = NO_IRQ; +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 = NO_IRQ; + + 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); + + dev_dbg(&dev->core, "%s:%d: (%s)\n", __func__, __LINE__, + drv->core.core.name); + + BUG_ON(!drv); + + 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 index d21e04ccb02..437fc35beb7 100644 --- a/drivers/ps3/ps3av.c +++ b/drivers/ps3/ps3av.c @@ -1,32 +1,32 @@ /* - * Copyright (C) 2006 Sony Computer Entertainment Inc. - * Copyright 2006, 2007 Sony Corporation + * PS3 AV backend support. * - * AV backend support for PS3 + * 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 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. + * 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. + * 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/reboot.h> -#include <linux/kernel.h> #include <linux/ioctl.h> +#include <linux/fb.h> +#include <linux/slab.h> #include <asm/firmware.h> -#include <asm/lv1call.h> #include <asm/ps3av.h> #include <asm/ps3.h> @@ -35,14 +35,32 @@ #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 ps3av; - -static struct ps3_vuart_port_device ps3av_dev = { - .match_id = PS3_MATCH_ID_AV_SETTINGS -}; +static struct ps3av { + struct mutex mutex; + struct work_struct work; + struct completion done; + struct workqueue_struct *wq; + 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 @@ -59,23 +77,21 @@ static const struct avset_video_mode { u32 aspect; u32 x; u32 y; - u32 interlace; - u32 freq; } video_mode_table[] = { { 0, }, /* auto */ - {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_480I, A_N, 720, 480, 1, 60}, - {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_480P, A_N, 720, 480, 0, 60}, - {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_720P_60HZ, A_N, 1280, 720, 0, 60}, - {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_1080I_60HZ, A_W, 1920, 1080, 1, 60}, - {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_1080P_60HZ, A_W, 1920, 1080, 0, 60}, - {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_576I, A_N, 720, 576, 1, 50}, - {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_576P, A_N, 720, 576, 0, 50}, - {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_720P_50HZ, A_N, 1280, 720, 0, 50}, - {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_1080I_50HZ, A_W, 1920, 1080, 1, 50}, - {YUV444, XRGB, PS3AV_CMD_VIDEO_VID_1080P_50HZ, A_W, 1920, 1080, 0, 50}, - { RGB8, XRGB, PS3AV_CMD_VIDEO_VID_WXGA, A_W, 1280, 768, 0, 60}, - { RGB8, XRGB, PS3AV_CMD_VIDEO_VID_SXGA, A_N, 1280, 1024, 0, 60}, - { RGB8, XRGB, PS3AV_CMD_VIDEO_VID_WUXGA, A_W, 1920, 1200, 0, 60}, + {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 */ @@ -152,19 +168,54 @@ static int ps3av_parse_event_packet(const struct ps3av_reply_hdr *hdr) 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, + 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", - __FUNCTION__, hdr->cid, hdr->size); + __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) @@ -173,15 +224,15 @@ static int ps3av_send_cmd_pkt(const struct ps3av_send_hdr *send_buf, u32 cmd; int event; - if (!ps3av.available) + if (!ps3av) return -ENODEV; /* send pkt */ - res = ps3av_vuart_write(ps3av.dev, send_buf, write_len); + res = ps3av_vuart_write(ps3av->dev, send_buf, write_len); if (res < 0) { - dev_dbg(&ps3av_dev.core, + dev_dbg(&ps3av->dev->core, "%s: ps3av_vuart_write() failed (result=%d)\n", - __FUNCTION__, res); + __func__, res); return res; } @@ -189,22 +240,22 @@ static int ps3av_send_cmd_pkt(const struct ps3av_send_hdr *send_buf, cmd = send_buf->cid; do { /* read header */ - res = ps3av_vuart_read(ps3av.dev, recv_buf, PS3AV_HDR_SIZE, + res = ps3av_vuart_read(ps3av->dev, recv_buf, PS3AV_HDR_SIZE, timeout); if (res != PS3AV_HDR_SIZE) { - dev_dbg(&ps3av_dev.core, + dev_dbg(&ps3av->dev->core, "%s: ps3av_vuart_read() failed (result=%d)\n", - __FUNCTION__, res); + __func__, res); return res; } /* read body */ - res = ps3av_vuart_read(ps3av.dev, &recv_buf->cid, + res = ps3av_vuart_read(ps3av->dev, &recv_buf->cid, recv_buf->size, timeout); if (res < 0) { - dev_dbg(&ps3av_dev.core, + dev_dbg(&ps3av->dev->core, "%s: ps3av_vuart_read() failed (result=%d)\n", - __FUNCTION__, res); + __func__, res); return res; } res += PS3AV_HDR_SIZE; /* total len */ @@ -213,8 +264,8 @@ static int ps3av_send_cmd_pkt(const struct ps3av_send_hdr *send_buf, } while (event); if ((cmd | PS3AV_REPLY_BIT) != recv_buf->cid) { - dev_dbg(&ps3av_dev.core, "%s: reply err (result=%x)\n", - __FUNCTION__, recv_buf->cid); + dev_dbg(&ps3av->dev->core, "%s: reply err (result=%x)\n", + __func__, recv_buf->cid); return -EINVAL; } @@ -228,7 +279,7 @@ static int ps3av_process_reply_packet(struct ps3av_send_hdr *cmd_buf, int return_len; if (recv_buf->version != PS3AV_VERSION) { - dev_dbg(&ps3av_dev.core, "reply_packet invalid version:%x\n", + dev_dbg(&ps3av->dev->core, "reply_packet invalid version:%x\n", recv_buf->version); return -EFAULT; } @@ -250,17 +301,11 @@ int ps3av_do_pkt(u32 cid, u16 send_len, size_t usr_buf_size, struct ps3av_send_hdr *buf) { int res = 0; - union { - struct ps3av_reply_hdr reply_hdr; - u8 raw[PS3AV_BUF_SIZE]; - } recv_buf; - u32 *table; - BUG_ON(!ps3av.available); + BUG_ON(!ps3av); - if (down_interruptible(&ps3av.sem)) - return -ERESTARTSYS; + mutex_lock(&ps3av->mutex); table = ps3av_search_cmd_table(cid, PS3AV_CID_MASK); BUG_ON(!table); @@ -272,30 +317,30 @@ int ps3av_do_pkt(u32 cid, u16 send_len, size_t usr_buf_size, ps3av_set_hdr(cid, send_len, buf); /* send packet via vuart */ - res = ps3av_send_cmd_pkt(buf, &recv_buf.reply_hdr, send_len, + 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", - __FUNCTION__, res); + __func__, res); goto err; } /* process reply packet */ - res = ps3av_process_reply_packet(buf, &recv_buf.reply_hdr, + 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", - __FUNCTION__, res); + __func__, res); goto err; } - up(&ps3av.sem); + mutex_unlock(&ps3av->mutex); return 0; - err: - up(&ps3av.sem); - printk(KERN_ERR "%s: failed cid:%x res:%d\n", __FUNCTION__, cid, res); +err: + mutex_unlock(&ps3av->mutex); + printk(KERN_ERR "%s: failed cid:%x res:%d\n", __func__, cid, res); return res; } @@ -303,11 +348,11 @@ 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; + 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); + res = ps3av_cmd_av_video_mute(1, &ps3av->av_port[i], mute); if (res < 0) return -1; } @@ -319,13 +364,13 @@ 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; + 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], + res = ps3av_cmd_av_tv_mute(ps3av->av_port[i], PS3AV_CMD_MUTE_ON); if (res < 0) return -1; @@ -334,11 +379,11 @@ static int ps3av_set_video_disable_sig(void) /* video mute on */ for (i = 0; i < num_of_av_port; i++) { - res = ps3av_cmd_av_video_disable_sig(ps3av.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], + res = ps3av_cmd_av_tv_mute(ps3av->av_port[i], PS3AV_CMD_MUTE_OFF); if (res < 0) return -1; @@ -353,17 +398,17 @@ 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; + 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); + 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); + res = ps3av_cmd_audio_mute(1, &ps3av->opt_port[i], mute); if (res < 0) return -1; } @@ -378,40 +423,40 @@ int ps3av_set_audio_mode(u32 ch, u32 fs, u32 word_bits, u32 format, u32 source) 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; + 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; + avb_param.num_of_av_audio_pkt = ps3av->av_hw_conf.num_of_hdmi; - vid = video_mode_table[ps3av.ps3av_mode].vid; + 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); + res = ps3av_cmd_audio_active(0, ps3av->audio_port); if (res < 0) - dev_dbg(&ps3av_dev.core, + 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) { + 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], + 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, + dev_dbg(&ps3av->dev->core, "ps3av_cmd_audio_mode failed, port:%x\n", i); } @@ -419,19 +464,19 @@ int ps3av_set_audio_mode(u32 ch, u32 fs, u32 word_bits, u32 format, u32 source) 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"); + 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); + res = ps3av_cmd_audio_active(1, ps3av->audio_port); if (res < 0) - dev_dbg(&ps3av_dev.core, "ps3av_cmd_audio_active ON failed\n"); + 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) @@ -440,49 +485,31 @@ static int ps3av_set_videomode(void) ps3av_set_av_video_mute(PS3AV_CMD_MUTE_ON); /* wake up ps3avd to do the actual video mode setting */ - up(&ps3av.ping); + queue_work(ps3av->wq, &ps3av->work); return 0; } -static void ps3av_set_videomode_cont(u32 id, u32 old_id) +static void ps3av_set_videomode_packet(u32 id) { struct ps3av_pkt_avb_param avb_param; - int i; + 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_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_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 signal off */ - ps3av_set_video_disable_sig(); - - /* 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"); - } - /* 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, + 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++) { @@ -491,12 +518,12 @@ static void ps3av_set_videomode_cont(u32 id, u32 old_id) 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 */ + 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], + ps3av->av_port[i], video_mode->vid, av_video_cs, video_mode->aspect, id); } @@ -505,189 +532,301 @@ static void ps3av_set_videomode_cont(u32 id, u32 old_id) 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", - __FUNCTION__); + "%s: Command failed. Please try your request again.\n", + __func__); else if (res) - dev_dbg(&ps3av_dev.core, "ps3av_cmd_avb_param failed\n"); + 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 int ps3avd(void *p) +static void ps3avd(struct work_struct *work) { - struct ps3av *info = p; - - daemonize("ps3avd"); - while (1) { - down(&info->ping); - ps3av_set_videomode_cont(info->ps3av_mode, - info->ps3av_mode_old); - up(&info->pong); - } - return 0; + ps3av_set_videomode_cont(ps3av->ps3av_mode, ps3av->ps3av_mode_old); + complete(&ps3av->done); } -static int ps3av_vid2table_id(int vid) +#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) { - int i; + 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; - for (i = 1; i < ARRAY_SIZE(video_mode_table); i++) - if (video_mode_table[i].vid == vid) - return i; - return -1; + return 0; } -static int ps3av_resbit2vid(u32 res_50, u32 res_60) +static enum ps3av_mode_num ps3av_hdmi_get_id(struct ps3av_info_monitor *info) { - int vid = -1; + enum ps3av_mode_num id; - if (res_50 > res_60) { /* if res_50 == res_60, res_60 will be used */ - if (res_50 & PS3AV_RESBIT_1920x1080P) - vid = PS3AV_CMD_VIDEO_VID_1080P_50HZ; - else if (res_50 & PS3AV_RESBIT_1920x1080I) - vid = PS3AV_CMD_VIDEO_VID_1080I_50HZ; - else if (res_50 & PS3AV_RESBIT_1280x720P) - vid = PS3AV_CMD_VIDEO_VID_720P_50HZ; - else if (res_50 & PS3AV_RESBIT_720x576P) - vid = PS3AV_CMD_VIDEO_VID_576P; - else - vid = -1; - } else { - if (res_60 & PS3AV_RESBIT_1920x1080P) - vid = PS3AV_CMD_VIDEO_VID_1080P_60HZ; - else if (res_60 & PS3AV_RESBIT_1920x1080I) - vid = PS3AV_CMD_VIDEO_VID_1080I_60HZ; - else if (res_60 & PS3AV_RESBIT_1280x720P) - vid = PS3AV_CMD_VIDEO_VID_720P_60HZ; - else if (res_60 & PS3AV_RESBIT_720x480P) - vid = PS3AV_CMD_VIDEO_VID_480P; - else - vid = -1; + 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; } - return vid; + + /* 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 int ps3av_hdmi_get_vid(struct ps3av_info_monitor *info) +static void ps3av_monitor_info_dump( + const struct ps3av_pkt_av_get_monitor_info *monitor_info) { - u32 res_50, res_60; - int vid = -1; - - if (info->monitor_type != PS3AV_MONITOR_TYPE_HDMI) - return -1; + 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; - /* check native resolution */ - res_50 = info->res_50.native & PS3AV_RES_MASK_50; - res_60 = info->res_60.native & PS3AV_RES_MASK_60; - if (res_50 || res_60) { - vid = ps3av_resbit2vid(res_50, res_60); - return vid; + 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++; } +} - /* check resolution */ - res_50 = info->res_50.res_bits & PS3AV_RES_MASK_50; - res_60 = info->res_60.res_bits & PS3AV_RES_MASK_60; - if (res_50 || res_60) { - vid = ps3av_resbit2vid(res_50, res_60); - return vid; +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 } +}; - if (ps3av.region & PS3AV_REGION_60) - vid = PS3AV_DEFAULT_HDMI_VID_REG_60; - else - vid = PS3AV_DEFAULT_HDMI_VID_REG_50; - return vid; +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 boot) +static int ps3av_auto_videomode(struct ps3av_pkt_av_get_hw_conf *av_hw_conf) { - int i, res, vid = -1, dvi = 0, rgb = 0; + int i, res, id = 0, dvi = 0, rgb = 0; struct ps3av_pkt_av_get_monitor_info monitor_info; struct ps3av_info_monitor *info; - /* get vid for hdmi */ - for (i = 0; i < av_hw_conf->num_of_hdmi; i++) { + /* 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_cmd_av_monitor_info_dump(&monitor_info); + ps3av_monitor_info_dump(&monitor_info); + info = &monitor_info.info; - /* check DVI */ - if (info->monitor_type == PS3AV_MONITOR_TYPE_DVI) { + ps3av_fixup_monitor_info(info); + + switch (info->monitor_type) { + case PS3AV_MONITOR_TYPE_DVI: dvi = PS3AV_MODE_DVI; - break; - } - /* check HDMI */ - vid = ps3av_hdmi_get_vid(info); - if (vid != -1) { - /* got valid vid */ + /* fall through */ + case PS3AV_MONITOR_TYPE_HDMI: + id = ps3av_hdmi_get_id(info); break; } } - if (dvi) { - /* DVI mode */ - vid = PS3AV_DEFAULT_DVI_VID; - } else if (vid == -1) { + if (!id) { /* no HDMI interface or HDMI is off */ - if (ps3av.region & PS3AV_REGION_60) - vid = PS3AV_DEFAULT_AVMULTI_VID_REG_60; + if (ps3av->region & PS3AV_REGION_60) + id = PS3AV_DEFAULT_AVMULTI_MODE_ID_REG_60; else - vid = PS3AV_DEFAULT_AVMULTI_VID_REG_50; - if (ps3av.region & PS3AV_REGION_RGB) + id = PS3AV_DEFAULT_AVMULTI_MODE_ID_REG_50; + if (ps3av->region & PS3AV_REGION_RGB) rgb = PS3AV_MODE_RGB; - } else if (boot) { - /* HDMI: using DEFAULT HDMI_VID while booting up */ - info = &monitor_info.info; - if (ps3av.region & PS3AV_REGION_60) { - if (info->res_60.res_bits & PS3AV_RESBIT_720x480P) - vid = PS3AV_DEFAULT_HDMI_VID_REG_60; - else if (info->res_50.res_bits & PS3AV_RESBIT_720x576P) - vid = PS3AV_DEFAULT_HDMI_VID_REG_50; - else { - /* default */ - vid = PS3AV_DEFAULT_HDMI_VID_REG_60; - } - } else { - if (info->res_50.res_bits & PS3AV_RESBIT_720x576P) - vid = PS3AV_DEFAULT_HDMI_VID_REG_50; - else if (info->res_60.res_bits & PS3AV_RESBIT_720x480P) - vid = PS3AV_DEFAULT_HDMI_VID_REG_60; - else { - /* default */ - vid = PS3AV_DEFAULT_HDMI_VID_REG_50; - } - } + pr_debug("%s: Using avmulti mode %d\n", __func__, id); } - return (ps3av_vid2table_id(vid) | dvi | rgb); + 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; - ps3av_cmd_av_hw_conf_dump(&ps3av->av_hw_conf); + 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 < ps3av->av_hw_conf.num_of_hdmi; 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 < ps3av->av_hw_conf.num_of_avmulti; j++) + 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 < ps3av->av_hw_conf.num_of_spdif; k++) + 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 */ @@ -700,100 +839,51 @@ static int ps3av_get_hw_conf(struct ps3av *ps3av) } /* set mode using id */ -int ps3av_set_video_mode(u32 id, int boot) +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", __FUNCTION__, - id); + 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) == 0) { - id = ps3av_auto_videomode(&ps3av.av_hw_conf, boot); + 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", __FUNCTION__, - id); + printk(KERN_ERR "%s: invalid id :%d\n", __func__, id); return -EINVAL; } id |= option; } /* set videomode */ - down(&ps3av.pong); - ps3av.ps3av_mode_old = ps3av.ps3av_mode; - ps3av.ps3av_mode = id; + 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; + ps3av->ps3av_mode = ps3av->ps3av_mode_old; return 0; } - EXPORT_SYMBOL_GPL(ps3av_set_video_mode); -int ps3av_set_mode(u32 id, int boot) +int ps3av_get_auto_mode(void) { - int res; - - res = ps3av_set_video_mode(id, boot); - if (res) - return res; - - res = ps3av_set_audio_mode(PS3AV_CMD_AUDIO_NUM_OF_CH_2, - PS3AV_CMD_AUDIO_FS_48K, - PS3AV_CMD_AUDIO_WORD_BITS_16, - PS3AV_CMD_AUDIO_FORMAT_PCM, - PS3AV_CMD_AUDIO_SOURCE_SERIAL); - if (res) - return res; - - return 0; + return ps3av_auto_videomode(&ps3av->av_hw_conf); } - -EXPORT_SYMBOL_GPL(ps3av_set_mode); +EXPORT_SYMBOL_GPL(ps3av_get_auto_mode); int ps3av_get_mode(void) { - return ps3av.ps3av_mode; + return ps3av ? ps3av->ps3av_mode : 0; } - EXPORT_SYMBOL_GPL(ps3av_get_mode); -int ps3av_get_scanmode(int id) -{ - 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", __FUNCTION__, id); - return -EINVAL; - } - return video_mode_table[id].interlace; -} - -EXPORT_SYMBOL_GPL(ps3av_get_scanmode); - -int ps3av_get_refresh_rate(int id) -{ - 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", __FUNCTION__, id); - return -EINVAL; - } - return video_mode_table[id].freq; -} - -EXPORT_SYMBOL_GPL(ps3av_get_refresh_rate); - /* get resolution by video_mode */ int ps3av_video_mode2res(u32 id, u32 *xres, u32 *yres) { @@ -802,14 +892,13 @@ int ps3av_video_mode2res(u32 id, u32 *xres, u32 *yres) 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", __FUNCTION__, id); + 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 */ @@ -818,164 +907,174 @@ 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); -int ps3av_audio_mute(int mute) +/* mute analog output only */ +int ps3av_audio_mute_analog(int mute) { - return ps3av_set_audio_mute(mute ? PS3AV_CMD_MUTE_ON - : PS3AV_CMD_MUTE_OFF); -} - -EXPORT_SYMBOL_GPL(ps3av_audio_mute); - -int ps3av_dev_open(void) -{ - int status = 0; + int i, res; - mutex_lock(&ps3av.mutex); - if (!ps3av.open_count++) { - status = lv1_gpu_open(0); - if (status) { - printk(KERN_ERR "%s: lv1_gpu_open failed %d\n", - __FUNCTION__, status); - ps3av.open_count--; - } + 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; } - mutex_unlock(&ps3av.mutex); - - return status; + return 0; } +EXPORT_SYMBOL_GPL(ps3av_audio_mute_analog); -EXPORT_SYMBOL_GPL(ps3av_dev_open); - -int ps3av_dev_close(void) +int ps3av_audio_mute(int mute) { - int status = 0; - - mutex_lock(&ps3av.mutex); - if (ps3av.open_count <= 0) { - printk(KERN_ERR "%s: GPU already closed\n", __FUNCTION__); - status = -1; - } else if (!--ps3av.open_count) { - status = lv1_gpu_close(); - if (status) - printk(KERN_WARNING "%s: lv1_gpu_close failed %d\n", - __FUNCTION__, status); - } - mutex_unlock(&ps3av.mutex); - - return status; + return ps3av_set_audio_mute(mute ? PS3AV_CMD_MUTE_ON + : PS3AV_CMD_MUTE_OFF); } +EXPORT_SYMBOL_GPL(ps3av_audio_mute); -EXPORT_SYMBOL_GPL(ps3av_dev_close); - -static int ps3av_probe(struct ps3_vuart_port_device *dev) +static int ps3av_probe(struct ps3_system_bus_device *dev) { int res; - u32 id; + int id; - dev_dbg(&ps3av_dev.core, "init ...\n"); - dev_dbg(&ps3av_dev.core, " timeout=%d\n", timeout); + dev_dbg(&dev->core, " -> %s:%d\n", __func__, __LINE__); + dev_dbg(&dev->core, " timeout=%d\n", timeout); - memset(&ps3av, 0, sizeof(ps3av)); + if (ps3av) { + dev_err(&dev->core, "Only one ps3av device is supported\n"); + return -EBUSY; + } - init_MUTEX(&ps3av.sem); - init_MUTEX_LOCKED(&ps3av.ping); - init_MUTEX(&ps3av.pong); - mutex_init(&ps3av.mutex); - ps3av.ps3av_mode = 0; - ps3av.dev = dev; - kernel_thread(ps3avd, &ps3av, CLONE_KERNEL); + 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); + ps3av->wq = create_singlethread_workqueue("ps3avd"); + if (!ps3av->wq) { + res = -ENOMEM; + goto fail; + } - ps3av.available = 1; switch (ps3_os_area_get_av_multi_out()) { case PS3_PARAM_AV_MULTI_OUT_NTSC: - ps3av.region = PS3AV_REGION_60; + 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; + ps3av->region = PS3AV_REGION_50; break; case PS3_PARAM_AV_MULTI_OUT_PAL_RGB: - ps3av.region = PS3AV_REGION_50 | PS3AV_REGION_RGB; + ps3av->region = PS3AV_REGION_50 | PS3AV_REGION_RGB; break; default: - ps3av.region = PS3AV_REGION_60; + 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", __FUNCTION__, + printk(KERN_ERR "%s: ps3av_cmd_init failed %d\n", __func__, res); - ps3av_get_hw_conf(&ps3av); - id = ps3av_auto_videomode(&ps3av.av_hw_conf, 1); - mutex_lock(&ps3av.mutex); - ps3av.ps3av_mode = id; - mutex_unlock(&ps3av.mutex); + 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; - dev_dbg(&ps3av_dev.core, "init...done\n"); + 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_vuart_port_device *dev) +static int ps3av_remove(struct ps3_system_bus_device *dev) { - if (ps3av.available) { + dev_dbg(&dev->core, " -> %s:%d\n", __func__, __LINE__); + if (ps3av) { ps3av_cmd_fin(); - ps3av.available = 0; + if (ps3av->wq) + destroy_workqueue(ps3av->wq); + kfree(ps3av); + ps3av = NULL; } + dev_dbg(&dev->core, " <- %s:%d\n", __func__, __LINE__); return 0; } -static void ps3av_shutdown(struct ps3_vuart_port_device *dev) +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 = { - .match_id = PS3_MATCH_ID_AV_SETTINGS, - .core = { - .name = "ps3_av", - }, + .core.match_id = PS3_MATCH_ID_AV_SETTINGS, + .core.core.name = "ps3_av", .probe = ps3av_probe, .remove = ps3av_remove, .shutdown = ps3av_shutdown, }; -static int ps3av_module_init(void) +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", - __FUNCTION__, error); + __func__, error); return error; } - error = ps3_vuart_port_device_register(&ps3av_dev); - if (error) - printk(KERN_ERR - "%s: ps3_vuart_port_device_register failed %d\n", - __FUNCTION__, error); - + pr_debug(" <- %s:%d\n", __func__, __LINE__); return error; } static void __exit ps3av_module_exit(void) { - device_unregister(&ps3av_dev.core); + 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 index bc70e81f8cb..f555fedd507 100644 --- a/drivers/ps3/ps3av_cmd.c +++ b/drivers/ps3/ps3av_cmd.c @@ -21,9 +21,10 @@ #include <linux/module.h> #include <linux/kernel.h> #include <linux/delay.h> + #include <asm/ps3av.h> -#include <asm/ps3fb.h> #include <asm/ps3.h> +#include <asm/ps3gpu.h> #include "vuart.h" @@ -143,6 +144,14 @@ static u32 ps3av_vid_video2av(int vid) 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; @@ -350,6 +359,10 @@ u32 ps3av_cmd_set_av_video_cs(void *p, u32 avport, int video_vid, int 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 @@ -392,10 +405,14 @@ u32 ps3av_cmd_set_video_mode(void *p, u32 head, int video_vid, int video_fmt, 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", - __FUNCTION__, video_vid, video_mode->width, video_mode->height, + __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); @@ -477,7 +494,7 @@ static u8 ps3av_cnv_mclk(u32 fs) if (ps3av_cnv_mclk_table[i].fs == fs) return ps3av_cnv_mclk_table[i].mclk; - printk(KERN_ERR "%s failed, fs:%x\n", __FUNCTION__, fs); + printk(KERN_ERR "%s failed, fs:%x\n", __func__, fs); return 0; } @@ -496,7 +513,6 @@ static const u32 ps3av_ns_table[][5] = { static void ps3av_cnv_ns(u8 *ns, u32 fs, u32 video_vid) { u32 av_vid, ns_val; - u8 *p = ns; int d; d = ns_val = 0; @@ -526,47 +542,41 @@ static void ps3av_cnv_ns(u8 *ns, u32 fs, u32 video_vid) d = 4; break; default: - printk(KERN_ERR "%s failed, vid:%x\n", __FUNCTION__, - video_vid); + 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", __FUNCTION__, fs); + printk(KERN_ERR "%s failed, fs:%x\n", __func__, fs); else ns_val = ps3av_ns_table[PS3AV_CMD_AUDIO_FS_44K-BASE][d]; - *p++ = ns_val & 0x000000FF; - *p++ = (ns_val & 0x0000FF00) >> 8; - *p = (ns_val & 0x00FF0000) >> 16; + *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) { - const u8 *p; u8 ret = 0; if (source == PS3AV_CMD_AUDIO_SOURCE_SPDIF) { ret = 0x03; } else if (source == PS3AV_CMD_AUDIO_SOURCE_SERIAL) { - p = enable; - ret = ((p[0] << 4) + (p[1] << 5) + (p[2] << 6) + (p[3] << 7)) | - 0x01; + ret = ((enable[0] << 4) + (enable[1] << 5) + (enable[2] << 6) + + (enable[3] << 7)) | 0x01; } else - printk(KERN_ERR "%s failed, source:%x\n", __FUNCTION__, - source); + printk(KERN_ERR "%s failed, source:%x\n", __func__, source); return ret; } static u8 ps3av_cnv_fifomap(const u8 *map) { - const u8 *p; u8 ret = 0; - p = map; - ret = p[0] + (p[1] << 2) + (p[2] << 4) + (p[3] << 6); + ret = map[0] + (map[1] << 2) + (map[2] << 4) + (map[3] << 6); return ret; } @@ -585,7 +595,7 @@ static u8 ps3av_cnv_inputlen(u32 word_bits) ret = PS3AV_CMD_AV_INPUTLEN_24; break; default: - printk(KERN_ERR "%s failed, word_bits:%x\n", __FUNCTION__, + printk(KERN_ERR "%s failed, word_bits:%x\n", __func__, word_bits); break; } @@ -595,7 +605,7 @@ static u8 ps3av_cnv_inputlen(u32 word_bits) 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", __FUNCTION__, + printk(KERN_ERR "%s failed, num_of_ch:%x\n", __func__, num_of_ch); return 0; } @@ -651,9 +661,10 @@ u32 ps3av_cmd_set_av_audio_param(void *p, u32 port, } /* default cs val */ -static const u8 ps3av_mode_cs_info[] = { +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 @@ -668,7 +679,7 @@ 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, spdif_bitstream; + int spdif_through; int i; if (!(ch | fs | format | word_bits | source)) { @@ -678,7 +689,6 @@ void ps3av_cmd_set_audio_mode(struct ps3av_pkt_audio_mode *audio, u32 avport, format = PS3AV_CMD_AUDIO_FORMAT_PCM; source = PS3AV_CMD_AUDIO_SOURCE_SERIAL; } - spdif_through = spdif_bitstream = 0; /* XXX not supported */ /* audio mode */ memset(audio, 0, sizeof(*audio)); @@ -768,16 +778,17 @@ void ps3av_cmd_set_audio_mode(struct ps3av_pkt_audio_mode *audio, u32 avport, 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_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_source = PS3AV_CMD_AUDIO_SOURCE_SPDIF; - if (spdif_bitstream) { - audio->audio_format = PS3AV_CMD_AUDIO_FORMAT_BITSTREAM; - audio->audio_cs_info[0] |= CS_BIT; - } + audio->audio_format = PS3AV_CMD_AUDIO_FORMAT_BITSTREAM; } } @@ -854,7 +865,7 @@ int ps3av_cmd_avb_param(struct ps3av_pkt_avb_param *avb, u32 send_len) { int res; - ps3fb_flip_ctl(0); /* flip off */ + mutex_lock(&ps3_gpu_mutex); /* avb packet */ res = ps3av_do_pkt(PS3AV_CID_AVB_PARAM, send_len, sizeof(*avb), @@ -864,11 +875,11 @@ int ps3av_cmd_avb_param(struct ps3av_pkt_avb_param *avb, u32 send_len) res = get_status(avb); if (res) - pr_debug("%s: PS3AV_CID_AVB_PARAM: failed %x\n", __FUNCTION__, + pr_debug("%s: PS3AV_CID_AVB_PARAM: failed %x\n", __func__, res); out: - ps3fb_flip_ctl(1); /* flip on */ + mutex_unlock(&ps3_gpu_mutex); return res; } @@ -913,72 +924,6 @@ int ps3av_cmd_video_get_monitor_info(struct ps3av_pkt_av_get_monitor_info *info, return res; } -#ifdef PS3AV_DEBUG -void ps3av_cmd_av_hw_conf_dump(const struct ps3av_pkt_av_get_hw_conf *hw_conf) -{ - printk("av_h_conf:num of hdmi:%d\n", hw_conf->num_of_hdmi); - printk("av_h_conf:num of avmulti:%d\n", hw_conf->num_of_avmulti); - printk("av_h_conf:num of spdif:%d\n", hw_conf->num_of_spdif); -} - -void ps3av_cmd_av_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; - int i; - - printk("Monitor Info: size%d\n", monitor_info->send_hdr.size); - - printk("avport:%02x\n", info->avport); - printk("monitor_id:"); - for (i = 0; i < 10; i++) - printk("%02x ", info->monitor_id[i]); - printk("\nmonitor_type:%02x\n", info->monitor_type); - printk("monitor_name:"); - for (i = 0; i < 16; i++) - printk("%c", info->monitor_name[i]); - - /* resolution */ - printk("\nresolution_60: bits:%08x native:%08x\n", - info->res_60.res_bits, info->res_60.native); - printk("resolution_50: bits:%08x native:%08x\n", - info->res_50.res_bits, info->res_50.native); - printk("resolution_other: bits:%08x native:%08x\n", - info->res_other.res_bits, info->res_other.native); - printk("resolution_vesa: bits:%08x native:%08x\n", - info->res_vesa.res_bits, info->res_vesa.native); - - /* color space */ - printk("color space rgb:%02x\n", info->cs.rgb); - printk("color space yuv444:%02x\n", info->cs.yuv444); - printk("color space yuv422:%02x\n", info->cs.yuv422); - - /* color info */ - printk("color info red:X %04x Y %04x\n", - info->color.red_x, info->color.red_y); - printk("color info green:X %04x Y %04x\n", - info->color.green_x, info->color.green_y); - printk("color info blue:X %04x Y %04x\n", - info->color.blue_x, info->color.blue_y); - printk("color info white:X %04x Y %04x\n", - info->color.white_x, info->color.white_y); - printk("color info gamma: %08x\n", info->color.gamma); - - /* other info */ - printk("supported_AI:%02x\n", info->supported_ai); - printk("speaker_info:%02x\n", info->speaker_info); - printk("num of audio:%02x\n", info->num_of_audio_block); - - /* audio block */ - for (i = 0; i < info->num_of_audio_block; i++) { - printk("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++; - } -} -#endif /* PS3AV_DEBUG */ - #define PS3AV_AV_LAYOUT_0 (PS3AV_CMD_AV_LAYOUT_32 \ | PS3AV_CMD_AV_LAYOUT_44 \ | PS3AV_CMD_AV_LAYOUT_48) @@ -989,34 +934,3 @@ void ps3av_cmd_av_monitor_info_dump(const struct ps3av_pkt_av_get_monitor_info * | PS3AV_CMD_AV_LAYOUT_176 \ | PS3AV_CMD_AV_LAYOUT_192) -/************************* vuart ***************************/ - -#define POLLING_INTERVAL 25 /* in msec */ - -int ps3av_vuart_write(struct ps3_vuart_port_device *dev, const void *buf, - unsigned long size) -{ - int error = ps3_vuart_write(dev, buf, size); - return error ? error : size; -} - -int ps3av_vuart_read(struct ps3_vuart_port_device *dev, void *buf, - unsigned long size, int timeout) -{ - int error; - int loopcnt = 0; - - 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", - __FUNCTION__, error); - return error; - } - msleep(POLLING_INTERVAL); - } - return -EWOULDBLOCK; -} diff --git a/drivers/ps3/ps3stor_lib.c b/drivers/ps3/ps3stor_lib.c new file mode 100644 index 00000000000..8c3f5adf1bc --- /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 00000000000..0e41737ea83 --- /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 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 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 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.c b/drivers/ps3/vuart.c deleted file mode 100644 index 6c12744eeb9..00000000000 --- a/drivers/ps3/vuart.c +++ /dev/null @@ -1,1118 +0,0 @@ -/* - * 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/module.h> -#include <linux/interrupt.h> -#include <linux/workqueue.h> -#include <asm/ps3.h> - -#include <asm/firmware.h> -#include <asm/lv1call.h> -#include <asm/bitops.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 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))); - -/* redefine dev_dbg to do a syntax check */ - -#if !defined(DEBUG) -#undef dev_dbg -static inline int __attribute__ ((format (printf, 2, 3))) dev_dbg( - const struct device *_dev, const char *fmt, ...) {return 0;} -#endif - -#define dump_ports_bmp(_b) _dump_ports_bmp(_b, __func__, __LINE__) -static void __attribute__ ((unused)) _dump_ports_bmp( - const struct ports_bmp* bmp, const char* func, int line) -{ - pr_debug("%s:%d: ports_bmp: %016lxh\n", func, line, bmp->status); -} - -static int ps3_vuart_match_id_to_port(enum ps3_match_id match_id, - unsigned int *port_number) -{ - switch(match_id) { - case PS3_MATCH_ID_AV_SETTINGS: - *port_number = 0; - return 0; - case PS3_MATCH_ID_SYSTEM_MANAGER: - *port_number = 2; - return 0; - default: - WARN_ON(1); - *port_number = UINT_MAX; - return -EINVAL; - }; -} - -#define dump_port_params(_b) _dump_port_params(_b, __func__, __LINE__) -static void __attribute__ ((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 -} - -struct vuart_triggers { - unsigned long rx; - unsigned long tx; -}; - -int ps3_vuart_get_triggers(struct ps3_vuart_port_device *dev, - struct vuart_triggers *trig) -{ - int result; - unsigned long size; - unsigned long val; - - result = lv1_get_virtual_uart_param(dev->priv->port_number, - PARAM_TX_TRIGGER, &trig->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->priv->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->priv->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_vuart_port_device *dev, unsigned int tx, - unsigned int rx) -{ - int result; - unsigned long size; - - result = lv1_set_virtual_uart_param(dev->priv->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->priv->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->priv->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_vuart_port_device *dev, - u64 *bytes_waiting) -{ - int result = lv1_get_virtual_uart_param(dev->priv->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: %lxh\n", __func__, __LINE__, - *bytes_waiting); - return result; -} - -static int ps3_vuart_set_interrupt_mask(struct ps3_vuart_port_device *dev, - unsigned long mask) -{ - int result; - - dev_dbg(&dev->core, "%s:%d: %lxh\n", __func__, __LINE__, mask); - - dev->priv->interrupt_mask = mask; - - result = lv1_set_virtual_uart_param(dev->priv->port_number, - PARAM_INTERRUPT_MASK, dev->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_vuart_port_device *dev, - unsigned long *status) -{ - u64 tmp; - int result = lv1_get_virtual_uart_param(dev->priv->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 & dev->priv->interrupt_mask; - - dev_dbg(&dev->core, "%s:%d: m %lxh, s %lxh, m&s %lxh\n", - __func__, __LINE__, dev->priv->interrupt_mask, tmp, *status); - - return result; -} - -int ps3_vuart_enable_interrupt_tx(struct ps3_vuart_port_device *dev) -{ - return (dev->priv->interrupt_mask & INTERRUPT_MASK_TX) ? 0 - : ps3_vuart_set_interrupt_mask(dev, dev->priv->interrupt_mask - | INTERRUPT_MASK_TX); -} - -int ps3_vuart_enable_interrupt_rx(struct ps3_vuart_port_device *dev) -{ - return (dev->priv->interrupt_mask & INTERRUPT_MASK_RX) ? 0 - : ps3_vuart_set_interrupt_mask(dev, dev->priv->interrupt_mask - | INTERRUPT_MASK_RX); -} - -int ps3_vuart_enable_interrupt_disconnect(struct ps3_vuart_port_device *dev) -{ - return (dev->priv->interrupt_mask & INTERRUPT_MASK_DISCONNECT) ? 0 - : ps3_vuart_set_interrupt_mask(dev, dev->priv->interrupt_mask - | INTERRUPT_MASK_DISCONNECT); -} - -int ps3_vuart_disable_interrupt_tx(struct ps3_vuart_port_device *dev) -{ - return (dev->priv->interrupt_mask & INTERRUPT_MASK_TX) - ? ps3_vuart_set_interrupt_mask(dev, dev->priv->interrupt_mask - & ~INTERRUPT_MASK_TX) : 0; -} - -int ps3_vuart_disable_interrupt_rx(struct ps3_vuart_port_device *dev) -{ - return (dev->priv->interrupt_mask & INTERRUPT_MASK_RX) - ? ps3_vuart_set_interrupt_mask(dev, dev->priv->interrupt_mask - & ~INTERRUPT_MASK_RX) : 0; -} - -int ps3_vuart_disable_interrupt_disconnect(struct ps3_vuart_port_device *dev) -{ - return (dev->priv->interrupt_mask & INTERRUPT_MASK_DISCONNECT) - ? ps3_vuart_set_interrupt_mask(dev, dev->priv->interrupt_mask - & ~INTERRUPT_MASK_DISCONNECT) : 0; -} - -/** - * ps3_vuart_raw_write - Low level write helper. - * - * Do not call ps3_vuart_raw_write directly, use ps3_vuart_write. - */ - -static int ps3_vuart_raw_write(struct ps3_vuart_port_device *dev, - const void* buf, unsigned int bytes, unsigned long *bytes_written) -{ - int result; - - result = lv1_write_virtual_uart(dev->priv->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; - } - - dev->priv->stats.bytes_written += *bytes_written; - - dev_dbg(&dev->core, "%s:%d: wrote %lxh/%xh=>%lxh\n", __func__, __LINE__, - *bytes_written, bytes, dev->priv->stats.bytes_written); - - return result; -} - -/** - * ps3_vuart_raw_read - Low level read helper. - * - * Do not call ps3_vuart_raw_read directly, use ps3_vuart_read. - */ - -static int ps3_vuart_raw_read(struct ps3_vuart_port_device *dev, void* buf, - unsigned int bytes, unsigned long *bytes_read) -{ - int result; - - dev_dbg(&dev->core, "%s:%d: %xh\n", __func__, __LINE__, bytes); - - result = lv1_read_virtual_uart(dev->priv->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; - } - - dev->priv->stats.bytes_read += *bytes_read; - - dev_dbg(&dev->core, "%s:%d: read %lxh/%xh=>%lxh\n", __func__, __LINE__, - *bytes_read, bytes, dev->priv->stats.bytes_read); - - return result; -} - -/** - * ps3_vuart_clear_rx_bytes - Discard bytes received. - * @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_vuart_port_device *dev, - unsigned int bytes) -{ - int result; - 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. */ - - dev->priv->stats.bytes_read -= bytes_waiting; -} - -/** - * 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 - * - * 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_vuart_port_device *dev, const void* buf, - unsigned int bytes) -{ - static unsigned long dbg_number; - int result; - unsigned long flags; - struct list_buffer *lb; - - dev_dbg(&dev->core, "%s:%d: %u(%xh) bytes\n", __func__, __LINE__, - bytes, bytes); - - spin_lock_irqsave(&dev->priv->tx_list.lock, flags); - - if (list_empty(&dev->priv->tx_list.head)) { - unsigned long bytes_written; - - result = ps3_vuart_raw_write(dev, buf, bytes, &bytes_written); - - spin_unlock_irqrestore(&dev->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(&dev->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(&dev->priv->tx_list.lock, flags); - list_add_tail(&lb->link, &dev->priv->tx_list.head); - ps3_vuart_enable_interrupt_tx(dev); - spin_unlock_irqrestore(&dev->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; -} - -/** - * ps3_vuart_read - the entry point for reading data from a port - * - * 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_vuart_port_device *dev, void* buf, - unsigned int bytes) -{ - 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(&dev->priv->rx_list.lock, flags); - - if (dev->priv->rx_list.bytes_held < bytes) { - spin_unlock_irqrestore(&dev->priv->rx_list.lock, flags); - dev_dbg(&dev->core, "%s:%d: starved for %lxh bytes\n", - __func__, __LINE__, - bytes - dev->priv->rx_list.bytes_held); - return -EAGAIN; - } - - list_for_each_entry_safe(lb, n, &dev->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; - dev->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(&dev->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(&dev->priv->rx_list.lock, flags); - return 0; -} - -int ps3_vuart_read_async(struct ps3_vuart_port_device *dev, work_func_t func, - unsigned int bytes) -{ - unsigned long flags; - - if(dev->priv->work.trigger) { - dev_dbg(&dev->core, "%s:%d: warning, multiple calls\n", - __func__, __LINE__); - return -EAGAIN; - } - - BUG_ON(!bytes); - - PREPARE_WORK(&dev->priv->work.work, func); - - spin_lock_irqsave(&dev->priv->work.lock, flags); - if(dev->priv->rx_list.bytes_held >= bytes) { - dev_dbg(&dev->core, "%s:%d: schedule_work %xh bytes\n", - __func__, __LINE__, bytes); - schedule_work(&dev->priv->work.work); - spin_unlock_irqrestore(&dev->priv->work.lock, flags); - return 0; - } - - dev->priv->work.trigger = bytes; - spin_unlock_irqrestore(&dev->priv->work.lock, flags); - - dev_dbg(&dev->core, "%s:%d: waiting for %u(%xh) bytes\n", __func__, - __LINE__, bytes, bytes); - - return 0; -} - -void ps3_vuart_cancel_async(struct ps3_vuart_port_device *dev) -{ - dev->priv->work.trigger = 0; -} - -/** - * 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_vuart_port_device *dev) -{ - int result = 0; - 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(&dev->priv->tx_list.lock, flags); - - list_for_each_entry_safe(lb, n, &dev->priv->tx_list.head, link) { - - unsigned long 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, %lxh 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(&dev->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_vuart_port_device *dev) -{ - static unsigned long dbg_number; - int result = 0; - unsigned long flags; - struct list_buffer *lb; - unsigned long bytes; - - dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__); - - result = ps3_vuart_get_rx_bytes_waiting(dev, &bytes); - - if (result) - return -EIO; - - BUG_ON(!bytes); - - /* 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; - - spin_lock_irqsave(&dev->priv->rx_list.lock, flags); - list_add_tail(&lb->link, &dev->priv->rx_list.head); - dev->priv->rx_list.bytes_held += bytes; - spin_unlock_irqrestore(&dev->priv->rx_list.lock, flags); - - dev_dbg(&dev->core, "%s:%d: buf_%lu: queued %lxh bytes\n", - __func__, __LINE__, lb->dbg_number, bytes); - - spin_lock_irqsave(&dev->priv->work.lock, flags); - if(dev->priv->work.trigger - && dev->priv->rx_list.bytes_held >= dev->priv->work.trigger) { - dev_dbg(&dev->core, "%s:%d: schedule_work %lxh bytes\n", - __func__, __LINE__, dev->priv->work.trigger); - dev->priv->work.trigger = 0; - schedule_work(&dev->priv->work.work); - } - spin_unlock_irqrestore(&dev->priv->work.lock, flags); - return 0; -} - -static int ps3_vuart_handle_interrupt_disconnect( - struct ps3_vuart_port_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_vuart_port_device *dev) -{ - int result; - 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) { - dev->priv->stats.disconnect_interrupts++; - result = ps3_vuart_handle_interrupt_disconnect(dev); - if (result) - ps3_vuart_disable_interrupt_disconnect(dev); - } - - if (status & INTERRUPT_MASK_TX) { - dev->priv->stats.tx_interrupts++; - result = ps3_vuart_handle_interrupt_tx(dev); - if (result) - ps3_vuart_disable_interrupt_tx(dev); - } - - if (status & INTERRUPT_MASK_RX) { - dev->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 { - const struct ports_bmp bmp; - unsigned int virq; - struct semaphore probe_mutex; - int use_count; - struct ps3_vuart_port_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; - - BUG_ON(!_private); - bus_priv = (struct vuart_bus_priv *)_private; - - 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_match(struct device *_dev, struct device_driver *_drv) -{ - int result; - struct ps3_vuart_port_driver *drv = to_ps3_vuart_port_driver(_drv); - struct ps3_vuart_port_device *dev = to_ps3_vuart_port_device(_dev); - - result = dev->match_id == drv->match_id; - - dev_info(&dev->core, "%s:%d: dev=%u(%s), drv=%u(%s): %s\n", __func__, - __LINE__, dev->match_id, dev->core.bus_id, drv->match_id, - drv->core.name, (result ? "match" : "miss")); - - return result; -} - -static int ps3_vuart_probe(struct device *_dev) -{ - int result; - unsigned int port_number; - struct ps3_vuart_port_device *dev = to_ps3_vuart_port_device(_dev); - struct ps3_vuart_port_driver *drv = - to_ps3_vuart_port_driver(_dev->driver); - - dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__); - - BUG_ON(!drv); - - down(&vuart_bus_priv.probe_mutex); - - /* Setup vuart_bus_priv.devices[]. */ - - result = ps3_vuart_match_id_to_port(dev->match_id, - &port_number); - - if (result) { - dev_dbg(&dev->core, "%s:%d: unknown match_id (%d)\n", - __func__, __LINE__, dev->match_id); - result = -EINVAL; - goto fail_match; - } - - if (vuart_bus_priv.devices[port_number]) { - dev_dbg(&dev->core, "%s:%d: port busy (%d)\n", __func__, - __LINE__, port_number); - result = -EBUSY; - goto fail_match; - } - - vuart_bus_priv.devices[port_number] = dev; - - /* Setup dev->priv. */ - - dev->priv = kzalloc(sizeof(struct ps3_vuart_port_priv), GFP_KERNEL); - - if (!dev->priv) { - result = -ENOMEM; - goto fail_alloc; - } - - dev->priv->port_number = port_number; - - INIT_LIST_HEAD(&dev->priv->tx_list.head); - spin_lock_init(&dev->priv->tx_list.lock); - - INIT_LIST_HEAD(&dev->priv->rx_list.head); - spin_lock_init(&dev->priv->rx_list.lock); - - INIT_WORK(&dev->priv->work.work, NULL); - spin_lock_init(&dev->priv->work.lock); - dev->priv->work.trigger = 0; - dev->priv->work.dev = dev; - - if (++vuart_bus_priv.use_count == 1) { - - result = ps3_alloc_vuart_irq(PS3_BINDING_CPU_ANY, - (void*)&vuart_bus_priv.bmp.status, &vuart_bus_priv.virq); - - if (result) { - dev_dbg(&dev->core, - "%s:%d: ps3_alloc_vuart_irq failed (%d)\n", - __func__, __LINE__, result); - result = -EPERM; - goto fail_alloc_irq; - } - - result = request_irq(vuart_bus_priv.virq, ps3_vuart_irq_handler, - IRQF_DISABLED, "vuart", &vuart_bus_priv); - - if (result) { - dev_info(&dev->core, "%s:%d: request_irq failed (%d)\n", - __func__, __LINE__, result); - goto fail_request_irq; - } - } - - /* 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__); - down(&vuart_bus_priv.probe_mutex); - goto fail_probe; - } - - up(&vuart_bus_priv.probe_mutex); - - return result; - -fail_probe: - ps3_vuart_set_interrupt_mask(dev, 0); -fail_request_irq: - ps3_free_vuart_irq(vuart_bus_priv.virq); - vuart_bus_priv.virq = NO_IRQ; -fail_alloc_irq: - --vuart_bus_priv.use_count; - kfree(dev->priv); - dev->priv = NULL; -fail_alloc: - vuart_bus_priv.devices[port_number] = NULL; -fail_match: - up(&vuart_bus_priv.probe_mutex); - dev_dbg(&dev->core, "%s:%d failed\n", __func__, __LINE__); - return result; -} - -static int ps3_vuart_remove(struct device *_dev) -{ - struct ps3_vuart_port_device *dev = to_ps3_vuart_port_device(_dev); - struct ps3_vuart_port_driver *drv = - to_ps3_vuart_port_driver(_dev->driver); - - down(&vuart_bus_priv.probe_mutex); - - dev_dbg(&dev->core, "%s:%d: %s\n", __func__, __LINE__, - dev->core.bus_id); - - BUG_ON(vuart_bus_priv.use_count < 1); - - if (drv->remove) - drv->remove(dev); - else - dev_dbg(&dev->core, "%s:%d: %s no remove method\n", __func__, - __LINE__, dev->core.bus_id); - - vuart_bus_priv.devices[dev->priv->port_number] = NULL; - - if (--vuart_bus_priv.use_count == 0) { - BUG(); - free_irq(vuart_bus_priv.virq, &vuart_bus_priv); - ps3_free_vuart_irq(vuart_bus_priv.virq); - vuart_bus_priv.virq = NO_IRQ; - } - - kfree(dev->priv); - dev->priv = NULL; - - up(&vuart_bus_priv.probe_mutex); - return 0; -} - -static void ps3_vuart_shutdown(struct device *_dev) -{ - struct ps3_vuart_port_device *dev = to_ps3_vuart_port_device(_dev); - struct ps3_vuart_port_driver *drv = - to_ps3_vuart_port_driver(_dev->driver); - - dev_dbg(&dev->core, "%s:%d: %s\n", __func__, __LINE__, - dev->core.bus_id); - - if (drv->shutdown) - drv->shutdown(dev); - else - dev_dbg(&dev->core, "%s:%d: %s no shutdown method\n", __func__, - __LINE__, dev->core.bus_id); -} - -/** - * ps3_vuart_bus - The vuart bus instance. - * - * The vuart is managed as a bus that port devices connect to. - */ - -struct bus_type ps3_vuart_bus = { - .name = "ps3_vuart", - .match = ps3_vuart_match, - .probe = ps3_vuart_probe, - .remove = ps3_vuart_remove, - .shutdown = ps3_vuart_shutdown, -}; - -int __init ps3_vuart_bus_init(void) -{ - int result; - - pr_debug("%s:%d:\n", __func__, __LINE__); - - if (!firmware_has_feature(FW_FEATURE_PS3_LV1)) - return -ENODEV; - - init_MUTEX(&vuart_bus_priv.probe_mutex); - result = bus_register(&ps3_vuart_bus); - BUG_ON(result); - - return result; -} - -void __exit ps3_vuart_bus_exit(void) -{ - pr_debug("%s:%d:\n", __func__, __LINE__); - bus_unregister(&ps3_vuart_bus); -} - -core_initcall(ps3_vuart_bus_init); -module_exit(ps3_vuart_bus_exit); - -/** - * ps3_vuart_port_release_device - Remove a vuart port device. - */ - -static void ps3_vuart_port_release_device(struct device *_dev) -{ -#if defined(DEBUG) - struct ps3_vuart_port_device *dev = to_ps3_vuart_port_device(_dev); - - dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__); - - BUG_ON(dev->priv && "forgot to free"); - memset(&dev->core, 0, sizeof(dev->core)); -#endif -} - -/** - * ps3_vuart_port_device_register - Add a vuart port device. - */ - -int ps3_vuart_port_device_register(struct ps3_vuart_port_device *dev) -{ - static unsigned int dev_count = 1; - - BUG_ON(dev->priv && "forgot to free"); - - dev->core.parent = NULL; - dev->core.bus = &ps3_vuart_bus; - dev->core.release = ps3_vuart_port_release_device; - - snprintf(dev->core.bus_id, sizeof(dev->core.bus_id), "vuart_%02x", - dev_count++); - - dev_dbg(&dev->core, "%s:%d register\n", __func__, __LINE__); - - return device_register(&dev->core); -} - -EXPORT_SYMBOL_GPL(ps3_vuart_port_device_register); - -/** - * 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.name); - drv->core.bus = &ps3_vuart_bus; - result = 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.name); - driver_unregister(&drv->core); -} - -EXPORT_SYMBOL_GPL(ps3_vuart_port_driver_unregister); diff --git a/drivers/ps3/vuart.h b/drivers/ps3/vuart.h index 1be992d568c..eb7f6d94a89 100644 --- a/drivers/ps3/vuart.h +++ b/drivers/ps3/vuart.h @@ -34,29 +34,7 @@ struct ps3_vuart_stats { struct ps3_vuart_work { struct work_struct work; unsigned long trigger; - spinlock_t lock; - struct ps3_vuart_port_device* dev; /* to convert work to device */ -}; - -/** - * struct ps3_vuart_port_priv - private vuart device data. - */ - -struct ps3_vuart_port_priv { - unsigned int port_number; - u64 interrupt_mask; - - struct { - spinlock_t lock; - struct list_head head; - } tx_list; - struct { - unsigned long bytes_held; - spinlock_t lock; - struct list_head head; - } rx_list; - struct ps3_vuart_stats stats; - struct ps3_vuart_work work; + struct ps3_system_bus_device *dev; /* to convert work to device */ }; /** @@ -64,32 +42,30 @@ struct ps3_vuart_port_priv { */ struct ps3_vuart_port_driver { - enum ps3_match_id match_id; - struct device_driver core; - int (*probe)(struct ps3_vuart_port_device *); - int (*remove)(struct ps3_vuart_port_device *); - void (*shutdown)(struct ps3_vuart_port_device *); - int (*tx_event)(struct ps3_vuart_port_device *dev); - int (*rx_event)(struct ps3_vuart_port_device *dev); - int (*disconnect_event)(struct ps3_vuart_port_device *dev); - /* int (*suspend)(struct ps3_vuart_port_device *, pm_message_t); */ - /* int (*resume)(struct ps3_vuart_port_device *); */ + 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 *to_ps3_vuart_port_driver( - struct device_driver *_drv) -{ - return container_of(_drv, struct ps3_vuart_port_driver, core); -} -static inline struct ps3_vuart_port_device *to_ps3_vuart_port_device( - struct device *_dev) +static inline struct ps3_vuart_port_driver * + ps3_system_bus_dev_to_vuart_drv(struct ps3_system_bus_device *_dev) { - return container_of(_dev, struct ps3_vuart_port_device, core); + 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_vuart_port_device *ps3_vuart_work_to_port_device( +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, @@ -97,14 +73,13 @@ static inline struct ps3_vuart_port_device *ps3_vuart_work_to_port_device( return vw->dev; } -int ps3_vuart_write(struct ps3_vuart_port_device *dev, const void* buf, - unsigned int bytes); -int ps3_vuart_read(struct ps3_vuart_port_device *dev, void* buf, +int ps3_vuart_write(struct ps3_system_bus_device *dev, const void *buf, unsigned int bytes); -int ps3_vuart_read_async(struct ps3_vuart_port_device *dev, work_func_t func, +int ps3_vuart_read(struct ps3_system_bus_device *dev, void *buf, unsigned int bytes); -void ps3_vuart_cancel_async(struct ps3_vuart_port_device *dev); -void ps3_vuart_clear_rx_bytes(struct ps3_vuart_port_device *dev, +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); #endif |
