/* ICPDAS VXC PCI series device driver.

   Author: Golden Wang 

   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, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 


   File level history (record changes for this file here.)

   Use vi or gvim to edit this file and set tabstop=4.
   v 0.3.1  15 Mar 2022 by Winson Chen
     Give support to linux kernel 5.16.0

   v 0.3.0  2 Jan 2014 by Winson Chen
     Give support to linux kernel 3.12.0

   v 0.2.1 21 Aug 2007 by Golden Wang
     Give support to the VXC-114U and VXC-144U serial card.

   v 0.2.0  10 May 2007 by Golden Wang
     fix a bug in the "ixcom_interrupt" function that return error argument result in
     kernel disable the driver interrupt(when the error happened, system would show the
     error message "kernel: irq XX: nobody cared!").

   v 0.1.0  4 May 2007 by Golden Wang
     fix a bug in the "pci_vxc_fn" function that doesn't initialize VXC-112A and VXC-142
     device.
	
   v 0.0.0  3 May 2007 by Golden Wang
     Give support to linux kernel 2.6.x .
*/

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/serial_reg.h>
#include <linux/major.h>
#include <linux/string.h>
#include <linux/fcntl.h>
#include <linux/ptrace.h>
#include <linux/gfp.h>
#include <linux/ioport.h>
#include <linux/mm.h>
#include <linux/delay.h>
#include <linux/pci.h>

#include <asm/io.h>
#include <asm/irq.h>
#include <asm/bitops.h>
#include <asm/uaccess.h>
#include "ixcom.h"
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,13,0)
#include "tty.h"
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0)
#include <asm/switch_to.h>
#else
#include <asm/system.h>
#endif

#if LINUX_VERSION_CODE <= KERNEL_VERSION(3,0,0)
#include <linux/autoconf.h>
#include <linux/smp_lock.h>
#endif

#define LOCAL_VERSTRING		""
#define MODULE_NAME 		"ixcom"
#define ICPDAS_BOARDS		16       /* Max. boards */
#define ICPDAS_PORTS_PER_BOARD	8       /* Max. ports per board */
#define ICPDAS_ISR_PASS_LIMIT 	256
#define UART_LSR_SPECIAL        0x1E
#define ICPDAS_EVENT_TXLOW	1
#define ICPDAS_EVENT_HANGUP	2
#define WAKEUP_CHARS		256
#define PORT_VXC_MAX		128

#define RELEVANT_IFLAG(iflag)   (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK|IXON|IXOFF))

#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
//#define TTY_SET_INIT(p) ((p)->flags |= ASYNC_INITIALIZED)
//#define TTY_CLR_INIT(p) ((p)->flags &= ~(ASYNC_INITIALIZED))
//#define IS_TTY_ACTIVE(p) ((p)->flags & ASYNC_NORMAL_ACTIVE)
//#define TTY_SET_ACTIVE(p) ((p)->flags |= ASYNC_NORMAL_ACTIVE)
//#define TTY_CLR_ACTIVE(p) ((p)->flags &= ~(ASYNC_NORMAL_ACTIVE))
//#define TTY_SET_CHECK_CAR(p) ((p)->flags |= ASYNC_CHECK_CD)
//#define TTY_CLR_CHECK_CAR(p) ((p)->flags &= ~(ASYNC_CHECK_CD))
#else
//#define IS_TTY_INIT(p) tty_port_initialized(p) 
//#define TTY_SET_INIT(p) tty_port_set_initialized(p, 1)
//#define TTY_CLR_INIT(p) tty_port_set_initialized(p, 0)
//#define IS_TTY_ACTIVE(p) tty_port_active(p)
//#define TTY_SET_ACTIVE(p) tty_port_set_active(p, 1)
//#define TTY_CLR_ACTIVE(p) tty_port_set_active(p, 0)
#define IS_TTY_CHECK_CAR(p) tty_port_check_carrier(p)
//#define TTY_SET_CHECK_CAR(p) tty_port_set_check_carrier(p, 1)
//#define TTY_CLR_CHECK_CAR(p) tty_port_set_check_carrier(p, 0)
#endif


enum pci_board_num_t {
	ICPDAS_VXC_182I_PCI = 0,
	ICPDAS_VXC_142_PCI,
	ICPDAS_VXC_142I_PCI,
	ICPDAS_VXC_112A_PCI,
	ICPDAS_VXC_112AU_PCI,
	ICPDAS_VXC_142IU_PCI,
	ICPDAS_VXC_142AU_PCI,	//VXC-142AU/142iAU
	ICPDAS_VXC_114U_PCI,
	ICPDAS_VXC_144U_PCI,
	ICPDAS_VXC_1001AU_PCI,
	ICPDAS_VXC_182IAU_PCI,
};

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
struct serial_uart_config {
        char    *name;
        int     dfl_xmit_fifo_size;
        int     flags;
};
#endif

static struct serial_uart_config uart_config[] = {
        { "16550A", 16, UART_CLEAR_FIFO | UART_USE_FIFO },
        { "16550A", 16, UART_CLEAR_FIFO | UART_USE_FIFO },
        { "16550A", 16, UART_CLEAR_FIFO | UART_USE_FIFO },
        { "16550A", 16, UART_CLEAR_FIFO | UART_USE_FIFO },
	{ "16C950/954", 128, UART_CLEAR_FIFO | UART_USE_FIFO},
	{ "16C950/954", 128, UART_CLEAR_FIFO | UART_USE_FIFO},
	{ "16C950/954", 128, UART_CLEAR_FIFO | UART_USE_FIFO},
	{ "16C950/954", 128, UART_CLEAR_FIFO | UART_USE_FIFO},
	{ "16C950/954", 128, UART_CLEAR_FIFO | UART_USE_FIFO},
	{ "16C950/954", 128, UART_CLEAR_FIFO | UART_USE_FIFO},
	{ "16C950/954", 128, UART_CLEAR_FIFO | UART_USE_FIFO},
        { 0, 0}
};

static char *ixcom_brdname[] = {
        "VXC-182i series",
        "VXC-142 series",
        "VXC-142i series",
        "VXC-112A series",
        "VXC-112AU series",
        "VXC-142iU series",
        "VXC-142AU series",	//VXC-142AU/142iAU
        "VXC-114U series",
        "VXC-144U series",
        "VXC-1001AU series",
        "VXC-182iAU series",
};

static int ixcom_numports[] = {
        2,                      /* VXC-182i */
        2,                      /* VXC-142 */
        2,                      /* VXC-142i */
        2,                      /* VXC-112A */
        2,                      /* VXC-112AU */
        2,                      /* VXC-142iU */
        2,                      /* VXC-142AU/142iAU */
        4,			/* VXC-114U */
        4,			/* VXC-144U */
        2,			/* VXC-1001AU */
        2,			/* VXC-182iAU */
};

static struct pci_device_id ixcom_pci_tbl[] = {
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID, PCI_SUBVENDOR_ID_VXC, PCI_SUBDEVICE_ID_182I, 0, 0, ICPDAS_VXC_182I_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID01_VXC, PCI_SUBDEVICE_ID2_182I, 0, 0, ICPDAS_VXC_182I_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID11_VXC, PCI_SUBDEVICE_ID2_182I, 0, 0, ICPDAS_VXC_182I_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID, PCI_SUBVENDOR_ID_VXC, PCI_SUBDEVICE_ID_142, 0, 0, ICPDAS_VXC_142_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID00_VXC, PCI_SUBDEVICE_ID2_142, 0, 0, ICPDAS_VXC_142_PCI},
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID10_VXC, PCI_SUBDEVICE_ID2_142, 0, 0, ICPDAS_VXC_142_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID20_VXC, PCI_SUBDEVICE_ID2_142, 0, 0,ICPDAS_VXC_142_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID30_VXC, PCI_SUBDEVICE_ID2_142, 0, 0,ICPDAS_VXC_142_PCI },

  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID, PCI_SUBVENDOR_ID_VXC, PCI_SUBDEVICE_ID_142I, 0, 0, ICPDAS_VXC_142I_PCI },
  { IXCOM_VENDOR_ID2, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID01_VXC, PCI_SUBDEVICE_ID2_142I, 0, 0,ICPDAS_VXC_142I_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID01_VXC, PCI_SUBDEVICE_ID_142I, 0, 0,ICPDAS_VXC_142I_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID11_VXC, PCI_SUBDEVICE_ID2_142I, 0, 0, ICPDAS_VXC_142I_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID21_VXC, PCI_SUBDEVICE_ID2_142I, 0, 0, ICPDAS_VXC_142I_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID31_VXC, PCI_SUBDEVICE_ID2_142I, 0, 0,ICPDAS_VXC_142I_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID, PCI_SUBVENDOR_ID13_VXC, PCI_SUBDEVICE_ID3_142I, 0, 0,ICPDAS_VXC_142I_PCI },
  { IXCOM_VENDOR_ID2, IXCOM_DEVICE_ID3, PCI_SUBVENDOR_ID34_VXC, PCI_SUBDEVICE_ID4_142I, 0, 0,ICPDAS_VXC_142IU_PCI },//VXC-142iU
  { IXCOM_VENDOR_ID2, IXCOM_DEVICE_ID5, PCI_SUBVENDOR_ID34_VXC, PCI_SUBDEVICE_ID4_142I, 0, 0,ICPDAS_VXC_142AU_PCI },//VXC-142AU/142iAU
  { IXCOM_VENDOR_ID2, IXCOM_DEVICE_ID5, PCI_SUBVENDOR_ID36_VXC, PCI_SUBDEVICE_ID3_182IAU, 0, 0,ICPDAS_VXC_182IAU_PCI },//VXC-182iAU
/*
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID, PCI_SUBVENDOR_ID_VXC, PCI_SUBDEVICE_ID_142I, 0, 0, ICPDAS_VXC_142I_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID01_VXC, PCI_SUBDEVICE_ID2_142I, 0, 0,ICPDAS_VXC_142I_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID11_VXC, PCI_SUBDEVICE_ID2_142I, 0, 0, ICPDAS_VXC_142I_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID21_VXC, PCI_SUBDEVICE_ID2_142I, 0, 0, ICPDAS_VXC_142I_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID31_VXC, PCI_SUBDEVICE_ID2_142I, 0, 0,ICPDAS_VXC_142I_PCI },
*/
  //{ IXCOM_VENDOR_ID, IXCOM_DEVICE_ID, PCI_SUBVENDOR_ID_VXC, PCI_SUBDEVICE_ID_112A, 0, 0, ICPDAS_VXC_112A_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID02_VXC, PCI_SUBDEVICE_ID2_112A, 0, 0,ICPDAS_VXC_112A_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID12_VXC, PCI_SUBDEVICE_ID2_112A, 0, 0, ICPDAS_VXC_112A_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID22_VXC, PCI_SUBDEVICE_ID2_112A, 0, 0,ICPDAS_VXC_112A_PCI },
  { IXCOM_VENDOR_ID, IXCOM_DEVICE_ID2, PCI_SUBVENDOR_ID32_VXC, PCI_SUBDEVICE_ID2_112A, 0, 0,ICPDAS_VXC_112A_PCI },
  { IXCOM_VENDOR_ID2, IXCOM_DEVICE_ID5, PCI_SUBVENDOR_ID33_VXC, PCI_SUBDEVICE_ID3_112AU, 0, 0,ICPDAS_VXC_112AU_PCI },
  { IXCOM_VENDOR_ID2, IXCOM_DEVICE_ID3, PCI_SUBVENDOR_ID33_VXC, PCI_SUBDEVICE_ID_114U, 0, 0, ICPDAS_VXC_114U_PCI },
  { IXCOM_VENDOR_ID2, IXCOM_DEVICE_ID4, PCI_SUBVENDOR_ID33_VXC, PCI_SUBDEVICE_ID_114U, 0, 0, ICPDAS_VXC_114U_PCI },
  { IXCOM_VENDOR_ID2, IXCOM_DEVICE_ID3, PCI_SUBVENDOR_ID34_VXC, PCI_SUBDEVICE_ID_144U, 0, 0, ICPDAS_VXC_144U_PCI },
  { IXCOM_VENDOR_ID2, IXCOM_DEVICE_ID4, PCI_SUBVENDOR_ID34_VXC, PCI_SUBDEVICE_ID_144U, 0, 0, ICPDAS_VXC_144U_PCI },
  { IXCOM_VENDOR_ID2, IXCOM_DEVICE_ID5, PCI_SUBVENDOR_ID35_VXC, PCI_SUBDEVICE_ID_1001AU, 0, 0,ICPDAS_VXC_1001AU_PCI },
  //{ IXCOM_VENDOR_ID2, IXCOM_DEVICE_ID6, PCI_SUBVENDOR_ID35_VXC, PCI_SUBDEVICE_ID_1001AU, 0, 0,ICPDAS_VXC_1001AU_PCI },
  {0,}
};

