Files
linux-nv-oot/drivers/tty/serial/wch_35x/wch_serial.c
Jon Hunter 42b3ff7b23 tty: wch: Disable informational prints
When loading the WCH driver on IGX platforms the various information is
printed to the kernel log using printk. However, because no level is
specified in printk statements, the prints appear as warnings even
though they are informational. The WCH driver implements some
definitions to enable/disable prints and so wrap these informational
prints with WCH_DBG so that they are disabled by default.

Bug 5699474

Change-Id: Ic1426ceab94cd457a1ebdfb57a55ff305ad58ff9
Signed-off-by: Jon Hunter <jonathanh@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3500555
(cherry picked from commit c3a03860ada48533d2f81bf33ada02c44d079d9f)
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3502173
Reviewed-by: svcacv <svcacv@nvidia.com>
GVS: buildbot_gerritrpt <buildbot_gerritrpt@nvidia.com>
Reviewed-by: Brad Griffis <bgriffis@nvidia.com>
2025-12-01 08:42:27 -08:00

3004 lines
80 KiB
C

/*
* SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: GPL-2.0-only
*/
#include <nvidia/conftest.h>
#include "wch_common.h"
#if defined(NV_DEFINE_SEMAPHORE_HAS_NUMBER_ARG)
static DEFINE_SEMAPHORE(ser_port_sem, 1);
#else
static DEFINE_SEMAPHORE(ser_port_sem);
#endif
#define WCH_HIGH_BITS_OFFSET ((sizeof(long) - sizeof(int)) * 8)
#define wch_ser_users(state) ((state)->count + ((state)->info ? (state)->info->blocked_open : 0))
struct serial_uart_config {
char *name;
int dfl_xmit_fifo_size;
int flags;
};
static const struct serial_uart_config wch_uart_config[PORT_SER_MAX_UART + 1] = {
{ "unknown", 1, 0},
{ "8250", 1, 0},
{ "16450", 1, 0},
{ "16550", 1, 0},
{ "16550A", 16, UART_CLEAR_FIFO | UART_USE_FIFO},
{ "Cirrus", 1, 0},
{ "ST16650", 1, 0},
{"ST16650V2", 32, UART_CLEAR_FIFO | UART_USE_FIFO},
{ "TI16750", 64, UART_CLEAR_FIFO | UART_USE_FIFO},
};
static _INLINE_ void ser_handle_cts_change(struct ser_port *, unsigned int);
static _INLINE_ void ser_update_mctrl(struct ser_port *, unsigned int, unsigned int);
static void ser_write_wakeup(struct ser_port *);
static void ser_stop(struct tty_struct *);
static void _ser_start(struct tty_struct *);
static void ser_start(struct tty_struct *);
static void ser_tasklet_action(unsigned long);
static int ser_startup(struct ser_state *, int);
static void ser_shutdown(struct ser_state *);
static _INLINE_ void _ser_put_char(struct ser_port *, struct circ_buf *, unsigned char);
#if defined(NV_TTY_OPERATIONS_STRUCT_WRITE_HAS_U8_PTR_ARG) /* Linux 6.5 */
static int ser_put_char(struct tty_struct *, u8);
#else
static int ser_put_char(struct tty_struct *, unsigned char);
#endif
static void ser_flush_chars(struct tty_struct *);
static unsigned int ser_chars_in_buffer(struct tty_struct *tty);
static void ser_flush_buffer(struct tty_struct *);
#if defined(NV_TTY_OPERATIONS_STRUCT_SEND_XCHAR_HAS_U8_ARG) /* Linux 6.8 */
static void ser_send_xchar(struct tty_struct *, u8);
#else
static void ser_send_xchar(struct tty_struct *, char);
#endif
static void ser_throttle(struct tty_struct *);
static void ser_unthrottle(struct tty_struct *);
static int ser_get_info(struct ser_state *, struct serial_struct *);
static int ser_set_info(struct ser_state *, struct serial_struct *);
static unsigned int ser_write_room(struct tty_struct *tty);
#if defined(NV_TTY_OPERATIONS_STRUCT_WRITE_HAS_U8_PTR_ARG) /* Linux 6.5 */
static ssize_t ser_write(struct tty_struct *, const u8 *, size_t);
#else
static int ser_write(struct tty_struct *, const unsigned char *, int);
#endif
// static int ser_get_lsr_info(struct ser_state *, unsigned int *);
static int ser_tiocmget(struct tty_struct *);
static int ser_tiocmset(struct tty_struct *, unsigned int, unsigned int);
static int ser_break_ctl(struct tty_struct *, int);
static int ser_wait_modem_status(struct ser_state *, unsigned long);
static int ser_get_count(struct ser_state *, struct serial_icounter_struct *);
static int ser_ioctl(struct tty_struct *, unsigned int, unsigned long);
static void ser_hangup(struct tty_struct *);
unsigned int ser_get_divisor(struct ser_port *, unsigned int, bool);
#if defined(NV_TTY_OPERATIONS_STRUCT_SET_TERMIOS_HAS_CONST_KTERMIOS_ARG) /* Linux 6.1 */
unsigned int ser_get_baud_rate(struct ser_port *, struct WCHTERMIOS *, const struct WCHTERMIOS *, unsigned int, unsigned int);
static void ser_change_speed(struct ser_state *state, const struct WCHTERMIOS *old_termios);
#else
unsigned int ser_get_baud_rate(struct ser_port *, struct WCHTERMIOS *, struct WCHTERMIOS *, unsigned int, unsigned int);
static void ser_change_speed(struct ser_state *state, struct WCHTERMIOS *old_termios);
#endif
static void ser_update_termios(struct ser_state *);
static void ser_update_timeout(struct ser_port *, unsigned int, unsigned int);
static struct ser_state *ser_get(struct ser_driver *, int);
static int ser_block_til_ready(struct file *, struct ser_state *);
static void ser_wait_until_sent(struct tty_struct *, int);
static int ser_open(struct tty_struct *, struct file *);
static void ser_close(struct tty_struct *, struct file *);
static void wch_ser_set_mctrl(struct ser_port *, unsigned int);
static unsigned int wch_ser_tx_empty(struct ser_port *);
static unsigned int wch_ser_get_mctrl(struct ser_port *);
static void wch_ser_stop_tx(struct ser_port *, unsigned int);
static void wch_ser_start_tx(struct ser_port *, unsigned int);
static void wch_ser_stop_rx(struct ser_port *);
static void wch_ser_enable_ms(struct ser_port *);
static void wch_ser_break_ctl(struct ser_port *, int);
static int wch_ser_startup(struct ser_port *);
static void wch_ser_shutdown(struct ser_port *);
static unsigned int wch_ser_get_divisor(struct ser_port *, unsigned int, bool);
#if defined(NV_TTY_OPERATIONS_STRUCT_SET_TERMIOS_HAS_CONST_KTERMIOS_ARG) /* Linux 6.1 */
static void wch_ser_set_termios(struct ser_port *port, struct WCHTERMIOS *termios, const struct WCHTERMIOS *old);
#else
static void wch_ser_set_termios(struct ser_port *port, struct WCHTERMIOS *termios, struct WCHTERMIOS *old);
#endif
static void wch_ser_timeout(struct timer_list *);
static _INLINE_ void ser_receive_chars(struct wch_ser_port *, unsigned char *, unsigned char);
static _INLINE_ void ser_transmit_chars(struct wch_ser_port *);
static _INLINE_ void ser_check_modem_status(struct wch_ser_port *, unsigned char);
static _INLINE_ void ser_handle_port(struct wch_ser_port *, unsigned char);
extern int wch_ser_interrupt(struct wch_board *, struct wch_ser_port *);
static void wch_ser_release_io(struct ser_port *);
static void wch_ser_request_io(struct ser_port *);
static void wch_ser_configure_port(struct ser_driver *, struct ser_state *, struct ser_port *);
static void wch_ser_unconfigure_port(struct ser_driver *, struct ser_state *);
static int wch_ser_add_one_port(struct ser_driver *, struct ser_port *);
static int wch_ser_remove_one_port(struct ser_driver *, struct ser_port *);
extern int wch_ser_register_ports(struct ser_driver *);
extern void wch_ser_unregister_ports(struct ser_driver *);
extern int wch_ser_register_driver(struct ser_driver *);
extern void wch_ser_unregister_driver(struct ser_driver *);
static unsigned char READ_INTERRUPT_VECTOR_BYTE(struct wch_ser_port *);
static unsigned int READ_INTERRUPT_VECTOR_WORD(struct wch_ser_port *sp);
static unsigned char READ_UART_RX(struct wch_ser_port *);
static unsigned char READ_UART_IER(struct wch_ser_port *);
static unsigned char READ_UART_IIR(struct wch_ser_port *);
static unsigned char READ_UART_LCR(struct wch_ser_port *);
static unsigned char READ_UART_MCR(struct wch_ser_port *);
static unsigned char READ_UART_LSR(struct wch_ser_port *);
static unsigned char READ_UART_MSR(struct wch_ser_port *);
static void WRITE_UART_TX(struct wch_ser_port *, unsigned char);
static void WRITE_UART_IER(struct wch_ser_port *, unsigned char);
static void WRITE_UART_FCR(struct wch_ser_port *, unsigned char);
static void WRITE_UART_LCR(struct wch_ser_port *, unsigned char);
static void WRITE_UART_MCR(struct wch_ser_port *, unsigned char);
static void WRITE_UART_DLL(struct wch_ser_port *, int);
static void WRITE_UART_DLM(struct wch_ser_port *, int);
static unsigned char READ_INTERRUPT_VECTOR_BYTE(struct wch_ser_port *sp)
{
unsigned char data = 0;
if (sp->port.vector) {
data = inb(sp->port.vector);
return data;
}
return 0;
}
static unsigned int READ_INTERRUPT_VECTOR_WORD(struct wch_ser_port *sp)
{
unsigned int data = 0;
unsigned int vet1 = 0;
unsigned int vet2 = 0;
if (sp->port.vector) {
vet1 = inb(sp->port.vector);
vet2 = inb(sp->port.vector - 0x10);
vet2 <<= 8;
data = (vet1 | vet2);
return data;
}
return 0;
}
static unsigned long READ_INTERRUPT_VECTOR_DWORD(struct wch_ser_port *sp)
{
unsigned long data = 0;
if (sp->port.iobase) {
data = inl(sp->port.chip_iobase + 0xE8);
}
return data;
}
static unsigned char READ_UART_RX(struct wch_ser_port *sp)
{
unsigned char data = 0;
if (sp->port.iobase) {
if (ch365_32s) {
data = readb(sp->port.port_membase + UART_RX);
} else {
data = inb(sp->port.iobase + UART_RX);
}
return data;
}
return 0;
}
static unsigned char READ_UART_RX_BUFFER(struct wch_ser_port *sp, unsigned char *buf, int count)
{
if (sp->port.iobase) {
{
insb(sp->port.iobase + UART_RX, buf, count);
}
}
return 0;
}
static void WRITE_UART_TX(struct wch_ser_port *sp, unsigned char data)
{
if (sp->port.iobase) {
if (ch365_32s) {
writeb(data, sp->port.port_membase + UART_TX);
} else {
outb(data, sp->port.iobase + UART_TX);
}
}
}
static void WRITE_UART_IER(struct wch_ser_port *sp, unsigned char data)
{
if (sp->port.iobase) {
if (ch365_32s) {
writeb(data, sp->port.port_membase + UART_IER);
} else {
outb(data, sp->port.iobase + UART_IER);
}
}
}
static unsigned char READ_UART_IER(struct wch_ser_port *sp)
{
unsigned char data = 0;
if (sp->port.iobase) {
if (ch365_32s) {
data = readb(sp->port.port_membase + UART_IER);
} else {
data = inb(sp->port.iobase + UART_IER);
}
return data;
}
return 0;
}
static unsigned char READ_UART_IIR(struct wch_ser_port *sp)
{
unsigned char data = 0;
if (sp->port.iobase) {
if (ch365_32s) {
data = readb(sp->port.port_membase + UART_IIR);
} else {
data = inb(sp->port.iobase + UART_IIR);
}
return data;
}
return 0;
}
static void WRITE_UART_FCR(struct wch_ser_port *sp, unsigned char data)
{
if (sp->port.iobase) {
if (ch365_32s) {
writeb(data, sp->port.port_membase + UART_FCR);
} else {
outb(data, sp->port.iobase + UART_FCR);
}
}
}
static unsigned char READ_UART_LCR(struct wch_ser_port *sp)
{
unsigned char data = 0;
if (sp->port.iobase) {
if (ch365_32s) {
data = readb(sp->port.port_membase + UART_LCR);
} else {
data = inb(sp->port.iobase + UART_LCR);
}
return data;
}
return 0;
}
static unsigned char READ_UART_MCR(struct wch_ser_port *sp)
{
unsigned char data = 0;
if (sp->port.iobase) {
if (ch365_32s) {
data = readb(sp->port.port_membase + UART_MCR);
} else {
data = inb(sp->port.iobase + UART_MCR);
}
return data;
}
return 0;
}
static void WRITE_UART_LCR(struct wch_ser_port *sp, unsigned char data)
{
if (sp->port.iobase) {
if (ch365_32s) {
writeb(data, sp->port.port_membase + UART_LCR);
} else {
outb(data, sp->port.iobase + UART_LCR);
}
}
}
static void WRITE_UART_MCR(struct wch_ser_port *sp, unsigned char data)
{
if (sp->port.iobase) {
if (ch365_32s) {
writeb(data, sp->port.port_membase + UART_MCR);
} else {
outb(data, sp->port.iobase + UART_MCR);
}
}
}
static unsigned char READ_UART_LSR(struct wch_ser_port *sp)
{
unsigned char data = 0;
if (sp->port.iobase) {
if (ch365_32s) {
data = readb(sp->port.port_membase + UART_LSR);
} else {
data = inb(sp->port.iobase + UART_LSR);
}
return data;
}
return 0;
}
static unsigned char READ_UART_MSR(struct wch_ser_port *sp)
{
unsigned char data = 0;
if (sp->port.iobase) {
if (ch365_32s) {
data = readb(sp->port.port_membase + UART_MSR);
} else {
data = inb(sp->port.iobase + UART_MSR);
}
return data;
}
return 0;
}
static void WRITE_UART_DLL(struct wch_ser_port *sp, int data)
{
if (sp->port.iobase) {
if (ch365_32s) {
writeb(data, sp->port.port_membase + UART_DLL);
} else {
outb(data, sp->port.iobase + UART_DLL);
}
}
}
static void WRITE_UART_DLM(struct wch_ser_port *sp, int data)
{
if (sp->port.iobase) {
if (ch365_32s) {
writeb(data, sp->port.port_membase + UART_DLM);
} else {
outb(data, sp->port.iobase + UART_DLM);
}
}
}
static _INLINE_ void ser_handle_cts_change(struct ser_port *port, unsigned int status)
{
struct ser_info *info = port->info;
struct tty_struct *tty = info->tty;
port->icount.cts++;
if (info->flags & WCH_UIF_CTS_FLOW) {
if (tty->hw_stopped) {
if (status) {
tty->hw_stopped = 0;
wch_ser_start_tx(port, 0);
ser_write_wakeup(port);
}
} else {
if (!status) {
tty->hw_stopped = 1;
wch_ser_stop_tx(port, 0);
}
}
}
}
static _INLINE_ void ser_update_mctrl(struct ser_port *port, unsigned int set, unsigned int clear)
{
unsigned long flags;
unsigned int old;
spin_lock_irqsave(&port->lock, flags);
old = port->mctrl;
port->mctrl = (old & ~clear) | set;
if (port->hardflow)
port->mctrl |= UART_MCR_RTS;
if (old != port->mctrl) {
wch_ser_set_mctrl(port, port->mctrl);
}
spin_unlock_irqrestore(&port->lock, flags);
}
#define set_mctrl(port, set) ser_update_mctrl(port, set, 0)
#define clear_mctrl(port, clear) ser_update_mctrl(port, 0, clear)
static void ser_write_wakeup(struct ser_port *port)
{
struct ser_info *info = port->info;
tasklet_schedule(&info->tlet);
}
static void ser_stop(struct tty_struct *tty)
{
struct ser_state *state = NULL;
struct ser_port *port = NULL;
unsigned long flags;
int line = WCH_SER_DEVNUM(tty);
if (line >= wch_ser_port_total_cnt) {
return;
}
state = tty->driver_data;
port = state->port;
spin_lock_irqsave(&port->lock, flags);
wch_ser_stop_tx(port, 1);
spin_unlock_irqrestore(&port->lock, flags);
}
static void _ser_start(struct tty_struct *tty)
{
struct ser_state *state = tty->driver_data;
struct ser_port *port = state->port;
if (!ser_circ_empty(&state->info->xmit) && state->info->xmit.buf && !tty->flow.stopped && !tty->hw_stopped)
wch_ser_start_tx(port, 1);
}
static void ser_start(struct tty_struct *tty)
{
struct ser_state *state = NULL;
struct ser_port *port = NULL;
unsigned long flags;
int line = WCH_SER_DEVNUM(tty);
if (line >= wch_ser_port_total_cnt)
return;
state = tty->driver_data;
port = state->port;
spin_lock_irqsave(&port->lock, flags);
_ser_start(tty);
spin_unlock_irqrestore(&port->lock, flags);
}
static void ser_tasklet_action(unsigned long data)
{
struct ser_state *state = (struct ser_state *)data;
struct tty_struct *tty = NULL;
tty = state->info->tty;
if (tty) {
if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc->ops->write_wakeup) {
tty->ldisc->ops->write_wakeup(tty);
}
wake_up_interruptible(&tty->write_wait);
}
}
static int ser_startup(struct ser_state *state, int init_hw)
{
struct ser_info *info = state->info;
struct ser_port *port = state->port;
unsigned long page;
int retval = 0;
if (info->flags & WCH_UIF_INITIALIZED) {
return 0;
}
if (info->tty) {
set_bit(TTY_IO_ERROR, &info->tty->flags);
}
if (port->type == PORT_UNKNOWN) {
return 0;
}
if (!info->xmit.buf) {
page = get_zeroed_page(GFP_KERNEL);
if (!page) {
return -ENOMEM;
}
info->xmit.buf = (unsigned char *)page;
info->tmpbuf = info->xmit.buf + WCH_UART_XMIT_SIZE;
sema_init(&info->tmpbuf_sem, 1);
ser_circ_clear(&info->xmit);
}
retval = wch_ser_startup(port);
if (retval == 0) {
if (init_hw && info->tty) {
ser_change_speed(state, NULL);
if (info->tty->termios.c_cflag & CBAUD)
{
set_mctrl(port, TIOCM_RTS | TIOCM_DTR);
}
}
info->flags |= WCH_UIF_INITIALIZED;
if (info->tty)
clear_bit(TTY_IO_ERROR, &info->tty->flags);
}
if (retval && capable(CAP_SYS_ADMIN)) {
retval = 0;
}
set_mctrl(port, TIOCM_OUT2);
return retval;
}
static void ser_shutdown(struct ser_state *state)
{
struct ser_info *info = state->info;
struct ser_port *port = state->port;
struct wch_ser_port *sp = (struct wch_ser_port *)port;
if (!(info->flags & WCH_UIF_INITIALIZED)) {
return;
}
if (!info->tty || (info->tty->termios.c_cflag & HUPCL))
{
clear_mctrl(port, TIOCM_DTR | TIOCM_RTS);
}
wake_up_interruptible(&info->delta_msr_wait);
wch_ser_shutdown(port);
synchronize_irq(port->irq);
if (info->xmit.buf) {
free_page((unsigned long)info->xmit.buf);
info->xmit.buf = NULL;
if (info->tmpbuf)
info->tmpbuf = NULL;
}
tasklet_kill(&info->tlet);
if (info->tty) {
set_bit(TTY_IO_ERROR, &info->tty->flags);
}
// modified on 20200929
sp->mcr = 0;
clear_mctrl(port, TIOCM_OUT2 | TIOCM_DTR | TIOCM_RTS);
info->flags &= ~WCH_UIF_INITIALIZED;
}
static _INLINE_ void _ser_put_char(struct ser_port *port, struct circ_buf *circ, unsigned char c)
{
unsigned long flags;
if (!circ->buf) {
return;
}
spin_lock_irqsave(&port->lock, flags);
if (ser_circ_chars_free(circ) != 0) {
circ->buf[circ->head] = c;
circ->head = (circ->head + 1) & (WCH_UART_XMIT_SIZE - 1);
}
spin_unlock_irqrestore(&port->lock, flags);
}
#if defined(NV_TTY_OPERATIONS_STRUCT_WRITE_HAS_U8_PTR_ARG) /* Linux 6.5 */
static int ser_put_char(struct tty_struct *tty, u8 ch)
#else
static int ser_put_char(struct tty_struct *tty, unsigned char ch)
#endif
{
struct ser_state *state = NULL;
int line = WCH_SER_DEVNUM(tty);
if (line >= wch_ser_port_total_cnt) {
return 0;
}
state = tty->driver_data;
_ser_put_char(state->port, &state->info->xmit, ch);
return 0;
}
static void ser_flush_chars(struct tty_struct *tty)
{
int line = WCH_SER_DEVNUM(tty);
if (line >= wch_ser_port_total_cnt) {
return;
}
ser_start(tty);
}
static unsigned int ser_chars_in_buffer(struct tty_struct *tty)
{
struct ser_state *state = NULL;
int line = WCH_SER_DEVNUM(tty);
if (line >= wch_ser_port_total_cnt) {
return 0;
}
state = tty->driver_data;
return ser_circ_chars_pending(&state->info->xmit);
}
static void ser_flush_buffer(struct tty_struct *tty)
{
struct ser_state *state = NULL;
struct ser_port *port = NULL;
unsigned long flags;
int line = WCH_SER_DEVNUM(tty);
if (line >= wch_ser_port_total_cnt) {
return;
}
state = tty->driver_data;
port = state->port;
if (!state || !state->info) {
return;
}
spin_lock_irqsave(&port->lock, flags);
ser_circ_clear(&state->info->xmit);
spin_unlock_irqrestore(&port->lock, flags);
wake_up_interruptible(&tty->write_wait);
if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc->ops->write_wakeup) {
(tty->ldisc->ops->write_wakeup)(tty);
}
}
#if defined(NV_TTY_OPERATIONS_STRUCT_SEND_XCHAR_HAS_U8_ARG) /* Linux 6.8 */
static void ser_send_xchar(struct tty_struct *tty, u8 ch)
#else
static void ser_send_xchar(struct tty_struct *tty, char ch)
#endif
{
struct ser_state *state = NULL;
struct ser_port *port = NULL;
unsigned long flags;
int line = WCH_SER_DEVNUM(tty);
if (line >= wch_ser_port_total_cnt) {
return;
}
state = tty->driver_data;
port = state->port;
port->x_char = ch;
if (ch) {
spin_lock_irqsave(&port->lock, flags);
wch_ser_start_tx(port, 0);
spin_unlock_irqrestore(&port->lock, flags);
}
}
static void ser_throttle(struct tty_struct *tty)
{
struct ser_state *state = NULL;
struct ser_port *port = NULL;
int line = WCH_SER_DEVNUM(tty);
if (line >= wch_ser_port_total_cnt) {
return;
}
state = tty->driver_data;
port = state->port;
port->ldisc_stop_rx = 1;
if (I_IXOFF(tty)) {
ser_send_xchar(tty, STOP_CHAR(tty));
}
if (tty->termios.c_cflag & CRTSCTS)
{
clear_mctrl(state->port, TIOCM_RTS);
}
}
static void ser_unthrottle(struct tty_struct *tty)
{
struct ser_state *state = NULL;
struct ser_port *port = NULL;
int line = WCH_SER_DEVNUM(tty);
if (line >= wch_ser_port_total_cnt) {
return;
}
state = tty->driver_data;
port = state->port;
port->ldisc_stop_rx = 0;
if (I_IXOFF(tty)) {
if (port->x_char) {
port->x_char = 0;
} else {
ser_send_xchar(tty, START_CHAR(tty));
}
}
if (tty->termios.c_cflag & CRTSCTS)
{
set_mctrl(port, TIOCM_RTS);
}
}
static int ser_get_info(struct ser_state *state, struct serial_struct *retinfo)
{
struct ser_port *port = state->port;
struct serial_struct tmp;
memset(&tmp, 0, sizeof(tmp));
tmp.type = port->type;
tmp.line = port->line;
tmp.port = port->iobase;
if (WCH_HIGH_BITS_OFFSET) {
tmp.port_high = (long)port->iobase >> WCH_HIGH_BITS_OFFSET;
}
tmp.irq = port->irq;
tmp.flags = port->flags;
tmp.xmit_fifo_size = port->fifosize;
tmp.baud_base = port->uartclk / 16;
tmp.close_delay = state->close_delay;
tmp.closing_wait = state->closing_wait;
tmp.custom_divisor = port->custom_divisor;
tmp.io_type = port->iotype;
if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) {
return -EFAULT;
}
return 0;
}
static int ser_set_info(struct ser_state *state, struct serial_struct *newinfo)
{
struct serial_struct new_serial;
struct ser_port *port = state->port;
unsigned long new_port;
unsigned int change_irq;
unsigned int change_port;
unsigned int old_custom_divisor;
unsigned int closing_wait;
unsigned int close_delay;
unsigned int old_flags;
unsigned int new_flags;
int retval = 0;
if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) {
return -EFAULT;
}
new_port = new_serial.port;
if (WCH_HIGH_BITS_OFFSET) {
new_port += (unsigned long)new_serial.port_high << WCH_HIGH_BITS_OFFSET;
}
new_serial.irq = irq_canonicalize(new_serial.irq);
close_delay = new_serial.close_delay;
closing_wait =
new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ? WCH_USF_CLOSING_WAIT_NONE : new_serial.closing_wait;
down(&state->sem);
change_irq = new_serial.irq != port->irq;
change_port = new_port != port->iobase || new_serial.io_type != port->iotype || new_serial.type != port->type;
old_flags = port->flags;
new_flags = new_serial.flags;
old_custom_divisor = port->custom_divisor;
if (!capable(CAP_SYS_ADMIN)) {
retval = -EPERM;
if (change_irq || change_port || (new_serial.baud_base != port->uartclk / 16) ||
(close_delay != state->close_delay) || (closing_wait != state->closing_wait) ||
(new_serial.xmit_fifo_size != port->fifosize) || (((new_flags ^ old_flags) & ~WCH_UPF_USR_MASK) != 0)) {
goto exit;
}
port->flags = ((port->flags & ~WCH_UPF_USR_MASK) | (new_flags & WCH_UPF_USR_MASK));
port->custom_divisor = new_serial.custom_divisor;
goto check_and_exit;
}
if (change_port || change_irq) {
retval = -EBUSY;
if (wch_ser_users(state) > 1) {
goto exit;
}
ser_shutdown(state);
}
if (change_port) {
unsigned long old_iobase;
unsigned int old_type;
unsigned int old_iotype;
old_iobase = port->iobase;
old_type = port->type;
old_iotype = port->iotype;
if (old_type != PORT_UNKNOWN) {
wch_ser_release_io(port);
}
port->iobase = new_port;
port->type = new_serial.type;
port->iotype = new_serial.io_type;
retval = 0;
}
port->irq = new_serial.irq;
port->uartclk = new_serial.baud_base * 16;
port->flags = ((port->flags & ~WCH_UPF_CHANGE_MASK) | (new_flags & WCH_UPF_CHANGE_MASK));
port->custom_divisor = new_serial.custom_divisor;
state->close_delay = close_delay;
state->closing_wait = closing_wait;
port->fifosize = new_serial.xmit_fifo_size;
check_and_exit:
retval = 0;
if (port->type == PORT_UNKNOWN) {
goto exit;
}
if (state->info->flags & WCH_UIF_INITIALIZED) {
if (((old_flags ^ port->flags) & WCH_UPF_SPD_MASK) || old_custom_divisor != port->custom_divisor) {
if (port->flags & WCH_UPF_SPD_MASK) {
printk("WCH Info : %s sets custom speed on ttyWCH%d. This is deprecated.\n", current->comm, port->line);
}
ser_change_speed(state, NULL);
}
} else {
retval = ser_startup(state, 1);
}
exit:
up(&state->sem);
return retval;
}
static unsigned int ser_write_room(struct tty_struct *tty)
{
struct ser_state *state = NULL;
int line = WCH_SER_DEVNUM(tty);
int status = 0;
if (line >= wch_ser_port_total_cnt) {
return 0;
}
state = tty->driver_data;
status = ser_circ_chars_free(&state->info->xmit);
return status;
}
#if defined(NV_TTY_OPERATIONS_STRUCT_WRITE_HAS_U8_PTR_ARG) /* Linux 6.5 */
static ssize_t ser_write(struct tty_struct *tty, const u8 *buf, size_t count)
#else
static int ser_write(struct tty_struct *tty, const unsigned char *buf, int count)
#endif
{
struct ser_state *state = tty->driver_data;
struct ser_port *port = NULL;
struct circ_buf *circ = NULL;
unsigned long flags;
int c;
int ret = 0;
if (!state || !state->info) {
return -EL3HLT;
}
port = state->port;
circ = &state->info->xmit;
if (!circ->buf) {
return 0;
}
spin_lock_irqsave(&port->lock, flags);
while (1) {
c = CIRC_SPACE_TO_END(circ->head, circ->tail, WCH_UART_XMIT_SIZE);
if (count < c) {
c = count;
}
if (c <= 0) {
break;
}
memcpy(circ->buf + circ->head, buf, c);
circ->head = (circ->head + c) & (WCH_UART_XMIT_SIZE - 1);
buf += c;
count -= c;
ret += c;
}
_ser_start(tty);
spin_unlock_irqrestore(&port->lock, flags);
return ret;
}
/*
static int
ser_get_lsr_info(
struct ser_state *state,
unsigned int *value
)
{
struct ser_port *port = state->port;
unsigned int result = 0;
result = wch_ser_tx_empty(port);
if ((port->x_char) ||
((ser_circ_chars_pending(&state->info->xmit) > 0) &&
!state->info->tty->flow.stopped && !state->info->tty->hw_stopped))
result &= ~TIOCSER_TEMT;
return put_user(result, value);
}
*/
static int ser_tiocmget(struct tty_struct *tty)
{
struct ser_state *state;
struct ser_port *port;
int result = -EIO;
int line = WCH_SER_DEVNUM(tty);
if (line >= wch_ser_port_total_cnt) {
return 0;
}
state = tty->driver_data;
port = state->port;
down(&state->sem);
if (!(tty->flags & (1 << TTY_IO_ERROR))) {
result = port->mctrl;
result |= wch_ser_get_mctrl(port);
}
up(&state->sem);
return result;
}
static int ser_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear)
{
struct ser_state *state;
struct ser_port *port;
int ret = -EIO;
int line = WCH_SER_DEVNUM(tty);
if (line >= wch_ser_port_total_cnt) {
return 0;
}
state = tty->driver_data;
port = state->port;
down(&state->sem);
if (!(tty->flags & (1 << TTY_IO_ERROR))) {
ser_update_mctrl(port, set, clear);
ret = 0;
}
up(&state->sem);
return ret;
}
static int ser_break_ctl(struct tty_struct *tty, int break_state)
{
struct ser_state *state = NULL;
struct ser_port *port = NULL;
int line = WCH_SER_DEVNUM(tty);
if (line >= wch_ser_port_total_cnt) {
return 0;
}
state = tty->driver_data;
port = state->port;
down(&state->sem);
if (port->type != PORT_UNKNOWN) {
wch_ser_break_ctl(port, break_state);
}
up(&state->sem);
return 0;
}
static int ser_wait_modem_status(struct ser_state *state, unsigned long arg)
{
struct ser_port *port = state->port;
DECLARE_WAITQUEUE(wait, current);
struct ser_icount cprev;
struct ser_icount cnow;
int ret = 0;
spin_lock_irq(&port->lock);
memcpy(&cprev, &port->icount, sizeof(struct ser_icount));
wch_ser_enable_ms(port);
spin_unlock_irq(&port->lock);
add_wait_queue(&state->info->delta_msr_wait, &wait);
for (;;) {
spin_lock_irq(&port->lock);
memcpy(&cnow, &port->icount, sizeof(struct ser_icount));
spin_unlock_irq(&port->lock);
set_current_state(TASK_INTERRUPTIBLE);
if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts))) {
ret = 0;
break;
}
schedule();
if (signal_pending(current)) {
ret = -ERESTARTSYS;
break;
}
cprev = cnow;
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&state->info->delta_msr_wait, &wait);
return ret;
}
static int ser_get_count(struct ser_state *state, struct serial_icounter_struct *icnt)
{
struct serial_icounter_struct *icount;
struct ser_icount cnow;
struct ser_port *port = state->port;
spin_lock_irq(&port->lock);
memcpy(&cnow, &port->icount, sizeof(struct ser_icount));
spin_unlock_irq(&port->lock);
icount = kzalloc(sizeof(*icount), GFP_KERNEL);
icount->cts = cnow.cts;
icount->dsr = cnow.dsr;
icount->rng = cnow.rng;
icount->dcd = cnow.dcd;
icount->rx = cnow.rx;
icount->tx = cnow.tx;
icount->frame = cnow.frame;
icount->overrun = cnow.overrun;
icount->parity = cnow.parity;
icount->brk = cnow.brk;
icount->buf_overrun = cnow.buf_overrun;
return copy_to_user(icnt, icount, sizeof(*icount)) ? -EFAULT : 0;
}
static void ser_config_rs485(struct ser_state *state, struct serial_rs485 *rs485)
{
struct ser_port *port = state->port;
struct wch_ser_port *sp = (struct wch_ser_port *)port;
struct serial_rs485 rs485val = *rs485;
unsigned char cval, mval;
cval = READ_UART_LCR(sp);
mval = READ_UART_MCR(sp);
WRITE_UART_LCR(sp, cval | UART_LCR_DLAB);
if (rs485val.flags & SER_RS485_ENABLED) {
WRITE_UART_MCR(sp, mval | BIT(7));
} else {
WRITE_UART_MCR(sp, mval & ~BIT(7));
}
WRITE_UART_LCR(sp, cval);
}
static int ser_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
{
struct ser_state *state = NULL;
int ret = -ENOIOCTLCMD;
int line = WCH_SER_DEVNUM(tty);
struct serial_rs485 rs485;
if (line < wch_ser_port_total_cnt) {
state = tty->driver_data;
}
switch (cmd) {
case TIOCGSERIAL: {
if (line < wch_ser_port_total_cnt) {
ret = ser_get_info(state, (struct serial_struct *)arg);
}
break;
}
case TIOCSSERIAL: {
if (line < wch_ser_port_total_cnt) {
state->port->setserial_flag = WCH_SER_BAUD_SETSERIAL;
ret = ser_set_info(state, (struct serial_struct *)arg);
}
break;
}
case TCGETS: {
struct ktermios kterm;
mutex_lock(&tty->throttle_mutex);
kterm = tty->termios;
mutex_unlock(&tty->throttle_mutex);
if (copy_to_user((struct termios __user *)arg, &kterm, sizeof(struct termios)))
ret = -EFAULT;
else
ret = 0;
break;
}
case TCSETS: {
if (line < wch_ser_port_total_cnt) {
state->port->flags &= ~(WCH_UPF_SPD_HI | WCH_UPF_SPD_VHI | WCH_UPF_SPD_SHI | WCH_UPF_SPD_WARP);
state->port->setserial_flag = WCH_SER_BAUD_NOTSETSER;
ser_update_termios(state);
}
break;
}
case TIOCSRS485: {
if (copy_from_user(&rs485, (void __user *)arg, sizeof(rs485)))
return -EFAULT;
ser_config_rs485(state, &rs485);
break;
}
case TIOCSERGWILD:
case TIOCSERSWILD: {
if (line < wch_ser_port_total_cnt) {
ret = 0;
}
break;
}
case TIOCMIWAIT:
if (line < wch_ser_port_total_cnt) {
ret = ser_wait_modem_status(state, arg);
}
break;
case TIOCGICOUNT:
if (line < wch_ser_port_total_cnt) {
ret = ser_get_count(state, (struct serial_icounter_struct *)arg);
}
break;
}
if (ret != -ENOIOCTLCMD) {
goto out;
}
if (tty->flags & (1 << TTY_IO_ERROR)) {
ret = -EIO;
goto out;
}
out:
return ret;
}
static void ser_hangup(struct tty_struct *tty)
{
struct ser_state *state = NULL;
int line = WCH_SER_DEVNUM(tty);
if (line >= wch_ser_port_total_cnt) {
return;
}
state = tty->driver_data;
down(&state->sem);
if (state->info && state->info->flags & WCH_UIF_NORMAL_ACTIVE) {
ser_flush_buffer(tty);
ser_shutdown(state);
state->count = 0;
state->info->flags &= ~WCH_UIF_NORMAL_ACTIVE;
state->info->tty = NULL;
wake_up_interruptible(&state->info->open_wait);
wake_up_interruptible(&state->info->delta_msr_wait);
}
up(&state->sem);
}
unsigned int ser_get_divisor(struct ser_port *port, unsigned int baud, bool btwicefreq)
{
unsigned int quot;
if (baud == 38400 && (port->flags & WCH_UPF_SPD_MASK) == WCH_UPF_SPD_CUST) {
quot = port->custom_divisor;
} else {
if (btwicefreq) {
if (10 * port->uartclk / 16 / baud % 10 >= 5)
quot = port->uartclk / 16 / baud + 1;
else
quot = port->uartclk / 16 / baud;
} else {
if (10 * port->uartclk / 24 / 16 / baud % 10 >= 5)
quot = port->uartclk / 24 / 16 / baud + 1;
else
quot = port->uartclk / 24 / 16 / baud;
}
}
return quot;
}
#if defined(NV_TTY_OPERATIONS_STRUCT_SET_TERMIOS_HAS_CONST_KTERMIOS_ARG) /* Linux 6.1 */
unsigned int ser_get_baud_rate(struct ser_port *port, struct WCHTERMIOS *termios,
const struct WCHTERMIOS *old, unsigned int min, unsigned int max)
#else
unsigned int ser_get_baud_rate(struct ser_port *port, struct WCHTERMIOS *termios, struct WCHTERMIOS *old,
unsigned int min, unsigned int max)
#endif
{
unsigned int try;
unsigned int baud;
unsigned int altbaud = 38400;
int hung_up = 0;
unsigned int flags = port->flags & WCH_UPF_SPD_MASK;
if (port->flags & WCH_UPF_SPD_MASK) {
altbaud = 38400;
if (flags == WCH_UPF_SPD_HI) {
altbaud = 57600;
}
if (flags == WCH_UPF_SPD_VHI) {
altbaud = 115200;
}
if (flags == WCH_UPF_SPD_SHI) {
altbaud = 230400;
}
if (flags == WCH_UPF_SPD_WARP) {
altbaud = 460800;
}
}
for (try = 0; try < 2; try++) {
baud = tty_termios_baud_rate(termios);
if (try == 0 && baud == 38400)
baud = altbaud;
if (baud == 0) {
hung_up = 1;
baud = 9600;
}
if (baud >= min && baud <= max) {
return baud;
}
termios->c_cflag &= ~CBAUD;
if (old) {
baud = tty_termios_baud_rate(old);
if (!hung_up)
tty_termios_encode_baud_rate(termios, baud, baud);
old = NULL;
continue;
}
/*
* As a last resort, if the range cannot be met then clip to
* the nearest chip supported rate.
*/
if (!hung_up) {
if (baud <= min)
tty_termios_encode_baud_rate(termios, min + 1, min + 1);
else
tty_termios_encode_baud_rate(termios, max - 1, max - 1);
}
}
return 0;
}
#if defined(NV_TTY_OPERATIONS_STRUCT_SET_TERMIOS_HAS_CONST_KTERMIOS_ARG) /* Linux 6.1 */
static void ser_change_speed(struct ser_state *state, const struct WCHTERMIOS *old_termios)
#else
static void ser_change_speed(struct ser_state *state, struct WCHTERMIOS *old_termios)
#endif
{
struct tty_struct *tty = state->info->tty;
struct ser_port *port = state->port;
struct WCHTERMIOS termios;
if (!tty || port->type == PORT_UNKNOWN) {
return;
}
termios = tty->termios;
if (termios.c_cflag & CRTSCTS) {
state->info->flags |= WCH_UIF_CTS_FLOW;
} else {
state->info->flags &= ~WCH_UIF_CTS_FLOW;
}
if (termios.c_cflag & CLOCAL) {
state->info->flags &= ~WCH_UIF_CHECK_CD;
} else {
state->info->flags |= WCH_UIF_CHECK_CD;
}
wch_ser_set_termios(port, &termios, old_termios);
}
#if defined(NV_TTY_OPERATIONS_STRUCT_SET_TERMIOS_HAS_CONST_KTERMIOS_ARG) /* Linux 6.1 */
static void ser_set_termios(struct tty_struct *tty, const struct WCHTERMIOS *old_termios)
#else
static void ser_set_termios(struct tty_struct *tty, struct WCHTERMIOS *old_termios)
#endif
{
struct ser_state *state;
unsigned long flags;
unsigned int cflag = tty->termios.c_cflag;
int line = WCH_SER_DEVNUM(tty);
if (line >= wch_ser_port_total_cnt) {
return;
}
state = tty->driver_data;
#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK))
if ((cflag ^ old_termios->c_cflag) == 0 && RELEVANT_IFLAG(tty->termios.c_iflag ^ old_termios->c_iflag) == 0)
{
return;
}
ser_change_speed(state, old_termios);
/* Drop RTS and DTR */
if ((cflag & CBAUD) == B0) {
clear_mctrl(state->port, TIOCM_RTS | TIOCM_DTR);
} else {
/* Ensure RTS and DTR are raised when baudrate changed from 0 */
if (old_termios && ((old_termios->c_cflag & CBAUD) == B0)) {
unsigned int mask = TIOCM_DTR;
if (!(cflag & CRTSCTS) || !test_bit(TTY_THROTTLED, &tty->flags)) {
mask |= TIOCM_RTS;
}
set_mctrl(state->port, mask);
}
}
if ((old_termios->c_cflag & CRTSCTS) && !(cflag & CRTSCTS)) {
spin_lock_irqsave(&state->port->lock, flags);
tty->hw_stopped = 0;
_ser_start(tty);
spin_unlock_irqrestore(&state->port->lock, flags);
}
}
static void ser_update_termios(struct ser_state *state)
{
struct tty_struct *tty = state->info->tty;
struct ser_port *port = state->port;
if (!(tty->flags & (1 << TTY_IO_ERROR))) {
ser_change_speed(state, NULL);
if (tty->termios.c_cflag & CBAUD)
{
set_mctrl(port, TIOCM_DTR | TIOCM_RTS);
}
}
}
static void ser_update_timeout(struct ser_port *port, unsigned int cflag, unsigned int baud)
{
unsigned int bits;
switch (cflag & CSIZE) {
case CS5:
bits = 7;
break;
case CS6:
bits = 8;
break;
case CS7:
bits = 9;
break;
default:
bits = 10;
break;
}
if (cflag & CSTOPB) {
bits++;
}
if (cflag & PARENB) {
bits++;
}
bits = bits * port->fifosize;
port->timeout = (HZ * bits) / baud + HZ / 50;
}
static struct ser_state *ser_get(struct ser_driver *drv, int line)
{
struct ser_state *state = NULL;
down(&ser_port_sem);
state = drv->state + line;
if (down_interruptible(&state->sem)) {
state = ERR_PTR(-ERESTARTSYS);
goto out;
}
state->count++;
if (!state->port) {
state->count--;
up(&state->sem);
state = ERR_PTR(-ENXIO);
goto out;
}
if (!state->port->iobase) {
state->count--;
up(&state->sem);
state = ERR_PTR(-ENXIO);
goto out;
}
if (!state->info) {
state->info = kmalloc(sizeof(struct ser_info), GFP_KERNEL);
if (state->info) {
memset(state->info, 0, sizeof(struct ser_info));
init_waitqueue_head(&state->info->open_wait);
init_waitqueue_head(&state->info->delta_msr_wait);
state->port->info = state->info;
tasklet_init(&state->info->tlet, ser_tasklet_action, (unsigned long)state);
} else {
state->count--;
up(&state->sem);
state = ERR_PTR(-ENOMEM);
}
}
out:
up(&ser_port_sem);
return state;
}
static int ser_block_til_ready(struct file *filp, struct ser_state *state)
{
DECLARE_WAITQUEUE(wait, current);
struct ser_info *info = state->info;
struct ser_port *port = state->port;
info->blocked_open++;
state->count--;
add_wait_queue(&info->open_wait, &wait);
while (1) {
set_current_state(TASK_INTERRUPTIBLE);
if (tty_hung_up_p(filp) || info->tty == NULL) {
break;
}
if (!(info->flags & WCH_UIF_INITIALIZED)) {
break;
}
if ((filp->f_flags & O_NONBLOCK) || (info->tty->termios.c_cflag & CLOCAL) ||
(info->tty->flags & (1 << TTY_IO_ERROR)))
{
break;
}
if (info->tty->termios.c_cflag & CBAUD)
{
set_mctrl(port, TIOCM_DTR);
}
if (wch_ser_get_mctrl(port) & TIOCM_CAR) {
break;
}
up(&state->sem);
schedule();
down(&state->sem);
if (signal_pending(current)) {
break;
}
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&info->open_wait, &wait);
state->count++;
info->blocked_open--;
if (signal_pending(current)) {
return -ERESTARTSYS;
}
if (!info->tty || tty_hung_up_p(filp)) {
return -EAGAIN;
}
return 0;
}
static void ser_wait_until_sent(struct tty_struct *tty, int timeout)
{
struct ser_state *state = NULL;
struct ser_port *port = NULL;
unsigned long char_time;
unsigned long expire;
int line = WCH_SER_DEVNUM(tty);
if (line >= wch_ser_port_total_cnt) {
return;
}
state = tty->driver_data;
port = state->port;
if (port->type == PORT_UNKNOWN || port->fifosize == 0) {
return;
}
char_time = (port->timeout - HZ / 50) / port->fifosize;
char_time = char_time / 5;
if (char_time == 0) {
char_time = 1;
}
if (timeout && timeout < char_time) {
char_time = timeout;
}
if (timeout == 0 || timeout > 2 * port->timeout) {
timeout = 2 * port->timeout;
}
expire = jiffies + timeout;
while (!wch_ser_tx_empty(port)) {
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(char_time);
if (signal_pending(current)) {
break;
}
if (time_after(jiffies, expire)) {
break;
}
}
set_current_state(TASK_RUNNING);
}
static int ser_open(struct tty_struct *tty, struct file *filp)
{
struct ser_driver *drv = (struct ser_driver *)tty->driver->driver_state;
struct ser_state *state;
int retval = 0;
int line = WCH_SER_DEVNUM(tty);
if (line < wch_ser_port_total_cnt) {
retval = -ENODEV;
if (line >= wch_ser_port_total_cnt) {
goto fail;
}
state = ser_get(drv, line);
if (IS_ERR(state)) {
retval = PTR_ERR(state);
goto fail;
}
if (!state) {
goto fail;
}
state->port->state = state;
tty->driver_data = state;
state->info->tty = tty;
if (tty_hung_up_p(filp)) {
retval = -EAGAIN;
state->count--;
up(&state->sem);
goto fail;
}
tty_port_tty_set(&state->port0, tty); // 20140606 add
retval = ser_startup(state, 0);
if (retval == 0) {
retval = ser_block_til_ready(filp, state);
}
up(&state->sem);
if (retval == 0 && !(state->info->flags & WCH_UIF_NORMAL_ACTIVE)) {
state->info->flags |= WCH_UIF_NORMAL_ACTIVE;
ser_update_termios(state);
}
try_module_get(THIS_MODULE);
}
fail:
return retval;
}
static void ser_close(struct tty_struct *tty, struct file *filp)
{
struct ser_state *state = tty->driver_data;
struct ser_port *port;
int line = WCH_SER_DEVNUM(tty);
if (line < wch_ser_port_total_cnt) {
if (!state || !state->port) {
return;
}
port = state->port;
down(&state->sem);
if (tty_hung_up_p(filp)) {
goto done;
}
if ((tty->count == 1) && (state->count != 1)) {
printk("WCH Info : bad serial port count; tty->count is 1, state->count is %d\n", state->count);
state->count = 1;
}
if (--state->count < 0) {
printk("WCH Info : bad serial port count for ttyWCH%d: %d\n", port->line, state->count);
state->count = 0;
}
if (state->count) {
goto done;
}
tty->closing = 1;
if (state->closing_wait != WCH_USF_CLOSING_WAIT_NONE) {
tty_wait_until_sent(tty, state->closing_wait);
}
if (state->info->flags & WCH_UIF_INITIALIZED) {
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
wch_ser_stop_rx(port);
spin_unlock_irqrestore(&port->lock, flags);
ser_wait_until_sent(tty, port->timeout);
}
ser_shutdown(state);
ser_flush_buffer(tty);
tty_ldisc_flush(tty);
/*
if (tty->ldisc->ops->flush_buffer) {
tty->ldisc->ops->flush_buffer(tty);
}
*/
tty->closing = 0;
if (state->info->tty)
state->info->tty = NULL;
if (state->port0.tty)
tty_port_tty_set(&state->port0, NULL); // 20140606 add
if (state->info->blocked_open) {
if (state->close_delay) {
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(state->close_delay);
}
}
state->info->flags &= ~WCH_UIF_NORMAL_ACTIVE;
wake_up_interruptible(&state->info->open_wait);
done:
up(&state->sem);
module_put(THIS_MODULE);
}
}
static void wch_ser_set_mctrl(struct ser_port *port, unsigned int mctrl)
{
struct wch_ser_port *sp = (struct wch_ser_port *)port;
unsigned char mcr = 0;
if (mctrl & TIOCM_RTS) {
mcr |= UART_MCR_RTS;
}
if (mctrl & TIOCM_DTR) {
mcr |= UART_MCR_DTR;
}
if (mctrl & TIOCM_OUT1) {
mcr |= UART_MCR_OUT1;
}
if (mctrl & TIOCM_OUT2) {
mcr |= UART_MCR_OUT2;
}
if (mctrl & TIOCM_LOOP) {
mcr |= UART_MCR_LOOP;
}
mcr = (mcr & sp->mcr_mask) | sp->mcr_force | sp->mcr;
WRITE_UART_MCR(sp, mcr);
}
static unsigned int wch_ser_tx_empty(struct ser_port *port)
{
struct wch_ser_port *sp = (struct wch_ser_port *)port;
unsigned long flags;
unsigned int ret;
spin_lock_irqsave(&sp->port.lock, flags);
ret = READ_UART_LSR(sp) & UART_LSR_TEMT ? TIOCSER_TEMT : 0;
spin_unlock_irqrestore(&sp->port.lock, flags);
return ret;
}
static unsigned int wch_ser_get_mctrl(struct ser_port *port)
{
struct wch_ser_port *sp = (struct wch_ser_port *)port;
unsigned long flags;
unsigned char status;
unsigned int ret = 0;
spin_lock_irqsave(&sp->port.lock, flags);
status = READ_UART_MSR(sp);
spin_unlock_irqrestore(&sp->port.lock, flags);
if (status & UART_MSR_DCD) {
ret |= TIOCM_CAR;
}
if (status & UART_MSR_RI) {
ret |= TIOCM_RNG;
}
if (status & UART_MSR_DSR) {
ret |= TIOCM_DSR;
}
if (status & UART_MSR_CTS) {
ret |= TIOCM_CTS;
}
return ret;
}
static void wch_ser_stop_tx(struct ser_port *port, unsigned int tty_stop)
{
struct wch_ser_port *sp = (struct wch_ser_port *)port;
if (sp->ier & UART_IER_THRI) {
sp->ier &= ~UART_IER_THRI;
WRITE_UART_IER(sp, sp->ier);
}
}
static void wch_ser_start_tx(struct ser_port *port, unsigned int tty_start)
{
struct wch_ser_port *sp = (struct wch_ser_port *)port;
if (!(sp->ier & UART_IER_THRI)) {
sp->ier |= UART_IER_THRI;
WRITE_UART_IER(sp, sp->ier);
}
}
static void wch_ser_stop_rx(struct ser_port *port)
{
struct wch_ser_port *sp = (struct wch_ser_port *)port;
sp->ier &= ~UART_IER_RLSI;
sp->port.read_status_mask &= ~UART_LSR_DR;
WRITE_UART_IER(sp, sp->ier);
}
static void wch_ser_enable_ms(struct ser_port *port)
{
struct wch_ser_port *sp = (struct wch_ser_port *)port;
sp->ier |= UART_IER_MSI;
WRITE_UART_IER(sp, sp->ier);
}
static void wch_ser_break_ctl(struct ser_port *port, int break_state)
{
struct wch_ser_port *sp = (struct wch_ser_port *)port;
unsigned long flags;
spin_lock_irqsave(&sp->port.lock, flags);
if (break_state == -1) {
sp->lcr |= UART_LCR_SBC;
} else {
sp->lcr &= ~UART_LCR_SBC;
}
WRITE_UART_LCR(sp, sp->lcr);
spin_unlock_irqrestore(&sp->port.lock, flags);
}
static int wch_ser_startup(struct ser_port *port)
{
struct wch_ser_port *sp = (struct wch_ser_port *)port;
sp->capabilities = wch_uart_config[sp->port.type].flags;
sp->mcr = 0;
if (sp->capabilities & UART_CLEAR_FIFO) {
WRITE_UART_FCR(sp, UART_FCR_ENABLE_FIFO);
WRITE_UART_FCR(sp, UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
WRITE_UART_FCR(sp, 0);
}
(void)READ_UART_LSR(sp);
(void)READ_UART_RX(sp);
(void)READ_UART_IIR(sp);
(void)READ_UART_MSR(sp);
if (!(sp->port.flags & WCH_UPF_BUGGY_UART) && (READ_UART_LSR(sp) == 0xff)) {
printk("WCH Info : ttyWCH%d: LSR safety check engaged!\n", sp->port.line);
return -ENODEV;
}
WRITE_UART_LCR(sp, UART_LCR_WLEN8);
sp->ier = UART_IER_RLSI | UART_IER_RDI;
WRITE_UART_IER(sp, sp->ier);
(void)READ_UART_LSR(sp);
(void)READ_UART_RX(sp);
(void)READ_UART_IIR(sp);
(void)READ_UART_MSR(sp);
return 0;
}
static void wch_ser_shutdown(struct ser_port *port)
{
struct wch_ser_port *sp = (struct wch_ser_port *)port;
sp->ier = 0;
WRITE_UART_IER(sp, 0);
WRITE_UART_LCR(sp, READ_UART_LCR(sp) & ~UART_LCR_SBC);
WRITE_UART_FCR(sp, UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
WRITE_UART_FCR(sp, 0);
(void)READ_UART_RX(sp);
}
static unsigned int wch_ser_get_divisor(struct ser_port *port, unsigned int baud, bool btwicefreq)
{
unsigned int quot;
if ((port->flags & WCH_UPF_MAGIC_MULTIPLIER) && baud == (port->uartclk / 4)) {
quot = 0x8001;
} else if ((port->flags & WCH_UPF_MAGIC_MULTIPLIER) && baud == (port->uartclk / 8)) {
quot = 0x8002;
} else {
quot = ser_get_divisor(port, baud, btwicefreq);
}
return quot;
}
#if defined(NV_TTY_OPERATIONS_STRUCT_SET_TERMIOS_HAS_CONST_KTERMIOS_ARG) /* Linux 6.1 */
static void wch_ser_set_termios(struct ser_port *port, struct WCHTERMIOS *termios, const struct WCHTERMIOS *old)
#else
static void wch_ser_set_termios(struct ser_port *port, struct WCHTERMIOS *termios, struct WCHTERMIOS *old)
#endif
{
struct wch_ser_port *sp = (struct wch_ser_port *)port;
unsigned char cval;
unsigned char fcr = 0;
unsigned long flags;
unsigned int baud;
unsigned int quot;
unsigned char ier;
switch (termios->c_cflag & CSIZE) {
case CS5:
cval = 0x00;
break;
case CS6:
cval = 0x01;
break;
case CS7:
cval = 0x02;
break;
default:
case CS8:
cval = 0x03;
break;
}
if (termios->c_cflag & CSTOPB) {
cval |= 0x04;
}
if (termios->c_cflag & PARENB) {
cval |= UART_LCR_PARITY;
}
if (!(termios->c_cflag & PARODD)) {
cval |= UART_LCR_EPAR;
}
#ifdef CMSPAR
if (termios->c_cflag & CMSPAR) {
cval |= UART_LCR_SPAR;
}
#endif
/* fixed on 20210706 */
if (port->bspe1stport == true) {
baud = ser_get_baud_rate(port, termios, old, port->uartclk / 16 / 65536, port->uartclk / 16);
if (baud <= 0)
baud = 9600;
quot = wch_ser_get_divisor(port, baud, true);
sp->ier &= ~(1 << 5);
ier = READ_UART_IER(sp + 1);
WRITE_UART_IER(sp + 1, ier | (1 << 5));
} else if (port->bext1stport == false) {
baud = ser_get_baud_rate(port, termios, old, port->uartclk / 16 / 65536, port->uartclk / 16);
if (baud <= 0)
baud = 9600;
quot = wch_ser_get_divisor(port, baud, true);
sp->ier |= 1 << 5;
} else {
baud = ser_get_baud_rate(port, termios, old, port->uartclk / 24 / 16 / 65536, port->uartclk / 24 / 16);
if (baud <= 0)
baud = 9600;
quot = wch_ser_get_divisor(port, baud, false);
sp->ier &= ~(1 << 5);
}
if (sp->capabilities & UART_USE_FIFO) {
fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10;
}
sp->mcr &= ~UART_MCR_AFE;
if (termios->c_cflag & CRTSCTS) {
sp->mcr |= UART_MCR_AFE;
sp->mcr |= UART_MCR_RTS;
port->hardflow = true;
} else {
sp->mcr &= ~UART_MCR_AFE;
sp->mcr &= ~UART_MCR_RTS;
port->hardflow = false;
}
/* added on 20200928 */
sp->mcr |= UART_MCR_OUT2;
spin_lock_irqsave(&sp->port.lock, flags);
ser_update_timeout(port, termios->c_cflag, baud);
sp->port.read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
if (termios->c_iflag & INPCK) {
sp->port.read_status_mask |= UART_LSR_FE | UART_LSR_PE;
}
if (termios->c_iflag & (BRKINT | PARMRK)) {
sp->port.read_status_mask |= UART_LSR_BI;
}
sp->port.ignore_status_mask = 0;
if (termios->c_iflag & IGNPAR) {
sp->port.ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
}
if (termios->c_iflag & IGNBRK) {
sp->port.ignore_status_mask |= UART_LSR_BI;
if (termios->c_iflag & IGNPAR) {
sp->port.ignore_status_mask |= UART_LSR_OE;
}
}
if ((termios->c_cflag & CREAD) == 0) {
sp->port.ignore_status_mask |= UART_LSR_DR;
}
sp->ier &= ~UART_IER_MSI;
if (WCH_ENABLE_MS(&sp->port, termios->c_cflag)) {
sp->ier |= UART_IER_MSI;
}
WRITE_UART_LCR(sp, cval | UART_LCR_DLAB);
WRITE_UART_DLL(sp, quot & 0xff);
WRITE_UART_DLM(sp, quot >> 8);
WRITE_UART_FCR(sp, fcr);
WRITE_UART_LCR(sp, cval);
sp->lcr = cval;
wch_ser_set_mctrl(&sp->port, sp->port.mctrl);
WRITE_UART_IER(sp, sp->ier);
spin_unlock_irqrestore(&sp->port.lock, flags);
}
static void wch_ser_timeout(struct timer_list *t)
{
#if defined(timer_container_of) /* Linux v6.16 */
struct wch_ser_port *sp = timer_container_of(sp, t, timer);
#else
struct wch_ser_port *sp = from_timer(sp, t, timer);
#endif
unsigned int timeout;
unsigned int iir;
iir = READ_UART_IIR(sp);
if (!(iir & UART_IIR_NO_INT)) {
spin_lock(&sp->port.lock);
ser_handle_port(sp, iir);
spin_unlock(&sp->port.lock);
}
timeout = sp->port.timeout;
timeout = timeout > 6 ? (timeout / 2 - 2) : 1;
mod_timer(&sp->timer, jiffies + timeout);
}
static _INLINE_ void ser_receive_chars(struct wch_ser_port *sp, unsigned char *status, unsigned char iir)
{
struct tty_struct *tty = sp->port.info->tty;
unsigned char ch = 0;
int max_count = 256;
unsigned char lsr = *status;
unsigned char flag;
unsigned char rbuf[256];
int readcont = 0;
int count;
do {
if (iir == UART_IIR_RDI) {
READ_UART_RX_BUFFER(sp, rbuf, sp->port.rx_trigger);
sp->port.icount.rx += sp->port.rx_trigger;
count = sp->port.rx_trigger;
readcont = 1;
iir = 0;
} else {
ch = READ_UART_RX(sp);
sp->port.icount.rx++;
readcont = 0;
}
flag = TTY_NORMAL;
if (unlikely(lsr & (UART_LSR_BI | UART_LSR_PE | UART_LSR_FE | UART_LSR_OE)))
{
if (lsr & UART_LSR_BI) {
lsr &= ~(UART_LSR_FE | UART_LSR_PE);
sp->port.icount.brk++;
if (ser_handle_break(&sp->port)) {
goto ignore_char;
}
} else if (lsr & UART_LSR_PE) {
sp->port.icount.parity++;
} else if (lsr & UART_LSR_FE) {
sp->port.icount.frame++;
}
if (lsr & UART_LSR_OE) {
sp->port.icount.overrun++;
}
lsr &= sp->port.read_status_mask;
if (lsr & UART_LSR_BI) {
flag = TTY_BREAK;
} else if (lsr & UART_LSR_PE) {
flag = TTY_PARITY;
} else if (lsr & UART_LSR_FE) {
flag = TTY_FRAME;
}
}
if ((I_IXOFF(tty)) || I_IXON(tty)) {
if (ch == START_CHAR(tty)) {
tty->flow.stopped = 0;
wch_ser_start_tx(&sp->port, 1);
goto ignore_char;
} else if (ch == STOP_CHAR(tty)) {
tty->flow.stopped = 1;
wch_ser_stop_tx(&sp->port, 1);
goto ignore_char;
}
}
if (readcont)
ser_insert_buffer(&sp->port, lsr, UART_LSR_OE, rbuf, count, flag);
else
ser_insert_char(&sp->port, lsr, UART_LSR_OE, ch, flag);
ignore_char:
lsr = READ_UART_LSR(sp);
if (lsr == 0xff) {
lsr = 0x01;
}
} while (lsr & (UART_LSR_DR | UART_LSR_BI) && (max_count-- > 0));
spin_unlock(&sp->port.lock);
tty_flip_buffer_push(&(sp->port.state->port0));
spin_lock(&sp->port.lock);
*status = lsr;
}
static _INLINE_ void ser_transmit_chars(struct wch_ser_port *sp)
{
struct circ_buf *xmit = &sp->port.info->xmit;
int count;
if ((!sp) || (!sp->port.iobase)) {
return;
}
if (!sp->port.info) {
return;
}
if (!xmit) {
return;
}
if (sp->port.x_char) {
WRITE_UART_TX(sp, sp->port.x_char);
sp->port.icount.tx++;
sp->port.x_char = 0;
return;
}
if (ser_circ_empty(xmit) || ser_tx_stopped(&sp->port)) {
wch_ser_stop_tx(&sp->port, 0);
return;
}
count = sp->port.fifosize / 2;
do {
WRITE_UART_TX(sp, xmit->buf[xmit->tail]);
xmit->tail = (xmit->tail + 1) & (WCH_UART_XMIT_SIZE - 1);
sp->port.icount.tx++;
if (ser_circ_empty(xmit)) {
break;
}
} while (--count > 0);
if (ser_circ_chars_pending(xmit) < WAKEUP_CHARS_SER) {
ser_write_wakeup(&sp->port);
}
/*
if (ser_circ_empty(xmit))
{
wch_ser_stop_tx(&sp->port, 0);
}
*/
}
static _INLINE_ void ser_check_modem_status(struct wch_ser_port *sp, unsigned char status)
{
if ((status & UART_MSR_ANY_DELTA) == 0) {
return;
}
if (!sp->port.info) {
return;
}
if (status & UART_MSR_TERI) {
sp->port.icount.rng++;
}
if (status & UART_MSR_DDSR) {
sp->port.icount.dsr++;
}
if (status & UART_MSR_DDCD) {
ser_handle_dcd_change(&sp->port, status & UART_MSR_DCD);
}
if (status & UART_MSR_DCTS) {
ser_handle_cts_change(&sp->port, status & UART_MSR_CTS);
}
wake_up_interruptible(&sp->port.info->delta_msr_wait);
}
static _INLINE_ void ser_handle_port(struct wch_ser_port *sp, unsigned char iir)
{
unsigned char lsr = READ_UART_LSR(sp);
unsigned char msr = 0;
if (lsr == 0xff) {
lsr = 0x01;
}
if ((iir == UART_IIR_RLSI) || (iir == UART_IIR_CTO) || (iir == UART_IIR_RDI)) {
ser_receive_chars(sp, &lsr, iir);
}
if ((iir == UART_IIR_THRI) && (lsr & UART_LSR_THRE)) {
ser_transmit_chars(sp);
}
msr = READ_UART_MSR(sp);
if (msr & UART_MSR_ANY_DELTA) {
ser_check_modem_status(sp, msr);
}
}
static struct tty_operations wch_tty_ops = {
.open = ser_open,
.close = ser_close,
.write = ser_write,
.put_char = ser_put_char,
.flush_chars = ser_flush_chars,
.write_room = ser_write_room,
.chars_in_buffer = ser_chars_in_buffer,
.flush_buffer = ser_flush_buffer,
.ioctl = ser_ioctl,
.throttle = ser_throttle,
.unthrottle = ser_unthrottle,
.send_xchar = ser_send_xchar,
.set_termios = ser_set_termios,
.stop = ser_stop,
.start = ser_start,
.hangup = ser_hangup,
.break_ctl = ser_break_ctl,
.wait_until_sent = ser_wait_until_sent,
.tiocmget = ser_tiocmget,
.tiocmset = ser_tiocmset,
};
extern int wch_ser_register_driver(struct ser_driver *drv)
{
struct tty_driver *normal = NULL;
int i;
int ret = 0;
#if WCH_DBG
printk("%s : %s\n", __FILE__, __FUNCTION__);
#endif
drv->nr = wch_ser_port_total_cnt;
drv->state = kmalloc(sizeof(struct ser_state) * drv->nr, GFP_KERNEL);
ret = -ENOMEM;
if (!drv->state) {
printk("WCH Error: Allocate memory fail !\n\n");
goto out;
}
memset(drv->state, 0, sizeof(struct ser_state) * drv->nr);
normal = tty_alloc_driver(drv->nr, TTY_DRIVER_REAL_RAW);
if (!normal) {
printk("WCH Error: Allocate tty driver fail !\n\n");
goto out;
}
drv->tty_driver = normal;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
normal->num = wch_ser_port_total_cnt;
normal->type = TTY_DRIVER_TYPE_SERIAL;
normal->subtype = SERIAL_TYPE_NORMAL;
normal->init_termios = tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_iflag = 0;
normal->driver_state = drv;
tty_set_operations(normal, &wch_tty_ops);
for (i = 0; i < drv->nr; i++) {
struct ser_state *state = drv->state + i;
if (!state) {
ret = -1;
printk("WCH Error: Memory error !\n\n");
goto out;
}
state->close_delay = 5 * HZ / 100;
state->closing_wait = 3 * HZ;
tty_port_init(&state->port0);
sema_init(&state->sem, 1);
}
kref_init(&normal->kref);
ret = tty_register_driver(normal);
if (ret < 0) {
printk("WCH Error: Register tty driver fail !\n\n");
goto out;
}
out:
if (ret < 0) {
tty_driver_kref_put(normal);
kfree(drv->state);
}
return (ret);
}
extern void wch_ser_unregister_driver(struct ser_driver *drv)
{
struct tty_driver *normal;
#if WCH_DBG
printk("%s : %s\n", __FILE__, __FUNCTION__);
#endif
normal = drv->tty_driver;
if (!normal) {
return;
}
tty_unregister_driver(normal);
tty_driver_kref_put(normal);
drv->tty_driver = NULL;
if (drv->state) {
kfree(drv->state);
}
}
static void wch_ser_request_io(struct ser_port *port)
{
struct wch_ser_port *sp = (struct wch_ser_port *)port;
switch (sp->port.iotype) {
case WCH_UPIO_PORT:
request_region(sp->port.iobase, WCH_SER_ADDRESS_LENGTH, "wch_ser");
break;
case WCH_UPIO_MEM:
break;
default:
break;
}
}
static void wch_ser_configure_port(struct ser_driver *drv, struct ser_state *state, struct ser_port *port)
{
unsigned long flags;
if (!port->iobase) {
return;
}
flags = WCH_UART_CONFIG_TYPE;
if (port->type != PORT_UNKNOWN) {
wch_ser_request_io(port);
spin_lock_irqsave(&port->lock, flags);
wch_ser_set_mctrl(port, 0);
spin_unlock_irqrestore(&port->lock, flags);
}
}
static int wch_ser_add_one_port(struct ser_driver *drv, struct ser_port *port)
{
struct ser_state *state = NULL;
int ret = 0;
if (port->line >= drv->nr) {
return -EINVAL;
}
state = drv->state + port->line;
down(&ser_port_sem);
if (state->port) {
ret = -EINVAL;
goto out;
}
state->port = port;
port->info = state->info;
// port->state = state;
drv->tty_driver->ports[port->line] = &state->port0;
wch_ser_configure_port(drv, state, port);
out:
up(&ser_port_sem);
return ret;
}
extern int wch_ser_register_ports(struct ser_driver *drv)
{
int i;
int ret;
#if WCH_DBG
printk("%s : %s\n", __FILE__, __FUNCTION__);
#endif
for (i = 0; i < wch_ser_port_total_cnt; i++) {
struct wch_ser_port *sp = &wch_ser_table[i];
if (!sp) {
return -1;
}
sp->port.line = i;
{
timer_setup(&sp->timer, wch_ser_timeout, 0);
sp->mcr_mask = ~0;
sp->mcr_force = 0;
ret = wch_ser_add_one_port(drv, &sp->port);
if (ret != 0) {
return ret;
}
}
#if WCH_DBG
printk("Setup ttyWCH%d - PCIe port: port %lx, irq %d, type %d\n", sp->port.line, sp->port.iobase, sp->port.irq,
sp->port.iotype);
#endif
}
return 0;
}
static void wch_ser_release_io(struct ser_port *port)
{
struct wch_ser_port *sp = (struct wch_ser_port *)port;
switch (sp->port.iotype) {
case WCH_UPIO_PORT:
release_region(sp->port.iobase, WCH_SER_ADDRESS_LENGTH);
break;
case WCH_UPIO_MEM:
break;
default:
break;
}
}
static void wch_ser_unconfigure_port(struct ser_driver *drv, struct ser_state *state)
{
struct ser_port *port = state->port;
struct ser_info *info = state->info;
if (info && info->tty) {
tty_hangup(info->tty);
}
down(&state->sem);
state->info = NULL;
if (port->type != PORT_UNKNOWN) {
wch_ser_release_io(port);
}
port->type = PORT_UNKNOWN;
if (info) {
tasklet_kill(&info->tlet);
kfree(info);
}
up(&state->sem);
}
static int wch_ser_remove_one_port(struct ser_driver *drv, struct ser_port *port)
{
struct ser_state *state = drv->state + port->line;
if (state->port != port) {
printk("WCH Info : Removing wrong port: %p != %p\n\n", state->port, port);
}
down(&ser_port_sem);
wch_ser_unconfigure_port(drv, state);
state->port = NULL;
drv->tty_driver->ports[port->line] = NULL;
up(&ser_port_sem);
return 0;
}
extern void wch_ser_unregister_ports(struct ser_driver *drv)
{
int i;
#if WCH_DBG
printk("%s : %s\n", __FILE__, __FUNCTION__);
#endif
for (i = 0; i < wch_ser_port_total_cnt; i++) {
struct wch_ser_port *sp = &wch_ser_table[i];
if (sp->port.iobase) {
wch_ser_remove_one_port(drv, &sp->port);
}
}
}
extern int wch_ser_interrupt(struct wch_board *sb, struct wch_ser_port *first_sp)
{
struct wch_ser_port *sp;
int i;
int max;
unsigned long irqbits;
unsigned char ch438irqbits;
unsigned long bits;
int pass_counter = 0;
unsigned char iir;
max = sb->ser_ports;
if ((first_sp->port.port_flag & PORTFLAG_REMAP) == PORTFLAG_REMAP) { // CH352_2S CH352_1S1P
while (1) {
for (i = 0; i < max; i++) {
sp = first_sp + i;
if (!sp->port.iobase) {
continue;
}
iir = READ_UART_IIR(sp) & 0x0f;
if (iir & UART_IIR_NO_INT) {
continue;
} else {
spin_lock(&sp->port.lock);
ser_handle_port(sp, iir);
spin_unlock(&sp->port.lock);
}
}
if (pass_counter++ > INTERRUPT_COUNT) {
break;
}
}
} else if ((first_sp->port.pb_info.vendor_id == VENDOR_ID_WCH_PCI) &&
(first_sp->port.pb_info.device_id == DEVICE_ID_WCH_CH353_4S)) { // CH353_4S
while (1) {
irqbits = READ_INTERRUPT_VECTOR_BYTE(first_sp) & first_sp->port.vector_mask;
if (irqbits == 0x0000) {
break;
}
for (i = 0, bits = 1; i < max; i++, bits <<= 1) {
if (i == 0x02) {
bits <<= 2;
}
if (!(bits & irqbits)) {
continue;
}
sp = first_sp + i;
iir = READ_UART_IIR(sp) & 0x0f;
if (iir & UART_IIR_NO_INT) {
continue;
} else {
spin_lock(&sp->port.lock);
ser_handle_port(sp, iir);
spin_unlock(&sp->port.lock);
}
}
if (pass_counter++ > INTERRUPT_COUNT) {
break;
}
}
} else if ((first_sp->port.pb_info.vendor_id == VENDOR_ID_WCH_PCI) &&
(first_sp->port.pb_info.device_id == DEVICE_ID_WCH_CH359_16S)) { // CH359_16S
while (1) {
irqbits = READ_INTERRUPT_VECTOR_WORD(first_sp) & first_sp->port.vector_mask;
if (irqbits == 0x0000) {
break;
}
for (i = 0, bits = 1; i < max; i++, bits <<= 1) {
if (!(bits & irqbits)) {
continue;
}
sp = first_sp + i;
iir = READ_UART_IIR(sp) & 0x0f;
if (iir & UART_IIR_NO_INT) {
continue;
} else {
spin_lock(&sp->port.lock);
ser_handle_port(sp, iir);
spin_unlock(&sp->port.lock);
}
}
if (pass_counter++ > INTERRUPT_COUNT) {
break;
}
}
} else if ((first_sp->port.pb_info.vendor_id == VENDOR_ID_WCH_PCIE &&
first_sp->port.pb_info.device_id == DEVICE_ID_WCH_CH384_28S) ||
(first_sp->port.pb_info.vendor_id == VENDOR_ID_WCH_PCIE &&
first_sp->port.pb_info.device_id == DEVICE_ID_WCH_CH384_8S)) { // CH384_8S CH384_28S
while (1) {
irqbits = READ_INTERRUPT_VECTOR_DWORD(first_sp) & first_sp->port.vector_mask;
if ((irqbits & 0x80000000) != 0 && (irqbits & 0x40000000) != 0 && (irqbits & 0x20000000) != 0 &&
(irqbits & 0x00000100) == 0 && (irqbits & 0x00000200) == 0 && (irqbits & 0x00000400) == 0 &&
(irqbits & 0x00000800) == 0) {
break;
}
/* handle CH438 #3 */
if ((irqbits & 0x80000000) == 0) {
sp = first_sp + 0x14;
ch438irqbits = READ_INTERRUPT_VECTOR_BYTE(sp) & sp->port.vector_mask;
if (ch438irqbits != 0) {
for (i = 0; i < 0x08; i++) {
if (ch438irqbits & (1 << i)) {
sp += i;
iir = READ_UART_IIR(sp) & 0x0f;
if (iir & UART_IIR_NO_INT) {
continue;
} else {
spin_lock(&sp->port.lock);
ser_handle_port(sp, iir);
spin_unlock(&sp->port.lock);
}
}
}
}
}
/* handle CH438 #2 */
if ((irqbits & 0x40000000) == 0) {
sp = first_sp + 0x0C;
ch438irqbits = READ_INTERRUPT_VECTOR_BYTE(sp) & sp->port.vector_mask;
if (ch438irqbits != 0) {
for (i = 0; i < 0x08; i++) {
if (ch438irqbits & (1 << i)) {
sp += i;
iir = READ_UART_IIR(sp) & 0x0f;
if (iir & UART_IIR_NO_INT) {
continue;
} else {
spin_lock(&sp->port.lock);
ser_handle_port(sp, iir);
spin_unlock(&sp->port.lock);
}
}
}
}
}
/* handle CH438 #1 */
if ((irqbits & 0x20000000) == 0) {
/* CH384_28S */
if (first_sp->port.pb_info.device_id == DEVICE_ID_WCH_CH384_28S) {
sp = first_sp + 0x04;
} else {
/* CH384_8S */
sp = first_sp;
}
ch438irqbits = READ_INTERRUPT_VECTOR_BYTE(sp) & sp->port.vector_mask;
if (ch438irqbits != 0) {
for (i = 0; i < 0x08; i++) {
if (ch438irqbits & (1 << i)) {
sp += i;
iir = READ_UART_IIR(sp) & 0x0f;
if (iir & UART_IIR_NO_INT) {
continue;
} else {
spin_lock(&sp->port.lock);
ser_handle_port(sp, iir);
spin_unlock(&sp->port.lock);
}
}
}
}
}
if ((irqbits & 0x00000100) == 0x00000100) {
sp = first_sp;
iir = READ_UART_IIR(sp) & 0x0f;
if (iir & UART_IIR_NO_INT) {
continue;
} else {
spin_lock(&sp->port.lock);
ser_handle_port(sp, iir);
spin_unlock(&sp->port.lock);
}
}
if ((irqbits & 0x00000200) == 0x00000200) {
sp = first_sp + 0x01;
iir = READ_UART_IIR(sp) & 0x0f;
if (iir & UART_IIR_NO_INT) {
continue;
} else {
spin_lock(&sp->port.lock);
ser_handle_port(sp, iir);
spin_unlock(&sp->port.lock);
}
}
if ((irqbits & 0x00000400) == 0x00000400) {
sp = first_sp + 0x02;
iir = READ_UART_IIR(sp) & 0x0f;
if (iir & UART_IIR_NO_INT) {
continue;
} else {
spin_lock(&sp->port.lock);
ser_handle_port(sp, iir);
spin_unlock(&sp->port.lock);
}
}
if ((irqbits & 0x00000800) == 0x00000800) {
sp = first_sp + 0x03;
iir = READ_UART_IIR(sp) & 0x0f;
if (iir & UART_IIR_NO_INT) {
continue;
} else {
spin_lock(&sp->port.lock);
ser_handle_port(sp, iir);
spin_unlock(&sp->port.lock);
}
}
if (pass_counter++ > INTERRUPT_COUNT) {
break;
}
}
} else if ((first_sp->port.pb_info.vendor_id == VENDOR_ID_WCH_PCI) &&
(first_sp->port.pb_info.device_id == DEVICE_ID_WCH_CH365_32S)) {
while (1) {
if ((inb(first_sp->port.chip_iobase + 0xF8) & 0x04) != 0x04) {
break;
}
irqbits = inb(first_sp->port.chip_iobase) & first_sp->port.vector_mask;
if (irqbits == 0xFF) {
break;
}
if ((irqbits & 0x00000010) == 0) { // first ch438 irq
for (i = 0, bits = 1; i < 0x08; i++, bits <<= 1) {
sp = first_sp + i;
ch438irqbits = readb(sp->port.board_membase + 0x100 + 0x4F) & sp->port.vector_mask;
if (ch438irqbits == 0x00000000) {
break;
}
if (!(bits & ch438irqbits)) {
continue;
} else {
break;
}
}
iir = READ_UART_IIR(sp) & 0x0f;
if (iir & UART_IIR_NO_INT) {
continue;
} else {
spin_lock(&sp->port.lock);
ser_handle_port(sp, iir);
spin_unlock(&sp->port.lock);
}
outb(inb(sp->port.chip_iobase + 0xF8) & 0xFB, sp->port.chip_iobase + 0xF8);
} else if ((irqbits & 0x00000020) == 0) {
for (i = 0, bits = 1; i < 0x08; i++, bits <<= 1) {
sp = first_sp + i + 0x08;
ch438irqbits = readb(sp->port.board_membase + 0x180 + 0x4F) & sp->port.vector_mask;
if (ch438irqbits == 0x00000000) {
break;
}
if (!(bits & ch438irqbits)) {
continue;
} else {
break;
}
}
iir = READ_UART_IIR(sp) & 0x0f;
if (iir & UART_IIR_NO_INT) {
continue;
} else {
spin_lock(&sp->port.lock);
ser_handle_port(sp, iir);
spin_unlock(&sp->port.lock);
}
outb(inb(sp->port.chip_iobase + 0xF8) & 0xFB, sp->port.chip_iobase + 0xF8);
} else if ((irqbits & 0x00000040) == 0) {
for (i = 0, bits = 1; i < 0x08; i++, bits <<= 1) {
sp = first_sp + i + 0x10;
ch438irqbits = readb(sp->port.board_membase + 0x200 + 0x4F) & sp->port.vector_mask;
if (ch438irqbits == 0x00000000) {
break;
}
if (!(bits & ch438irqbits)) {
continue;
} else {
break;
}
}
iir = READ_UART_IIR(sp) & 0x0f;
if (iir & UART_IIR_NO_INT) {
continue;
} else {
spin_lock(&sp->port.lock);
ser_handle_port(sp, iir);
spin_unlock(&sp->port.lock);
}
outb(inb(sp->port.chip_iobase + 0xF8) & 0xFB, sp->port.chip_iobase + 0xF8);
} else if ((irqbits & 0x00000080) == 0) {
for (i = 0, bits = 1; i < 0x08; i++, bits <<= 1) {
sp = first_sp + i + 0x18;
ch438irqbits = readb(sp->port.board_membase + 0x280 + 0x4F) & sp->port.vector_mask;
if (ch438irqbits == 0x00000000) {
break;
}
if (!(bits & ch438irqbits)) {
continue;
} else {
break;
}
}
iir = READ_UART_IIR(sp) & 0x0f;
if (iir & UART_IIR_NO_INT) {
continue;
} else {
spin_lock(&sp->port.lock);
ser_handle_port(sp, iir);
spin_unlock(&sp->port.lock);
}
outb(inb(sp->port.chip_iobase + 0xF8) & 0xFB, sp->port.chip_iobase + 0xF8);
} else {
break;
}
if (pass_counter++ > INTERRUPT_COUNT) {
break;
}
}
} else { // CH353_2S1P CH353_2S1PAR CH355_4S CH356_4S1P CH356_8S CH358_4S1P CH358_8S CH382_2S1P CH384_4S1P
while (1) {
irqbits = READ_INTERRUPT_VECTOR_BYTE(first_sp) & first_sp->port.vector_mask;
if (irqbits == 0x0000) {
break;
}
for (i = 0, bits = 1; i < max; i++, bits <<= 1) {
if (!(bits & irqbits)) {
continue;
}
sp = first_sp + i;
iir = READ_UART_IIR(sp) & 0x0f;
if (iir & UART_IIR_NO_INT) {
continue;
} else {
spin_lock(&sp->port.lock);
ser_handle_port(sp, iir);
spin_unlock(&sp->port.lock);
}
}
if (pass_counter++ > INTERRUPT_COUNT) {
break;
}
}
}
return 0;
}