From 50c935058e2e190b0ff67f86d2dbed9fb9f18fd5 Mon Sep 17 00:00:00 2001 From: Xavier BRASSOUD Date: Thu, 9 Jan 2025 21:52:30 +0100 Subject: [PATCH] feat(ps2_uart): Enable EE UART Support UART serial communication over Emotional Engine (EE) using "ps2_uart" module Only polling mode is currently supported Signed-off-by: Xavier Brassoud --- arch/mips/boot/compressed/dbg.c | 11 + arch/mips/configs/ps2_defconfig | 5 +- arch/mips/include/asm/mach-ps2/ee-registers.h | 52 +++ arch/mips/ps2/prom.c | 11 + drivers/tty/serial/Kconfig | 20 + drivers/tty/serial/Makefile | 1 + drivers/tty/serial/ps2_uart.c | 392 ++++++++++++++++++ include/uapi/linux/serial_core.h | 3 + 8 files changed, 493 insertions(+), 2 deletions(-) create mode 100644 arch/mips/include/asm/mach-ps2/ee-registers.h create mode 100644 drivers/tty/serial/ps2_uart.c diff --git a/arch/mips/boot/compressed/dbg.c b/arch/mips/boot/compressed/dbg.c index 62e6d6ebc3f823..28025a15b99eff 100644 --- a/arch/mips/boot/compressed/dbg.c +++ b/arch/mips/boot/compressed/dbg.c @@ -17,6 +17,7 @@ #include #include +#include #include #define D2_CHCR 0x1000a000 /* Channel 2 control */ @@ -295,6 +296,12 @@ void wrlX(u32 value, unsigned long addr) wmb(); } +static inline void wrb(u8 value, unsigned long addr) +{ + __raw_writeb(value, (void __iomem *)KSEG1ADDR(addr)); + wmb(); +} + static inline void wrl(u32 value, unsigned long addr) { __raw_writel(value, (void __iomem *)KSEG1ADDR(addr)); @@ -693,6 +700,10 @@ void putc(char c) if (!IS_ENABLED(CONFIG_EARLY_PRINTK)) return; + // Enable EE UART + if (IS_ENABLED(CONFIG_SERIAL_PS2_UART_CONSOLE)) + wrb(c, SIO_TXFIFO); + if (!initialized) { set_res(); set_env(); diff --git a/arch/mips/configs/ps2_defconfig b/arch/mips/configs/ps2_defconfig index 075bb6b79920c6..4d2ab26ec7c47b 100644 --- a/arch/mips/configs/ps2_defconfig +++ b/arch/mips/configs/ps2_defconfig @@ -1192,7 +1192,8 @@ CONFIG_DEVKMEM=y # Serial drivers # # CONFIG_SERIAL_8250 is not set - +CONFIG_SERIAL_PS2_UART=y +CONFIG_SERIAL_PS2_UART_CONSOLE=y # # Non-8250 serial port support # @@ -2696,7 +2697,7 @@ CONFIG_UBSAN_ALIGNMENT=y CONFIG_TRACE_IRQFLAGS_SUPPORT=y CONFIG_EARLY_PRINTK=y CONFIG_CMDLINE_BOOL=y -CONFIG_CMDLINE="video=AV-MULTI-OUT:1920x1080@50" +CONFIG_CMDLINE="video=AV-MULTI-OUT:1920x1080@50 console=tty0 console=tty1" # CONFIG_CMDLINE_OVERRIDE is not set CONFIG_DEBUG_ZBOOT=y # CONFIG_SPINLOCK_TEST is not set diff --git a/arch/mips/include/asm/mach-ps2/ee-registers.h b/arch/mips/include/asm/mach-ps2/ee-registers.h new file mode 100644 index 00000000000000..111687b1233a87 --- /dev/null +++ b/arch/mips/include/asm/mach-ps2/ee-registers.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PlayStation 2 Emotion Engine (EE) registers + * + */ + + +#ifndef __ASM_MACH_PS2_EE_REGISTERS_H +#define __ASM_MACH_PS2_EE_REGISTERS_H + +/* SIO Registers. */ +/* Most of these are based off of Toshiba documentation for the TX49 and the + TX79 companion chip. However, looking at the kernel SIOP (Debug) exception + handler, it looks like some registers are borrowed from the TX7901 UART + (0x1000f110 or LSR, in particular). I'm still trying to find the correct + register names and values. */ +#define SIO_LCR 0x1000f100 /* Line Control Register. */ +#define SIO_LCR_UMODE_8BIT 0x00 /* UART Mode. */ +#define SIO_LCR_UMODE_7BIT 0x01 +#define SIO_LCR_USBL_1BIT 0x00 /* UART Stop Bit Length. */ +#define SIO_LCR_USBL_2BITS 0x01 +#define SIO_LCR_UPEN_OFF 0x00 /* UART Parity Enable. */ +#define SIO_LCR_UPEN_ON 0x01 +#define SIO_LCR_UEPS_ODD 0x00 /* UART Even Parity Select. */ +#define SIO_LCR_UEPS_EVEN 0x01 + +#define SIO_LSR 0x1000f110 /* Line Status Register. */ +#define SIO_LSR_DR 0x01 /* Data Ready. (Not tested) */ +#define SIO_LSR_OE 0x02 /* Overrun Error. */ +#define SIO_LSR_PE 0x04 /* Parity Error. */ +#define SIO_LSR_FE 0x08 /* Framing Error. */ + +#define SIO_IER 0x1000f120 /* Interrupt Enable Register. */ +#define SIO_IER_ERDAI 0x01 /* Enable Received Data Available Interrupt */ +#define SIO_IER_ELSI 0x04 /* Enable Line Status Interrupt. */ + +#define SIO_ISR 0x1000f130 /* Interrupt Status Register (?). */ +#define SIO_ISR_RX_DATA 0x01 +#define SIO_ISR_TX_EMPTY 0x02 +#define SIO_ISR_RX_ERROR 0x04 + +#define SIO_FCR 0x1000f140 /* FIFO Control Register. */ +#define SIO_FCR_FRSTE 0x01 /* FIFO Reset Enable. */ +#define SIO_FCR_RFRST 0x02 /* RX FIFO Reset. */ +#define SIO_FCR_TFRST 0x04 /* TX FIFO Reset. */ + +#define SIO_BGR 0x1000f150 /* Baud Rate Control Register. */ + +#define SIO_TXFIFO 0x1000f180 /* Transmit FIFO. */ +#define SIO_RXFIFO 0x1000f1c0 /* Receive FIFO. */ + +#endif /* __ASM_MACH_PS2_EE_REGISTERS_H */ diff --git a/arch/mips/ps2/prom.c b/arch/mips/ps2/prom.c index 441a3e31e68168..cf3cd6a0792c77 100644 --- a/arch/mips/ps2/prom.c +++ b/arch/mips/ps2/prom.c @@ -17,6 +17,7 @@ #include #include +#include #include @@ -301,6 +302,12 @@ static inline u32 rdl(unsigned long addr) return __raw_readl((void __iomem *)KSEG1ADDR(addr)); } +static inline void wrb(u8 value, unsigned long addr) +{ + __raw_writeb(value, (void __iomem *)KSEG1ADDR(addr)); + wmb(); +} + static inline void wrl(u32 value, unsigned long addr) { __raw_writel(value, (void __iomem *)KSEG1ADDR(addr)); @@ -429,6 +436,10 @@ void prom_putchar(char c) if (!IS_ENABLED(CONFIG_EARLY_PRINTK)) return; + // Enable EE UART + if (IS_ENABLED(CONFIG_SERIAL_PS2_UART_CONSOLE)) + wrb(c, SIO_TXFIFO); + if (c == '\r') { col = 0; return; diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index a9751a83d5dbb2..1590f25ecb7a48 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1573,6 +1573,26 @@ config SERIAL_MILBEAUT_USIO_CONSOLE receives all kernel messages and warnings and which allows logins in single user mode). +config SERIAL_PS2_UART + tristate "PS2 serial port" + depends on SONY_PS2 + select SERIAL_CORE + default n + help + This driver is for the Playstation 2 UART/SIO port. You can see + the output with a serial cable connected to the internal serial + pins of the PS2. + To use this as system console, you need to specify the kernel + parameter: console=ttyS0 + +config SERIAL_PS2_UART_CONSOLE + bool "PS2 serial port console" + depends on SERIAL_PS2_UART=y + select SERIAL_CORE_CONSOLE + select SERIAL_EARLYCON + help + Enable the PS2 UART port to be the system console. + endmenu config SERIAL_MCTRL_GPIO diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 863f47056539cb..7daac5dab16585 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -89,6 +89,7 @@ obj-$(CONFIG_SERIAL_OWL) += owl-uart.o obj-$(CONFIG_SERIAL_RDA) += rda-uart.o obj-$(CONFIG_SERIAL_MILBEAUT_USIO) += milbeaut_usio.o obj-$(CONFIG_SERIAL_SIFIVE) += sifive.o +obj-$(CONFIG_SERIAL_PS2_UART) += ps2_uart.o # GPIOLIB helpers for modem control lines obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o diff --git a/drivers/tty/serial/ps2_uart.c b/drivers/tty/serial/ps2_uart.c new file mode 100644 index 00000000000000..ba316aa28acd81 --- /dev/null +++ b/drivers/tty/serial/ps2_uart.c @@ -0,0 +1,392 @@ +/* + * ps2_uart.c - PS2 Emotional Engine (EE) UART driver + * + * Copyright (C) 2010 Mega Man + * - PS2 SBIOS serial driver (ps2sbioscon.c) + * + * Copyright (C) 2015 Rick Gaiser + * - PS2 Emotional Engine (EE) UART driver (ps2_uart.c) + * + * Copyright (C) 2025 Xavier Brassoud + * - ported to kernel v5 + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define PS2_UART_DRIVER_NAME "ps2_uart" +#define PS2_UART_DEVICE_NAME "ttyS" + +/* 20 ms */ +#define DELAY_TIME_MS 20 + +struct ps2_uart { + struct uart_port port; + struct timer_list timer; +}; +static struct ps2_uart *ps2_uart_dev; + +static void ps2_uart_putchar_block(char c) +{ + while ((inw(SIO_ISR) & 0xf000) == 0x8000) + ; + outb(c, SIO_TXFIFO); +} + +static unsigned int ps2_uart_tx_empty(struct uart_port *port) +{ + return 0; +} + +static unsigned int ps2_uart_get_mctrl(struct uart_port *port) +{ + return 0; +} + +static void ps2_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ +} + +static void ps2_uart_start_tx(struct uart_port *port) +{ +} + +static void ps2_uart_stop_tx(struct uart_port *port) +{ +} + +static void ps2_uart_stop_rx(struct uart_port *port) +{ +} + +static void ps2_uart_break_ctl(struct uart_port *port, int break_state) +{ +} + +static void ps2_uart_enable_ms(struct uart_port *port) +{ +} + +static void ps2_uart_set_termios(struct uart_port *port, + struct ktermios *termios, struct ktermios *old) +{ +} + +static int ps2_uart_rx_chars(struct uart_port *port) +{ + unsigned char ch, flag; + unsigned short status; + int rv = 0; + + if (!(inw(SIO_ISR) & 0x0f00)) + return rv; + + while ((status = inw(SIO_ISR)) & 0x0f00) { + ch = inb(SIO_RXFIFO); + flag = TTY_NORMAL; + port->icount.rx++; + rv++; + + outw(7, SIO_ISR); + + if (uart_handle_sysrq_char(port, ch)) + continue; + uart_insert_char(port, status, 0, ch, flag); + } + + tty_flip_buffer_push(&port->state->port); + + return rv; +} + +static void ps2_uart_tx_chars(struct uart_port *port) +{ + struct circ_buf *xmit = &port->state->xmit; + + if (port->x_char) { + ps2_uart_putchar_block(port->x_char); + port->icount.tx++; + port->x_char = 0; + return; + } + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { + ps2_uart_stop_tx(port); + return; + } + + while ((inw(SIO_ISR) & 0xf000) != 0x8000) { + if (uart_circ_empty(xmit)) + break; + outb(xmit->buf[xmit->tail], SIO_TXFIFO); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + } + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); + + if (uart_circ_empty(xmit)) + ps2_uart_stop_tx(port); +} + +static void ps2_uart_timer(struct timer_list *t) +{ + struct uart_port *port; + struct tty_struct *tty; + struct circ_buf *xmit; + int irx; + + struct ps2_uart *pp = from_timer(pp, t, timer); + port = &pp->port; + if (!port) + return; + if (!port->state) + return; + tty = port->state->port.tty; + if (!tty) + return; + + /* Receive data */ + irx = ps2_uart_rx_chars(port); + + /* Transmit data */ + ps2_uart_tx_chars(port); + + /* Restart the timer */ + xmit = &port->state->xmit; + if ((uart_circ_chars_pending(xmit) > 0) || (irx > 0)) + /* Transmit/Receive ASAP */ + mod_timer(&pp->timer, jiffies + 1); + else + /* Normal polling */ + mod_timer(&pp->timer, jiffies + DELAY_TIME_MS * HZ / 1000); +} + +static void ps2_uart_config_port(struct uart_port *port, int flags) +{ +} + +static int ps2_uart_startup(struct uart_port *port) +{ + /* Create timer for transmit */ + timer_setup(&ps2_uart_dev->timer, ps2_uart_timer, 0); + mod_timer(&ps2_uart_dev->timer, jiffies + DELAY_TIME_MS * HZ / 1000); + + return 0; +} + +static void ps2_uart_shutdown(struct uart_port *port) +{ + /* Stop timer */ + del_timer(&ps2_uart_dev->timer); +} + +static const char *ps2_uart_type(struct uart_port *port) +{ + return (port->type == PORT_PS2_UART) ? PS2_UART_DRIVER_NAME : NULL; +} + +static int ps2_uart_request_port(struct uart_port *port) +{ + return 0; +} + +static void ps2_uart_release_port(struct uart_port *port) +{ +} + +static int ps2_uart_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + return 0; +} + +static struct uart_ops ps2_uart_ops = { + .tx_empty = ps2_uart_tx_empty, + .get_mctrl = ps2_uart_get_mctrl, + .set_mctrl = ps2_uart_set_mctrl, + .start_tx = ps2_uart_start_tx, + .stop_tx = ps2_uart_stop_tx, + .stop_rx = ps2_uart_stop_rx, + .enable_ms = ps2_uart_enable_ms, + .break_ctl = ps2_uart_break_ctl, + .startup = ps2_uart_startup, + .shutdown = ps2_uart_shutdown, + .set_termios = ps2_uart_set_termios, + .type = ps2_uart_type, + .request_port = ps2_uart_request_port, + .release_port = ps2_uart_release_port, + .config_port = ps2_uart_config_port, + .verify_port = ps2_uart_verify_port, +}; + +#if defined(CONFIG_SERIAL_PS2_UART_CONSOLE) + +static void ps2_uart_console_write(struct console *con, const char *s, + unsigned n) +{ + while (n-- && *s) { + if (*s == '\n') + ps2_uart_putchar_block('\r'); + ps2_uart_putchar_block(*s); + s++; + } +} + +static int __init ps2_uart_console_setup(struct console *con, char *options) +{ + pr_info(PS2_UART_DRIVER_NAME ": UART console registered as port %s%d\n", + con->name, con->index); + return 0; +} + +static struct console ps2_uart_console; + +static struct uart_driver ps2_uart_driver; + +static struct console ps2_uart_console = { + .name = PS2_UART_DEVICE_NAME, + .write = ps2_uart_console_write, + .device = uart_console_device, + .setup = ps2_uart_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &ps2_uart_driver, +}; + +static int __init ps2_uart_console_init(void) +{ + register_console(&ps2_uart_console); + return 0; +} + +console_initcall(ps2_uart_console_init); + +#define PS2_UART_CONSOLE (&ps2_uart_console) + +#else + +#define PS2_UART_CONSOLE NULL + +#endif /* CONFIG_SERIAL_PS2_UART_CONSOLE */ + +static struct uart_driver ps2_uart_driver = { + .owner = THIS_MODULE, + .driver_name = PS2_UART_DRIVER_NAME, + .dev_name = PS2_UART_DEVICE_NAME, + .major = TTY_MAJOR, + .minor = 64, + .nr = 1, + .cons = PS2_UART_CONSOLE, +}; + +static int ps2_uart_probe(struct platform_device *dev) +{ + int result; + result = uart_add_one_port(&ps2_uart_driver, &ps2_uart_dev->port); + if (result != 0) { + pr_err(PS2_UART_DRIVER_NAME ": Failed to register UART port\n"); + } + return result; +} + +static int ps2_uart_remove(struct platform_device *dev) +{ + struct uart_port *port = platform_get_drvdata(dev); + + uart_remove_one_port(&ps2_uart_driver, port); + kfree(port); + + return 0; +} + +static struct platform_driver ps2_uart_platform_driver = { + .probe = ps2_uart_probe, + .remove = ps2_uart_remove, + .driver = { + .name = PS2_UART_DRIVER_NAME, + .owner = THIS_MODULE, + .bus = &platform_bus_type, + }, +}; + +static struct platform_device *ps2_uart_plat_devs; + +static int __init ps2_uart_init(void) +{ + int result; + + ps2_uart_dev = kzalloc(sizeof(*ps2_uart_dev), GFP_KERNEL); + if (!ps2_uart_dev) { + return -ENOMEM; + } + + result = uart_register_driver(&ps2_uart_driver); + if (result != 0) { + pr_err(PS2_UART_DRIVER_NAME + ": Failed to register uart driver\n"); + kfree(ps2_uart_dev); + return result; + } + + ps2_uart_plat_devs = platform_device_alloc(PS2_UART_DRIVER_NAME, -1); + if (!ps2_uart_plat_devs) { + pr_err(PS2_UART_DRIVER_NAME + ": Failed to alloc platform device\n"); + uart_unregister_driver(&ps2_uart_driver); + return -ENOMEM; + } + + result = platform_device_add(ps2_uart_plat_devs); + if (result) + platform_device_put(ps2_uart_plat_devs); + + ps2_uart_dev->port.line = 0; + ps2_uart_dev->port.ops = &ps2_uart_ops; + ps2_uart_dev->port.type = PORT_PS2_UART; + ps2_uart_dev->port.flags = UPF_BOOT_AUTOCONF; + + result = platform_driver_register(&ps2_uart_platform_driver); + if (result != 0) { + pr_err(PS2_UART_DRIVER_NAME + ": Failed to register platform driver\n"); + uart_unregister_driver(&ps2_uart_driver); + } + + pr_info(PS2_UART_DRIVER_NAME ": module loaded\n"); + + return result; +}; + +void __exit ps2_uart_exit(void) +{ + platform_driver_unregister(&ps2_uart_platform_driver); + uart_unregister_driver(&ps2_uart_driver); + del_timer_sync(&ps2_uart_dev->timer); + kfree(&ps2_uart_dev); + + pr_info(PS2_UART_DRIVER_NAME ": module unloaded\n"); +}; + +module_init(ps2_uart_init); +module_exit(ps2_uart_exit); + +MODULE_DESCRIPTION("PS2 UART driver"); +MODULE_AUTHOR("Mega Man, Rick Gaiser, Xavier Brassoud"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" PS2_UART_DRIVER_NAME); diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index e7fe550b6038af..b7243ba87686a9 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -293,4 +293,7 @@ /* Freescale Linflex UART */ #define PORT_LINFLEXUART 122 +/* PS2 EE UART */ +#define PORT_PS2_UART 123 + #endif /* _UAPILINUX_SERIAL_CORE_H */