MODULE_DEVICE_TABLE (pci, ixcom_pci_tbl);

#ifdef MODULE_LICENSE
MODULE_AUTHOR("Reed Lai <reed@icpdas.com>");
MODULE_DESCRIPTION("ICPDAS PIO-series driver, common interface");
MODULE_LICENSE("GPL");
#endif

typedef struct _ixcom_pci_info {
        unsigned short busNum;
        unsigned short devNum;
        struct pci_dev *pdev; 
} ixcom_pci_info;

struct ixcom_hwconf {
        int board_type;
        int num_port;
        int irq;
        int vector;
        int vector_mask;
        int uart_type;
        int ioaddr[ICPDAS_PORTS_PER_BOARD];
        int baud_base[ICPDAS_PORTS_PER_BOARD];
        ixcom_pci_info pciInfo;
        int MaxCanSetBaudRate[ICPDAS_PORTS_PER_BOARD];  
        int opmode_ioaddr[ICPDAS_PORTS_PER_BOARD];     
};

struct ixcom_struct {
	int board_type;
        int port;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0))
        struct tty_port ttyPort;
#endif
        int base;               /* port base address */
        int irq;                /* port using irq no. */
	char irq_name[24];      /* irq_name for /proc/interrupt */
        int irq_request;        /* check the status of "request_irq" */
        int vector;             /* port irq vector */
        int vectormask;         /* port vector mask */
        int rx_high_water;
        int rx_trigger;         /* Rx fifo trigger level */
        int rx_low_water;
        int baud_base;          /* max. speed */
        int flags;              /* defined in tty.h */
        int uart_type;               /* UART type */
        struct tty_struct *tty;
        int read_status_mask;
        int ignore_status_mask;
        int xmit_fifo_size;
        int custom_divisor;
        int x_char;             /* xon/xoff character */
        int close_delay;
        unsigned short closing_wait;
        int IER;                /* Interrupt Enable Register */
        int MCR;                /* Modem control register */
	int ACR;
        unsigned long event;
        int count;              /* # of fd on device */
        int blocked_open;       /* # of blocked opens */
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
        long session;           /* Session of opening process */
        long pgrp;
#endif
	unsigned char *xmit_buf;
        int xmit_head;
        int xmit_tail;
        int xmit_cnt;
        struct work_struct tqueue;
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
	struct termios normal_termios;
        struct termios callout_termios;
#else
        struct ktermios normal_termios;
        struct ktermios callout_termios;
#endif
        wait_queue_head_t open_wait;
        wait_queue_head_t close_wait;
        wait_queue_head_t delta_msr_wait;
        struct async_icount icount;     /* kernel counters for the 4 input interrupts */
        int timeout;
        int MaxCanSetBaudRate;  
        int opmode_ioaddr;     
        unsigned char stop_rx;
        unsigned char ldisc_stop_rx;
        long realbaud;
        unsigned char err_shadow;
        spinlock_t slock;
};

static char *serial_version = "ixcom-0.11.5";
//static char *serial_revdate = "";
static char *serial_name = "ICPDAS VXC multi-serial card Serial driver";
static struct tty_driver *ixcom_driver;

//ixcom_table save the information of each port at vxc card.
static struct ixcom_struct ixcom_table[PORT_VXC_MAX];
static struct ixcom_hwconf ixcomcfg[ICPDAS_BOARDS];
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3,7,0))
static struct tty_struct *ixcom_tty[PORT_VXC_MAX];
#endif
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
static struct termios *ixcom_termios[PORT_VXC_MAX];
static struct termios *ixcom_termios_locked[PORT_VXC_MAX];
#else

#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3,7,0))
static struct ktermios *ixcom_termios[PORT_VXC_MAX];
#endif

#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3,2,20))
static struct ktermios *ixcom_termios_locked[PORT_VXC_MAX];
#endif
#endif

static spinlock_t gm_lock;

int vxc_major;
int ixcom_refcount;
static int current_num;
static int current_port;
static int BOARD_COUNT;
//int table_index = 0;

static unsigned int serial_in(struct ixcom_struct *, int);
static void serial_icr_write(struct ixcom_struct *, int, int);
unsigned int serial_icr_read(struct ixcom_struct *, int);
static void serial_out(struct ixcom_struct *, int, int);
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)
static void ixcom_do_softint(struct work_struct *);
#else
static void ixcom_do_softint(void *);
#endif

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
static void ixcom_receive_chars(struct ixcom_struct *, int *, struct pt_regs *);
#else
static void ixcom_receive_chars(struct ixcom_struct *, int *);
#endif


static void ixcom_transmit_chars(struct ixcom_struct *);
static void ixcom_check_modem_status(struct ixcom_struct *, int);
static void ixcom_getcfg(int board, struct ixcom_hwconf *hwconf);
static int get_ixcom_conf(int, int, int, struct ixcom_hwconf *);
static int pci_vxc_fn(struct pci_dev *, int enable, int);
static int ixcom_startup(struct ixcom_struct *);
static void ixcom_shutdown(struct ixcom_struct *);
//static int ixcom_change_speed(struct ixcom_struct *, struct termios *old_termios);
static int ixcom_set_baud(struct ixcom_struct *info, long newspd);
static int ixcom_block_til_ready(struct tty_struct *, struct file *, struct ixcom_struct *);

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
static irqreturn_t ixcom_interrupt(int, void *, struct pt_regs *);
#else
static irqreturn_t ixcom_interrupt(int, void *);
#endif

static void ixcom_send_break(struct ixcom_struct *, int);
static int ixcom_get_serial_info(struct ixcom_struct *, struct serial_struct __user *);
static int ixcom_set_serial_info(struct ixcom_struct *, struct serial_struct __user *);
static int ixcom_get_lsr_info(struct ixcom_struct *, unsigned int __user *);

static int ixcom_open(struct tty_struct *, struct file *);
static void ixcom_close(struct tty_struct *, struct file *);
static int ixcom_write(struct tty_struct *, const unsigned char *, int);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0)
static unsigned int ixcom_write_room(struct tty_struct *);
static unsigned int ixcom_chars_in_buffer(struct tty_struct *);
#else
static int ixcom_write_room(struct tty_struct *);
static int ixcom_chars_in_buffer(struct tty_struct *);
#endif
static void ixcom_flush_buffer(struct tty_struct *);
static void ixcom_flush_chars(struct tty_struct *);
//static void ixcom_put_char(struct tty_struct *, unsigned char);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
static int ixcom_ioctl(struct tty_struct *, uint, ulong);
#else
static int ixcom_ioctl(struct tty_struct *, struct file *, uint, ulong);
#endif
static void ixcom_throttle(struct tty_struct *);
static void ixcom_unthrottle(struct tty_struct *);

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
static int ixcom_change_speed(struct ixcom_struct *, struct termios *old_termios);
static void ixcom_set_termios(struct tty_struct *, struct termios *);
#else
static int ixcom_change_speed(struct ixcom_struct *, struct ktermios *old_termios);
static void ixcom_set_termios(struct tty_struct *, struct ktermios *);
#endif

static void ixcom_stop(struct tty_struct *);
static void ixcom_start(struct tty_struct *);
static void ixcom_hangup(struct tty_struct *);
static void ixcom_wait_until_sent(struct tty_struct *tty, int timeout);
//static void ixcom_rs_break(struct tty_struct *, int);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
static int ixcom_tiocmget(struct tty_struct *);
static int ixcom_tiocmset(struct tty_struct *, unsigned int, unsigned int);
#else
static int ixcom_tiocmget(struct tty_struct *, struct file *);
static int ixcom_tiocmset(struct tty_struct *, struct file *, unsigned int, unsigned int);
#endif
static void ixcom_startrx(struct tty_struct *tty);
static void ixcom_stoprx(struct tty_struct *tty);

static void ixcom_getcfg(int board, struct ixcom_hwconf *hwconf)
{
        ixcomcfg[board] = *hwconf;
}

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
static void ixcom_receive_chars(struct ixcom_struct *info, int *status, struct pt_regs *regs)
#else
static void ixcom_receive_chars(struct ixcom_struct *info, int *status)
#endif
{
	struct tty_struct *tty = info->tty;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,0)
	struct tty_port *ttyport = &info->ttyPort;
#endif
        unsigned char ch;
        int ignored = 0;
        int cnt = 0;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
        unsigned char *cp;
        char *fp;
#endif
        int recv_room;
        int max = 256;
        unsigned long flags;

        spin_lock_irqsave(&info->slock, flags);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)	
        recv_room = tty->receive_room;
#else
	recv_room = tty->ldisc.receive_room(tty);
#endif

        if ((recv_room == 0) && (!info->ldisc_stop_rx))
	{
                ixcom_stoprx(tty);
        }

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
        cp = tty->flip.char_buf;
        fp = tty->flip.flag_buf;
#endif

	do {
		if (max-- < 0)
                        break;

		ch = serial_in(info,UART_RX);
               
		if (*status & info->ignore_status_mask)
		{
                        if (++ignored > 100)
                                break;
                }
		else
		{

			/*
				UART_LSR_SPECIAL = UART_LSR_BI | UART_LSR_PE
						   | UART_LSR_FE | UART_LSR_OE
			*/

                        if (*status & UART_LSR_SPECIAL)
			{
                		*status &= info->read_status_mask;

                                if (*status & UART_LSR_BI)
				{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
                                        *fp++ = TTY_BREAK;
#endif
                                        info->icount.brk++;

                                        if (info->flags & ASYNC_SAK)
                                                do_SAK(tty);
                                }
				else if (*status & UART_LSR_PE)
				{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
                                        *fp++ = TTY_PARITY;
#endif
                                        info->icount.parity++;
                                }
				else if (*status & UART_LSR_FE)
				{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
                                        *fp++ = TTY_FRAME;
#endif
                                        info->icount.frame++;
                                }
				else if (*status & UART_LSR_OE)
				{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
                                        *fp++ = TTY_OVERRUN;
#endif
                                        info->icount.overrun++;
                                }
				else
				{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
                                        *fp++ = 0;
#endif
				}
                        }
			else
			{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
				*fp++ = TTY_NORMAL;
#endif
			}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
			tty_insert_flip_char(tty, ch, *fp);
#else

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,0)
			tty_insert_flip_char(ttyport, ch, 0);
#else
			tty_insert_flip_char(tty, ch, 0);
#endif
#endif

                        cnt++;
                        if (cnt >= recv_room)
			{
                                if (!info->ldisc_stop_rx)
				{
                                        ixcom_stoprx(tty);
                                }
                                break;
                        }

                }

                *status = serial_in(info, UART_LSR);
        } while (*status & UART_LSR_DR);

        spin_unlock_irqrestore(&info->slock, flags);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,0)
        tty_flip_buffer_push(ttyport);
