From a3d4d6435b56eb0b6ff4f88e5a513cfccfb3e770 Mon Sep 17 00:00:00 2001 From: Geoff Levand Date: Thu, 23 Nov 2006 00:47:00 +0100 Subject: [POWERPC] ps3: add ps3 platform system bus support Adds a PS3 system bus driver. This system bus is a virtual bus used to present the PS3 system devices in the LDM. Signed-off-by: Geoff Levand Signed-off-by: Arnd Bergmann --- drivers/ps3/Makefile | 1 + drivers/ps3/system-bus.c | 358 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 359 insertions(+) create mode 100644 drivers/ps3/Makefile create mode 100644 drivers/ps3/system-bus.c (limited to 'drivers/ps3') diff --git a/drivers/ps3/Makefile b/drivers/ps3/Makefile new file mode 100644 index 000000000000..b52d547b7a78 --- /dev/null +++ b/drivers/ps3/Makefile @@ -0,0 +1 @@ +obj-y += system-bus.o diff --git a/drivers/ps3/system-bus.c b/drivers/ps3/system-bus.c new file mode 100644 index 000000000000..e19992c4db41 --- /dev/null +++ b/drivers/ps3/system-bus.c @@ -0,0 +1,358 @@ +/* + * PS3 system bus driver. + * + * 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 +#include +#include +#include +#include + +#include +#include +#include + +#define dump_mmio_region(_a) _dump_mmio_region(_a, __func__, __LINE__) +static void _dump_mmio_region(const struct ps3_mmio_region* r, + const char* func, int line) +{ + pr_debug("%s:%d: dev %u:%u\n", func, line, r->did.bus_id, + r->did.dev_id); + pr_debug("%s:%d: bus_addr %lxh\n", func, line, r->bus_addr); + pr_debug("%s:%d: len %lxh\n", func, line, r->len); + pr_debug("%s:%d: lpar_addr %lxh\n", func, line, r->lpar_addr); +} + +int ps3_mmio_region_create(struct ps3_mmio_region *r) +{ + int result; + + result = lv1_map_device_mmio_region(r->did.bus_id, r->did.dev_id, + r->bus_addr, r->len, r->page_size, &r->lpar_addr); + + if (result) { + pr_debug("%s:%d: lv1_map_device_mmio_region failed: %s\n", + __func__, __LINE__, ps3_result(result)); + r->lpar_addr = r->len = r->bus_addr = 0; + } + + dump_mmio_region(r); + return result; +} + +int ps3_free_mmio_region(struct ps3_mmio_region *r) +{ + int result; + + result = lv1_unmap_device_mmio_region(r->did.bus_id, r->did.dev_id, + r->bus_addr); + + if (result) + pr_debug("%s:%d: lv1_unmap_device_mmio_region failed: %s\n", + __func__, __LINE__, ps3_result(result)); + + r->lpar_addr = r->len = r->bus_addr = 0; + return result; +} + +static int ps3_system_bus_match(struct device *_dev, + struct device_driver *_drv) +{ + int result; + struct ps3_system_bus_driver *drv = to_ps3_system_bus_driver(_drv); + struct ps3_system_bus_device *dev = to_ps3_system_bus_device(_dev); + + result = dev->match_id == drv->match_id; + + pr_info("%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_system_bus_probe(struct device *_dev) +{ + int result; + struct ps3_system_bus_device *dev = to_ps3_system_bus_device(_dev); + struct ps3_system_bus_driver *drv = + to_ps3_system_bus_driver(_dev->driver); + + result = lv1_open_device(dev->did.bus_id, dev->did.dev_id, 0); + + if (result) { + pr_debug("%s:%d: lv1_open_device failed (%d)\n", + __func__, __LINE__, result); + result = -EACCES; + goto clean_none; + } + + if (dev->d_region->did.bus_id) { + result = ps3_dma_region_create(dev->d_region); + + if (result) { + pr_debug("%s:%d: ps3_dma_region_create failed (%d)\n", + __func__, __LINE__, result); + BUG_ON("check region type"); + result = -EINVAL; + goto clean_device; + } + } + + BUG_ON(!drv); + + if (drv->probe) + result = drv->probe(dev); + else + pr_info("%s:%d: %s no probe method\n", __func__, __LINE__, + dev->core.bus_id); + + if (result) { + pr_debug("%s:%d: drv->probe failed\n", __func__, __LINE__); + goto clean_dma; + } + + return result; + +clean_dma: + ps3_dma_region_free(dev->d_region); +clean_device: + lv1_close_device(dev->did.bus_id, dev->did.dev_id); +clean_none: + return result; +} + +static int ps3_system_bus_remove(struct device *_dev) +{ + struct ps3_system_bus_device *dev = to_ps3_system_bus_device(_dev); + struct ps3_system_bus_driver *drv = + to_ps3_system_bus_driver(_dev->driver); + + if (drv->remove) + drv->remove(dev); + else + pr_info("%s:%d: %s no remove method\n", __func__, __LINE__, + dev->core.bus_id); + + ps3_dma_region_free(dev->d_region); + ps3_free_mmio_region(dev->m_region); + lv1_close_device(dev->did.bus_id, dev->did.dev_id); + + return 0; +} + +struct bus_type ps3_system_bus_type = { + .name = "ps3_system_bus", + .match = ps3_system_bus_match, + .probe = ps3_system_bus_probe, + .remove = ps3_system_bus_remove, +}; + +int __init ps3_system_bus_init(void) +{ + int result; + + result = bus_register(&ps3_system_bus_type); + BUG_ON(result); + return result; +} + +core_initcall(ps3_system_bus_init); + +/* Allocates a contiguous real buffer and creates mappings over it. + * Returns the virtual address of the buffer and sets dma_handle + * to the dma address (mapping) of the first page. + */ + +static void * ps3_alloc_coherent(struct device *_dev, size_t size, + dma_addr_t *dma_handle, gfp_t flag) +{ + int result; + struct ps3_system_bus_device *dev = to_ps3_system_bus_device(_dev); + unsigned long virt_addr; + + BUG_ON(!dev->d_region->bus_addr); + + flag &= ~(__GFP_DMA | __GFP_HIGHMEM); + flag |= __GFP_ZERO; + + virt_addr = __get_free_pages(flag, get_order(size)); + + if (!virt_addr) { + pr_debug("%s:%d: get_free_pages failed\n", __func__, __LINE__); + goto clean_none; + } + + result = ps3_dma_map(dev->d_region, virt_addr, size, dma_handle); + + if (result) { + pr_debug("%s:%d: ps3_dma_map failed (%d)\n", + __func__, __LINE__, result); + BUG_ON("check region type"); + goto clean_alloc; + } + + return (void*)virt_addr; + +clean_alloc: + free_pages(virt_addr, get_order(size)); +clean_none: + dma_handle = NULL; + return NULL; +} + +static void ps3_free_coherent(struct device *_dev, size_t size, void *vaddr, + dma_addr_t dma_handle) +{ + struct ps3_system_bus_device *dev = to_ps3_system_bus_device(_dev); + + ps3_dma_unmap(dev->d_region, dma_handle, size); + free_pages((unsigned long)vaddr, get_order(size)); +} + +/* Creates TCEs for a user provided buffer. The user buffer must be + * contiguous real kernel storage (not vmalloc). The address of the buffer + * passed here is the kernel (virtual) address of the buffer. The buffer + * need not be page aligned, the dma_addr_t returned will point to the same + * byte within the page as vaddr. + */ + +static dma_addr_t ps3_map_single(struct device *_dev, void *ptr, size_t size, + enum dma_data_direction direction) +{ + struct ps3_system_bus_device *dev = to_ps3_system_bus_device(_dev); + int result; + unsigned long bus_addr; + + result = ps3_dma_map(dev->d_region, (unsigned long)ptr, size, + &bus_addr); + + if (result) { + pr_debug("%s:%d: ps3_dma_map failed (%d)\n", + __func__, __LINE__, result); + } + + return bus_addr; +} + +static void ps3_unmap_single(struct device *_dev, dma_addr_t dma_addr, + size_t size, enum dma_data_direction direction) +{ + struct ps3_system_bus_device *dev = to_ps3_system_bus_device(_dev); + int result; + + result = ps3_dma_unmap(dev->d_region, dma_addr, size); + + if (result) { + pr_debug("%s:%d: ps3_dma_unmap failed (%d)\n", + __func__, __LINE__, result); + } +} + +static int ps3_map_sg(struct device *_dev, struct scatterlist *sg, int nents, + enum dma_data_direction direction) +{ +#if defined(CONFIG_PS3_DYNAMIC_DMA) + BUG_ON("do"); +#endif + return 0; +} + +static void ps3_unmap_sg(struct device *_dev, struct scatterlist *sg, + int nents, enum dma_data_direction direction) +{ +#if defined(CONFIG_PS3_DYNAMIC_DMA) + BUG_ON("do"); +#endif +} + +static int ps3_dma_supported(struct device *_dev, u64 mask) +{ + return 1; +} + +static struct dma_mapping_ops ps3_dma_ops = { + .alloc_coherent = ps3_alloc_coherent, + .free_coherent = ps3_free_coherent, + .map_single = ps3_map_single, + .unmap_single = ps3_unmap_single, + .map_sg = ps3_map_sg, + .unmap_sg = ps3_unmap_sg, + .dma_supported = ps3_dma_supported +}; + +/** + * ps3_system_bus_release_device - remove a device from the system bus + */ + +static void ps3_system_bus_release_device(struct device *_dev) +{ + struct ps3_system_bus_device *dev = to_ps3_system_bus_device(_dev); + kfree(dev); +} + +/** + * ps3_system_bus_device_register - add a device to the system bus + * + * ps3_system_bus_device_register() expects the dev object to be allocated + * dynamically by the caller. The system bus takes ownership of the dev + * object and frees the object in ps3_system_bus_release_device(). + */ + +int ps3_system_bus_device_register(struct ps3_system_bus_device *dev) +{ + int result; + static unsigned int dev_count = 1; + + dev->core.parent = NULL; + dev->core.bus = &ps3_system_bus_type; + dev->core.release = ps3_system_bus_release_device; + + dev->core.archdata.of_node = NULL; + dev->core.archdata.dma_ops = &ps3_dma_ops; + dev->core.archdata.numa_node = 0; + + snprintf(dev->core.bus_id, sizeof(dev->core.bus_id), "sb_%02x", + dev_count++); + + pr_debug("%s:%d add %s\n", __func__, __LINE__, dev->core.bus_id); + + result = device_register(&dev->core); + return result; +} + +EXPORT_SYMBOL_GPL(ps3_system_bus_device_register); + +int ps3_system_bus_driver_register(struct ps3_system_bus_driver *drv) +{ + int result; + + drv->core.bus = &ps3_system_bus_type; + + result = driver_register(&drv->core); + return result; +} + +EXPORT_SYMBOL_GPL(ps3_system_bus_driver_register); + +void ps3_system_bus_driver_unregister(struct ps3_system_bus_driver *drv) +{ + driver_unregister(&drv->core); +} + +EXPORT_SYMBOL_GPL(ps3_system_bus_driver_unregister); -- cgit v1.2.3 From e22ba7e38144c1cccac5024cfd6ec88bb64d3e1f Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 27 Nov 2006 19:18:57 +0100 Subject: [POWERPC] ps3: multiplatform build fixes A few code paths need to check whether or not they are running on the PS3's LV1 hypervisor before making hcalls. This introduces a new firmware feature bit for this, FW_FEATURE_PS3_LV1. Now when both PS3 and IBM_CELL_BLADE are enabled, but not PSERIES, FW_FEATURE_PS3_LV1 and FW_FEATURE_LPAR get enabled at compile time, which is a bug. The same problem can also happen for (PPC_ISERIES && !PPC_PSERIES && PPC_SOMETHING_ELSE). In order to solve this, I introduce a new CONFIG_PPC_NATIVE option that is set when at least one platform is selected that can run without a hypervisor and then turns the firmware feature check into a run-time option. The new cell oprofile support that was recently merged does not work on hypervisor based platforms like the PS3, therefore make it depend on PPC_CELL_NATIVE instead of PPC_CELL. This may change if we get oprofile support for PS3. Signed-off-by: Arnd Bergmann --- drivers/ps3/system-bus.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/ps3') diff --git a/drivers/ps3/system-bus.c b/drivers/ps3/system-bus.c index e19992c4db41..d79f949bcb2a 100644 --- a/drivers/ps3/system-bus.c +++ b/drivers/ps3/system-bus.c @@ -27,6 +27,7 @@ #include #include #include +#include #define dump_mmio_region(_a) _dump_mmio_region(_a, __func__, __LINE__) static void _dump_mmio_region(const struct ps3_mmio_region* r, @@ -167,6 +168,9 @@ int __init ps3_system_bus_init(void) { int result; + if (!firmware_has_feature(FW_FEATURE_PS3_LV1)) + return 0; + result = bus_register(&ps3_system_bus_type); BUG_ON(result); return result; -- cgit v1.2.3 From 74e95d5de9d8eb243cda68b546bdb29f6ef0f01c Mon Sep 17 00:00:00 2001 From: Geoff Levand Date: Fri, 8 Dec 2006 18:27:47 -0800 Subject: [POWERPC] ps3: Add vuart support Adds support for the PS3 virtual UART (vuart). The vuart provides a bi-directional byte stream data link between logical partitions. This is needed for the ps3 graphics driver and the ps3 power control support to be able to communicate with the lv1 policy module. Signed-off-by: Geoff Levand Acked-by: Arnd Bergmann Signed-off-by: Paul Mackerras --- drivers/ps3/Makefile | 1 + drivers/ps3/vuart.c | 965 +++++++++++++++++++++++++++++++++++++++++++++++++++ drivers/ps3/vuart.h | 94 +++++ 3 files changed, 1060 insertions(+) create mode 100644 drivers/ps3/vuart.c create mode 100644 drivers/ps3/vuart.h (limited to 'drivers/ps3') diff --git a/drivers/ps3/Makefile b/drivers/ps3/Makefile index b52d547b7a78..8433eb7562cb 100644 --- a/drivers/ps3/Makefile +++ b/drivers/ps3/Makefile @@ -1 +1,2 @@ obj-y += system-bus.o +obj-$(CONFIG_PS3_VUART) += vuart.o diff --git a/drivers/ps3/vuart.c b/drivers/ps3/vuart.c new file mode 100644 index 000000000000..6974f65bcda5 --- /dev/null +++ b/drivers/ps3/vuart.c @@ -0,0 +1,965 @@ +/* + * 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 +#include +#include +#include + +#include +#include + +#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->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->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_vuart_port_device *dev, unsigned int tx, + unsigned int rx) +{ + int result; + unsigned long 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_vuart_port_device *dev, + unsigned long *bytes_waiting) +{ + int 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: %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->interrupt_mask = mask; + + result = lv1_set_virtual_uart_param(dev->port_number, + PARAM_INTERRUPT_MASK, dev->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_mask(struct ps3_vuart_port_device *dev, + unsigned long *status) +{ + int result = lv1_get_virtual_uart_param(dev->port_number, + PARAM_INTERRUPT_STATUS, status); + + if (result) + dev_dbg(&dev->core, "%s:%d: interrupt_status failed: %s\n", + __func__, __LINE__, ps3_result(result)); + + dev_dbg(&dev->core, "%s:%d: m %lxh, s %lxh, m&s %lxh\n", + __func__, __LINE__, dev->interrupt_mask, *status, + dev->interrupt_mask & *status); + + return result; +} + +int ps3_vuart_enable_interrupt_tx(struct ps3_vuart_port_device *dev) +{ + return (dev->interrupt_mask & INTERRUPT_MASK_TX) ? 0 + : ps3_vuart_set_interrupt_mask(dev, dev->interrupt_mask + | INTERRUPT_MASK_TX); +} + +int ps3_vuart_enable_interrupt_rx(struct ps3_vuart_port_device *dev) +{ + return (dev->interrupt_mask & INTERRUPT_MASK_RX) ? 0 + : ps3_vuart_set_interrupt_mask(dev, dev->interrupt_mask + | INTERRUPT_MASK_RX); +} + +int ps3_vuart_enable_interrupt_disconnect(struct ps3_vuart_port_device *dev) +{ + return (dev->interrupt_mask & INTERRUPT_MASK_DISCONNECT) ? 0 + : ps3_vuart_set_interrupt_mask(dev, dev->interrupt_mask + | INTERRUPT_MASK_DISCONNECT); +} + +int ps3_vuart_disable_interrupt_tx(struct ps3_vuart_port_device *dev) +{ + return (dev->interrupt_mask & INTERRUPT_MASK_TX) + ? ps3_vuart_set_interrupt_mask(dev, dev->interrupt_mask + & ~INTERRUPT_MASK_TX) : 0; +} + +int ps3_vuart_disable_interrupt_rx(struct ps3_vuart_port_device *dev) +{ + return (dev->interrupt_mask & INTERRUPT_MASK_RX) + ? ps3_vuart_set_interrupt_mask(dev, dev->interrupt_mask + & ~INTERRUPT_MASK_RX) : 0; +} + +int ps3_vuart_disable_interrupt_disconnect(struct ps3_vuart_port_device *dev) +{ + return (dev->interrupt_mask & INTERRUPT_MASK_DISCONNECT) + ? ps3_vuart_set_interrupt_mask(dev, dev->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; + + dev_dbg(&dev->core, "%s:%d: %xh\n", __func__, __LINE__, bytes); + + 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; + } + + dev->stats.bytes_written += *bytes_written; + + dev_dbg(&dev->core, "%s:%d: wrote %lxh/%xh=>%lxh\n", __func__, + __LINE__, *bytes_written, bytes, dev->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->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->stats.bytes_read += *bytes_read; + + dev_dbg(&dev->core, "%s:%d: read %lxh/%xh=>%lxh\n", __func__, __LINE__, + *bytes_read, bytes, dev->stats.bytes_read); + + return result; +} + +/** + * 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->tx_list.lock, flags); + + if (list_empty(&dev->tx_list.head)) { + unsigned long bytes_written; + + result = ps3_vuart_raw_write(dev, buf, bytes, &bytes_written); + + spin_unlock_irqrestore(&dev->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->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->tx_list.lock, flags); + list_add_tail(&lb->link, &dev->tx_list.head); + ps3_vuart_enable_interrupt_tx(dev); + spin_unlock_irqrestore(&dev->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->rx_list.lock, flags); + + if (dev->rx_list.bytes_held < bytes) { + spin_unlock_irqrestore(&dev->rx_list.lock, flags); + dev_dbg(&dev->core, "%s:%d: starved for %lxh bytes\n", + __func__, __LINE__, bytes - dev->rx_list.bytes_held); + return -EAGAIN; + } + + list_for_each_entry_safe(lb, n, &dev->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->rx_list.bytes_held -= bytes_read; + + if (bytes_read < lb->tail - lb->head) { + lb->head += bytes_read; + spin_unlock_irqrestore(&dev->rx_list.lock, flags); + + dev_dbg(&dev->core, + "%s:%d: dequeued buf_%lu, %lxh bytes\n", + __func__, __LINE__, lb->dbg_number, bytes_read); + return 0; + } + + dev_dbg(&dev->core, "%s:%d free buf_%lu\n", __func__, __LINE__, + lb->dbg_number); + + list_del(&lb->link); + kfree(lb); + } + spin_unlock_irqrestore(&dev->rx_list.lock, flags); + + dev_dbg(&dev->core, "%s:%d: dequeued buf_%lu, %xh bytes\n", + __func__, __LINE__, lb->dbg_number, bytes); + + return 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->tx_list.lock, flags); + + list_for_each_entry_safe(lb, n, &dev->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->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->rx_list.lock, flags); + list_add_tail(&lb->link, &dev->rx_list.head); + dev->rx_list.bytes_held += bytes; + spin_unlock_irqrestore(&dev->rx_list.lock, flags); + + dev_dbg(&dev->core, "%s:%d: queued buf_%lu, %lxh bytes\n", + __func__, __LINE__, lb->dbg_number, bytes); + + 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_mask(dev, &status); + + if (result) + return result; + + dev_dbg(&dev->core, "%s:%d: status: %lxh\n", __func__, __LINE__, + status); + + if (status & INTERRUPT_MASK_DISCONNECT) { + dev->stats.disconnect_interrupts++; + result = ps3_vuart_handle_interrupt_disconnect(dev); + if (result) + ps3_vuart_disable_interrupt_disconnect(dev); + } + + if (status & INTERRUPT_MASK_TX) { + dev->stats.tx_interrupts++; + result = ps3_vuart_handle_interrupt_tx(dev); + if (result) + ps3_vuart_disable_interrupt_tx(dev); + } + + if (status & INTERRUPT_MASK_RX) { + dev->stats.rx_interrupts++; + result = ps3_vuart_handle_interrupt_rx(dev); + if (result) + ps3_vuart_disable_interrupt_rx(dev); + } + + return 0; +} + +struct vuart_private { + unsigned int in_use; + unsigned int virq; + struct ps3_vuart_port_device *devices[PORT_COUNT]; + const struct ports_bmp bmp; +}; + +/** + * 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_private *private; + + BUG_ON(!_private); + private = (struct vuart_private *)_private; + + while (1) { + unsigned int port; + + dump_ports_bmp(&private->bmp); + + port = (BITS_PER_LONG - 1) - __ilog2(private->bmp.status); + + if (port == BITS_PER_LONG) + break; + + BUG_ON(port >= PORT_COUNT); + BUG_ON(!private->devices[port]); + + ps3_vuart_handle_port_interrupt(private->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 struct vuart_private vuart_private; + +static int ps3_vuart_probe(struct device *_dev) +{ + int result; + unsigned long tmp; + 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); + + result = ps3_vuart_match_id_to_port(dev->match_id, &dev->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_private.devices[dev->port_number]) { + dev_dbg(&dev->core, "%s:%d: port busy (%d)\n", __func__, + __LINE__, dev->port_number); + result = -EBUSY; + goto fail_match; + } + + vuart_private.devices[dev->port_number] = dev; + + INIT_LIST_HEAD(&dev->tx_list.head); + spin_lock_init(&dev->tx_list.lock); + INIT_LIST_HEAD(&dev->rx_list.head); + spin_lock_init(&dev->rx_list.lock); + + vuart_private.in_use++; + if (vuart_private.in_use == 1) { + result = ps3_alloc_vuart_irq((void*)&vuart_private.bmp.status, + &vuart_private.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_private.virq, ps3_vuart_irq_handler, + IRQF_DISABLED, "vuart", &vuart_private); + + if (result) { + dev_info(&dev->core, "%s:%d: request_irq failed (%d)\n", + __func__, __LINE__, result); + goto fail_request_irq; + } + } + + ps3_vuart_set_interrupt_mask(dev, INTERRUPT_MASK_RX); + + /* clear stale pending interrupts */ + ps3_vuart_get_interrupt_mask(dev, &tmp); + + 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; + } + + return result; + +fail_probe: +fail_request_irq: + vuart_private.in_use--; + if (!vuart_private.in_use) { + ps3_free_vuart_irq(vuart_private.virq); + vuart_private.virq = NO_IRQ; + } +fail_alloc_irq: +fail_match: + 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); + + dev_dbg(&dev->core, "%s:%d: %s\n", __func__, __LINE__, + dev->core.bus_id); + + BUG_ON(vuart_private.in_use < 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_private.in_use--; + + if (!vuart_private.in_use) { + free_irq(vuart_private.virq, &vuart_private); + ps3_free_vuart_irq(vuart_private.virq); + vuart_private.virq = NO_IRQ; + } + return 0; +} + +/** + * ps3_vuart - The vuart instance. + * + * The vuart is managed as a bus that port devices connect to. + */ + +struct bus_type ps3_vuart = { + .name = "ps3_vuart", + .match = ps3_vuart_match, + .probe = ps3_vuart_probe, + .remove = ps3_vuart_remove, +}; + +int __init ps3_vuart_init(void) +{ + int result; + + pr_debug("%s:%d:\n", __func__, __LINE__); + result = bus_register(&ps3_vuart); + BUG_ON(result); + return result; +} + +void __exit ps3_vuart_exit(void) +{ + pr_debug("%s:%d:\n", __func__, __LINE__); + bus_unregister(&ps3_vuart); +} + +core_initcall(ps3_vuart_init); +module_exit(ps3_vuart_exit); + +/** + * ps3_vuart_port_release_device - Remove a vuart port device. + */ + +static void ps3_vuart_port_release_device(struct device *_dev) +{ + struct ps3_vuart_port_device *dev = to_ps3_vuart_port_device(_dev); +#if defined(DEBUG) + memset(dev, 0xad, sizeof(struct ps3_vuart_port_device)); +#endif + kfree(dev); +} + +/** + * ps3_vuart_port_device_register - Add a vuart port device. + */ + +int ps3_vuart_port_device_register(struct ps3_vuart_port_device *dev) +{ + int result; + static unsigned int dev_count = 1; + + dev->core.parent = NULL; + dev->core.bus = &ps3_vuart; + 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__); + + result = device_register(&dev->core); + + return result; +} + +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; + 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) +{ + driver_unregister(&drv->core); +} + +EXPORT_SYMBOL_GPL(ps3_vuart_port_driver_unregister); diff --git a/drivers/ps3/vuart.h b/drivers/ps3/vuart.h new file mode 100644 index 000000000000..28fd89f0c8aa --- /dev/null +++ b/drivers/ps3/vuart.h @@ -0,0 +1,94 @@ +/* + * PS3 virtual uart + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if !defined(_PS3_VUART_H) +#define _PS3_VUART_H + +struct ps3_vuart_stats { + unsigned long bytes_written; + unsigned long bytes_read; + unsigned long tx_interrupts; + unsigned long rx_interrupts; + unsigned long disconnect_interrupts; +}; + +/** + * struct ps3_vuart_port_device - a device on a vuart port + */ + +struct ps3_vuart_port_device { + enum ps3_match_id match_id; + struct device core; + + /* private driver variables */ + unsigned int port_number; + unsigned long 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_port_driver - a driver for a device on a vuart port + */ + +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 *); + 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 *); */ +}; + +int ps3_vuart_port_device_register(struct ps3_vuart_port_device *dev); +int ps3_vuart_port_driver_register(struct ps3_vuart_port_driver *drv); +void ps3_vuart_port_driver_unregister(struct ps3_vuart_port_driver *drv); +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, + unsigned int bytes); +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) +{ + return container_of(_dev, struct ps3_vuart_port_device, core); +} + +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, + unsigned int bytes); + +#endif -- cgit v1.2.3