/*
*                                                                                      	
*                WCH Multi-I/O Board Device Driver                    
*                                                                                                                
*					 		
*	
*	Driver for WCH Multi-I/O Board device driver						
*	Based on drivers/char/serial.c, by Linus Torvalds, Theodore Ts'o.
*										
* 	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.
*
* 	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 
*          
*
*                                                    		Version: 1.0.0.0          	
*                                                           Date: 2010/09/11       	
*/
#include "wch_common.h"

MODULE_AUTHOR(WCH_DRIVER_AUTHOR);
MODULE_DESCRIPTION(WCH_DRIVER_DESC);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,18))
MODULE_LICENSE("GPL");
#endif


extern struct wch_board			wch_board_table[WCH_BOARDS_MAX];
extern struct wch_ser_port		wch_ser_table[WCH_SER_TOTAL_MAX + 1];


char wch_ser_ic_table[WCH_SER_PORT_MAX_UART][10] = 
{
	{"UNKNOWN"},
	{"CH352"},
	{"CH353"},
	{"CH355"},
	{"CH356"},
	{"CH357"},
	{"CH358"},
	{"CH359"}	
};

char wch_port_remap[2][10] = 
{
	{"NON-REMAP"},
	{"REMAP"}
};

enum 
{
	WCH_BOARD_CH352_2S = 1,	
	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,
};

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0))
static  struct pci_device_id wch_pci_board_id[] = 
{
	{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},
	/*========================================================new vid 2011-03-23 add========================================================*/
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH352_2S, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH352_2S, 0, 0, WCH_BOARD_CH352_2S},	
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH352_1S1P, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH352_1S1P, 0, 0, WCH_BOARD_CH352_1S1P},	
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH353_4S, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH353_4S, 0, 0, WCH_BOARD_CH353_4S},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH353_2S1P, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH353_2S1P, 0, 0, WCH_BOARD_CH353_2S1P},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH353_2S1PAR, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH353_2S1PAR, 0, 0, WCH_BOARD_CH353_2S1PAR},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH355_4S, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH355_4S, 0, 0, WCH_BOARD_CH355_4S},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH356_4S1P, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH356_4S1P, 0, 0, WCH_BOARD_CH356_4S1P}, 
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH356_6S, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH356_6S, 0, 0, WCH_BOARD_CH356_6S},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH356_8S, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH356_8S, 0, 0, WCH_BOARD_CH356_8S},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH357_4S, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH357_4S, 0, 0, WCH_BOARD_CH357_4S},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH358_4S1P, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH358_4S1P, 0, 0, WCH_BOARD_CH358_4S1P},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH358_8S, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH358_8S, 0, 0, WCH_BOARD_CH358_8S},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH359_16S, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH359_16S, 0, 0, WCH_BOARD_CH359_16S},
	{0}
};

MODULE_DEVICE_TABLE(pci, wch_pci_board_id);
#else
typedef struct
{
	unsigned short vendor;
	unsigned short device;
	unsigned short subvendor;
	unsigned short subdevice;
	unsigned short driver_data;
	unsigned short part_number;
} wch_pci_info;