#else
	tty_flip_buffer_push(tty);
#endif
	
}

static void ixcom_transmit_chars(struct ixcom_struct *info)
{
	int count, cnt;
        unsigned long flags;

        spin_lock_irqsave(&info->slock, flags);

        if (info->x_char)
	{
                serial_out(info, UART_TX, info->x_char);
                info->x_char = 0;

                info->icount.tx++;
                spin_unlock_irqrestore(&info->slock, flags);
                return;
        }

	if (info->xmit_buf == 0) {
                spin_unlock_irqrestore(&info->slock, flags);
                return;
        }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0)
        if ((info->xmit_cnt <= 0) || info->tty->flow.stopped || (info->tty->hw_stopped && (info->uart_type != PORT_16550A))) {
#else
        if ((info->xmit_cnt <= 0) || info->tty->stopped || (info->tty->hw_stopped && (info->uart_type != PORT_16550A))) {
#endif
                info->IER &= ~UART_IER_THRI;
                serial_out(info, UART_IER, info->IER);
                spin_unlock_irqrestore(&info->slock, flags);
                return;
        }

        cnt = info->xmit_cnt;
        count = info->xmit_fifo_size;

	do
	{
                serial_out(info, UART_TX, info->xmit_buf[info->xmit_tail++]);
                info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE - 1);
                if (--info->xmit_cnt <= 0)
                        break;
        } while (--count > 0);


        info->icount.tx += (cnt - info->xmit_cnt);

        if (info->xmit_cnt < WAKEUP_CHARS) {
                set_bit(ICPDAS_EVENT_TXLOW, &info->event);
                schedule_work(&info->tqueue);
        }

        if (info->xmit_cnt <= 0) {
                info->IER &= ~UART_IER_THRI;
                serial_out(info, UART_IER, info->IER);
        }

        spin_unlock_irqrestore(&info->slock, flags);
}

static void ixcom_check_modem_status(struct ixcom_struct *info, int status)
{
	/* update input line counters */
        if (status & UART_MSR_TERI)
                info->icount.rng++;
        if (status & UART_MSR_DDSR)
                info->icount.dsr++;
        if (status & UART_MSR_DDCD)
                info->icount.dcd++;
        if (status & UART_MSR_DCTS)
                info->icount.cts++;

        wake_up_interruptible(&info->delta_msr_wait);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
	if (tty_port_check_carrier(&info->ttyPort) && (status & UART_MSR_DDCD)) {
#else
	if ((info->flags & ASYNC_CHECK_CD) && (status & UART_MSR_DDCD)) {
#endif
                if (status & UART_MSR_DCD)
                        wake_up_interruptible(&info->open_wait);
                schedule_work(&info->tqueue);
        }

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
	if (tty_port_cts_enabled(&info->ttyPort)) {
#else
        if (info->flags & ASYNC_CTS_FLOW) {
#endif
                if (info->tty->hw_stopped) {
                        if (status & UART_MSR_CTS) {
                                info->tty->hw_stopped = 0;

                                if ((info->uart_type != PORT_16550A)) {
                                        info->IER |= UART_IER_THRI;
                                        serial_out(info, UART_IER, info->IER);
                                }
                                set_bit(ICPDAS_EVENT_TXLOW, &info->event);
                                schedule_work(&info->tqueue);                   }
                } else {
                        if (!(status & UART_MSR_CTS)) {
                                info->tty->hw_stopped = 1;
                                if ((info->uart_type != PORT_16550A)) {
                                        info->IER &= ~UART_IER_THRI;
                                        serial_out(info, UART_IER, info->IER);
                                }
                        }
                }
        }
}

static int ixcom_initbrd(int board, struct ixcom_hwconf *hwconf)
{

	struct ixcom_struct *info;
        int i, pre_current_port;

	
	pre_current_port = current_port;
	//printk("HHH %s ... pre_current_port : %d current_port : %d\n",__FUNCTION__, pre_current_port, current_port);
	
	info = &ixcom_table[pre_current_port];

	for (i = 0; i < hwconf->num_port; i++, current_port++, info++)
	{
          info->board_type = hwconf->board_type;
          info->port = current_port;
	  sprintf(info->irq_name, "ixcom:ttySV%d", info->port);
          info->base = hwconf->ioaddr[i];
          info->irq = hwconf->irq;
	  info->irq_request = 0;
          info->vector = hwconf->vector;
          info->vectormask = hwconf->vector_mask;
          info->opmode_ioaddr = hwconf->opmode_ioaddr[i]; 
          info->stop_rx = 0;
          info->ldisc_stop_rx = 0;
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
          info->flags = ASYNC_SHARE_IRQ;
#endif
          info->uart_type = hwconf->uart_type;
          info->baud_base = hwconf->baud_base[i];

          info->MaxCanSetBaudRate = hwconf->MaxCanSetBaudRate[i];
	  info->xmit_fifo_size = uart_config[info->board_type].dfl_xmit_fifo_size;

	  info->custom_divisor = 0;
          info->close_delay = 5 * HZ / 10;
          info->closing_wait = 30 * HZ;

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
          INIT_WORK(&info->tqueue, ixcom_do_softint, info);
#else
          INIT_WORK(&info->tqueue, ixcom_do_softint);
#endif
          info->normal_termios = ixcom_driver->init_termios;
          init_waitqueue_head(&info->open_wait);
          init_waitqueue_head(&info->close_wait);
          init_waitqueue_head(&info->delta_msr_wait);
          info->err_shadow = 0;
          spin_lock_init(&info->slock);
        }


	for (i = pre_current_port; i < (hwconf->num_port + pre_current_port); i++)
	{
	  info = &ixcom_table[i];
          serial_out(info, UART_IER, serial_in(info, UART_IER) & 0xf0);
        }

/*
        n = board * ICPDAS_PORTS_PER_BOARD;
        info = &ixcom_table[n];

        retval = request_irq(hwconf->irq, ixcom_interrupt, SA_SHIRQ, "icpdas", info);

        if (retval) {
                printk(KERN_ERR "Board %d: %s",
                        board, ixcom_brdname[hwconf->board_type - 1]);
                printk("  Request irq failed, IRQ (%d) may conflict with"
                        " another device.\n", info->irq);
                return retval;
        }
*/
        return 0;

}

static void show_serial_version(void)
{
        printk(KERN_INFO "%s version %s%s\n", serial_name,
               serial_version, LOCAL_VERSTRING);
}

/*
 * For the 16C950
 */
static void serial_icr_write(struct ixcom_struct *info, int offset, int value)
{
        serial_out(info, UART_SCR, offset);
        serial_out(info, UART_ICR, value);
}

unsigned int serial_icr_read(struct ixcom_struct *info, int offset)
{
        int value;

        serial_icr_write(info, UART_ACR, info->ACR | UART_ACR_ICRRD);
        serial_out(info, UART_SCR, offset);
        value = serial_in(info, UART_ICR);
        serial_icr_write(info, UART_ACR, info->ACR);
        return value;
}

static unsigned int serial_in(struct ixcom_struct *info, int offset)
{
	if ( info->uart_type == PORT_16C950 )
        {
	  return inb(info->base + offset);
        }
	else
	{
	  return inb(info->base + (offset<<2));
        }
}

static void serial_out(struct ixcom_struct *info, int offset, int value)
{
	if ( info->uart_type == PORT_16C950 )
        {
	  outb(value, info->base + offset);
	}
	else
	{
	  outb(value, info->base + (offset<<2));
	}
}

static void ixcom_startrx(struct tty_struct *tty)
{
        struct ixcom_struct *info = (struct ixcom_struct *) tty->driver_data;

        info->ldisc_stop_rx = 0;
        if (I_IXOFF(tty))
	{
                if (info->x_char)
		{
                        info->x_char = 0;
		}
                else
		{
			info->x_char = START_CHAR(tty);
                        serial_out(info, UART_IER, 0); 
                        info->IER |= UART_IER_THRI;    
                        serial_out(info, UART_IER, info->IER);
                }
        }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
	if (info->tty->termios.c_cflag & CRTSCTS)
#else
	if (info->tty->termios->c_cflag & CRTSCTS)
#endif
	{
                info->MCR |= UART_MCR_RTS;
                serial_out(info, UART_MCR, info->MCR);
        }
}

static void ixcom_stoprx(struct tty_struct *tty)
{
        struct ixcom_struct *info = (struct ixcom_struct *) tty->driver_data;

        info->ldisc_stop_rx = 1;
        if (I_IXOFF(tty))
	{
		info->x_char = STOP_CHAR(tty);
                serial_out(info, UART_IER, 0);
                info->IER |= UART_IER_THRI;
                serial_out(info, UART_IER, info->IER); /* force Tx interrupt */
        }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
        if (info->tty->termios.c_cflag & CRTSCTS)
#else
	if (info->tty->termios->c_cflag & CRTSCTS)
#endif
	{
                info->MCR &= ~UART_MCR_RTS;
                serial_out(info, UART_MCR, info->MCR);
        }
}

static int get_ixcom_conf(int busnum, int devnum, int board_type, struct ixcom_hwconf *hwconf)
{
	int i;
        unsigned long port = 0;
        struct pci_dev *pdev = hwconf->pciInfo.pdev;
	int pre_current_port;

	pre_current_port = current_port;

        /* io address */
        hwconf->board_type = board_type;
        hwconf->num_port = ixcom_numports[board_type];
        hwconf->irq = hwconf->pciInfo.pdev->irq;
        
        if ( board_type == ICPDAS_VXC_114U_PCI || board_type == ICPDAS_VXC_144U_PCI || board_type == ICPDAS_VXC_112AU_PCI || board_type == ICPDAS_VXC_142AU_PCI || board_type == ICPDAS_VXC_1001AU_PCI || board_type == ICPDAS_VXC_182IAU_PCI)
        {
          hwconf->uart_type = PORT_16C950;
        }
	else if( board_type == ICPDAS_VXC_142IU_PCI)
	{
          hwconf->uart_type = PORT_16C950;
	  port = pci_resource_start(pdev, 0);
	}
        else
        {
          hwconf->uart_type = PORT_16550A;
	  port = pci_resource_start(pdev, 0);
          port += 192;
        }

/*
        port = pci_resource_start(pdev, 0);
	hwconf->vector = port;

        if ( board_type != ICPDAS_VXC_114U_PCI )
        {
	  port += 192;
        }
*/
        for (i = 0; i < hwconf->num_port; i++)
	{
          
	  if ( board_type == ICPDAS_VXC_114U_PCI || board_type == ICPDAS_VXC_144U_PCI || board_type == ICPDAS_VXC_112AU_PCI || board_type == ICPDAS_VXC_142AU_PCI || board_type == ICPDAS_VXC_1001AU_PCI || board_type == ICPDAS_VXC_182IAU_PCI)
	
          {
            hwconf->ioaddr[i] = pci_resource_start(pdev, i);
          }
	  else if( board_type == ICPDAS_VXC_142IU_PCI)
	  {
            hwconf->ioaddr[i] = port + ( i * 8 );
	  }	
          else
          {
            hwconf->ioaddr[i] = port + ( i * 32 );
          }

	  if( !request_region(hwconf->ioaddr[i], 8, "serial(auto)"))
          {
		release_region(hwconf->ioaddr[i],8);
		request_region(hwconf->ioaddr[i], 8, "serial(auto)");
          }

	  printk("%s%d at port %x (irq = %d) is a %s\n",ixcom_driver->name, pre_current_port, hwconf->ioaddr[i], hwconf->irq, uart_config[hwconf->board_type].name);
	  pre_current_port++; 	
	}

        hwconf->vector_mask = 0;

	for (i = 0; i < hwconf->num_port; i++)
	{
	  hwconf->vector_mask |= (1 << i);
          hwconf->MaxCanSetBaudRate[i] = 921600;
	  hwconf->baud_base[i] = 921600;
        }

        return 0;
}

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
static irqreturn_t ixcom_interrupt(int irq, void *dev_id, struct pt_regs *regs)
#else
static irqreturn_t ixcom_interrupt(int irq, void *dev_id)
#endif
{
	struct ixcom_struct * info;
	int status, iir;
        int msr;
        int pass_counter = 0;

        info = (struct ixcom_struct *)dev_id;
        
        if (!info->tty) {
                return IRQ_NONE;
        }


	iir = serial_in(info, UART_IIR);

	do
	{
		status = serial_in(info, UART_LSR);
		if (status & UART_LSR_DR)
		{
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
                        ixcom_receive_chars(info, &status, regs);
#else
			ixcom_receive_chars(info, &status);
#endif
		}

		msr = serial_in(info, UART_MSR);
                if (msr & UART_MSR_ANY_DELTA)
                {
                        ixcom_check_modem_status(info, msr);
                }

		if ((status & UART_LSR_THRE) || ((iir & UART_IIR_ID) == UART_IIR_THRI))
		{
                        ixcom_transmit_chars(info);
		}

                if (pass_counter++ > ICPDAS_ISR_PASS_LIMIT)
		{
                        break;
                }

                iir = serial_in(info, UART_IIR);

	} while ((iir & UART_IIR_NO_INT) == 0);
	//} while ((iir & UART_IIR_NO_INT) == 0);
	
	return IRQ_HANDLED;
}

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)
static void ixcom_do_softint(struct work_struct *work)
{
        struct ixcom_struct *info = container_of(work, struct ixcom_struct, tqueue);
        struct tty_struct *tty;

        tty = info->tty;

	if (tty) {
                if (test_and_clear_bit(ICPDAS_EVENT_TXLOW, &info->event))
                        tty_wakeup(tty);
                if (test_and_clear_bit(ICPDAS_EVENT_HANGUP, &info->event))
                        tty_hangup(tty);
        }
}
#else
static void ixcom_do_softint(void *private_)
{
        struct ixcom_struct *info = (struct ixcom_struct *) private_;
        struct tty_struct *tty;

        tty = info->tty;

        if (tty) {
                if (test_and_clear_bit(ICPDAS_EVENT_TXLOW, &info->event))
                        tty_wakeup(tty);
                if (test_and_clear_bit(ICPDAS_EVENT_HANGUP, &info->event))
                        tty_hangup(tty);
        }
}
#endif

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,8,0))
static int pci_vxc_fn(struct pci_dev *dev, int enable, int board_type)
#else
static int __devinit pci_vxc_fn(struct pci_dev *dev, int enable, int board_type)
#endif
{
        unsigned long port;
        port=pci_resource_start(dev,0);

	if ( (board_type == ICPDAS_VXC_182I_PCI) || (board_type == ICPDAS_VXC_142_PCI) ||
             (board_type == ICPDAS_VXC_142I_PCI) || (board_type == ICPDAS_VXC_142IU_PCI) ||		(board_type == ICPDAS_VXC_114U_PCI) || (board_type == ICPDAS_VXC_144U_PCI) || 		   (board_type == ICPDAS_VXC_112AU_PCI) || (board_type == ICPDAS_VXC_142AU_PCI)||
	     (board_type == ICPDAS_VXC_1001AU_PCI) || (board_type == ICPDAS_VXC_182IAU_PCI))

        {
                if(!enable)
                {
                        outb(0x80,port+3);
                }
                else
                {
                        outb(0x1,port);
                        outb(0x80,port+2);
                        outb(0x00,port+3);
                        current_num = (~inb(port+0xdc))&0xff;
                        outb(0x03,port+0x2a);
                        outb(0x03,port+5);
                        outb(0x02,port+0xdc);   //high speed mode for port1
                        outb(0x02,port+0xfc);   //high speed mode for port2
                }
        }
	else
	{
		if(!enable)
                {
                        outb(0x20,port+3);
                }
                else
                {
                        outb(0x1,port);
                        outb(0x20,port+2);
                        outb(0x00,port+3);
                        current_num = (~inb(port+0xdc))&0xff;
                        outb(0x0c,port+0x2a);
                        outb(0x0c,port+5);
                        outb(0x02,port+0xdc);
                        outb(0x02,port+0xfc);
                }
	}
       
	return 0;
}

