diff --git a/drivers/Makefile b/drivers/Makefile index 48a61ea6..d7720c8c 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -45,6 +45,9 @@ obj-m += nvpps/ obj-m += nvpmodel/ ifdef CONFIG_PCI obj-m += pci/ +ifdef CONFIG_TTY +obj-m += tty/ +endif endif obj-m += pinctrl/ obj-m += platform/tegra/ @@ -72,4 +75,3 @@ obj-m += media/ endif obj-m += staging/platform/tegra/gte/ obj-m += staging/platform/tegra/gte_test/ - diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile new file mode 100644 index 00000000..dd4ccfa1 --- /dev/null +++ b/drivers/tty/Makefile @@ -0,0 +1 @@ +obj-m += serial/ diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile new file mode 100644 index 00000000..f472d69b --- /dev/null +++ b/drivers/tty/serial/Makefile @@ -0,0 +1 @@ +obj-m += wch_35x/ diff --git a/drivers/tty/serial/wch_35x/Makefile b/drivers/tty/serial/wch_35x/Makefile new file mode 100644 index 00000000..7dc6067f --- /dev/null +++ b/drivers/tty/serial/wch_35x/Makefile @@ -0,0 +1,4 @@ +DRIVERNAME := wch + +obj-m += $(DRIVERNAME).o +$(DRIVERNAME)-m := wch_devtable.o wch_serial.o wch_main.o wch_pre_main.o diff --git a/drivers/tty/serial/wch_35x/wch_common.h b/drivers/tty/serial/wch_35x/wch_common.h new file mode 100644 index 00000000..4de18085 --- /dev/null +++ b/drivers/tty/serial/wch_35x/wch_common.h @@ -0,0 +1,725 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#ifndef WCH_COMMON +#define WCH_COMMON + +#ifdef MODVERSIONS +#ifndef MODULE +#define MODULE +#endif +#endif + +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef PCI_ANY_ID +#define PCI_ANY_ID (~0) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include + +#include + +/*------------------------------------------------------------------------------- + + for wch_main.c + +-------------------------------------------------------------------------------*/ +/******************************************************* +WCH driver information +*******************************************************/ +#define WCH_DRIVER_VERSION "1.24" +#define WCH_DRIVER_DATE "2023.02" +#define WCH_DRIVER_AUTHOR "WCH GROUP" +#define WCH_DRIVER_DESC "WCH Multi-I/O Board Driver Module" + +#define WCH_TTY_MAJOR 205 + +/******************************************************* +WCH driver debug +*******************************************************/ +#define WCH_DBG 0 +#define WCH_DBG_SERIAL 0 +#define WCH_DBG_BOARD 1 +#define WCH_DBG_SERPORT 1 + +#if WCH_DBG_SERIAL +static void dbg_serial(const char *fmt, ...) +{ + char buffer[256]; + int i, len; + va_list arglist; + + va_start(arglist, fmt); + vsprintf(&buffer[0], fmt, arglist); + va_end(arglist); + len = strlen(buffer); + for (i = 0; i < len; i++) { + outb(buffer[i], 0x3F8); + mdelay(1); + } +} +#endif + +/******************************************************* + * WCH board information + *******************************************************/ + +// for vid pid subvid subpid +#define VENDOR_ID_WCH_PCIE 0x1C00 +#define SUB_VENDOR_ID_WCH_PCIE 0x1C00 +#define VENDOR_ID_WCH_PCI 0x4348 +#define SUB_VENDOR_ID_WCH_PCI 0x4348 +#define VENDOR_ID_WCH_CH351 0x1C00 +#define SUB_VENDOR_ID_WCH_CH351 0x1C00 +#define DEVICE_ID_WCH_CH351_2S 0x2273 +#define SUB_DEVICE_ID_WCH_CH351_2S 0x2273 +#define DEVICE_ID_WCH_CH352_1S1P 0x5053 +#define SUB_DEVICE_ID_WCH_CH352_1S1P 0x5053 +#define DEVICE_ID_WCH_CH352_2S 0x3253 +#define SUB_DEVICE_ID_WCH_CH352_2S 0x3253 +#define DEVICE_ID_WCH_CH353_4S 0x3453 +#define SUB_DEVICE_ID_WCH_CH353_4S 0x3453 +#define DEVICE_ID_WCH_CH353_2S1P 0x7053 +#define SUB_DEVICE_ID_WCH_CH353_2S1P 0x3253 +#define DEVICE_ID_WCH_CH353_2S1PAR 0x5046 +#define SUB_DEVICE_ID_WCH_CH353_2S1PAR 0x5046 +#define DEVICE_ID_WCH_CH355_4S 0x7173 +#define SUB_DEVICE_ID_WCH_CH355_4S 0x3473 +#define DEVICE_ID_WCH_CH356_4S1P 0x7073 +#define SUB_DEVICE_ID_WCH_CH356_4S1P 0x3473 +#define DEVICE_ID_WCH_CH356_6S 0x3873 +#define SUB_DEVICE_ID_WCH_CH356_6S 0x3873 +#define DEVICE_ID_WCH_CH356_8S 0x3853 +#define SUB_DEVICE_ID_WCH_CH356_8S 0x3853 +#define DEVICE_ID_WCH_CH357_4S 0x5334 +#define SUB_DEVICE_ID_WCH_CH357_4S 0x5053 +#define DEVICE_ID_WCH_CH358_4S1P 0x5334 +#define SUB_DEVICE_ID_WCH_CH358_4S1P 0x5334 +#define DEVICE_ID_WCH_CH358_8S 0x5338 +#define SUB_DEVICE_ID_WCH_CH358_8S 0x5338 +#define DEVICE_ID_WCH_CH359_16S 0x5838 +#define SUB_DEVICE_ID_WCH_CH359_16S 0x5838 +#define DEVICE_ID_WCH_CH382_2S 0x3253 +#define SUB_DEVICE_ID_WCH_CH382_2S 0x3253 +#define DEVICE_ID_WCH_CH382_2S1P 0x3250 +#define SUB_DEVICE_ID_WCH_CH382_2S1P 0x3250 +#define DEVICE_ID_WCH_CH384_4S 0x3470 +#define SUB_DEVICE_ID_WCH_CH384_4S 0x3470 +#define DEVICE_ID_WCH_CH384_4S1P 0x3450 +#define SUB_DEVICE_ID_WCH_CH384_4S1P 0x3450 +#define DEVICE_ID_WCH_CH384_8S 0x3853 +#define SUB_DEVICE_ID_WCH_CH384_8S 0x3853 +#define DEVICE_ID_WCH_CH384_28S 0x4353 +#define SUB_DEVICE_ID_WCH_CH384_28S 0x4353 +#define DEVICE_ID_WCH_CH365_32S 0x5049 +#define SUB_DEVICE_ID_WCH_CH365_32S 0x5049 + +// for chip_flag +enum { + NONE_BOARD = 0, + WCH_BOARD_CH351_2S, + WCH_BOARD_CH352_2S, + WCH_BOARD_CH352_1S1P, + WCH_BOARD_CH353_4S, + WCH_BOARD_CH353_2S1P, + WCH_BOARD_CH353_2S1PAR, + WCH_BOARD_CH355_4S, + WCH_BOARD_CH356_4S1P, + WCH_BOARD_CH356_6S, + WCH_BOARD_CH356_8S, + WCH_BOARD_CH357_4S, + WCH_BOARD_CH358_4S1P, + WCH_BOARD_CH358_8S, + WCH_BOARD_CH359_16S, + WCH_BOARD_CH382_2S, + WCH_BOARD_CH382_2S1P, + WCH_BOARD_CH384_4S, + WCH_BOARD_CH384_4S1P, + WCH_BOARD_CH384_8S, + WCH_BOARD_CH384_28S, + WCH_BOARD_CH365_32S, +}; + +// for board_flag +#define BOARDFLAG_NONE 0x0000 +#define BOARDFLAG_REMAP 0x0001 +#define BOARDFLAG_CH365_04_PORTS 0x0002 +#define BOARDFLAG_CH365_08_PORTS 0x0004 +#define BOARDFLAG_CH365_32_PORTS 0x0008 +#define BOARDFLAG_CH384_8_PORTS 0x0010 +#define BOARDFLAG_CH384_28_PORTS 0x0020 + +// for port_flag +#define PORTFLAG_NONE 0x0000 +#define PORTFLAG_REMAP 0x0001 +#define PORTFLAG_CH365_04_PORTS 0x0002 +#define PORTFLAG_CH365_08_PORTS 0x0004 +#define PORTFLAG_CH365_32_PORTS 0x0008 +#define PORTFLAG_CH384_8_PORTS 0x0010 +#define PORTFLAG_CH384_28_PORTS 0x0020 + +// board info +#define WCH_BOARDS_MAX 0x08 +#define WCH_PORT_ONBOARD_MAX 0x20 +#define WCH_SER_TOTAL_MAX 0x100 + +extern int wch_ser_port_total_cnt; + +/******************************************************* + * uart information + *******************************************************/ + +// external crystal freq +#define CRYSTAL_FREQ 22118400 + +// uart fifo info +#define CH351_FIFOSIZE_16 16 +#define CH351_TRIGGER_LEVEL_16FIFO_01 1 +#define CH351_TRIGGER_LEVEL_16FIFO_04 4 +#define CH351_TRIGGER_LEVEL_16FIFO_08 8 +#define CH351_TRIGGER_LEVEL_16FIFO_14 14 + +#define CH352_FIFOSIZE_16 16 +#define CH352_TRIGGER_LEVEL_16FIFO_01 1 +#define CH352_TRIGGER_LEVEL_16FIFO_04 4 +#define CH352_TRIGGER_LEVEL_16FIFO_08 8 +#define CH352_TRIGGER_LEVEL_16FIFO_14 14 + +#define CH353_FIFOSIZE_16 16 +#define CH353_TRIGGER_LEVEL_16FIFO_01 1 +#define CH353_TRIGGER_LEVEL_16FIFO_04 4 +#define CH353_TRIGGER_LEVEL_16FIFO_08 8 +#define CH353_TRIGGER_LEVEL_16FIFO_14 14 + +#define CH355_FIFOSIZE_16 16 +#define CH355_TRIGGER_LEVEL_16FIFO_01 1 +#define CH355_TRIGGER_LEVEL_16FIFO_04 4 +#define CH355_TRIGGER_LEVEL_16FIFO_08 8 +#define CH355_TRIGGER_LEVEL_16FIFO_14 14 + +#define CH356_FIFOSIZE_16 16 +#define CH356_TRIGGER_LEVEL_16FIFO_01 1 +#define CH356_TRIGGER_LEVEL_16FIFO_04 4 +#define CH356_TRIGGER_LEVEL_16FIFO_08 8 +#define CH356_TRIGGER_LEVEL_16FIFO_14 14 + +#define CH357_FIFOSIZE_128 128 +#define CH357_TRIGGER_LEVEL_128FIFO_01 1 +#define CH357_TRIGGER_LEVEL_128FIFO_32 32 +#define CH357_TRIGGER_LEVEL_128FIFO_64 64 +#define CH357_TRIGGER_LEVEL_128FIFO_112 112 + +#define CH358_FIFOSIZE_128 128 +#define CH358_TRIGGER_LEVEL_128FIFO_01 1 +#define CH358_TRIGGER_LEVEL_128FIFO_32 32 +#define CH358_TRIGGER_LEVEL_128FIFO_64 64 +#define CH358_TRIGGER_LEVEL_128FIFO_112 112 + +#define CH359_FIFOSIZE_128 128 +#define CH359_TRIGGER_LEVEL_128FIFO_01 1 +#define CH359_TRIGGER_LEVEL_128FIFO_32 32 +#define CH359_TRIGGER_LEVEL_128FIFO_64 64 +#define CH359_TRIGGER_LEVEL_128FIFO_112 112 + +#define CH382_FIFOSIZE_256 256 +#define CH382_TRIGGER_LEVEL_256FIFO_01 1 +#define CH382_TRIGGER_LEVEL_256FIFO_32 32 +#define CH382_TRIGGER_LEVEL_256FIFO_128 128 +#define CH382_TRIGGER_LEVEL_256FIFO_224 224 + +#define CH384_FIFOSIZE_256 256 +#define CH384_TRIGGER_LEVEL_256FIFO_01 1 +#define CH384_TRIGGER_LEVEL_256FIFO_32 32 +#define CH384_TRIGGER_LEVEL_256FIFO_128 128 +#define CH384_TRIGGER_LEVEL_256FIFO_224 224 + +#define CH432_FIFOSIZE_16 16 +#define CH432_TRIGGER_LEVEL_16FIFO_01 1 +#define CH432_TRIGGER_LEVEL_16FIFO_04 4 +#define CH432_TRIGGER_LEVEL_16FIFO_08 8 +#define CH432_TRIGGER_LEVEL_16FIFO_14 14 + +#define CH438_FIFOSIZE_128 128 +#define CH438_TRIGGER_LEVEL_128FIFO_01 1 +#define CH438_TRIGGER_LEVEL_128FIFO_16 16 +#define CH438_TRIGGER_LEVEL_128FIFO_64 64 +#define CH438_TRIGGER_LEVEL_128FIFO_112 112 + +// uart fifo setup +#define CH351_FIFOSIZE_SET CH351_FIFOSIZE_16 +#define CH351_TRIGGER_LEVEL_SET CH351_TRIGGER_LEVEL_16FIFO_08 + +#define CH352_FIFOSIZE_SET CH352_FIFOSIZE_16 +#define CH352_TRIGGER_LEVEL_SET CH352_TRIGGER_LEVEL_16FIFO_08 + +#define CH353_FIFOSIZE_SET CH353_FIFOSIZE_16 +#define CH353_TRIGGER_LEVEL_SET CH353_TRIGGER_LEVEL_16FIFO_08 + +#define CH355_FIFOSIZE_SET CH355_FIFOSIZE_16 +#define CH355_TRIGGER_LEVEL_SET CH355_TRIGGER_LEVEL_16FIFO_08 + +#define CH356_FIFOSIZE_SET CH356_FIFOSIZE_16 +#define CH356_TRIGGER_LEVEL_SET CH356_TRIGGER_LEVEL_16FIFO_08 + +#define CH357_FIFOSIZE_SET CH357_FIFOSIZE_128 +#define CH357_TRIGGER_LEVEL_SET CH357_TRIGGER_LEVEL_128FIFO_64 + +#define CH358_FIFOSIZE_SET CH358_FIFOSIZE_128 +#define CH358_TRIGGER_LEVEL_SET CH358_TRIGGER_LEVEL_128FIFO_64 + +#define CH359_FIFOSIZE_SET CH359_FIFOSIZE_128 +#define CH359_TRIGGER_LEVEL_SET CH359_TRIGGER_LEVEL_128FIFO_64 + +#define CH382_FIFOSIZE_SET CH382_FIFOSIZE_256 +#define CH382_TRIGGER_LEVEL_SET CH382_TRIGGER_LEVEL_256FIFO_128 + +#define CH384_FIFOSIZE_SET CH384_FIFOSIZE_256 +#define CH384_TRIGGER_LEVEL_SET CH384_TRIGGER_LEVEL_256FIFO_128 + +#define CH432_FIFOSIZE_SET CH432_FIFOSIZE_16 +#define CH432_TRIGGER_LEVEL_SET CH432_TRIGGER_LEVEL_16FIFO_08 + +#define CH438_FIFOSIZE_SET CH438_FIFOSIZE_128 +#define CH438_TRIGGER_LEVEL_SET CH438_TRIGGER_LEVEL_128FIFO_16 + +#define UART_TRIGGER00_FCR 0x00 +#define UART_TRIGGER01_FCR 0x40 +#define UART_TRIGGER10_FCR 0x80 +#define UART_TRIGGER11_FCR 0xC0 + +#define UART_DEFAULT_FCR 0x00 + +#define DEFAULT_FIFOSIZE 1 +#define DEFAULT_TRIGGER_LEVEL 1 + +// register status info +#define UART_LSR_ERR_IN_RFIFO 0x80 +#define UART_MCR_AFE 0x20 +#define UART_IIR_CTO 0x0C + +// serial address length +#define WCH_SER_ADDRESS_LENGTH 0x08 + +// PCI configuration bar 0 ~ 5 +#define WCH_PCICFG_BAR_TOTAL 0x06 + +/******************************************************* + * miscellaneous Information + *******************************************************/ +#define INTERRUPT_COUNT 0x80 +#define WAKEUP_CHARS_SER 0x100 +#define WCHTERMIOS ktermios + +// for ser_port->setserial_flag +#define WCH_SER_BAUD_SETSERIAL 0x01 +#define WCH_SER_BAUD_NOTSETSER 0x00 + +/******************************************************* +struct define Information +*******************************************************/ + +// name length +#define WCH_BOARDNAME_LENGTH 0x0F +#define WCH_DRIVERVERSION_LENGTH 0x0F + +struct ser_port_info { + char board_name_info[WCH_BOARDNAME_LENGTH]; + unsigned int bus_number_info; + unsigned int dev_number_info; + unsigned int port_info; + unsigned int base_info; + unsigned int irq_info; +}; + +struct port { + char type; + + int bar1; + unsigned int offset1; + unsigned char length1; + + int bar2; + unsigned int offset2; + unsigned char length2; + + unsigned int chip_flag; +}; + +struct pci_board { + unsigned int vendor_id; + unsigned int device_id; + unsigned int sub_vendor_id; + unsigned int sub_device_id; + + unsigned int num_serport; + + unsigned int intr_vector_bar; + unsigned int intr_vector_offset; + unsigned int intr_vector_offset_1; + unsigned int intr_vector_offset_2; + unsigned int intr_vector_offset_3; + + char board_name[WCH_BOARDNAME_LENGTH]; + unsigned int board_flag; + + struct port port[WCH_PORT_ONBOARD_MAX]; +}; + +struct wch_board; +struct wch_ser_port; +struct wch_par_port; + +struct wch_board { + int board_enum; + int board_number; + unsigned int bus_number; + unsigned int dev_number; + + unsigned int ser_ports; + + unsigned int ser_port_index; + + unsigned long bar_addr[WCH_PCICFG_BAR_TOTAL]; + unsigned int irq; + void *board_membase; + unsigned int board_flag; + + unsigned int vector_mask; + struct pci_board pb_info; + struct pci_dev *pdev; + int (*ser_isr)(struct wch_board *, struct wch_ser_port *); +}; + +/*------------------------------------------------------------------------------- + + for wch_serial.c + +-------------------------------------------------------------------------------*/ +/******************************************************* + * ioctl user define + *******************************************************/ +#define WCH_IOCTL 0x900 +#define WCH_SER_DUMP_PORT_INFO (WCH_IOCTL + 50) +#define WCH_SER_DUMP_PORT_PERF (WCH_IOCTL + 51) +#define WCH_SER_DUMP_DRIVER_VER (WCH_IOCTL + 52) + +/******************************************************* + * serial define + *******************************************************/ +#define PORT_SER_UNKNOWN 0x00 +#define PORT_SER_8250 0x01 +#define PORT_SER_16450 0x02 +#define PORT_SER_16550 0x03 +#define PORT_SER_16550A 0x04 +#define PORT_SER_CIRRUS 0x05 +#define PORT_SER_16650 0x06 +#define PORT_SER_16650V2 0x07 +#define PORT_SER_16750 0x08 +#define PORT_SER_MAX_UART 0x08 + +#define WCH_USF_CLOSING_WAIT_INF (0) +#define WCH_USF_CLOSING_WAIT_NONE (65535) +#define WCH_UART_CONFIG_TYPE (1 << 0) +#define WCH_UART_CONFIG_IRQ (1 << 1) + +#define WCH_UART_XMIT_SIZE 0x1000 + +#define ser_circ_empty(circ) ((circ)->head == (circ)->tail) +#define ser_circ_clear(circ) ((circ)->head = (circ)->tail = 0) +#define ser_circ_chars_pending(circ) (CIRC_CNT((circ)->head, (circ)->tail, WCH_UART_XMIT_SIZE)) +#define ser_circ_chars_free(circ) (CIRC_SPACE((circ)->head, (circ)->tail, WCH_UART_XMIT_SIZE)) +#define ser_tx_stopped(port) ((port)->info->tty->flow.stopped || (port)->info->tty->hw_stopped) + +#if defined(__i386__) && (defined(CONFIG_M386) || defined(CONFIG_M486)) +#define WCH_SERIAL_INLINE +#endif + +#ifdef WCH_SERIAL_INLINE +#define _INLINE_ inline +#else +#define _INLINE_ +#endif + +#define WCH_UPIO_PORT (0) +#define WCH_UPIO_MEM (1) + +#define WCH_UPF_SAK (1 << 2) +#define WCH_UPF_SPD_MASK (0x1030) +#define WCH_UPF_SPD_HI (0x0010) +#define WCH_UPF_SPD_VHI (0x0020) +#define WCH_UPF_SPD_CUST (0x0030) +#define WCH_UPF_SPD_SHI (0x1000) +#define WCH_UPF_SPD_WARP (0x1010) +#define WCH_UPF_SKIP_TEST (1 << 6) +#define WCH_UPF_HARDPPS_CD (1 << 11) +#define WCH_UPF_LOW_LATENCY (1 << 13) +#define WCH_UPF_BUGGY_UART (1 << 14) +#define WCH_UPF_MAGIC_MULTIPLIER (1 << 16) + +#define WCH_UPF_CHANGE_MASK (0x17fff) +#define WCH_UPF_USR_MASK (WCH_UPF_SPD_MASK | WCH_UPF_LOW_LATENCY) + +#define WCH_UIF_CHECK_CD (1 << 25) +#define WCH_UIF_CTS_FLOW (1 << 26) + +#define WCH_UIF_NORMAL_ACTIVE (1 << 29) +#define WCH_UIF_INITIALIZED (1 << 31) + +#define WCH_ENABLE_MS(port, cflag) ((port)->flags & WCH_UPF_HARDPPS_CD || (cflag)&CRTSCTS || !((cflag)&CLOCAL)) + +#define WCH_SER_DEVNUM(x) ((x)->index) + +struct ser_info; +struct ser_port; + +struct ser_icount { + __u32 cts; + __u32 dsr; + __u32 rng; + __u32 dcd; + __u32 rx; + __u32 tx; + __u32 frame; + __u32 overrun; + __u32 parity; + __u32 brk; + __u32 buf_overrun; +}; + +struct ser_info { + struct tty_struct *tty; + struct circ_buf xmit; + unsigned int flags; + unsigned char *tmpbuf; + struct semaphore tmpbuf_sem; + int blocked_open; + struct tasklet_struct tlet; + + wait_queue_head_t open_wait; + wait_queue_head_t delta_msr_wait; +}; + +struct ser_driver { + const char *dev_name; + int major; + int minor; + int nr; + struct ser_state *state; + struct tty_driver *tty_driver; +}; + +struct ser_port { + spinlock_t lock; + void *port_membase; + void *board_membase; + unsigned long iobase; + unsigned int irq; + unsigned int uartclk; + unsigned int fifosize; + unsigned char x_char; + unsigned char iotype; + + unsigned int read_status_mask; + unsigned int ignore_status_mask; + struct ser_info *info; + struct ser_state *state; + struct ser_icount icount; + + unsigned int flags; + unsigned int mctrl; + unsigned int timeout; + unsigned int type; + unsigned int custom_divisor; + unsigned int line; + struct device *dev; + + int board_enum; + unsigned int bus_number; + unsigned int dev_number; + struct pci_board pb_info; + unsigned long vector; + unsigned int chip_iobase; + unsigned int vector_mask; + unsigned char chip_flag; + unsigned int port_flag; + unsigned int baud_base; + int rx_trigger; + bool bext1stport; + bool bspe1stport; + bool hardflow; + unsigned char ldisc_stop_rx; + + unsigned int setserial_flag; +}; + +struct ser_state { + struct tty_port port0; + unsigned int close_delay; + unsigned int closing_wait; + int count; + struct ser_info *info; + struct ser_port *port; + struct semaphore sem; +}; + +static inline int ser_handle_break(struct ser_port *port) +{ + struct ser_info *info = port->info; + + if (info->flags & WCH_UPF_SAK) { + do_SAK(info->tty); + } + return 0; +} + +static inline void ser_handle_dcd_change(struct ser_port *port, unsigned int status) +{ + struct ser_info *info = port->info; + + port->icount.dcd++; + + if (info->flags & WCH_UIF_CHECK_CD) { + if (status) { + wake_up_interruptible(&info->open_wait); + } else if (info->tty) { + tty_hangup(info->tty); + } + } +} + +#include + +static inline void ser_insert_buffer(struct ser_port *port, unsigned int status, unsigned int overrun, + unsigned char *buf, unsigned int count, unsigned char flag) +{ + struct tty_port *tty = &port->state->port0; + + if ((status & port->ignore_status_mask & ~overrun) == 0) { + if (tty_insert_flip_string_fixed_flag(tty, buf, flag, count) == 0) + ++port->icount.buf_overrun; + } + + if (status & ~port->ignore_status_mask & overrun) { + if (tty_insert_flip_char(tty, 0, TTY_OVERRUN) == 0) + ++port->icount.buf_overrun; + } +} + +static inline void ser_insert_char(struct ser_port *port, unsigned int status, unsigned int overrun, unsigned int ch, + unsigned int flag) +{ + struct tty_port *tty = &port->state->port0; + + if ((status & port->ignore_status_mask & ~overrun) == 0) { + if (tty_insert_flip_char(tty, ch, flag) == 0) + ++port->icount.buf_overrun; + } + + if (status & ~port->ignore_status_mask & overrun) { + if (tty_insert_flip_char(tty, 0, TTY_OVERRUN) == 0) + ++port->icount.buf_overrun; + } +} + + + +/******************************************************* + * wch serial port struct + *******************************************************/ +struct wch_ser_port { + struct ser_port port; + struct timer_list timer; + struct list_head list; + + unsigned int capabilities; + unsigned char ier; + unsigned char lcr; + unsigned char mcr; + unsigned char mcr_mask; + unsigned char mcr_force; + unsigned char lsr_break_flag; +}; + +/*------------------------------------------------------------------------------- + * function and variable extern + -------------------------------------------------------------------------------*/ +// wch_devtable.c +extern struct pci_board wch_pci_board_conf[]; +extern struct pci_device_id wch_pci_board_id[]; + +// wch_serial.c +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 *); +extern int wch_ser_interrupt(struct wch_board *, struct wch_ser_port *); + +// wch_main.c +extern struct wch_board wch_board_table[WCH_BOARDS_MAX]; +extern struct wch_ser_port wch_ser_table[WCH_SER_TOTAL_MAX + 1]; +extern int wch_35x_init(void); +extern void wch_35x_exit(void); +extern unsigned char ch365_32s; + +#endif diff --git a/drivers/tty/serial/wch_35x/wch_devtable.c b/drivers/tty/serial/wch_35x/wch_devtable.c new file mode 100644 index 00000000..a4076237 --- /dev/null +++ b/drivers/tty/serial/wch_35x/wch_devtable.c @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include "wch_common.h" + +#define PCIE_UART_MAX 28 + +struct pci_board wch_pci_board_conf[] = { + // NONE + { + // VenID DevID SubVenID SubSysID + 0x00, 0x00, 0x00, 0x00, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, "none", BOARDFLAG_NONE, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 'n', -1, 0, 0, -1, 0, 0, NONE_BOARD }, + }, + }, + + // CH351_2S + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_CH351, DEVICE_ID_WCH_CH351_2S, SUB_VENDOR_ID_WCH_CH351, SUB_DEVICE_ID_WCH_CH351_2S, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, "CH351_2S", BOARDFLAG_REMAP, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0, 8, -1, 0, 0, WCH_BOARD_CH351_2S }, + { 's', 1, 0, 8, -1, 0, 0, WCH_BOARD_CH351_2S }, + }, + }, + + // CH352_2S + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH352_2S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH352_2S, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, "CH352_2S", BOARDFLAG_REMAP, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0, 8, -1, 0, 0, WCH_BOARD_CH352_2S }, + { 's', 1, 0, 8, -1, 0, 0, WCH_BOARD_CH352_2S }, + }, + }, + + // CH352_1S1P + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH352_1S1P, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH352_1S1P, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 1, 0, 0x00, 0x00, 0x00, 0x00, "CH352_1S1P", BOARDFLAG_REMAP, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0, 8, -1, 0, 0, WCH_BOARD_CH352_1S1P }, + }, + }, + + // CH353_4S + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH353_4S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH353_4S, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 4, 3, 0x0F, 0x00, 0x00, 0x00, "CH353_4S", BOARDFLAG_NONE, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0, 8, -1, 0, 0, WCH_BOARD_CH353_4S }, + { 's', 1, 0, 8, -1, 0, 0, WCH_BOARD_CH353_4S }, + { 's', 2, 0, 8, -1, 0, 0, WCH_BOARD_CH353_4S }, + { 's', 3, 0, 8, -1, 0, 0, WCH_BOARD_CH353_4S }, + + }, + }, + + // CH353_2S1P + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH353_2S1P, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH353_2S1P, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 2, 3, 0x0F, 0x00, 0x00, 0x00, "CH353_2S1P", BOARDFLAG_NONE, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0, 8, -1, 0, 0, WCH_BOARD_CH353_2S1P }, + { 's', 1, 0, 8, -1, 0, 0, WCH_BOARD_CH353_2S1P }, + }, + }, + + // CH353_2S1PAR + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH353_2S1PAR, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH353_2S1PAR, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 2, 3, 0x0F, 0x00, 0x00, 0x00, "CH353_2S1PAR", BOARDFLAG_NONE, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0, 8, -1, 0, 0, WCH_BOARD_CH353_2S1PAR }, + { 's', 1, 0, 8, -1, 0, 0, WCH_BOARD_CH353_2S1PAR }, + }, + }, + + // CH355_4S + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH355_4S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH355_4S, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 4, 4, 0x20, 0x00, 0x00, 0x00, "CH355_4S", BOARDFLAG_NONE, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0, 8, -1, 0, 0, WCH_BOARD_CH355_4S }, + { 's', 1, 0, 8, -1, 0, 0, WCH_BOARD_CH355_4S }, + { 's', 2, 0, 8, -1, 0, 0, WCH_BOARD_CH355_4S }, + { 's', 3, 0, 8, -1, 0, 0, WCH_BOARD_CH355_4S }, + + }, + }, + + // CH356_4S1P + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH356_4S1P, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH356_4S1P, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 4, 4, 0x3F, 0x00, 0x00, 0x00, "CH356_4S1P", BOARDFLAG_NONE, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0, 8, -1, 0, 0, WCH_BOARD_CH356_4S1P }, + { 's', 1, 0, 8, -1, 0, 0, WCH_BOARD_CH356_4S1P }, + { 's', 2, 0, 8, -1, 0, 0, WCH_BOARD_CH356_4S1P }, + { 's', 3, 0, 8, -1, 0, 0, WCH_BOARD_CH356_4S1P }, + }, + }, + + // CH356_6S(CH356+CH432(1P)) + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH356_6S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH356_6S, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 6, 4, 0x3F, 0x00, 0x00, 0x00, "CH356_6S", BOARDFLAG_NONE, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0, 8, -1, 0, 0, WCH_BOARD_CH356_6S }, + { 's', 1, 0, 8, -1, 0, 0, WCH_BOARD_CH356_6S }, + { 's', 2, 0, 8, -1, 0, 0, WCH_BOARD_CH356_6S }, + { 's', 3, 0, 8, -1, 0, 0, WCH_BOARD_CH356_6S }, + { 's', 4, 0, 8, -1, 0, 0, WCH_BOARD_CH356_6S }, + { 's', 4, 0x08, 8, -1, 0, 0, WCH_BOARD_CH356_6S }, + }, + }, + + // CH356_8S(CH356+CH432(2P)) + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH356_8S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH356_8S, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 8, 4, 0x3F, 0x00, 0x00, 0x00, "CH356_8S", BOARDFLAG_NONE, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0, 8, -1, 0, 0, WCH_BOARD_CH356_8S }, + { 's', 1, 0, 8, -1, 0, 0, WCH_BOARD_CH356_8S }, + { 's', 2, 0, 8, -1, 0, 0, WCH_BOARD_CH356_8S }, + { 's', 3, 0, 8, -1, 0, 0, WCH_BOARD_CH356_8S }, + { 's', 4, 0, 8, -1, 0, 0, WCH_BOARD_CH356_8S }, + { 's', 4, 0x08, 8, -1, 0, 0, WCH_BOARD_CH356_8S }, + { 's', 4, 0x10, 8, -1, 0, 0, WCH_BOARD_CH356_8S }, + { 's', 4, 0x18, 8, -1, 0, 0, WCH_BOARD_CH356_8S }, + }, + }, + + // CH357_4S + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH357_4S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH357_4S, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 4, 4, 0x6F, 0x00, 0x00, 0x00, "CH357_4S", BOARDFLAG_NONE, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0, 8, -1, 0, 0, WCH_BOARD_CH357_4S }, + { 's', 1, 0, 8, -1, 0, 0, WCH_BOARD_CH357_4S }, + { 's', 2, 0, 8, -1, 0, 0, WCH_BOARD_CH357_4S }, + { 's', 3, 0, 8, -1, 0, 0, WCH_BOARD_CH357_4S }, + }, + }, + + // CH358_4S1P + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH358_4S1P, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH358_4S1P, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 4, 4, 0x6F, 0x00, 0x00, 0x00, "CH358_4S1P", BOARDFLAG_NONE, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0, 8, -1, 0, 0, WCH_BOARD_CH358_4S1P }, + { 's', 1, 0, 8, -1, 0, 0, WCH_BOARD_CH358_4S1P }, + { 's', 2, 0, 8, -1, 0, 0, WCH_BOARD_CH358_4S1P }, + { 's', 3, 0, 8, -1, 0, 0, WCH_BOARD_CH358_4S1P }, + }, + }, + + // CH358_8S + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH358_8S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH358_8S, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 8, 4, 0x6F, 0x00, 0x00, 0x00, "CH358_8S", BOARDFLAG_NONE, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0, 8, -1, 0, 0, WCH_BOARD_CH358_8S }, + { 's', 1, 0, 8, -1, 0, 0, WCH_BOARD_CH358_8S }, + { 's', 2, 0, 8, -1, 0, 0, WCH_BOARD_CH358_8S }, + { 's', 3, 0, 8, -1, 0, 0, WCH_BOARD_CH358_8S }, + { 's', 0, 8, 8, -1, 0, 0, WCH_BOARD_CH358_8S }, + { 's', 1, 8, 8, -1, 0, 0, WCH_BOARD_CH358_8S }, + { 's', 2, 8, 8, -1, 0, 0, WCH_BOARD_CH358_8S }, + { 's', 3, 8, 8, -1, 0, 0, WCH_BOARD_CH358_8S }, + }, + }, + + // CH359_16S + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH359_16S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH359_16S, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 16, 4, 0x6F, 0x00, 0x00, 0x00, "CH359_16S", BOARDFLAG_NONE, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0, 8, -1, 0, 0, WCH_BOARD_CH359_16S }, + { 's', 1, 0, 8, -1, 0, 0, WCH_BOARD_CH359_16S }, + { 's', 2, 0, 8, -1, 0, 0, WCH_BOARD_CH359_16S }, + { 's', 3, 0, 8, -1, 0, 0, WCH_BOARD_CH359_16S }, + { 's', 0, 8, 8, -1, 0, 0, WCH_BOARD_CH359_16S }, + { 's', 1, 8, 8, -1, 0, 0, WCH_BOARD_CH359_16S }, + { 's', 2, 8, 8, -1, 0, 0, WCH_BOARD_CH359_16S }, + { 's', 3, 8, 8, -1, 0, 0, WCH_BOARD_CH359_16S }, + + { 's', 4, 0, 8, -1, 0, 0, WCH_BOARD_CH359_16S }, + { 's', 4, 16, 8, -1, 0, 0, WCH_BOARD_CH359_16S }, + { 's', 4, 32, 8, -1, 0, 0, WCH_BOARD_CH359_16S }, + { 's', 4, 48, 8, -1, 0, 0, WCH_BOARD_CH359_16S }, + { 's', 4, 8, 8, -1, 0, 0, WCH_BOARD_CH359_16S }, + { 's', 4, 24, 8, -1, 0, 0, WCH_BOARD_CH359_16S }, + { 's', 4, 40, 8, -1, 0, 0, WCH_BOARD_CH359_16S }, + { 's', 4, 56, 8, -1, 0, 0, WCH_BOARD_CH359_16S }, + }, + }, + + // CH382_2S + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH382_2S, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH382_2S, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 2, 0, 0xE9, 0x00, 0x00, 0x00, "CH382_2S", BOARDFLAG_NONE, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0xC0, 8, -1, 0, 0, WCH_BOARD_CH382_2S }, + { 's', 0, 0xC8, 8, -1, 0, 0, WCH_BOARD_CH382_2S }, + }, + }, + + // CH382_2S1P + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH382_2S1P, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH382_2S1P, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 2, 0, 0xE9, 0x00, 0x00, 0x00, "CH382_2S1P", BOARDFLAG_NONE, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0xC0, 8, -1, 0, 0, WCH_BOARD_CH382_2S1P }, + { 's', 0, 0xC8, 8, -1, 0, 0, WCH_BOARD_CH382_2S1P }, + }, + }, + + + // CH384_4S + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH384_4S, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH384_4S, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 4, 0, 0xE9, 0x00, 0x00, 0x00, "CH384_4S", BOARDFLAG_NONE, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0xC0, 8, -1, 0, 0, WCH_BOARD_CH384_4S }, + { 's', 0, 0xC8, 8, -1, 0, 0, WCH_BOARD_CH384_4S }, + { 's', 0, 0xD0, 8, -1, 0, 0, WCH_BOARD_CH384_4S }, + { 's', 0, 0xD8, 8, -1, 0, 0, WCH_BOARD_CH384_4S }, + }, + }, + + // CH384_4S1P + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH384_4S1P, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH384_4S1P, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 4, 0, 0xE9, 0x00, 0x00, 0x00, "CH384_4S1P", BOARDFLAG_NONE, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0xC0, 8, -1, 0, 0, WCH_BOARD_CH384_4S1P }, + { 's', 0, 0xC8, 8, -1, 0, 0, WCH_BOARD_CH384_4S1P }, + { 's', 0, 0xD0, 8, -1, 0, 0, WCH_BOARD_CH384_4S1P }, + { 's', 0, 0xD8, 8, -1, 0, 0, WCH_BOARD_CH384_4S1P }, + }, + }, + + // CH384_8S + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH384_8S, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH384_8S, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 8, 0, 0xE0, 0x00, 0x00, 0x00, "CH384_8S", BOARDFLAG_CH384_8_PORTS, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH384_8S }, + { 's', 0, 0x10, 8, -1, 0, 0, WCH_BOARD_CH384_8S }, + { 's', 0, 0x20, 8, -1, 0, 0, WCH_BOARD_CH384_8S }, + { 's', 0, 0x30, 8, -1, 0, 0, WCH_BOARD_CH384_8S }, + { 's', 0, 0x08, 8, -1, 0, 0, WCH_BOARD_CH384_8S }, + { 's', 0, 0x18, 8, -1, 0, 0, WCH_BOARD_CH384_8S }, + { 's', 0, 0x28, 8, -1, 0, 0, WCH_BOARD_CH384_8S }, + { 's', 0, 0x38, 8, -1, 0, 0, WCH_BOARD_CH384_8S }, + }, + }, + + // CH384_28S + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH384_28S, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH384_28S, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + PCIE_UART_MAX, 0, 0xE9, 0xE0, 0xE4, 0xE6, "CH384_28S", BOARDFLAG_CH384_28_PORTS, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0xC0, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0xC8, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0xD0, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0xD8, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x10, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x20, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x30, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x08, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x18, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x28, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x38, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + + { 's', 0, 0x40, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x50, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x60, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x70, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x48, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x58, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x68, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x78, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + + { 's', 0, 0x80, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x90, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0xA0, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0xB0, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x88, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0x98, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0xA8, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + { 's', 0, 0xB8, 8, -1, 0, 0, WCH_BOARD_CH384_28S }, + }, + }, + + // CH365_32S + { + // VenID DevID SubVenID SubSysID + VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH365_32S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH365_32S, + // SerPort IntrBar IntrOffset IntrOffset1 IntrOffset2 IntrOffset3 Name BoardFlag + 32, 1, 0x00, 0x00, 0x00, 0x00, "CH365_32S", BOARDFLAG_CH365_32_PORTS, + { + // type bar1 ofs1 len1 bar2 ofs2 len2 flags + { 's', 0, 0x00, 0, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 0, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 0, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 0, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 0, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 0, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 0, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 0, -1, 0, 0, WCH_BOARD_CH365_32S }, + + { 's', 0, 0x00, 0, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 0, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 0, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 0, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 0, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 0, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + { 's', 0, 0x00, 8, -1, 0, 0, WCH_BOARD_CH365_32S }, + }, + }, +}; diff --git a/drivers/tty/serial/wch_35x/wch_main.c b/drivers/tty/serial/wch_35x/wch_main.c new file mode 100644 index 00000000..294311a7 --- /dev/null +++ b/drivers/tty/serial/wch_35x/wch_main.c @@ -0,0 +1,931 @@ +/* + * PCI/PCIE to serial driver for ch351/352/353/355/356/357/358/359/382/384, etc. + * + * Copyright (C) 2023 Nanjing Qinheng Microelectronics Co., Ltd. + * Web: http://wch.cn + * Author: WCH + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * Update Log: + * V1.00 - initial version + * V1.10 - fixed uart send bugs + * V1.11 - fixed the issue of serial ports number creation + * V1.12 - fixed modem signals support + * V1.13 - added automatic frequency multiplication when using baud rates higher than 115200bps + - added mutex protection in uart transmit process + * V1.14 - optimized the processing of serial ports in interruption + * V1.15 - added support for non-standard baud rate + * V1.16 - fixed uart clock frequency multiplication bugs + * V1.17 - modified uart data received process + * V1.18 - changed uart fifo trigger level + - changed uart transmission to half of the fifo size + * V1.19 - fixed ch358 uart0 setting bug + * V1.20 - added pre-load driver + * V1.21 - fixed modem setting when disable hardflow + * V1.22 - added support for rs485 configuration + * V1.23 - added supports for kernel version beyond 5.14.x + * V1.24 - fixed ch351/2/3 uart0 setting bug, merged pre-load driver + */ + +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + + +#include "wch_common.h" + +extern struct wch_board wch_board_table[WCH_BOARDS_MAX]; +extern struct wch_ser_port wch_ser_table[WCH_SER_TOTAL_MAX + 1]; +extern unsigned char ch365_32s; + +struct wch_board wch_board_table[WCH_BOARDS_MAX]; +struct wch_ser_port wch_ser_table[WCH_SER_TOTAL_MAX + 1]; + +int wch_ser_port_total_cnt; +unsigned char ch365_32s = 0; + +struct pci_device_id wch_pci_board_id[] = { + {VENDOR_ID_WCH_CH351, DEVICE_ID_WCH_CH351_2S, SUB_VENDOR_ID_WCH_CH351, SUB_DEVICE_ID_WCH_CH351_2S, 0, 0, + WCH_BOARD_CH351_2S}, + {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH352_2S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH352_2S, 0, 0, + WCH_BOARD_CH352_2S}, + {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH352_1S1P, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH352_1S1P, 0, 0, + WCH_BOARD_CH352_1S1P}, + {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH353_4S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH353_4S, 0, 0, + WCH_BOARD_CH353_4S}, + {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH353_2S1P, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH353_2S1P, 0, 0, + WCH_BOARD_CH353_2S1P}, + {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH353_2S1PAR, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH353_2S1PAR, 0, 0, + WCH_BOARD_CH353_2S1PAR}, + {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH355_4S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH355_4S, 0, 0, + WCH_BOARD_CH355_4S}, + {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH356_4S1P, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH356_4S1P, 0, 0, + WCH_BOARD_CH356_4S1P}, + {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH356_6S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH356_6S, 0, 0, + WCH_BOARD_CH356_6S}, + {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH356_8S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH356_8S, 0, 0, + WCH_BOARD_CH356_8S}, + {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH357_4S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH357_4S, 0, 0, + WCH_BOARD_CH357_4S}, + {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH358_4S1P, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH358_4S1P, 0, 0, + WCH_BOARD_CH358_4S1P}, + {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH358_8S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH358_8S, 0, 0, + WCH_BOARD_CH358_8S}, + {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH359_16S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH359_16S, 0, 0, + WCH_BOARD_CH359_16S}, + {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH382_2S, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH382_2S, 0, 0, + WCH_BOARD_CH382_2S}, + {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH382_2S1P, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH382_2S1P, 0, 0, + WCH_BOARD_CH382_2S1P}, + {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH384_4S, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH384_4S, 0, 0, + WCH_BOARD_CH384_4S}, + {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH384_4S1P, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH384_4S1P, 0, 0, + WCH_BOARD_CH384_4S1P}, + {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH384_8S, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH384_8S, 0, 0, + WCH_BOARD_CH384_8S}, + {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH384_28S, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH384_28S, 0, 0, + WCH_BOARD_CH384_28S}, + {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH365_32S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH365_32S, 0, 0, + WCH_BOARD_CH365_32S}, + {0} +}; +MODULE_DEVICE_TABLE(pci, wch_pci_board_id); + +static irqreturn_t wch_interrupt(int irq, void *dev_id) +{ + struct wch_ser_port *sp = NULL; + struct wch_board *sb = NULL; + int i; + int status = 0; + int handled = IRQ_NONE; + + for (i = 0; i < WCH_BOARDS_MAX; i++) { + if (dev_id == &(wch_board_table[i])) { + sb = dev_id; + break; + } + } + + if (i == WCH_BOARDS_MAX) { + status = 1; + } + + if (!sb) { + status = 1; + } + + if (sb->board_enum <= 0) { + status = 1; + } + + if (status != 0) { + return handled; + } + + if ((sb->ser_ports > 0) && (sb->ser_isr != NULL)) { + sp = &wch_ser_table[sb->ser_port_index]; + + if (!sp) { + status = 1; + } + + status = sb->ser_isr(sb, sp); + } + + if (status != 0) { + return handled; + } + + handled = IRQ_HANDLED; + return handled; +} + +static int wch_pci_board_probe(void) +{ + struct wch_board *sb; + struct pci_dev *pdev = NULL; + struct pci_dev *pdev_array[4] = {NULL, NULL, NULL, NULL}; + + int wch_pci_board_id_cnt; + int table_cnt; + int board_cnt; + int i; + unsigned short int sub_device_id; + int status; + +#if WCH_DBG + printk("%s : %s\n", __FILE__, __FUNCTION__); +#endif + + // clear and init some variable + memset(wch_board_table, 0, WCH_BOARDS_MAX * sizeof(struct wch_board)); + + for (i = 0; i < WCH_BOARDS_MAX; i++) { + wch_board_table[i].board_enum = -1; + wch_board_table[i].board_number = -1; + } + + wch_pci_board_id_cnt = (sizeof(wch_pci_board_id) / sizeof(wch_pci_board_id[0])) - 1; + + // search wch serial and multi-I/O board + pdev = NULL; + table_cnt = 0; + board_cnt = 0; + status = 0; + + while (table_cnt < wch_pci_board_id_cnt) { + pdev = pci_get_device(wch_pci_board_id[table_cnt].vendor, wch_pci_board_id[table_cnt].device, pdev); + + if (pdev == NULL) { + table_cnt++; + continue; + } + + if ((table_cnt > 0) && ((pdev == pdev_array[0]) || (pdev == pdev_array[1]) || (pdev == pdev_array[2]) || + (pdev == pdev_array[3]))) { + continue; + } + + if (wch_pci_board_id[table_cnt].driver_data == WCH_BOARD_CH365_32S) { + ch365_32s = 0x01; + } + + pci_read_config_word(pdev, 0x2e, &sub_device_id); + + if (ch365_32s) { + } else { + if (sub_device_id == 0) { + printk("WCH Error: WCH Board (bus:%d device:%d), in configuration space,\n", pdev->bus->number, + PCI_SLOT(pdev->devfn)); + printk(" subdevice id isn't vaild.\n\n"); + status = -EIO; + return status; + } + + if (sub_device_id != wch_pci_board_id[table_cnt].subdevice) { + continue; + } + } + if (pdev == NULL) { + printk("WCH Error: PCI device object is an NULL pointer !\n\n"); + status = -EIO; + return status; + } else { + status = pci_enable_device(pdev); + + if (status != 0) { + printk("WCH Error: WCH Board Enable Fail !\n\n"); + status = -ENXIO; + return status; + } + } + + board_cnt++; + if (board_cnt > WCH_BOARDS_MAX) { + printk("\n"); + printk("WCH Error: WCH Driver Module Support Four Boards In Maximum !\n\n"); + status = -ENOSPC; + return status; + } + + sb = &wch_board_table[board_cnt - 1]; + + pdev_array[board_cnt - 1] = pdev; + sb->pdev = pdev; + sb->bus_number = pdev->bus->number; + sb->dev_number = PCI_SLOT(pdev->devfn); + + sb->board_enum = (int)wch_pci_board_id[table_cnt].driver_data; + sb->pb_info = wch_pci_board_conf[sb->board_enum]; + + sb->board_flag = sb->pb_info.board_flag; + + sb->board_number = board_cnt - 1; + } + + if (board_cnt == 0) { + printk("WCH Info : No WCH Multi-I/O Board Found !\n\n"); + status = -ENXIO; + return status; + } else { + for (i = 0; i < WCH_BOARDS_MAX; i++) { + sb = &wch_board_table[i]; + if (sb->board_enum > 0) { + printk("\n"); + if ((sb->pb_info.num_serport) > 0) { + printk("WCH Info : Found WCH %s Series Board (%dS),\n", sb->pb_info.board_name, + sb->pb_info.num_serport); + } + + printk(" bus number:%d, device number:%d\n\n", sb->bus_number, sb->dev_number); + } + } + } + + return status; +} + +static int wch_get_pci_board_conf(void) +{ + struct wch_board *sb = NULL; + struct pci_dev *pdev = NULL; + int status = 0; + int i; + int j; + +#if WCH_DBG + printk("%s : %s\n", __FILE__, __FUNCTION__); +#endif + + for (i = 0; i < WCH_BOARDS_MAX; i++) { + sb = &wch_board_table[i]; + + if (sb->board_enum > 0) { + pdev = sb->pdev; + sb->ser_ports = sb->pb_info.num_serport; + + wch_ser_port_total_cnt = wch_ser_port_total_cnt + sb->ser_ports; + + if (wch_ser_port_total_cnt > WCH_SER_TOTAL_MAX) { + printk("WCH Error: Too much serial port, maximum %d ports can be supported !\n\n", WCH_SER_TOTAL_MAX); + status = -EIO; + return status; + } + + for (j = 0; j < WCH_PCICFG_BAR_TOTAL; j++) { + sb->bar_addr[j] = pci_resource_start(pdev, j); + } + + if ((sb->board_flag & BOARDFLAG_CH365_32_PORTS) == BOARDFLAG_CH365_32_PORTS) { + sb->board_membase = ioremap(sb->bar_addr[1], 4096); + if (!sb->board_membase) { + status = -EIO; + printk("WCH Error: ioremap failed !\n"); + return status; + } + } + + sb->irq = sb->pdev->irq; + if (sb->irq <= 0) { + printk( + "WCH Error: WCH Board %s Series (bus:%d device:%d), in configuartion space, irq isn't valid !\n\n", + sb->pb_info.board_name, sb->bus_number, sb->dev_number); + + status = -EIO; + return status; + } + } + } + + return status; +} + +static int wch_assign_resource(void) +{ + struct wch_board *sb = NULL; + struct wch_ser_port *sp = NULL; + int status = 0; + int i; + int j; + int ser_n; + int ser_port_index = 0; + +#if WCH_DBG + printk("%s : %s\n", __FILE__, __FUNCTION__); +#endif + + memset(wch_ser_table, 0, (WCH_SER_TOTAL_MAX + 1) * sizeof(struct wch_ser_port)); + + for (i = 0; i < WCH_BOARDS_MAX; i++) { + sb = &wch_board_table[i]; + + if (sb->board_enum > 0) { + if (sb->ser_ports > 0) { + sb->vector_mask = 0; + + // assign serial port resource + ser_n = sb->ser_port_index = ser_port_index; + + sp = &wch_ser_table[ser_n]; + + if (sp == NULL) { + status = -ENXIO; + printk("WCH Error: Serial port table address error !\n"); + return status; + } + + for (j = 0; j < sb->ser_ports; j++, ser_n++, sp++) { + sp->port.chip_flag = sb->pb_info.port[j].chip_flag; + sp->port.iobase = sb->bar_addr[sb->pb_info.port[j].bar1] + sb->pb_info.port[j].offset1; + + /* use scr reg to test io space */ + outb(0x55, sp->port.iobase + UART_SCR); + if (inb(sp->port.iobase + UART_SCR) != 0x55) { + status = -ENXIO; + if (j == 0) + printk("WCH Error: pci/pcie address error !\n"); + else + printk("WCH Error: ch432/ch438 communication error !\n"); + return status; + } + + if ((sb->board_flag & BOARDFLAG_REMAP) == BOARDFLAG_REMAP) { + sp->port.vector = 0; + } else if ((sb->board_flag & BOARDFLAG_CH384_8_PORTS) == BOARDFLAG_CH384_8_PORTS) { + sp->port.chip_iobase = sb->bar_addr[sb->pb_info.port[j].bar1]; + sp->port.vector = sb->bar_addr[sb->pb_info.intr_vector_bar] + sb->pb_info.intr_vector_offset; + } else if ((sb->board_flag & BOARDFLAG_CH384_28_PORTS) == BOARDFLAG_CH384_28_PORTS) { + sp->port.chip_iobase = sb->bar_addr[sb->pb_info.port[j].bar1]; + if (j >= 0 && j < 0x04) { + sp->port.vector = + sb->bar_addr[sb->pb_info.intr_vector_bar] + sb->pb_info.intr_vector_offset; + } else if (j >= 0x04 && j < 0x0C) { + sp->port.vector = + sb->bar_addr[sb->pb_info.intr_vector_bar] + sb->pb_info.intr_vector_offset_1; + } else if (j >= 0x0C && j < 0x14) { + sp->port.vector = + sb->bar_addr[sb->pb_info.intr_vector_bar] + sb->pb_info.intr_vector_offset_2; + } else if (j >= 0x14 && j < 0x1C) { + sp->port.vector = + sb->bar_addr[sb->pb_info.intr_vector_bar] + sb->pb_info.intr_vector_offset_3; + } else { + } + } else if ((sb->board_flag & BOARDFLAG_CH365_32_PORTS) == BOARDFLAG_CH365_32_PORTS) { + sp->port.chip_iobase = sb->bar_addr[sb->pb_info.port[j].bar1]; + sp->port.board_membase = sb->board_membase; + if (j >= 0 && j < 0x08) { + if (j >= 0 && j < 0x04) { + sp->port.port_membase = sb->board_membase + 0x100 + j * 0x10; + } + if (j >= 4 && j < 0x08) { + sp->port.port_membase = sb->board_membase + 0x100 + 0x08 + (j - 4) * 0x10; + } + } + if (j >= 0x08 && j < 0x10) { + if (j >= 0x08 && j < 0x0C) { + sp->port.port_membase = sb->board_membase + 0x100 + 0x80 + (j - 0x08) * 0x10; + } + if (j >= 0x0C && j < 0x10) { + sp->port.port_membase = sb->board_membase + 0x100 + 0x80 + 0x08 + (j - 0x0C) * 0x10; + } + } + if (j >= 0x10 && j < 0x18) { + if (j >= 0x10 && j < 0x14) { + sp->port.port_membase = sb->board_membase + 0x100 + 0x100 + (j - 0x10) * 0x10; + } + if (j >= 0x14 && j < 0x18) { + sp->port.port_membase = sb->board_membase + 0x100 + 0x100 + 0x08 + (j - 0x14) * 0x10; + } + } + if (j >= 0x18 && j < 0x20) { + if (j >= 0x18 && j < 0x1C) { + sp->port.port_membase = sb->board_membase + 0x100 + 0x180 + (j - 0x18) * 0x10; + } + if (j >= 0x1C && j < 0x20) { + sp->port.port_membase = sb->board_membase + 0x100 + 0x180 + 0x08 + (j - 0x1C) * 0x10; + } + } + } else { + sp->port.vector = sb->bar_addr[sb->pb_info.intr_vector_bar] + sb->pb_info.intr_vector_offset; + } + } + sb->vector_mask = 0xffffffff; + ser_port_index = ser_port_index + sb->ser_ports; + } + } + } + + return status; +} + +static int wch_ser_port_table_init(void) +{ + struct wch_board *sb = NULL; + struct wch_ser_port *sp = NULL; + int status = 0; + int i; + int j; + int n; + +#if WCH_DBG + printk("%s : %s\n", __FILE__, __FUNCTION__); +#endif + + for (i = 0; i < WCH_BOARDS_MAX; i++) { + sb = &wch_board_table[i]; + + if (sb == NULL) { + status = -ENXIO; + printk("WCH Error: Board table pointer error !\n"); + return status; + } + + if ((sb->board_enum > 0) && (sb->ser_ports > 0)) { + n = sb->ser_port_index; + sp = &wch_ser_table[n]; + + if (sp == NULL) { + status = -ENXIO; + printk("WCH Error: Serial port table pointer error !\n"); + return status; + } + + for (j = 0; j < sb->ser_ports; j++, n++, sp++) { + sp->port.board_enum = sb->board_enum; + sp->port.bus_number = sb->bus_number; + sp->port.dev_number = sb->dev_number; + sp->port.baud_base = CRYSTAL_FREQ * 2 / 16; + sp->port.pb_info = sb->pb_info; + if (sp->port.chip_flag == WCH_BOARD_CH384_8S) { + if (n == sb->ser_port_index) + sp->port.bext1stport = true; + else + sp->port.bext1stport = false; + } else if (sp->port.chip_flag == WCH_BOARD_CH384_28S) { + if ((n == sb->ser_port_index + 4) || (n == sb->ser_port_index + 12) || + (n == sb->ser_port_index + 20)) + sp->port.bext1stport = true; + else + sp->port.bext1stport = false; + } else if (sp->port.chip_flag == WCH_BOARD_CH355_4S || sp->port.chip_flag == WCH_BOARD_CH356_4S1P || + sp->port.chip_flag == WCH_BOARD_CH356_6S || sp->port.chip_flag == WCH_BOARD_CH356_8S || + sp->port.chip_flag == WCH_BOARD_CH358_4S1P || sp->port.chip_flag == WCH_BOARD_CH358_8S) { + if (n == sb->ser_port_index) + sp->port.bext1stport = true; + else + sp->port.bext1stport = false; + } else if (sp->port.chip_flag == WCH_BOARD_CH359_16S) { + if ((n == sb->ser_port_index) || (n == sb->ser_port_index + 8)) + sp->port.bext1stport = true; + else + sp->port.bext1stport = false; + } + + if (sp->port.chip_flag == WCH_BOARD_CH351_2S || sp->port.chip_flag == WCH_BOARD_CH352_1S1P || + sp->port.chip_flag == WCH_BOARD_CH352_2S || sp->port.chip_flag == WCH_BOARD_CH353_2S1P || + sp->port.chip_flag == WCH_BOARD_CH353_2S1PAR || sp->port.chip_flag == WCH_BOARD_CH353_4S) { + if (n == sb->ser_port_index) + sp->port.bspe1stport = true; + else + sp->port.bspe1stport = false; + } + + sp->port.irq = sb->irq; + sp->port.line = n; + sp->port.uartclk = CRYSTAL_FREQ * 2; + if (ch365_32s) { + sp->port.iotype = WCH_UPIO_MEM; + } else { + sp->port.iotype = WCH_UPIO_PORT; + } + + sp->port.ldisc_stop_rx = 0; + spin_lock_init(&sp->port.lock); + + if (sp->port.chip_flag == WCH_BOARD_CH351_2S) { + sp->port.type = PORT_SER_16550A; + sp->port.fifosize = CH351_FIFOSIZE_SET; + sp->port.rx_trigger = CH351_TRIGGER_LEVEL_SET; + } else if (sp->port.chip_flag == WCH_BOARD_CH352_2S || sp->port.chip_flag == WCH_BOARD_CH352_1S1P) { + sp->port.type = PORT_SER_16550A; + sp->port.fifosize = CH352_FIFOSIZE_SET; + sp->port.rx_trigger = CH352_TRIGGER_LEVEL_SET; + } else if (sp->port.chip_flag == WCH_BOARD_CH353_4S || sp->port.chip_flag == WCH_BOARD_CH353_2S1P || + sp->port.chip_flag == WCH_BOARD_CH353_2S1PAR) { + sp->port.type = PORT_SER_16550A; + sp->port.fifosize = CH353_FIFOSIZE_SET; + sp->port.rx_trigger = CH353_TRIGGER_LEVEL_SET; + } else if (sp->port.chip_flag == WCH_BOARD_CH355_4S) { + sp->port.type = PORT_SER_16550A; + sp->port.fifosize = CH355_FIFOSIZE_SET; + sp->port.rx_trigger = CH355_TRIGGER_LEVEL_SET; + } else if (sp->port.chip_flag == WCH_BOARD_CH356_4S1P || sp->port.chip_flag == WCH_BOARD_CH356_6S || + sp->port.chip_flag == WCH_BOARD_CH356_8S) { + sp->port.type = PORT_SER_16550A; + sp->port.fifosize = CH356_FIFOSIZE_SET; + sp->port.rx_trigger = CH356_TRIGGER_LEVEL_SET; + } else if (sp->port.chip_flag == WCH_BOARD_CH357_4S) { + sp->port.type = PORT_SER_16750; + sp->port.fifosize = CH357_FIFOSIZE_SET; + sp->port.rx_trigger = CH357_TRIGGER_LEVEL_SET; + } else if (sp->port.chip_flag == WCH_BOARD_CH358_4S1P || sp->port.chip_flag == WCH_BOARD_CH358_8S) { + sp->port.type = PORT_SER_16750; + sp->port.fifosize = CH358_FIFOSIZE_SET; + sp->port.rx_trigger = CH358_TRIGGER_LEVEL_SET; + } else if (sp->port.chip_flag == WCH_BOARD_CH359_16S) { + sp->port.type = PORT_SER_16750; + sp->port.fifosize = CH359_FIFOSIZE_SET; + sp->port.rx_trigger = CH359_TRIGGER_LEVEL_SET; + } else if (sp->port.chip_flag == WCH_BOARD_CH382_2S || sp->port.chip_flag == WCH_BOARD_CH382_2S1P) { + sp->port.type = PORT_SER_16750; + sp->port.fifosize = CH382_FIFOSIZE_SET; + sp->port.rx_trigger = CH382_TRIGGER_LEVEL_SET; + } else if (sp->port.chip_flag == WCH_BOARD_CH384_4S || sp->port.chip_flag == WCH_BOARD_CH384_4S1P) { + sp->port.type = PORT_SER_16750; + sp->port.fifosize = CH384_FIFOSIZE_SET; + sp->port.rx_trigger = CH384_TRIGGER_LEVEL_SET; + } else if (sp->port.chip_flag == WCH_BOARD_CH384_8S) { + sp->port.type = PORT_SER_16750; + sp->port.fifosize = CH358_FIFOSIZE_SET; + sp->port.rx_trigger = CH358_TRIGGER_LEVEL_SET; + } else if (sp->port.chip_flag == WCH_BOARD_CH384_28S) { + sp->port.type = PORT_SER_16750; + if (j >= 0 && j < 0x04) { + sp->port.fifosize = CH384_FIFOSIZE_SET; + sp->port.rx_trigger = CH384_TRIGGER_LEVEL_SET; + } else { + sp->port.fifosize = CH358_FIFOSIZE_SET; + sp->port.rx_trigger = CH358_TRIGGER_LEVEL_SET; + } + } else if (sp->port.chip_flag == WCH_BOARD_CH365_32S) { + sp->port.type = PORT_SER_16750; + sp->port.fifosize = CH438_FIFOSIZE_SET; + sp->port.rx_trigger = CH438_TRIGGER_LEVEL_SET; + } else { + sp->port.type = PORT_SER_16450; + sp->port.fifosize = DEFAULT_FIFOSIZE; + sp->port.rx_trigger = DEFAULT_TRIGGER_LEVEL; + } + + if ((sb->pb_info.board_flag & BOARDFLAG_REMAP) == BOARDFLAG_REMAP) { + sp->port.vector_mask = 0; + sp->port.port_flag = PORTFLAG_REMAP; + } else { + sp->port.vector_mask = sb->vector_mask; + sp->port.port_flag = PORTFLAG_NONE; + } + + sp->port.setserial_flag = WCH_SER_BAUD_NOTSETSER; + } + + sb->ser_isr = wch_ser_interrupt; + } else { + sb->ser_isr = NULL; + } + } + + return status; +} + +#if WCH_DBG +void wch_debug(void) +{ +#if WCH_DBG_BOARD + struct wch_board *sb = NULL; + int i; +#endif + +#if WCH_DBG_SERPORT + struct wch_ser_port *sp = NULL; + int j; +#endif + +#if WCH_DBG_BOARD + printk("\n"); + printk("======== board info ========\n"); + for (i = 0; i < WCH_BOARDS_MAX; i++) { + sb = &wch_board_table[i]; + if (sb->board_enum != -1) { + printk(" name : %s\n", sb->pb_info.board_name); + printk(" board_enum : %d\n", sb->board_enum); + printk(" board_number : %d\n", sb->board_number); + printk(" irq : %d\n", sb->irq); + printk(" vector_mask : 0x%x\n", sb->vector_mask); + printk(" bar[0] : 0x%lx\n", sb->bar_addr[0]); + printk(" bar[1] : 0x%lx\n", sb->bar_addr[1]); + printk(" bar[2] : 0x%lx\n", sb->bar_addr[2]); + printk(" bar[3] : 0x%lx\n", sb->bar_addr[3]); + printk(" bar[4] : 0x%lx\n", sb->bar_addr[4]); + printk(" bar[5] : 0x%lx\n", sb->bar_addr[5]); + printk("----------------------------\n"); + } + } + printk("============================\n"); + printk("\n"); +#endif + +#if WCH_DBG_SERPORT + printk("\n"); + printk("======== serial info ========\n"); + for (j = 0; j < wch_ser_port_total_cnt; j++) { + sp = &wch_ser_table[j]; + if (sp->port.iobase) { + printk(" number : %d\n", j); + printk(" name : %s\n", sp->port.pb_info.board_name); + printk(" iobase : 0x%lx\n", sp->port.iobase); + printk(" chip_iobase : 0x%x\n", sp->port.chip_iobase); + printk(" irq : %d\n", sp->port.irq); + printk(" vector : 0x%lx\n", sp->port.vector); + printk(" vector_mask : 0x%x\n", sp->port.vector_mask); + printk(" chip_flag : 0x%x\n", sp->port.chip_flag); + printk(" port_flag : 0x%x\n", sp->port.port_flag); + printk("----------------------------\n"); + } + } + printk("============================\n"); + printk("\n"); +#endif +} +#endif + +#if WCH_DBG_SERIAL +void ch365_32s_test(void) +{ + struct wch_board *sb = NULL; + int i; + dbg_serial("\n"); + dbg_serial("======== board info ========\n"); + for (i = 0; i < WCH_BOARDS_MAX; i++) { + sb = &wch_board_table[i]; + if (sb->board_enum != -1) { + dbg_serial(" ch438_1_uart0 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05)); + dbg_serial(" ch438_1_uart1 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x08)); + dbg_serial(" ch438_1_uart2 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x10)); + dbg_serial(" ch438_1_uart3 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x18)); + dbg_serial(" ch438_1_uart4 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x20)); + dbg_serial(" ch438_1_uart5 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x28)); + dbg_serial(" ch438_1_uart6 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x30)); + dbg_serial(" ch438_1_uart7 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x38)); + dbg_serial("----------------------------\n"); + dbg_serial(" ch438_2_uart0 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x80)); + dbg_serial(" ch438_2_uart1 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x08 + 0x80)); + dbg_serial(" ch438_2_uart2 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x10 + 0x80)); + dbg_serial(" ch438_2_uart3 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x18 + 0x80)); + dbg_serial(" ch438_2_uart4 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x20 + 0x80)); + dbg_serial(" ch438_2_uart5 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x28 + 0x80)); + dbg_serial(" ch438_2_uart6 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x30 + 0x80)); + dbg_serial(" ch438_2_uart7 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x38 + 0x80)); + dbg_serial("----------------------------\n"); + dbg_serial(" ch438_3_uart0 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x100)); + dbg_serial(" ch438_3_uart1 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x08 + 0x100)); + dbg_serial(" ch438_3_uart2 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x10 + 0x100)); + dbg_serial(" ch438_3_uart3 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x18 + 0x100)); + dbg_serial(" ch438_3_uart4 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x20 + 0x100)); + dbg_serial(" ch438_3_uart5 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x28 + 0x100)); + dbg_serial(" ch438_3_uart6 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x30 + 0x100)); + dbg_serial(" ch438_3_uart7 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x38 + 0x100)); + dbg_serial("----------------------------\n"); + dbg_serial(" ch438_4_uart0 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x180)); + dbg_serial(" ch438_4_uart1 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x08 + 0x180)); + dbg_serial(" ch438_4_uart2 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x10 + 0x180)); + dbg_serial(" ch438_4_uart3 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x18 + 0x180)); + dbg_serial(" ch438_4_uart4 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x20 + 0x180)); + dbg_serial(" ch438_4_uart5 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x28 + 0x180)); + dbg_serial(" ch438_4_uart6 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x30 + 0x180)); + dbg_serial(" ch438_4_uart7 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x38 + 0x180)); + dbg_serial("----------------------------\n"); + } + } + dbg_serial("============================\n"); + dbg_serial("\n"); +} +#endif + +int wch_register_irq(void) +{ + struct wch_board *sb = NULL; + int status = 0; + int i; + unsigned long chip_iobase; + +#if WCH_DBG + printk("%s : %s\n", __FILE__, __FUNCTION__); +#endif + + for (i = 0; i < WCH_BOARDS_MAX; i++) { + sb = &wch_board_table[i]; + + if (sb == NULL) { + status = -ENXIO; + printk("WCH Error: Board table pointer error !\n"); + return status; + } + + if (sb->board_enum > 0) { + status = request_irq(sb->irq, wch_interrupt, IRQF_SHARED, "wch", sb); + + if (status) { + printk("WCH Error: WCH Multi-I/O %s Board(bus:%d device:%d), request\n", sb->pb_info.board_name, + sb->bus_number, sb->dev_number); + printk(" IRQ %d fail, IRQ %d may be conflit with another device.\n", sb->irq, sb->irq); + return status; + } + } + if (ch365_32s) { + outb(inb(sb->bar_addr[0] + 0xF8) & 0xFE, sb->bar_addr[0] + 0xF8); + outb(((inb(sb->bar_addr[0] + 0xFA) & 0xFB) | 0x03), + sb->bar_addr[0] + 0xFA); // set read/write plus width 240ns->120ns + } + + if (((sb->board_flag & BOARDFLAG_CH384_8_PORTS) == BOARDFLAG_CH384_8_PORTS) || + ((sb->board_flag & BOARDFLAG_CH384_28_PORTS) == BOARDFLAG_CH384_28_PORTS)) { + chip_iobase = sb->bar_addr[0]; + if (chip_iobase) { + outb(inb(chip_iobase + 0xEB) | 0x02, chip_iobase + 0xEB); + /* set read/write plus width 120ns->210ns */ + outb(inb(chip_iobase + 0xFA) | 0x10, chip_iobase + 0xFA); + } + } + } + + return status; +} + +void wch_iounmap(void) +{ + struct wch_board *sb = NULL; + int i; + +#if WCH_DBG + printk("%s : %s\n", __FILE__, __FUNCTION__); +#endif + + for (i = 0; i < WCH_BOARDS_MAX; i++) { + sb = &wch_board_table[i]; + + if (sb->board_enum > 0) { + iounmap(sb->board_membase); + } + } +} + +void wch_release_irq(void) +{ + struct wch_board *sb = NULL; + int i; + unsigned long chip_iobase; + +#if WCH_DBG + printk("%s : %s\n", __FILE__, __FUNCTION__); +#endif + + for (i = 0; i < WCH_BOARDS_MAX; i++) { + sb = &wch_board_table[i]; + + if (sb->board_enum > 0) { + free_irq(sb->irq, sb); + } + if (ch365_32s) { + outb(inb(sb->bar_addr[0] + 0xF8) | 0x01, sb->bar_addr[0] + 0xF8); + } + if (((sb->board_flag & BOARDFLAG_CH384_8_PORTS) == BOARDFLAG_CH384_8_PORTS) || + ((sb->board_flag & BOARDFLAG_CH384_28_PORTS) == BOARDFLAG_CH384_28_PORTS)) { + chip_iobase = sb->bar_addr[0]; + if (chip_iobase) + outb(inb(chip_iobase + 0xEB) & 0xFD, chip_iobase + 0xEB); + } + } +} + +static struct ser_driver wch_ser_reg = { + .dev_name = "ttyWCH", + .major = WCH_TTY_MAJOR, + .minor = 0, +}; + +int wch_35x_init(void) +{ + int status = 0; + + printk("\n\n"); + printk("===================== WCH Device Driver Module Install =====================\n"); + printk("\n"); + printk("WCH Info : Loading WCH Multi-I/O Board Driver Module\n"); + printk(" -- Date : %s\n", WCH_DRIVER_DATE); + printk(" -- Version : %s\n\n", WCH_DRIVER_VERSION); + + wch_ser_port_total_cnt = 0; + + status = wch_pci_board_probe(); + if (status != 0) { + goto step1_fail; + } + printk("------------------->pci board probe success\n"); + status = wch_get_pci_board_conf(); + if (status != 0) { + goto step1_fail; + } + printk("------------------->pci board conf success\n"); + + status = wch_assign_resource(); + if (status != 0) { + goto step1_fail; + } + printk("------------------->pci assign success\n"); + + status = wch_ser_port_table_init(); + if (status != 0) { + goto step1_fail; + } + printk("------------------->ser port table init success\n"); + + status = wch_register_irq(); + if (status != 0) { + goto step1_fail; + } + printk("------------------->pci register irq success\n"); + + status = wch_ser_register_driver(&wch_ser_reg); + if (status != 0) { + goto step2_fail; + } + printk("------------------->ser register driver success\n"); + + status = wch_ser_register_ports(&wch_ser_reg); + if (status != 0) { + goto step3_fail; + } + +#if WCH_DBG + wch_debug(); +// ch365_32s_test(); +#endif + + printk("================================================================================\n"); + return status; + +step3_fail: + + wch_ser_unregister_driver(&wch_ser_reg); + +step2_fail: + + wch_release_irq(); + +step1_fail: + + printk("WCH Error: Couldn't Loading WCH Multi-I/O Board Driver Module correctly,\n"); + printk(" please reboot system and try again. If still can't loading driver,\n"); + printk(" contact support.\n\n"); + printk("================================================================================\n"); + return status; +} + +void wch_35x_exit(void) +{ + printk("\n\n"); + printk("==================== WCH Device Driver Module Uninstall ====================\n"); + printk("\n"); + + wch_ser_unregister_ports(&wch_ser_reg); + printk("***********wch_ser_unregister_ports***************\n"); + wch_ser_unregister_driver(&wch_ser_reg); + printk("***********wch_ser_unregister_driver_success***********\n"); + wch_iounmap(); + wch_release_irq(); + printk("WCH Info : Unload WCH Multi-I/O Board Driver Module Done.\n"); + printk("================================================================================\n"); +} diff --git a/drivers/tty/serial/wch_35x/wch_pre_main.c b/drivers/tty/serial/wch_35x/wch_pre_main.c new file mode 100644 index 00000000..c0b1e617 --- /dev/null +++ b/drivers/tty/serial/wch_35x/wch_pre_main.c @@ -0,0 +1,119 @@ +/* + * PCI/PCIe to serial driver(pre) for ch351/352/353/355/356/357/358/359/382/384, etc. + * This driver only needs to be used when the system does not assign an interrupt number for device. + * + * Copyright (C) 2023 Nanjing Qinheng Microelectronics Co., Ltd. + * Web: http://wch.cn + * Author: WCH + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * Update Log: + * V1.00 - initial version + */ + +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + + +#include +#include + +#include "wch_common.h" + +#define WCH_PRE_DRIVER_AUTHOR "WCH GROUP" +#define WCH_PRE_DRIVER_DESC "WCH Multi-I/O Board Driver Module(pre)" + +#define VENDOR_ID_WCH_PCIE 0x1C00 +#define SUB_VENDOR_ID_WCH_PCIE 0x1C00 +#define VENDOR_ID_WCH_PCI 0x4348 +#define SUB_VENDOR_ID_WCH_PCI 0x4348 +#define VENDOR_ID_WCH_CH351 0x1C00 +#define SUB_VENDOR_ID_WCH_CH351 0x1C00 +#define DEVICE_ID_WCH_CH351_2S 0x2273 +#define SUB_DEVICE_ID_WCH_CH351_2S 0x2273 +#define DEVICE_ID_WCH_CH352_1S1P 0x5053 +#define SUB_DEVICE_ID_WCH_CH352_1S1P 0x5053 +#define DEVICE_ID_WCH_CH352_2S 0x3253 +#define SUB_DEVICE_ID_WCH_CH352_2S 0x3253 +#define DEVICE_ID_WCH_CH353_4S 0x3453 +#define SUB_DEVICE_ID_WCH_CH353_4S 0x3453 +#define DEVICE_ID_WCH_CH353_2S1P 0x7053 +#define SUB_DEVICE_ID_WCH_CH353_2S1P 0x3253 +#define DEVICE_ID_WCH_CH353_2S1PAR 0x5046 +#define SUB_DEVICE_ID_WCH_CH353_2S1PAR 0x5046 +#define DEVICE_ID_WCH_CH355_4S 0x7173 +#define SUB_DEVICE_ID_WCH_CH355_4S 0x3473 +#define DEVICE_ID_WCH_CH356_4S1P 0x7073 +#define SUB_DEVICE_ID_WCH_CH356_4S1P 0x3473 +#define DEVICE_ID_WCH_CH356_6S 0x3873 +#define SUB_DEVICE_ID_WCH_CH356_6S 0x3873 +#define DEVICE_ID_WCH_CH356_8S 0x3853 +#define SUB_DEVICE_ID_WCH_CH356_8S 0x3853 +#define DEVICE_ID_WCH_CH357_4S 0x5334 +#define SUB_DEVICE_ID_WCH_CH357_4S 0x5053 +#define DEVICE_ID_WCH_CH358_4S1P 0x5334 +#define SUB_DEVICE_ID_WCH_CH358_4S1P 0x5334 +#define DEVICE_ID_WCH_CH358_8S 0x5338 +#define SUB_DEVICE_ID_WCH_CH358_8S 0x5338 +#define DEVICE_ID_WCH_CH359_16S 0x5838 +#define SUB_DEVICE_ID_WCH_CH359_16S 0x5838 +#define DEVICE_ID_WCH_CH382_2S 0x3253 +#define SUB_DEVICE_ID_WCH_CH382_2S 0x3253 +#define DEVICE_ID_WCH_CH382_2S1P 0x3250 +#define SUB_DEVICE_ID_WCH_CH382_2S1P 0x3250 +#define DEVICE_ID_WCH_CH384_4S 0x3470 +#define SUB_DEVICE_ID_WCH_CH384_4S 0x3470 +#define DEVICE_ID_WCH_CH384_4S1P 0x3450 +#define SUB_DEVICE_ID_WCH_CH384_4S1P 0x3450 +#define DEVICE_ID_WCH_CH384_8S 0x3853 +#define SUB_DEVICE_ID_WCH_CH384_8S 0x3853 +#define DEVICE_ID_WCH_CH384_28S 0x4353 +#define SUB_DEVICE_ID_WCH_CH384_28S 0x4353 +#define DEVICE_ID_WCH_CH365_32S 0x5049 +#define SUB_DEVICE_ID_WCH_CH365_32S 0x5049 + +/* + * Probe to serial board. + */ +static int wch_probe(struct pci_dev *dev, const struct pci_device_id *ent) +{ + printk("\n====================WCH Device Driver(pre) Module probe====================\n"); + printk("Probe Device VID: %4x, PID: 0x%4x\n", dev->vendor, dev->device); + + wch_35x_init(); + return 0; +} + +static void wch_remove(struct pci_dev *dev) +{ + printk("\n====================WCH Device Driver(pre) Module exit====================\n"); + printk("Remove Device VID: %4x, PID: 0x%4x\n", dev->vendor, dev->device); + wch_35x_exit(); +} + +static struct pci_driver wchserial_pci_driver = { + .name = "wchpciserial", + .probe = wch_probe, + .remove = wch_remove, + .id_table = wch_pci_board_id, +}; + +module_pci_driver(wchserial_pci_driver); + +MODULE_AUTHOR(WCH_PRE_DRIVER_AUTHOR); +MODULE_DESCRIPTION(WCH_PRE_DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/tty/serial/wch_35x/wch_serial.c b/drivers/tty/serial/wch_35x/wch_serial.c new file mode 100644 index 00000000..45a17404 --- /dev/null +++ b/drivers/tty/serial/wch_35x/wch_serial.c @@ -0,0 +1,2973 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include + +#include "wch_common.h" + +static DEFINE_SEMAPHORE(ser_port_sem); + +#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); +static int ser_put_char(struct tty_struct *, unsigned char); +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 *); +static void ser_send_xchar(struct tty_struct *, char); +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); +static int ser_write(struct tty_struct *, const unsigned char *, int); +// 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 LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +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 LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +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) { + ser_change_speed(state, NULL); + if (info->tty->termios.c_cflag & CBAUD) + { + set_mctrl(port, TIOCM_RTS | TIOCM_DTR); + } + } + + info->flags |= WCH_UIF_INITIALIZED; + + 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); +} + +static int ser_put_char(struct tty_struct *tty, unsigned char ch) +{ + 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); + } +} + +static void ser_send_xchar(struct tty_struct *tty, char ch) +{ + 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; +} + +static int ser_write(struct tty_struct *tty, const unsigned char *buf, int count) +{ + 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.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 LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +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 LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +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 LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +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 LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +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) +{ + struct wch_ser_port *sp = from_timer(sp, t, timer); + 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; + } + } + 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); + } + + 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; +}