static wch_pci_info wch_pci_board_id[] =
{
	{VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH352_2S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH352_2S, WCH_BOARD_CH352_2S},	
	{VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH352_1S1P, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH352_1S1P, WCH_BOARD_CH352_1S1P},	
	{VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH353_4S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH353_4S, WCH_BOARD_CH353_4S},
	{VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH353_2S1P, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH353_2S1P, WCH_BOARD_CH353_2S1P},
	{VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH353_2S1PAR, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH353_2S1PAR, WCH_BOARD_CH353_2S1PAR},
	{VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH355_4S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH355_4S, WCH_BOARD_CH355_4S},
	{VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH356_4S1P, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH356_4S1P, WCH_BOARD_CH356_4S1P}, 
	{VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH356_6S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH356_6S, WCH_BOARD_CH356_6S},
	{VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH356_8S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH356_8S, WCH_BOARD_CH356_8S},
	{VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH357_4S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH357_4S, WCH_BOARD_CH357_4S},
	{VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH358_4S1P, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH358_4S1P, WCH_BOARD_CH358_4S1P},
	{VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH358_8S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH358_8S, WCH_BOARD_CH358_8S},
	{VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH359_16S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH359_16S, WCH_BOARD_CH359_16S},
	/*========================================================new vid 2011-03-23 add============================================================*/
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH352_2S, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH352_2S, WCH_BOARD_CH352_2S},	
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH352_1S1P, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH352_1S1P, WCH_BOARD_CH352_1S1P},	
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH353_4S, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH353_4S, WCH_BOARD_CH353_4S},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH353_2S1P, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH353_2S1P, WCH_BOARD_CH353_2S1P},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH353_2S1PAR, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH353_2S1PAR, WCH_BOARD_CH353_2S1PAR},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH355_4S, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH355_4S, WCH_BOARD_CH355_4S},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH356_4S1P, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH356_4S1P, WCH_BOARD_CH356_4S1P}, 
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH356_6S, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH356_6S, WCH_BOARD_CH356_6S},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH356_8S, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH356_8S, WCH_BOARD_CH356_8S},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH357_4S, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH357_4S, WCH_BOARD_CH357_4S},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH358_4S1P, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH358_4S1P, WCH_BOARD_CH358_4S1P},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH358_8S, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH358_8S, WCH_BOARD_CH358_8S},
	{VENDOR_ID_WCH_PCI_NEW, DEVICE_ID_WCH_CH359_16S, SUB_VENDOR_ID_WCH_PCI_NEW, SUB_DEVICE_ID_WCH_CH359_16S, WCH_BOARD_CH359_16S},
	{0}
};

#endif


struct wch_board		wch_board_table[WCH_BOARDS_MAX];
struct wch_ser_port	    wch_ser_table[WCH_SER_TOTAL_MAX + 1];

static int wch_ser_port_total_cnt;

#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18))
static irqreturn_t wch_interrupt(int irq, void *dev_id)
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0))
static irqreturn_t wch_interrupt(int irq, void *dev_id, struct pt_regs *regs)
#else
static void wch_interrupt(int irq, void *dev_id, struct pt_regs *regs)
#endif
{
    struct wch_ser_port *sp = NULL;
    struct wch_board *sb = NULL;
    int i;
    int status = 0;
    
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0))    
    int handled = IRQ_NONE;
#endif 

    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)
    {
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0))
        return handled;
#else
        return;    
#endif 
    }
	
    
	if ((sb->ser_port > 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)
    {
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0))
        return handled;
#else
        return;
#endif
    }
	
    
    
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0))            
	handled = IRQ_HANDLED;
    return handled;
#endif
}

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 tablecnt;
    int boardcnt;
    int i;
	unsigned short int sub_device_id;
    int status;

	// 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;
	tablecnt = 0;
	boardcnt = 0;
	status = 0;
	
    while (tablecnt < wch_pci_board_id_cnt) 
    {
		
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0))
		pdev = pci_get_device(wch_pci_board_id[tablecnt].vendor, wch_pci_board_id[tablecnt].device, pdev);
#else
        pdev = pci_find_device(wch_pci_board_id[tablecnt].vendor, wch_pci_board_id[tablecnt].device, pdev);
#endif          
        if (pdev == NULL) 
        {
           	tablecnt++;
           	continue;
		}
        if ((tablecnt > 0) && ((pdev == pdev_array[0]) || (pdev == pdev_array[1]) || (pdev == pdev_array[2]) || (pdev == pdev_array[3])))
        {
            continue;
        }
         
		pci_read_config_word(pdev, 0x2e, &sub_device_id);

        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[tablecnt].subdevice)
        {
            continue;
        }

        if (pdev == NULL)
        {
            printk("WCH Error: PCI device object is an NULL pointer !\n\n");
            status = -EIO;
            return status; 
        }
        else
        {
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,4,3))
            pci_disable_device(pdev);