static int ixcom_startup(struct ixcom_struct *info)
{
        unsigned long page;
        unsigned long flags;
	int retval;

        page = __get_free_page(GFP_KERNEL);
        if (!page)
                return -ENOMEM;

	if (info->irq)
        {
		if(!info->irq_request)
                {
                        retval = request_irq(info->irq, ixcom_interrupt, SA_SHIRQ, info->irq_name, info);
                        if (retval)
                        {
                                printk("Request irq failed, IRQ (%d) may conflict with another device.\n", info->irq);
                                return retval;
                        }

                        info->irq_request = 1;
                }
        }

        spin_lock_irqsave(&info->slock, flags);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
	if (tty_port_initialized(&info->ttyPort)){
#else
        if (info->flags & ASYNC_INITIALIZED) {
#endif
                free_page(page);
                spin_unlock_irqrestore(&info->slock, flags);
                return 0;
        }

        if (!info->base || !info->uart_type) {
                if (info->tty)
                        set_bit(TTY_IO_ERROR, &info->tty->flags);
                free_page(page);
                spin_unlock_irqrestore(&info->slock, flags);
                return 0;
        }

        if (info->xmit_buf)
                free_page(page);
        else
                info->xmit_buf = (unsigned char *) page;

        if (info->uart_type == PORT_16C950)
        {
                //Wake up and initialize UART 16C950 

		if ( info->board_type == ICPDAS_VXC_144U_PCI || info->board_type == ICPDAS_VXC_112AU_PCI || info->board_type == ICPDAS_VXC_142IU_PCI || info->board_type == ICPDAS_VXC_142AU_PCI || info->board_type == ICPDAS_VXC_1001AU_PCI || info->board_type == ICPDAS_VXC_182IAU_PCI)
                {
                  info->ACR = 0x10;
		
		  if ( !(serial_icr_read(info, UART_ACR) & 0x10) )
                  {
                    serial_icr_write(info, UART_ACR, info->ACR);
                  }	

                }
                else
                {
                  info->ACR = 0;
                }

                serial_out(info, UART_LCR, 0xBF);
                serial_out(info, UART_EFR, UART_EFR_ECB);
                serial_out(info, UART_IER, 0);
                serial_out(info, UART_LCR, 0);
                serial_icr_write(info, UART_CSR, 0); 
                serial_out(info, UART_LCR, 0xBF);
                serial_out(info, UART_EFR, UART_EFR_ECB);
                serial_out(info, UART_LCR, 0);

		if(info->board_type == ICPDAS_VXC_142IU_PCI || info->board_type == ICPDAS_VXC_142AU_PCI || info->board_type == ICPDAS_VXC_182IAU_PCI || info->board_type == ICPDAS_VXC_144U_PCI)
		{
		  serial_icr_write(info, UART_ACR, info->ACR);	//for RS485
		}
        }

	/*
         * Clear the FIFO buffers and disable them
         * (they will be reenabled in change_speed())
         */
        if (uart_config[info->board_type].flags & UART_CLEAR_FIFO) {
                serial_out(info, UART_FCR, UART_FCR_ENABLE_FIFO);
                serial_out(info, UART_FCR, (UART_FCR_ENABLE_FIFO |
                                             UART_FCR_CLEAR_RCVR |
                                             UART_FCR_CLEAR_XMIT));
                serial_out(info, UART_FCR, 0);
        }

	/*
         * At this point there's no way the LSR could still be 0xFF;
         * if it is, then bail out, because there's likely no UART
         * here.
         */


        if (serial_in(info, UART_LSR) == 0xff) {
                spin_unlock_irqrestore(&info->slock, flags);
                if (capable(CAP_SYS_ADMIN)) {
                        if (info->tty)
                                set_bit(TTY_IO_ERROR, &info->tty->flags);
                        return 0;
                } else
                        return -ENODEV;
        }

/*
	if (info->irq)
	{
		printk("info->irq : %d\n",info->irq);
		retval = request_irq(info->irq, ixcom_interrupt, SA_SHIRQ, "icpdas", info);
		if (retval)
		{
                	printk("Request irq failed, IRQ (%d) may conflict with another device.\n", info->irq);
                	return retval;
        	}
	}
*/	
	/*
         * Clear the interrupt registers.
         */
        (void) serial_in(info, UART_LSR);
        (void) serial_in(info, UART_RX);
        (void) serial_in(info, UART_IIR);
        (void) serial_in(info, UART_MSR);

	/*
         * Now, initialize the UART
         */
        serial_out(info, UART_LCR, UART_LCR_WLEN8);    /* reset DLAB */
        info->MCR = UART_MCR_DTR | UART_MCR_RTS;
        serial_out(info, UART_MCR, info->MCR);

	/*
         * Finally, enable interrupts
         */
        info->IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI;
        serial_out(info, UART_IER, info->IER); /* enable interrupts */

	/*
         * And clear the interrupt registers again for luck.
         */
        (void)serial_in(info, UART_LSR);
        (void)serial_in(info, UART_RX);
        (void)serial_in(info, UART_IIR);
        (void)serial_in(info, UART_MSR);

	if (info->tty)
                clear_bit(TTY_IO_ERROR, &info->tty->flags);
        info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;

        /*
         * and set the speed of the serial port
         */
        spin_unlock_irqrestore(&info->slock, flags);
        ixcom_change_speed(info, NULL);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
	tty_port_set_initialized(&info->ttyPort, 1);
#else
        info->flags |= ASYNC_INITIALIZED;
#endif
        return 0;
}

static void ixcom_shutdown(struct ixcom_struct *info)
{
        unsigned long flags;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
        if (!tty_port_initialized(&info->ttyPort))
#else
        if (!(info->flags & ASYNC_INITIALIZED))
#endif
                return;

        spin_lock_irqsave(&info->slock, flags);

        /*
         * clear delta_msr_wait queue to avoid mem leaks: we may free the irq
         * here so the queue might never be waken up
         */
        wake_up_interruptible(&info->delta_msr_wait);

        /*
         * Free the IRQ, if necessary
         */


        if (info->xmit_buf) {
                free_page((unsigned long) info->xmit_buf);
                info->xmit_buf = NULL;
        }

        info->IER = 0;
        serial_out(info, UART_IER, 0x00);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
	if (!info->tty || (info->tty->termios.c_cflag & HUPCL))
#else
	if (!info->tty || (info->tty->termios->c_cflag & HUPCL))
#endif
                info->MCR &= ~(UART_MCR_DTR | UART_MCR_RTS);
        serial_out(info, UART_MCR, info->MCR);

        serial_out(info, UART_FCR, (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT));

        /* read data port to reset things */
        (void) serial_in(info, UART_RX);

        if (info->tty)
                set_bit(TTY_IO_ERROR, &info->tty->flags);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
        tty_port_set_initialized(&info->ttyPort, 0);
#else
        info->flags &= ~ASYNC_INITIALIZED;
#endif

        spin_unlock_irqrestore(&info->slock, flags);
	
	if(info->irq_request)
	{
		free_irq(info->irq, info);
		info->irq_request = 0;
	}

}

static void ixcom_send_break(struct ixcom_struct *info, int duration)
{
        unsigned long flags;

        if (!info->base)
                return;
        set_current_state(TASK_INTERRUPTIBLE);
        spin_lock_irqsave(&info->slock, flags);
        serial_out(info, UART_LCR, serial_in(info, UART_LCR) | UART_LCR_SBC);
        spin_unlock_irqrestore(&info->slock, flags);
        schedule_timeout(duration);
        spin_lock_irqsave(&info->slock, flags);
        serial_out(info, UART_LCR,serial_in(info, UART_LCR) & ~UART_LCR_SBC);
        spin_unlock_irqrestore(&info->slock, flags);
}

static int ixcom_get_serial_info(struct ixcom_struct *info, struct serial_struct __user *retinfo)
{
        struct serial_struct tmp;

        if (!retinfo)
                return (-EFAULT);
        memset(&tmp, 0, sizeof(tmp));
        tmp.type = info->uart_type;
        tmp.line = info->port;
        tmp.port = info->base;
        tmp.irq = info->irq;
        tmp.flags = info->flags;
        tmp.baud_base = info->baud_base;
        tmp.close_delay = info->close_delay;
        tmp.closing_wait = info->closing_wait;
        tmp.custom_divisor = info->custom_divisor;
        tmp.hub6 = 0;
        if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
                return -EFAULT;
        return (0);
}

static int ixcom_set_serial_info(struct ixcom_struct *info, struct serial_struct __user *new_info)
{
        struct serial_struct new_serial;
        unsigned int flags;
        int retval = 0;

        if (!new_info || !info->base)
                return (-EFAULT);
        if (copy_from_user(&new_serial, new_info, sizeof(new_serial)))
                return -EFAULT;

        if ((new_serial.irq != info->irq) || (new_serial.port != info->base) || (new_serial.custom_divisor != info->custom_divisor) || (new_serial.baud_base != info->baud_base))
                return (-EPERM);

        flags = info->flags & ASYNC_SPD_MASK;

        if (!capable(CAP_SYS_ADMIN))
	{
                if ((new_serial.baud_base != info->baud_base) || (new_serial.close_delay != info->close_delay) || ((new_serial.flags & ~ASYNC_USR_MASK) != (info->flags & ~ASYNC_USR_MASK)))
                        return (-EPERM);
                info->flags = ((info->flags & ~ASYNC_USR_MASK) | (new_serial.flags & ASYNC_USR_MASK));
        }
	else
	{
		info->flags = ((info->flags & ~ASYNC_FLAGS) | (new_serial.flags & ASYNC_FLAGS));
                info->close_delay = new_serial.close_delay * HZ / 100;
                info->closing_wait = new_serial.closing_wait * HZ / 100;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,12,0)

#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,0)
                info->ttyPort.low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
                info->ttyPort.low_latency = 0;
#else
		info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
		info->tty->low_latency = 0;
#endif  
        }

        info->uart_type = new_serial.type;


        /* */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
        if (tty_port_initialized(&info->ttyPort))
#else
        if (info->flags & ASYNC_INITIALIZED)
#endif
	{
                if (flags != (info->flags & ASYNC_SPD_MASK))
		{
                        ixcom_change_speed(info, NULL);
                }
        }
	else
	{
                retval = ixcom_startup(info);
        }
	
	return (retval);
}

static int ixcom_get_lsr_info(struct ixcom_struct *info, unsigned int __user *value)
{
        unsigned char status;
        unsigned int result;
        unsigned long flags;

        spin_lock_irqsave(&info->slock, flags);
        status = serial_in(info, UART_LSR);
        spin_unlock_irqrestore(&info->slock, flags);
        result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0);
        return put_user(result, value);
}


#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
static int ixcom_change_speed(struct ixcom_struct *info, struct termios *old_termios)
#else
static int ixcom_change_speed(struct ixcom_struct *info, struct ktermios *old_termios)
#endif
{
	unsigned cflag, cval, fcr;
        int ret = 0;
        unsigned char status;
        long baud;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
        if (!info->tty)
#else
	if (!info->tty || !info->tty->termios)
#endif
                return ret;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
        cflag = info->tty->termios.c_cflag;
#else
	cflag = info->tty->termios->c_cflag;
#endif
        if (!(info->base))
                return ret;

#ifndef B921600
#define B921600 (B460800 +1)
#endif
                switch (cflag & (CBAUD | CBAUDEX)) {
                case B921600:
                        baud = 921600;
                        break;
                case B460800:
                        baud = 460800;
                        break;
                case B230400:
                        baud = 230400;
                        break;
                case B115200:
                        baud = 115200;
                        break;
                case B57600:
                        baud = 57600;
                        break;
                case B38400:
                        baud = 38400;
                        break;
                case B19200:
                        baud = 19200;
                        break;
                case B9600:
                        baud = 9600;
                        break;
                case B4800:
                        baud = 4800;
                        break;
                case B2400:
                        baud = 2400;
                        break;

		case B1800:
                        baud = 1800;
                        break;
                case B1200:
                        baud = 1200;
                        break;
                case B600:
                        baud = 600;
                        break;
                case B300:
                        baud = 300;
                        break;
                case B200:
                        baud = 200;
                        break;
                case B150:
                        baud = 150;
                        break;
                case B134:
                        baud = 134;
                        break;
                case B110:
                        baud = 110;
                        break;
                case B75:
                        baud = 75;
                        break;
                case B50:
                        baud = 50;
                        break;

		default:
                        baud = 0;
                        break;
                }
                ixcom_set_baud(info, baud);

	/* byte size and parity */
        switch (cflag & CSIZE) {
        case CS5:
                cval = 0x00;
                break;
        case CS6:
                cval = 0x01;
                break;
        case CS7:
                cval = 0x02;
                break;
        case CS8:
                cval = 0x03;
                break;
        default:
                cval = 0x00;
                break;          /* too keep GCC shut... */
        }

        if (cflag & CSTOPB)
                cval |= 0x04;
        if (cflag & PARENB)
                cval |= UART_LCR_PARITY;
        if (!(cflag & PARODD))
                cval |= UART_LCR_EPAR;
        if (cflag & CMSPAR)
                cval |= UART_LCR_SPAR;

	if ((info->uart_type == PORT_8250) || (info->uart_type == PORT_16450))
	{
            fcr = 0;
        }
	else
	{
          fcr = UART_FCR_ENABLE_FIFO;

          switch (info->rx_trigger)
	  {
            case 1:
                    fcr |= UART_FCR_TRIGGER_1;
                    break;
            case 4:
                    fcr |= UART_FCR_TRIGGER_4;
                    break;
            case 8:
                    fcr |= UART_FCR_TRIGGER_8;
                    break;
            default:
                    fcr |= UART_FCR_TRIGGER_14;
                    break;
          }
        }

	/* CTS flow control flag and modem status interrupts */
        info->IER &= ~UART_IER_MSI;
        info->MCR &= ~UART_MCR_AFE;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
	tty_port_set_cts_flow(&info->ttyPort, cflag & CRTSCTS);
	if (cflag & CRTSCTS){
#else
        if (cflag & CRTSCTS){
          info->flags |= ASYNC_CTS_FLOW;
#endif
          info->IER |= UART_IER_MSI;
          if (info->uart_type == PORT_16550A)
	  {
            info->MCR |= UART_MCR_AFE;
  	  }
	  else
	  {
            status = serial_in(info, UART_MSR);
            if (info->tty->hw_stopped)
	    {
              if (status & UART_MSR_CTS)
	      {
                info->tty->hw_stopped = 0;
                if (info->uart_type != PORT_16550A)
		{
                  info->IER |= UART_IER_THRI;
                  serial_out(info, UART_IER, info->IER);
                }
                
                schedule_work(&info->tqueue);                          
	      }
            }
	    else
	    {
              if (!(status & UART_MSR_CTS))
	      {
                info->tty->hw_stopped = 1;
                if (info->uart_type != PORT_16550A)
		{
                  info->IER &= ~UART_IER_THRI;
                  serial_out(info, UART_IER, info->IER);
                }
              }
            }
          }
	}
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
	else
          info->flags &= ~ASYNC_CTS_FLOW;
#endif

        serial_out(info, UART_MCR, info->MCR);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
	tty_port_set_check_carrier(&info->ttyPort, ~cflag & CLOCAL);
	if (~cflag & CLOCAL)
		info->IER |= UART_IER_MSI;
#else
        if (cflag & CLOCAL)
	{
          info->flags &= ~ASYNC_CHECK_CD;
        }
	else
	{
          info->flags |= ASYNC_CHECK_CD;
	  info->IER |= UART_IER_MSI;
	}
#endif

        serial_out(info, UART_IER, info->IER);
	 
	info->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
       
	if (I_INPCK(info->tty))
                info->read_status_mask |= UART_LSR_FE | UART_LSR_PE;
        if (I_BRKINT(info->tty) || I_PARMRK(info->tty))
                info->read_status_mask |= UART_LSR_BI;

        info->ignore_status_mask = 0;

        if (I_IGNBRK(info->tty))
	{
          info->ignore_status_mask |= UART_LSR_BI;
          info->read_status_mask |= UART_LSR_BI;
          /*
           * If we're ignore parity and break indicators, ignore
           * overruns too.  (For real raw support).
           */
          if (I_IGNPAR(info->tty))
	  {
            info->ignore_status_mask |=
                                      UART_LSR_OE |
                                      UART_LSR_PE |
                                      UART_LSR_FE;
            info->read_status_mask |=
                                      UART_LSR_OE |
                                      UART_LSR_PE |
                                      UART_LSR_FE;
          }
        }

	serial_out(info, UART_FCR, fcr);       /* set fcr */
        serial_out(info, UART_LCR, cval);

        return ret;	

}

static int ixcom_set_baud(struct ixcom_struct *info, long newspd)
{
        int quot = 0;
        unsigned char cval;
        int ret = 0;
        unsigned long flags;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
        if (!info->tty)
#else
	if (!info->tty || !info->tty->termios)
#endif
                return ret;

        if (!(info->base))
                return ret;

        if (newspd > info->MaxCanSetBaudRate)
                return 0;

        info->realbaud = newspd;
        
	if (newspd == 134)
	{
          quot = (2 * info->baud_base / 269);
        }
	else if (newspd)
	{
          quot = info->baud_base / newspd;
          if (quot == 0)
             quot = 1;
        }
	else
	{
          quot = 0;
        }

	info->timeout = ((info->xmit_fifo_size * HZ * 10 * quot) / info->baud_base);
        info->timeout += HZ/50;         
	
	if (quot)
	{
          spin_lock_irqsave(&info->slock, flags);
          info->MCR |= UART_MCR_DTR;
          serial_out(info, UART_MCR, info->MCR);
          spin_unlock_irqrestore(&info->slock, flags);
        }
	else
	{
          spin_lock_irqsave(&info->slock, flags);
          info->MCR &= ~UART_MCR_DTR;
          serial_out(info, UART_MCR, info->MCR);
          spin_unlock_irqrestore(&info->slock, flags);
          return ret;
        }

	cval = serial_in(info, UART_LCR);

        serial_out(info, UART_LCR, cval | UART_LCR_DLAB);      /* set DLAB */

        serial_out(info, UART_DLL, quot & 0xff);       /* LS of divisor */
        serial_out(info, UART_DLM, quot >> 8); /* MS of divisor */
        serial_out(info, UART_LCR, cval);      /* reset DLAB */

	return ret;
}

static int ixcom_block_til_ready(struct tty_struct *tty, struct file *filp, struct ixcom_struct *info)
{
	DECLARE_WAITQUEUE(wait, current);
        int retval;
        int do_clocal = 0;
        unsigned long flags;

        /*
         * If non-blocking mode is set, or the port is not enabled,
         * then make the check up front and then exit.
         */
        if ((filp->f_flags & O_NONBLOCK) || (tty->flags & (1 << TTY_IO_ERROR))) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
		tty_port_set_active(&info->ttyPort, 1);
#else
                info->flags |= ASYNC_NORMAL_ACTIVE;
#endif
                return 0;
        }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
        if (tty->termios.c_cflag & CLOCAL)
#else
	if (tty->termios->c_cflag & CLOCAL)
#endif
	{
                do_clocal = 1;
	}

	/*
         * Block waiting for the carrier detect and the line to become
         * free (i.e., not in use by the callout).  While we are in
         * this loop, info->count is dropped by one, so that
         * ixcom_close() knows when to free things.  We restore it upon
         * exit, either normal or abnormal.
         */
        retval = 0;
        add_wait_queue(&info->open_wait, &wait);

        spin_lock_irqsave(&info->slock, flags);
        if (!tty_hung_up_p(filp))
                info->count--;

	spin_unlock_irqrestore(&info->slock, flags);
        info->blocked_open++;

	 while (1) {
                spin_lock_irqsave(&info->slock, flags);
                serial_out(info, UART_MCR, serial_in(info, UART_MCR) |
                        UART_MCR_DTR | UART_MCR_RTS);
                spin_unlock_irqrestore(&info->slock, flags);
                set_current_state(TASK_INTERRUPTIBLE);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
                if (tty_hung_up_p(filp) || !tty_port_initialized(&info->ttyPort)) {
#else
                if (tty_hung_up_p(filp) || !(info->flags & ASYNC_INITIALIZED)) {
#endif
                        if (info->flags & ASYNC_HUP_NOTIFY)
                                retval = -EAGAIN;
                        else
                                retval = -ERESTARTSYS;
                        break;
                }

#if LINUX_VERSION_CODE < KERNEL_VERSION(4,4,0)
                if (!(info->flags & ASYNC_CLOSING))
#endif
		  if (do_clocal || (serial_in(info, UART_MSR) & UART_MSR_DCD))
                        break;

                if (signal_pending(current)) {
                        retval = -ERESTARTSYS;
                        break;
                }
                schedule();
        }

        set_current_state(TASK_RUNNING);
        remove_wait_queue(&info->open_wait, &wait);
        if (!tty_hung_up_p(filp))
                info->count++;

	info->blocked_open--;
        if (retval)
                return retval;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
	tty_port_set_active(&info->ttyPort, 1);
#else
	info->flags |= ASYNC_NORMAL_ACTIVE;
#endif
        return 0;
}

int ixcom_open(struct tty_struct *tty, struct file * filp)
{
	struct ixcom_struct *info;
        int retval, line;
	
        tty->driver_data = NULL;

        line = tty->index;
	if ((line < 0) || (line >= PORT_VXC_MAX)) return 0;
 
        info = ixcom_table + line;
        
	if (!info->base)
                return -ENODEV;

        tty->driver_data = info;
        info->tty = tty;
	retval = ixcom_startup(info);
        if (retval)
                return retval;

	retval = ixcom_block_til_ready(tty, filp, info);
        
	if (retval)
                return retval;

        info->count++;

        if ((info->count == 1) && (info->flags & ASYNC_SPLIT_TERMIOS)) {
                if (tty->driver->subtype == SERIAL_TYPE_NORMAL)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
                        tty->termios = info->normal_termios;
#else
			*tty->termios = info->normal_termios;
#endif

                ixcom_change_speed(info, NULL);
        }
        

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
	info->session = current->signal->session;
        info->pgrp = process_group(current);
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
	clear_bit(TTY_DONT_FLIP, &tty->flags);
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,12,0)

#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,0)
        info->ttyPort.low_latency = 1;
#else
	tty->low_latency = 1;
#endif

        return 0;
}