#endif
            status = pci_enable_device(pdev);
            
            if (status != 0)
            { 
                printk("WCH Error: WCH Board Enable Fail !\n\n");
                status = -ENXIO;
                return status;
            }
        }
		
        boardcnt++;
        if (boardcnt > 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[boardcnt-1];
        
        pdev_array[boardcnt-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[tablecnt].driver_data;
        sb->pb_info = wch_pci_board_conf[sb->board_enum];
        
		sb->board_flag = sb->pb_info.board_flag;
		
        sb->board_number = boardcnt - 1;
    }   
	
	// print info
    if (boardcnt == 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;

    for (i = 0; i < WCH_BOARDS_MAX; i++)
    {
        sb = &wch_board_table[i];
        
        if (sb->board_enum > 0)
        {
			pdev = sb->pdev;
			sb->ports = sb->pb_info.num_serport;
			sb->ser_port = sb->pb_info.num_serport;

			wch_ser_port_total_cnt = wch_ser_port_total_cnt + sb->ser_port;
			
			
			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);
			}
			
			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;

	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_port > 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_port; 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;
					
					if ((sb->board_flag & BOARDFLAG_REMAP) == BOARDFLAG_REMAP)
					{
						sp->port.vector = 0;
						sb->vector_mask = 0x00;  
					}
					else
					{
						sp->port.vector = sb->bar_addr[sb->pb_info.intr_vector_bar] + sb->pb_info.intr_vector_offset;
						
					}
				}
				sb->vector_mask = 0xffff;
				ser_port_index = ser_port_index + sb->ser_port;
			}
		}
	}	
	
	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;

	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_port > 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_port; 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			= 115200;		
				sp->port.pb_info			= sb->pb_info;
				
				sp->port.irq 				= sb->irq;											
				sp->port.line 				= n;		
				sp->port.uartclk  			= sp->port.baud_base * 16;
				sp->port.iotype   			= WCH_UPIO_PORT;	
				sp->port.flags 				= ASYNC_SHARE_IRQ;	
				
				sp->port.ldisc_stop_rx		= 0;
				spin_lock_init(&sp->port.lock);

				if (sp->port.chip_flag == CH352_HWID) 
				{
					sp->port.wch_type		= WCH_SER_PORT_CH352;
					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 == CH353_HWID) 
				{
					sp->port.wch_type		= WCH_SER_PORT_CH353;
					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 == CH355_HWID) 
				{
					sp->port.wch_type		= WCH_SER_PORT_CH355;
					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 == CH356_HWID) 
				{
					sp->port.wch_type		= WCH_SER_PORT_CH356;
					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 == CH357_HWID) 
				{
					sp->port.wch_type		= WCH_SER_PORT_CH357;
					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 == CH358_HWID) 
				{
					sp->port.wch_type		= WCH_SER_PORT_CH358;
					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 == CH359_HWID) 
				{
					sp->port.wch_type		= WCH_SER_PORT_CH359;
					sp->port.type			= PORT_SER_16750;
					sp->port.fifosize		= CH359_FIFOSIZE_SET;
					sp->port.rx_trigger		= CH359_TRIGGER_LEVEL_SET;							
				}
				else
				{
					sp->port.wch_type		= WCH_SER_PORT_UNKNOWN;
					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;
				}
				
				if ((sb->pb_info.board_flag & BOARDFLAG_16PORTS) == BOARDFLAG_16PORTS)
				{
					sp->port.port_flag		|= PORTFLAG_16PORTS;
				}
				
				sp->port.setserial_flag = WCH_SER_BAUD_NOTSETSER;
			}		
			
			sb->ser_isr = wch_ser_interrupt;
		}
		else
		{
			sb->ser_isr = NULL;
		}		
	}
	
	// release io resource
	for (i = 0; i < WCH_SER_TOTAL_MAX; i++)
	{
		sp = &wch_ser_table[i];
		
		if (sp->port.iobase > 0)
		{
			release_region(sp->port.iobase, WCH_SER_ADDRESS_LENGTH);
		}
	}	
	
    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  : x%2x\n", sb->vector_mask);
			printk(" bar[0]       : x%4x\n", sb->bar_addr[0]);
			printk(" bar[1]       : x%4x\n", sb->bar_addr[1]);
			printk(" bar[2]       : x%4x\n", sb->bar_addr[2]);
			printk(" bar[3]       : x%4x\n", sb->bar_addr[3]);			
			printk(" bar[4]       : x%4x\n", sb->bar_addr[4]);
			printk(" bar[5]       : x%4x\n", sb->bar_addr[5]);					 
			printk("----------------------------\n");
		}
	}
	printk("============================\n");
	printk("\n");