static int ixcom_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
	int c, total = 0;
        struct ixcom_struct *info = (struct ixcom_struct *) tty->driver_data;
        unsigned long flags;

        if (!tty || !info->xmit_buf)
                return (0);

        while (1)
	{
                c = min_t(int, count, min(SERIAL_XMIT_SIZE - info->xmit_cnt - 1, SERIAL_XMIT_SIZE - info->xmit_head));
                if (c <= 0)
                        break;

                memcpy(info->xmit_buf + info->xmit_head, buf, c);
                spin_lock_irqsave(&info->slock, flags);
                info->xmit_head = (info->xmit_head + c) & (SERIAL_XMIT_SIZE - 1);
                info->xmit_cnt += c;
                spin_unlock_irqrestore(&info->slock, flags);

                buf += c;
                count -= c;
                total += c;

        }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0)
	if (info->xmit_cnt && !tty->flow.stopped && !(info->IER & UART_IER_THRI)) {
#else
	if (info->xmit_cnt && !tty->stopped && !(info->IER & UART_IER_THRI)) {
#endif
                if (!tty->hw_stopped || (info->uart_type == PORT_16550A)) {
                        spin_lock_irqsave(&info->slock, flags);
                        info->IER |= UART_IER_THRI;
                        serial_out(info, UART_IER, info->IER);
                        spin_unlock_irqrestore(&info->slock, flags);
                }
        }


        return total;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0)
static unsigned int ixcom_write_room(struct tty_struct *tty)
#else
static int ixcom_write_room(struct tty_struct *tty)
#endif
{
	struct ixcom_struct *info = (struct ixcom_struct *) tty->driver_data;
        int ret;

        ret = SERIAL_XMIT_SIZE - info->xmit_cnt - 1;
        if (ret < 0)
                ret = 0;

        return (ret);
}
/*
static void ixcom_put_char(struct tty_struct *tty, unsigned char ch)
{
	struct ixcom_struct *info = (struct ixcom_struct *) tty->driver_data;
        unsigned long flags;

        if (!tty || !info->xmit_buf)
                return;

        if (info->xmit_cnt >= SERIAL_XMIT_SIZE - 1)
                return;

        spin_lock_irqsave(&info->slock, flags);
        info->xmit_buf[info->xmit_head++] = ch;
        info->xmit_head &= SERIAL_XMIT_SIZE - 1;
        info->xmit_cnt++;
        spin_unlock_irqrestore(&info->slock, flags);
        if (!tty->stopped && !(info->IER & UART_IER_THRI)) {
                if (!tty->hw_stopped || (info->uart_type == PORT_16550A)) {
                        spin_lock_irqsave(&info->slock, flags);
                        info->IER |= UART_IER_THRI;
			serial_out(info, UART_IER, info->IER);
                        spin_unlock_irqrestore(&info->slock, flags);
                }
        }
}
*/

static void ixcom_flush_chars(struct tty_struct *tty)
{
	struct ixcom_struct *info = (struct ixcom_struct *) tty->driver_data;
        unsigned long flags;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0)
        if (info->xmit_cnt <= 0 || tty->flow.stopped || !info->xmit_buf || (tty->hw_stopped && (info->uart_type != PORT_16550A)))
#else
        if (info->xmit_cnt <= 0 || tty->stopped || !info->xmit_buf || (tty->hw_stopped && (info->uart_type != PORT_16550A)))
#endif
                return;

        spin_lock_irqsave(&info->slock, flags);

        info->IER |= UART_IER_THRI;
	serial_out(info, UART_IER, info->IER);

        spin_unlock_irqrestore(&info->slock, flags);
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0)
static unsigned int ixcom_chars_in_buffer(struct tty_struct *tty)
#else
static int ixcom_chars_in_buffer(struct tty_struct *tty)
#endif
{
	struct ixcom_struct *info = tty->driver_data;
        return info->xmit_cnt;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
static int ixcom_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
#else
static int ixcom_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
#endif
{
	struct ixcom_struct * info = (struct ixcom_struct *)tty->driver_data;
        struct async_icount cprev, cnow;        /* kernel counter temps */
	struct serial_icounter_struct __user *p_cuser;
        unsigned long templ;
        unsigned long flags;
        void __user *argp = (void __user *)arg;
        int retval;
       
        if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
            (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGSTRUCT) &&
            (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) {
                if (tty->flags & (1 << TTY_IO_ERROR))
                    return -EIO;
        }

	switch (cmd) {
        case TCSBRK:            /* SVID version: non-zero arg --> no break */
                retval = tty_check_change(tty);
                if (retval)
                        return (retval);
                tty_wait_until_sent(tty, 0);
                if (!arg)
                        ixcom_send_break(info, HZ / 4); 
                return (0);

        case TCSBRKP:           /* support for POSIX tcsendbreak() */

                retval = tty_check_change(tty);
                if (retval)
                        return (retval);
                tty_wait_until_sent(tty, 0);
                ixcom_send_break(info, arg ? arg * (HZ / 10) : HZ / 4);
                return (0);

        case TIOCGSOFTCAR:
                return put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *) argp);

        case TIOCSSOFTCAR:
                if (get_user(templ, (unsigned long __user *) argp))
                        return -EFAULT;
                arg = templ;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
                tty->termios.c_cflag = ((tty->termios.c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0));
#else
		tty->termios->c_cflag = ((tty->termios->c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0));
#endif
                return (0);


	case TIOCSERCONFIG:
	case TIOCGSERIAL:
                return ixcom_get_serial_info(info, argp);
        case TIOCSSERIAL:
                return ixcom_set_serial_info(info, argp);
        case TIOCSERGETLSR:     /* Get line status register */
                return ixcom_get_lsr_info(info, argp);
                /*
                 * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change
                 * - mask passed in arg for lines of interest
                 *   (use |'ed TIOCM_RNG/DSR/CD/CTS for masking)
                 * Caller should use TIOCGICOUNT to see which one it was
                 */

	case TIOCMIWAIT:{
                        DECLARE_WAITQUEUE(wait, current);
                        int ret;
                        spin_lock_irqsave(&info->slock, flags);
                        cprev = info->icount;   
                        spin_unlock_irqrestore(&info->slock, flags);

                        add_wait_queue(&info->delta_msr_wait, &wait);
                        while (1) {
                                spin_lock_irqsave(&info->slock, flags);
                                cnow = info->icount;    
                                spin_unlock_irqrestore(&info->slock, flags);

                                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;
                                }
                                if (signal_pending(current)) {
                                        ret = -ERESTARTSYS;
                                        break;
                                }
                                cprev = cnow;
                        }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0)
			current->__state = TASK_RUNNING;
#else
			current->state = TASK_RUNNING;
#endif
                        remove_wait_queue(&info->delta_msr_wait, &wait);
                        break;
		} 

	case TIOCGICOUNT:
                spin_lock_irqsave(&info->slock, flags);
                cnow = info->icount;
                spin_unlock_irqrestore(&info->slock, flags);
                p_cuser = argp;
                if (put_user(cnow.frame, &p_cuser->frame))
                        return -EFAULT;
                if (put_user(cnow.brk, &p_cuser->brk))
                        return -EFAULT;
                if (put_user(cnow.overrun, &p_cuser->overrun))
                        return -EFAULT;
                if (put_user(cnow.buf_overrun, &p_cuser->buf_overrun))
                        return -EFAULT;
                if (put_user(cnow.parity, &p_cuser->parity))
                        return -EFAULT;
                if (put_user(cnow.rx, &p_cuser->rx))
                        return -EFAULT;
                if (put_user(cnow.tx, &p_cuser->tx))
                        return -EFAULT;
                put_user(cnow.cts, &p_cuser->cts);
                put_user(cnow.dsr, &p_cuser->dsr);
                put_user(cnow.rng, &p_cuser->rng);
                put_user(cnow.dcd, &p_cuser->dcd);


                return 0;

	default:
                return -ENOIOCTLCMD;
        }
	return 0;
}

static void ixcom_throttle(struct tty_struct *tty)
{
	ixcom_stoprx(tty);
}

static void ixcom_unthrottle(struct tty_struct *tty)
{
	ixcom_startrx(tty);
}

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
static void ixcom_set_termios(struct tty_struct *tty, struct termios *old_termios)
#else
static void ixcom_set_termios(struct tty_struct *tty, struct ktermios *old_termios)
#endif

{
	struct ixcom_struct *info = (struct ixcom_struct *) tty->driver_data;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
	unsigned int cflag = tty->termios.c_cflag;
#else
	unsigned int cflag = tty->termios->c_cflag;
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
	if ((cflag == old_termios->c_cflag) && (RELEVANT_IFLAG(tty->termios.c_iflag) == RELEVANT_IFLAG(old_termios->c_iflag)))
#else
	if ((cflag == old_termios->c_cflag) && (RELEVANT_IFLAG(tty->termios->c_iflag) == RELEVANT_IFLAG(old_termios->c_iflag)))
#endif
        return;

	ixcom_change_speed(info, old_termios);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
	if ((old_termios->c_cflag & CRTSCTS) && !(tty->termios.c_cflag & CRTSCTS))                
#else
	if ((old_termios->c_cflag & CRTSCTS) && !(tty->termios->c_cflag & CRTSCTS))
#endif
	{
                        tty->hw_stopped = 0;
                        ixcom_start(tty);
        }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
	if ((old_termios->c_iflag & IXON) && !(tty->termios.c_iflag & IXON))
#else
	if ((old_termios->c_iflag & IXON) && !(tty->termios->c_iflag & IXON))
#endif
        {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0)
		tty->flow.stopped = 0;
#else
                tty->stopped = 0;
#endif
                ixcom_start(tty);
        }

}

static void ixcom_stop(struct tty_struct *tty)
{
	struct ixcom_struct *info = (struct ixcom_struct *) tty->driver_data;
        unsigned long flags;

        spin_lock_irqsave(&info->slock, flags);
        if (info->IER & UART_IER_THRI)
	{
                info->IER &= ~UART_IER_THRI;
                serial_out(info, UART_IER, info->IER);
        }

	/*
         * We really want to stop the transmitter from sending.
         */

        if (info->uart_type == PORT_16C950) {
                info->ACR |= UART_ACR_TXDIS;
                serial_icr_write(info, UART_ACR, info->ACR);
        }

        spin_unlock_irqrestore(&info->slock, flags);
}

static void ixcom_start(struct tty_struct *tty)
{
	struct ixcom_struct *info = (struct ixcom_struct *) tty->driver_data;
        unsigned long flags;

        spin_lock_irqsave(&info->slock, flags);
        if (info->xmit_cnt && info->xmit_buf && !(info->IER & UART_IER_THRI))
	{
                info->IER |= UART_IER_THRI;
                serial_out(info, UART_IER, info->IER);
        }

	/*
         * Re-enable the transmitter if we disabled it.
         */
		

        if (info->uart_type == PORT_16C950) {
                info->ACR &= ~UART_ACR_TXDIS;
                serial_icr_write(info, UART_ACR, info->ACR);
        }

        spin_unlock_irqrestore(&info->slock, flags);
}

void ixcom_hangup(struct tty_struct *tty)
{
	struct ixcom_struct *info = (struct ixcom_struct *) tty->driver_data;

        ixcom_flush_buffer(tty);
        ixcom_shutdown(info);
        info->event = 0;
        info->count = 0;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
	tty_port_set_active(&info->ttyPort, 0);
#else
        info->flags &= ~ASYNC_NORMAL_ACTIVE;
#endif
        info->tty = NULL;
        wake_up_interruptible(&info->open_wait);
}

/*
static void ixcom_rs_break(struct tty_struct *tty, int break_state)
{
	struct ixcom_struct *info = (struct ixcom_struct *) tty->driver_data;
        unsigned long flags;

        spin_lock_irqsave(&info->slock, flags);
        if (break_state == -1)
                serial_out(info, UART_LCR, serial_in(info, UART_LCR) | UART_LCR_SBC);
        else
                serial_out(info, UART_LCR, serial_in(info, UART_LCR) & ~UART_LCR_SBC);
        spin_unlock_irqrestore(&info->slock, flags);
}
*/

static void ixcom_wait_until_sent(struct tty_struct *tty, int timeout)
{
	struct ixcom_struct *info = tty->driver_data;
        unsigned long orig_jiffies, char_time;
        int lsr;

        if (info->uart_type == PORT_UNKNOWN)
                return;

        if (info->xmit_fifo_size == 0)
                return;         /* Just in case.... */

	orig_jiffies = jiffies;
        /*
         * Set the check interval to be 1/5 of the estimated time to
         * send a single character, and make it at least 1.  The check
         * interval should also be less than the timeout.
         *
         * Note: we have to use pretty tight timings here to satisfy
         * the NIST-PCTS.
         */
        char_time = (info->timeout - HZ / 50) / info->xmit_fifo_size;
        char_time = char_time / 5;
        if (char_time == 0)
                char_time = 1;
        if (timeout && timeout < char_time)
                char_time = timeout;
        /*
         * If the transmitter hasn't cleared in twice the approximate
         * amount of time to send the entire FIFO, it probably won't
         * ever clear.  This assumes the UART isn't doing flow
         * control, which is currently the case.  Hence, if it ever
         * takes longer than info->timeout, this is probably due to a
         * UART bug of some kind.  So, we clamp the timeout parameter at
         * 2*info->timeout.
         */
        if (!timeout || timeout > 2 * info->timeout)
                timeout = 2 * info->timeout;
#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
        printk(KERN_DEBUG "In rs_wait_until_sent(%d) check=%lu...",
                timeout, char_time);
        printk("jiff=%lu...", jiffies);
#endif

	while (!((lsr = inb(info->base + UART_LSR)) & UART_LSR_TEMT))
	{
#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
                printk("lsr = %d (jiff=%lu)...", lsr, jiffies);
#endif
                set_current_state(TASK_INTERRUPTIBLE);
                schedule_timeout(char_time);
                if (signal_pending(current))
                        break;
                if (timeout && time_after(jiffies, orig_jiffies + timeout))
                        break;
        }
        set_current_state(TASK_RUNNING);
#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
        printk("lsr = %d (jiff=%lu)...done\n", lsr, jiffies);
#endif
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
static int ixcom_tiocmget(struct tty_struct *tty)
#else
static int ixcom_tiocmget(struct tty_struct *tty, struct file *file)
#endif
{
	struct ixcom_struct *info = (struct ixcom_struct *) tty->driver_data;
        unsigned char control, status;
        unsigned long flags;


        if (tty->index == PORT_VXC_MAX)
                return (-ENOIOCTLCMD);
        if (tty->flags & (1 << TTY_IO_ERROR))
                return (-EIO);

        control = info->MCR;

        spin_lock_irqsave(&info->slock, flags);
        status = serial_in(info, UART_MSR);

        if (status & UART_MSR_ANY_DELTA)
                ixcom_check_modem_status(info, status);

        spin_unlock_irqrestore(&info->slock, flags);

        return ((control & UART_MCR_RTS) ? TIOCM_RTS : 0) |
            ((control & UART_MCR_DTR) ? TIOCM_DTR : 0) | ((status & UART_MSR_DCD) ? TIOCM_CAR : 0) | ((status & UART_MSR_RI) ? TIOCM_RNG : 0) | ((status & UART_MSR_DSR) ? TIOCM_DSR : 0) | ((status & UART_MSR_CTS) ? TIOCM_CTS : 0);
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
static int ixcom_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear)
#else
static int ixcom_tiocmset(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear)
#endif
{
	struct ixcom_struct *info = (struct ixcom_struct *) tty->driver_data;
        unsigned long flags;

        if (tty->index == PORT_VXC_MAX)
                return -ENOIOCTLCMD;
        if (tty->flags & (1 << TTY_IO_ERROR))
                return -EIO;

        spin_lock_irqsave(&info->slock, flags);

        if (set & TIOCM_RTS)
              info->MCR |= UART_MCR_RTS;

        if (set & TIOCM_DTR)
	{
              info->MCR |= UART_MCR_DTR;
        }
        if (clear & TIOCM_RTS)
              info->MCR &= ~UART_MCR_RTS;
        if (clear & TIOCM_DTR)
	{
              info->MCR &= ~UART_MCR_DTR;
	}

        serial_out(info, UART_MCR, info->MCR);
        spin_unlock_irqrestore(&info->slock, flags);

	return 0;
}


static void ixcom_close(struct tty_struct * tty, struct file * filp)
{
	struct ixcom_struct *info = tty->driver_data;

        unsigned long timeout;
        unsigned long flags;
        struct tty_ldisc *ld;
	
	if ((tty->index < 0) || (tty->index >= PORT_VXC_MAX))
                return;
        if (!info)
                return;

        spin_lock_irqsave(&info->slock, flags);
	 if (tty_hung_up_p(filp)) {
                spin_unlock_irqrestore(&info->slock, flags);
                return;
        }

        if ((tty->count == 1) && (info->count != 1)) {
                /*
                 * Uh, oh.  tty->count is 1, which means that the tty
                 * structure will be freed.  Info->count should always
                 * be one in these conditions.  If it's greater than
                 * one, we've got real problems, since it means the
                 * serial port won't be shutdown.
                 */
                printk(KERN_ERR "ixcom_close: bad serial port count; "
                        "tty->count is 1, info->count is %d\n", info->count);
                info->count = 1;
        }
        if (--info->count < 0) {
                printk(KERN_ERR "ixcom_close: bad serial port count for "
                        "ttys%d: %d\n", info->port, info->count);
                info->count = 0;
        }
        if (info->count) {
                spin_unlock_irqrestore(&info->slock, flags);
                return;
        }
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,6,0)
        info->flags |= ASYNC_CLOSING;
#endif
        spin_unlock_irqrestore(&info->slock, flags);

	 /*
         * Save the termios structure, since this port may have
         * separate termios for callout and dialin.
         */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
	if (tty_port_active(&info->ttyPort))
#else
        if (info->flags & ASYNC_NORMAL_ACTIVE)
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
                info->normal_termios = tty->termios;
#else
		info->normal_termios = *tty->termios;
#endif
        /*
         * Now we wait for the transmit buffer to clear; and we notify
         * the line discipline to only process XON/XOFF characters.
         */
        tty->closing = 1;
        if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE)
	{
                tty_wait_until_sent(tty, info->closing_wait);
	}
        /*
         * At this point we stop accepting input.  To do this, we
         * disable the receive line status interrupts, and tell the
         * interrupt driver to stop checking the data ready bit in the
         * line status register.
         */
        info->IER &= ~UART_IER_RLSI;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
        if (tty_port_initialized(&info->ttyPort)){
#else
        if (info->flags & ASYNC_INITIALIZED) {
#endif
                serial_out(info, UART_IER, info->IER);
                /*
                 * Before we drop DTR, make sure the UART transmitter
                 * has completely drained; this is especially
                 * important if there is a transmit FIFO!
                 */
                timeout = jiffies + HZ;
                while (!(serial_in(info, UART_LSR) & UART_LSR_TEMT)) {
			set_current_state(TASK_INTERRUPTIBLE);
                        schedule_timeout(5);
                        if (time_after(jiffies, timeout))
                                break;
                }
        }

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
        if (tty->driver->ops->flush_buffer)
	{
                tty->driver->ops->flush_buffer(tty);
	}
#else
	if (tty->driver->flush_buffer)
        {
                tty->driver->flush_buffer(tty);
        }
#endif

        ld = tty_ldisc_ref(tty);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
        if (ld) {
                if (ld->ops->flush_buffer)
                        ld->ops->flush_buffer(tty);
                tty_ldisc_deref(ld);
        }
#else
	if (ld) {
                if (ld->flush_buffer)
                        ld->flush_buffer(tty);
                tty_ldisc_deref(ld);
        }
#endif

        tty->closing = 0;
        info->event = 0;
        info->tty = NULL;
        if (info->blocked_open)
	{
                if (info->close_delay)
		{
			set_current_state(TASK_INTERRUPTIBLE);
                        schedule_timeout(info->close_delay);
		}
                wake_up_interruptible(&info->open_wait);
        }

#if LINUX_VERSION_CODE > KERNEL_VERSION(4,7,0)
	tty_port_set_active(&info->ttyPort, 0);
#elif LINUX_VERSION_CODE > KERNEL_VERSION(4,6,0)
        info->flags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CLOSING);