#endif	
	
	
#if WCH_DBG_SERPORT	
	printk("\n");
	printk("======== ser  info ========\n");
	for (j = 0; j < WCH_SER_TOTAL_MAX; 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       : x%4x\n", sp->port.iobase);
			printk(" irq          : %d\n",   sp->port.irq);
			printk(" vector       : x%4x\n", sp->port.vector);
			printk(" vector_mask  : x%4x\n", sp->port.vector_mask);
			printk(" chip_flag    : x%2x\n", sp->port.chip_flag);
			printk(" port_flag    : x%2x\n", sp->port.port_flag);				 
			printk("----------------------------\n");
		}
	}
	printk("============================\n");
	printk("\n");
#endif

}
#endif

int wch_register_irq(void)
{
	struct wch_board *sb = NULL;
	int status = 0;
	int i;

	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)
		{
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18))
			status = request_irq(sb->irq, wch_interrupt, IRQF_SHARED, "wch", sb);
#else
            status = request_irq(sb->irq, wch_interrupt, SA_SHIRQ, "wch", sb);
#endif	
			
			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;
			}		
			
		}
	}
	
	return status;
}

void wch_release_irq(void)
{
	struct wch_board *sb = NULL;
    int i;

    for (i = 0; i < WCH_BOARDS_MAX; i++)
    {
        sb = &wch_board_table[i];
        
        if (sb->board_enum > 0)
        {
            free_irq(sb->irq, sb);
        }
    }
}

static struct ser_driver wch_ser_reg = {
    .dev_name       = "ttyWCH",
	.major          = WCH_TTY_MAJOR,
	.minor          = 0,
	.nr             = (WCH_SER_TOTAL_MAX + 1),
};

static int __init wch_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;
	}
	
	
	status = wch_get_pci_board_conf();
	if (status != 0)
	{
		goto step1_fail;
	}
	
	
	status = wch_assign_resource();
	if (status != 0)
	{
		goto step1_fail;
	}
	
	
	status = wch_ser_port_table_init();
	if (status != 0)
	{
		goto step1_fail;
	}
	
	
	status = wch_register_irq();
	if (status != 0)
	{
		goto step1_fail;
	}
	
	
	status = wch_ser_register_driver(&wch_ser_reg);
	if (status != 0)
	{
		goto step2_fail;
	}	
	
	
   	status = wch_ser_register_ports(&wch_ser_reg);
    if (status != 0)
    {
        goto step3_fail;
    }
	
	
#if WCH_DBG
	wch_debug();
#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;
	
}

static void __exit wch_exit(void)
{
	printk("\n\n");
	printk("====================  WCH Device Driver Module Uninstall  ====================\n");
	printk("\n");
	
	wch_ser_unregister_ports(&wch_ser_reg);
	
	wch_ser_unregister_driver(&wch_ser_reg);
	
	wch_release_irq();
	
	printk("WCH Info : Unload WCH Multi-I/O Board Driver Module Done.\n");
	printk("================================================================================\n");
}

module_init(wch_init);
module_exit(wch_exit);