#else
        info->flags &= ~ASYNC_NORMAL_ACTIVE;
#endif
        wake_up_interruptible(&info->close_wait);
	ixcom_shutdown(info);
	
}

static void ixcom_flush_buffer(struct tty_struct *tty)
{
        struct ixcom_struct *info = tty->driver_data;
        char fcr;
        unsigned long flags;

        spin_lock_irqsave(&info->slock, flags);
        info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;

        fcr = serial_in(info, UART_FCR);
        serial_out(info, UART_FCR, (fcr | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT));
        serial_out(info, UART_FCR, fcr);

        spin_unlock_irqrestore(&info->slock, flags);

        wake_up_interruptible(&tty->write_wait);
        //if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup)

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
        if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc->ops->write_wakeup)
        {
                (tty->ldisc->ops->write_wakeup) (tty);
                //(tty->ldisc.write_wakeup) (tty);
        }
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
        if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.ops->write_wakeup)
	{
                (tty->ldisc.ops->write_wakeup) (tty);
                //(tty->ldisc.write_wakeup) (tty);
	}
#else
	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup)
        {
                (tty->ldisc.write_wakeup) (tty);
                //(tty->ldisc.write_wakeup) (tty);
        }
#endif
}

/* ixcom tty operation */
static struct tty_operations fops = {
        .open = ixcom_open,
        .close = ixcom_close,
        .write = ixcom_write,
//      .put_char = ixcom_put_char,
        .flush_chars = ixcom_flush_chars,
        .write_room = ixcom_write_room,
        .chars_in_buffer = ixcom_chars_in_buffer,
        .flush_buffer = ixcom_flush_buffer,
        .ioctl = ixcom_ioctl,
        .throttle = ixcom_throttle,
        .unthrottle = ixcom_unthrottle,
        .set_termios = ixcom_set_termios,
        .stop = ixcom_stop,
        .start = ixcom_start,
        .hangup = ixcom_hangup,
//	.break_ctl = ixcom_rs_break,
        .wait_until_sent = ixcom_wait_until_sent,
        .tiocmget = ixcom_tiocmget,
        .tiocmset = ixcom_tiocmset,
};

/*
 * The serial driver boot-time initialization code!
 */
static int __init ixcom_init(void)
{
	int i, j, m, retval, b, n;
	struct pci_dev *pdev = NULL;
	int index;
	unsigned char busnum, devnum;
	struct ixcom_hwconf hwconf;
        struct ixcom_struct *info;

	show_serial_version();
	
	current_port = 0;
	BOARD_COUNT = 0;  // number of device addmyself

	/* Initialize the tty_driver structure */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5,15,0))
	ixcom_driver = tty_alloc_driver(PORT_VXC_MAX, TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV);
#else
	ixcom_driver = alloc_tty_driver(PORT_VXC_MAX);
#endif
	
	spin_lock_init(&gm_lock);

	for (i = 0; i < ICPDAS_BOARDS; i++)
	{
		ixcomcfg[i].board_type = -1;
        }

	if (!ixcom_driver)
		return -ENOMEM;

        ixcom_driver->magic = TTY_DRIVER_MAGIC;
        ixcom_driver->owner = THIS_MODULE;
        ixcom_driver->driver_name = "vxc_serial";

	ixcom_driver->name = "ttySV";
        ixcom_driver->major = 0;
        ixcom_driver->minor_start = 0;
        //ixcom_driver->minor_num = PORT_VXC_MAX - 1;
        ixcom_driver->name_base = 0;
        ixcom_driver->num = PORT_VXC_MAX;
        ixcom_driver->type = TTY_DRIVER_TYPE_SERIAL;
        ixcom_driver->subtype = SERIAL_TYPE_NORMAL;
        ixcom_driver->init_termios = tty_std_termios;
	ixcom_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
        //ixcom_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
        ixcom_driver->flags = TTY_DRIVER_REAL_RAW;
        //ixcom_driver->refcount = 0;
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
        ixcom_driver->ttys = ixcom_tty;
        ixcom_driver->termios = ixcom_termios;
#endif

#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3,2,20))
        ixcom_driver->termios_locked = ixcom_termios_locked;
#endif

	/* set tty operation */
	tty_set_operations(ixcom_driver, &fops);

	memset(ixcom_table, 0, PORT_VXC_MAX * sizeof(struct ixcom_struct));
	memset(&hwconf, 0, sizeof(struct ixcom_hwconf));

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0))
        for(i = 0; i < PORT_VXC_MAX; i++)
        {
                tty_port_init(&ixcom_table[i].ttyPort);
                tty_port_link_device(&ixcom_table[i].ttyPort, ixcom_driver, i);
        }
#endif

	/* find icpdas serial board */
	m = 0;
	n = ARRAY_SIZE(ixcom_pci_tbl) - 1;
        index = 0;
        b = 0;

	while (b < n)
	{
		pdev = pci_get_subsys(ixcom_pci_tbl[b].vendor, ixcom_pci_tbl[b].device, ixcom_pci_tbl[b].subvendor, ixcom_pci_tbl[b].subdevice,  pdev);
          
		if (pdev == NULL)
		{
			b++;
			continue;
		}

		printk("%s : VID 0x%04x DID 0x%04x SVID 0x%04x SDID 0x%04x\n", __FUNCTION__,ixcom_pci_tbl[b].vendor, ixcom_pci_tbl[b].device, ixcom_pci_tbl[b].subvendor, ixcom_pci_tbl[b].subdevice);
		hwconf.pciInfo.busNum = busnum = pdev->bus->number;
		hwconf.pciInfo.devNum = devnum = PCI_SLOT(pdev->devfn) << 3;
		hwconf.pciInfo.pdev = pdev;
		printk(KERN_INFO "Found ICPDAS %s board(BusNo=%d,DevNo=%d)\n", ixcom_brdname[(int) (ixcom_pci_tbl[b].driver_data)], busnum, devnum >> 3);

		index++;

		if (pci_enable_device(pdev))
		{
			printk(KERN_ERR "ICPDAS %s board enable fail !\n", ixcom_brdname[(int) (ixcom_pci_tbl[b].driver_data)]);
			
			continue;
		}

		/*
		 * Run the initialization function, if any
		*/	
		pci_vxc_fn(pdev, 1, (int)ixcom_pci_tbl[b].driver_data);

		retval = get_ixcom_conf(busnum, devnum, (int)ixcom_pci_tbl[b].driver_data, &hwconf);

		if (retval < 0) 
			continue;

		ixcom_getcfg(m, &hwconf);


		if (ixcom_initbrd(m, &hwconf) < 0) 
			continue;

		m++;
	}

	BOARD_COUNT = m;
	printk("Total %d vxc device card found.\n", BOARD_COUNT);

	retval = tty_register_driver(ixcom_driver);

	if (!retval)
        {
		/* get major number */
		vxc_major = ixcom_driver->major;
        }
	else
	{
		printk(KERN_ERR "Couldn't install ICPDAS VXC driver !\n");
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5,15,0))
		tty_driver_kref_put(ixcom_driver);
#else
		put_tty_driver(ixcom_driver);
#endif

		current_port = 0;

		for (i=0; i < ICPDAS_BOARDS; i++)
		{
			if (ixcomcfg[i].board_type == -1)
			{
				continue;
			}
			else
			{
				pdev = ixcomcfg[i].pciInfo.pdev;

				if (pdev != NULL)
				{
					for(j = 0; j < ixcomcfg[i].num_port; j++, current_port++)
					{
						info = &ixcom_table[current_port];

						if(info->irq_request)
						{
							free_irq(info->irq, info);
							info->irq_request = 0;
						}

						if (ixcomcfg[i].ioaddr[j])
						{
							release_region(ixcomcfg[i].ioaddr[j],8);
							printk("ttySV%d at port %x released.\n", current_port, ixcomcfg[i].ioaddr[j]);
							//current_port++;
						}
					}
				}
			}
		}

		return retval;
	}

	if (ixcom_proc_init())
		printk("proc init error\n");

	return 0;
}

static void __exit ixcom_fini(void)
{
	int i,j;
	struct pci_dev *pdev;
	struct ixcom_struct *info;

	current_port = 0;

	for (i=0; i < ICPDAS_BOARDS; i++)
	{
		if (ixcomcfg[i].board_type == -1)
		{
			continue;
		}
		else
		{
			pdev = ixcomcfg[i].pciInfo.pdev;
			//free_irq(ixcomcfg[i].irq, &ixcom_table[i]);
           
			/* PCI */	

			if (pdev != NULL)
			{
				for(j = 0; j < ixcomcfg[i].num_port; j++)
				{
					info = &ixcom_table[current_port];

					if(info->irq_request)
					{
						//printk("%s ... Base : 0x%x\n",__FUNCTION__,info->base);
						free_irq(info->irq, info);
						info->irq_request = 0;
					}

					if (ixcomcfg[i].ioaddr[j])
					{
						release_region(ixcomcfg[i].ioaddr[j],8);
						printk("ttySV%d at port %x released.\n", current_port, ixcomcfg[i].ioaddr[j]);
						current_port++;
					}
				}
			}
		}
	}
       
	tty_unregister_driver(ixcom_driver);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5,15,0))
	tty_driver_kref_put(ixcom_driver);
#else
	put_tty_driver(ixcom_driver);
#endif
	ixcom_proc_exit();
}

module_init(ixcom_init);
module_exit(ixcom_fini);
