/* * PCI/PCIE to serial driver for ch351/352/353/355/356/357/358/359/382/384, etc. * * Copyright (C) 2021 WCH. * Author: TECH39 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Update Log: * V1.00 - initial version * V1.10 - fixed uart send bugs * V1.11 - fixed the issue of serial ports number creation * V1.12 - fixed modem signals support * V1.13 - added automatic frequency multiplication when using baud rates higher than 115200bps - added mutex protection in uart transmit process * V1.14 - optimized the processing of serial ports in interruption * V1.15 - added support for non-standard baud rate * V1.16 - fixed uart clock frequency multiplication bugs * V1.17 - modified uart data received process */ #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]; extern unsigned char ch365_32s; #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) static struct pci_device_id wch_pci_board_id[] = { {VENDOR_ID_WCH_CH351, DEVICE_ID_WCH_CH351_2S, SUB_VENDOR_ID_WCH_CH351, SUB_DEVICE_ID_WCH_CH351_2S, 0, 0, WCH_BOARD_CH351_2S}, {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH352_2S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH352_2S, 0, 0, WCH_BOARD_CH352_2S}, {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH352_1S1P, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH352_1S1P, 0, 0, WCH_BOARD_CH352_1S1P}, {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH353_4S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH353_4S, 0, 0, WCH_BOARD_CH353_4S}, {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH353_2S1P, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH353_2S1P, 0, 0, WCH_BOARD_CH353_2S1P}, {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH353_2S1PAR, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH353_2S1PAR, 0, 0, WCH_BOARD_CH353_2S1PAR}, {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH355_4S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH355_4S, 0, 0, WCH_BOARD_CH355_4S}, {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH356_4S1P, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH356_4S1P, 0, 0, WCH_BOARD_CH356_4S1P}, {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH356_6S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH356_6S, 0, 0, WCH_BOARD_CH356_6S}, {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH356_8S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH356_8S, 0, 0, WCH_BOARD_CH356_8S}, {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH357_4S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH357_4S, 0, 0, WCH_BOARD_CH357_4S}, {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH358_4S1P, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH358_4S1P, 0, 0, WCH_BOARD_CH358_4S1P}, {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH358_8S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH358_8S, 0, 0, WCH_BOARD_CH358_8S}, {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH359_16S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH359_16S, 0, 0, WCH_BOARD_CH359_16S}, {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH382_2S, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH382_2S, 0, 0, WCH_BOARD_CH382_2S}, {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH382_2S1P, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH382_2S1P, 0, 0, WCH_BOARD_CH382_2S1P}, {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH384_4S, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH384_4S, 0, 0, WCH_BOARD_CH384_4S}, {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH384_4S1P, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH384_4S1P, 0, 0, WCH_BOARD_CH384_4S1P}, {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH384_8S, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH384_8S, 0, 0, WCH_BOARD_CH384_8S}, {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH384_28S, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH384_28S, 0, 0, WCH_BOARD_CH384_28S}, {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH365_32S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH365_32S, 0, 0, WCH_BOARD_CH365_32S}, {0} }; MODULE_DEVICE_TABLE(pci, wch_pci_board_id); #else struct wch_pci_info { unsigned short vendor; unsigned short device; unsigned short subvendor; unsigned short subdevice; unsigned short driver_data; }; static struct wch_pci_info wch_pci_board_id[] = { {VENDOR_ID_WCH_CH351, DEVICE_ID_WCH_CH351_2S, SUB_VENDOR_ID_WCH_CH351, SUB_DEVICE_ID_WCH_CH351_2S, WCH_BOARD_CH351_2S}, {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}, {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH382_2S, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH382_2S, WCH_BOARD_CH382_2S}, {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH382_2S1P, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH382_2S1P, WCH_BOARD_CH382_2S1P}, {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH384_4S, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH384_4S, WCH_BOARD_CH384_4S}, {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH384_4S1P, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH384_4S1P, WCH_BOARD_CH384_4S1P}, {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH384_8S, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH384_8S, WCH_BOARD_CH384_8S}, {VENDOR_ID_WCH_PCIE, DEVICE_ID_WCH_CH384_28S, SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH384_28S, WCH_BOARD_CH384_28S}, {VENDOR_ID_WCH_PCI, DEVICE_ID_WCH_CH365_32S, SUB_VENDOR_ID_WCH_PCI, SUB_DEVICE_ID_WCH_CH365_32S, WCH_BOARD_CH365_32S}, {0} }; #endif struct wch_board wch_board_table[WCH_BOARDS_MAX]; struct wch_ser_port wch_ser_table[WCH_SER_TOTAL_MAX + 1]; int wch_ser_port_total_cnt; unsigned char ch365_32s = 0; #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_ports > 0) && (sb->ser_isr != NULL)) { sp = &wch_ser_table[sb->ser_port_index]; if (!sp) { status = 1; } status = sb->ser_isr(sb, sp); } if (status != 0) { #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 table_cnt; int board_cnt; int i; unsigned short int sub_device_id; int status; #if WCH_DBG printk("%s : %s\n", __FILE__, __FUNCTION__); #endif // clear and init some variable memset(wch_board_table, 0, WCH_BOARDS_MAX * sizeof(struct wch_board)); for (i = 0; i < WCH_BOARDS_MAX; i++) { wch_board_table[i].board_enum = -1; wch_board_table[i].board_number = -1; } wch_pci_board_id_cnt = (sizeof(wch_pci_board_id) / sizeof(wch_pci_board_id[0])) - 1; // search wch serial and multi-I/O board pdev = NULL; table_cnt = 0; board_cnt = 0; status = 0; while (table_cnt < wch_pci_board_id_cnt) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) pdev = pci_get_device(wch_pci_board_id[table_cnt].vendor, wch_pci_board_id[table_cnt].device, pdev); #else pdev = pci_find_device(wch_pci_board_id[table_cnt].vendor, wch_pci_board_id[table_cnt].device, pdev); #endif if (pdev == NULL) { table_cnt++; continue; } if ((table_cnt > 0) && ((pdev == pdev_array[0]) || (pdev == pdev_array[1]) || (pdev == pdev_array[2]) || (pdev == pdev_array[3]))) { continue; } if (wch_pci_board_id[table_cnt].driver_data == WCH_BOARD_CH365_32S) { ch365_32s = 0x01; } pci_read_config_word(pdev, 0x2e, &sub_device_id); if (ch365_32s) { } else { if (sub_device_id == 0) { printk("WCH Error: WCH Board (bus:%d device:%d), in configuration space,\n", pdev->bus->number, PCI_SLOT(pdev->devfn)); printk(" subdevice id isn't vaild.\n\n"); status = -EIO; return status; } if (sub_device_id != wch_pci_board_id[table_cnt].subdevice) { continue; } } if (pdev == NULL) { printk("WCH Error: PCI device object is an NULL pointer !\n\n"); status = -EIO; return status; } else { #if (LINUX_VERSION_CODE <= KERNEL_VERSION(3,8,0)) #if (LINUX_VERSION_CODE > KERNEL_VERSION(2,4,3)) pci_disable_device(pdev); #endif #endif status = pci_enable_device(pdev); if (status != 0) { printk("WCH Error: WCH Board Enable Fail !\n\n"); status = -ENXIO; return status; } } board_cnt++; if (board_cnt > WCH_BOARDS_MAX) { printk("\n"); printk("WCH Error: WCH Driver Module Support Four Boards In Maximum !\n\n"); status = -ENOSPC; return status; } sb = &wch_board_table[board_cnt - 1]; pdev_array[board_cnt - 1] = pdev; sb->pdev = pdev; sb->bus_number = pdev->bus->number; sb->dev_number = PCI_SLOT(pdev->devfn); sb->board_enum = (int)wch_pci_board_id[table_cnt].driver_data; sb->pb_info = wch_pci_board_conf[sb->board_enum]; sb->board_flag = sb->pb_info.board_flag; sb->board_number = board_cnt - 1; } if (board_cnt == 0) { printk("WCH Info : No WCH Multi-I/O Board Found !\n\n"); status = -ENXIO; return status; } else { for (i = 0; i < WCH_BOARDS_MAX; i++) { sb = &wch_board_table[i]; if (sb->board_enum > 0) { printk("\n"); if ((sb->pb_info.num_serport) > 0) { printk("WCH Info : Found WCH %s Series Board (%dS),\n", sb->pb_info.board_name, sb->pb_info.num_serport); } printk(" bus number:%d, device number:%d\n\n", sb->bus_number, sb->dev_number); } } } return status; } static int wch_get_pci_board_conf( void ) { struct wch_board *sb = NULL; struct pci_dev *pdev = NULL; int status = 0; int i; int j; #if WCH_DBG printk("%s : %s\n", __FILE__, __FUNCTION__); #endif for (i = 0; i < WCH_BOARDS_MAX; i++) { sb = &wch_board_table[i]; if (sb->board_enum > 0) { pdev = sb->pdev; sb->ser_ports = sb->pb_info.num_serport; wch_ser_port_total_cnt = wch_ser_port_total_cnt + sb->ser_ports; if (wch_ser_port_total_cnt > WCH_SER_TOTAL_MAX) { printk("WCH Error: Too much serial port, maximum %d ports can be supported !\n\n", WCH_SER_TOTAL_MAX); status = -EIO; return status; } for (j = 0; j < WCH_PCICFG_BAR_TOTAL; j++) { sb->bar_addr[j] = pci_resource_start(pdev, j); } if ((sb->board_flag & BOARDFLAG_CH365_32_PORTS) == BOARDFLAG_CH365_32_PORTS) { sb->board_membase = ioremap(sb->bar_addr[1], 4096); if (!sb->board_membase) { status = -EIO; printk("WCH Error: ioremap failed !\n"); return status; } } sb->irq = sb->pdev->irq; if (sb->irq <= 0) { printk("WCH Error: WCH Board %s Series (bus:%d device:%d), in configuartion space, irq isn't valid !\n\n", sb->pb_info.board_name, sb->bus_number, sb->dev_number ); status = -EIO; return status; } } } return status; } static int wch_assign_resource( void ) { struct wch_board *sb = NULL; struct wch_ser_port *sp = NULL; int status = 0; int i; int j; int ser_n; int ser_port_index = 0; #if WCH_DBG printk("%s : %s\n", __FILE__, __FUNCTION__); #endif memset(wch_ser_table, 0, (WCH_SER_TOTAL_MAX + 1) * sizeof(struct wch_ser_port)); for (i = 0; i < WCH_BOARDS_MAX; i++) { sb = &wch_board_table[i]; if (sb->board_enum > 0) { if (sb->ser_ports > 0) { sb->vector_mask = 0; // assign serial port resource ser_n = sb->ser_port_index = ser_port_index; sp = &wch_ser_table[ser_n]; if (sp == NULL) { status = -ENXIO; printk("WCH Error: Serial port table address error !\n"); return status; } for (j = 0; j < sb->ser_ports; j++, ser_n++, sp++) { sp->port.chip_flag = sb->pb_info.port[j].chip_flag; sp->port.iobase = sb->bar_addr[sb->pb_info.port[j].bar1] + sb->pb_info.port[j].offset1; /* use scr reg to test io space */ outb(0x55, sp->port.iobase + UART_SCR); if (inb(sp->port.iobase + UART_SCR) != 0x55) { status = -ENXIO; if (j == 0) printk("WCH Error: pci/pcie address error !\n"); else printk("WCH Error: ch432/ch438 communication error !\n"); return status; } if ((sb->board_flag & BOARDFLAG_REMAP) == BOARDFLAG_REMAP) { sp->port.vector = 0; } else if ((sb->board_flag & BOARDFLAG_CH384_8_PORTS) == BOARDFLAG_CH384_8_PORTS) { sp->port.chip_iobase = sb->bar_addr[sb->pb_info.port[j].bar1]; sp->port.vector = sb->bar_addr[sb->pb_info.intr_vector_bar] + sb->pb_info.intr_vector_offset; } else if ((sb->board_flag & BOARDFLAG_CH384_28_PORTS) == BOARDFLAG_CH384_28_PORTS) { sp->port.chip_iobase = sb->bar_addr[sb->pb_info.port[j].bar1]; if (j >= 0 && j < 0x04) { sp->port.vector = sb->bar_addr[sb->pb_info.intr_vector_bar] + sb->pb_info.intr_vector_offset; } else if (j >= 0x04 && j < 0x0C) { sp->port.vector = sb->bar_addr[sb->pb_info.intr_vector_bar] + sb->pb_info.intr_vector_offset_1; } else if (j >= 0x0C && j < 0x14) { sp->port.vector = sb->bar_addr[sb->pb_info.intr_vector_bar] + sb->pb_info.intr_vector_offset_2; } else if (j >= 0x14 && j < 0x1C) { sp->port.vector = sb->bar_addr[sb->pb_info.intr_vector_bar] + sb->pb_info.intr_vector_offset_3; } else { } } else if ((sb->board_flag & BOARDFLAG_CH365_32_PORTS) == BOARDFLAG_CH365_32_PORTS) { sp->port.chip_iobase = sb->bar_addr[sb->pb_info.port[j].bar1]; sp->port.board_membase = sb->board_membase; if (j >= 0 && j < 0x08) { if (j >= 0 && j < 0x04) { sp->port.port_membase = sb->board_membase + 0x100 + j * 0x10; } if (j >= 4 && j < 0x08) { sp->port.port_membase = sb->board_membase + 0x100 + 0x08 + (j - 4) * 0x10; } } if (j >= 0x08 && j < 0x10) { if (j >= 0x08 && j < 0x0C) { sp->port.port_membase = sb->board_membase + 0x100 + 0x80 + (j - 0x08) * 0x10; } if (j >= 0x0C && j < 0x10) { sp->port.port_membase = sb->board_membase + 0x100 + 0x80 + 0x08 + (j - 0x0C) * 0x10; } } if (j >= 0x10 && j < 0x18) { if (j >= 0x10 && j < 0x14) { sp->port.port_membase = sb->board_membase + 0x100 + 0x100 + (j - 0x10) * 0x10; } if (j >= 0x14 && j < 0x18) { sp->port.port_membase = sb->board_membase + 0x100 + 0x100 + 0x08 + (j - 0x14) * 0x10; } } if (j >= 0x18 && j < 0x20) { if (j >= 0x18 && j < 0x1C) { sp->port.port_membase = sb->board_membase + 0x100 + 0x180 + (j - 0x18) * 0x10; } if (j >= 0x1C && j < 0x20) { sp->port.port_membase = sb->board_membase + 0x100 + 0x180 + 0x08 + (j - 0x1C) * 0x10; } } } else { sp->port.vector = sb->bar_addr[sb->pb_info.intr_vector_bar] + sb->pb_info.intr_vector_offset; } } sb->vector_mask = 0xffffffff; ser_port_index = ser_port_index + sb->ser_ports; } } } return status; } static int wch_ser_port_table_init( void ) { struct wch_board *sb = NULL; struct wch_ser_port *sp = NULL; int status = 0; int i; int j; int n; #if WCH_DBG printk("%s : %s\n", __FILE__, __FUNCTION__); #endif for (i = 0; i < WCH_BOARDS_MAX; i++) { sb = &wch_board_table[i]; if (sb == NULL) { status = -ENXIO; printk("WCH Error: Board table pointer error !\n"); return status; } if ((sb->board_enum > 0) && (sb->ser_ports > 0)) { n = sb->ser_port_index; sp = &wch_ser_table[n]; if (sp == NULL) { status = -ENXIO; printk("WCH Error: Serial port table pointer error !\n"); return status; } for (j = 0; j < sb->ser_ports; j++, n++, sp++) { sp->port.board_enum = sb->board_enum; sp->port.bus_number = sb->bus_number; sp->port.dev_number = sb->dev_number; sp->port.baud_base = CRYSTAL_FREQ * 2 / 16; sp->port.pb_info = sb->pb_info; if (sp->port.chip_flag == WCH_BOARD_CH384_8S) { if (n == 0) sp->port.bext1stport = true; else sp->port.bext1stport = false; } else if (sp->port.chip_flag == WCH_BOARD_CH384_28S) { if ((n == 4) || (n == 12) || (n == 20)) sp->port.bext1stport = true; else sp->port.bext1stport = false; } sp->port.irq = sb->irq; sp->port.line = n; sp->port.uartclk = CRYSTAL_FREQ * 2; if (ch365_32s) { sp->port.iotype = WCH_UPIO_MEM; } else { sp->port.iotype = WCH_UPIO_PORT; } sp->port.flags = ASYNC_SHARE_IRQ; sp->port.ldisc_stop_rx = 0; spin_lock_init(&sp->port.lock); if (sp->port.chip_flag == WCH_BOARD_CH351_2S) { sp->port.type = PORT_SER_16550A; sp->port.fifosize = CH351_FIFOSIZE_SET; sp->port.rx_trigger = CH351_TRIGGER_LEVEL_SET; } else if (sp->port.chip_flag == WCH_BOARD_CH352_2S || sp->port.chip_flag == WCH_BOARD_CH352_1S1P) { sp->port.type = PORT_SER_16550A; sp->port.fifosize = CH352_FIFOSIZE_SET; sp->port.rx_trigger = CH352_TRIGGER_LEVEL_SET; } else if (sp->port.chip_flag == WCH_BOARD_CH353_4S || sp->port.chip_flag == WCH_BOARD_CH353_2S1P || sp->port.chip_flag == WCH_BOARD_CH353_2S1PAR) { sp->port.type = PORT_SER_16550A; sp->port.fifosize = CH353_FIFOSIZE_SET; sp->port.rx_trigger = CH353_TRIGGER_LEVEL_SET; } else if (sp->port.chip_flag == WCH_BOARD_CH355_4S) { sp->port.type = PORT_SER_16550A; sp->port.fifosize = CH355_FIFOSIZE_SET; sp->port.rx_trigger = CH355_TRIGGER_LEVEL_SET; } else if (sp->port.chip_flag == WCH_BOARD_CH356_4S1P || sp->port.chip_flag == WCH_BOARD_CH356_6S || sp->port.chip_flag == WCH_BOARD_CH356_8S) { sp->port.type = PORT_SER_16550A; sp->port.fifosize = CH356_FIFOSIZE_SET; sp->port.rx_trigger = CH356_TRIGGER_LEVEL_SET; } else if (sp->port.chip_flag == WCH_BOARD_CH357_4S) { sp->port.type = PORT_SER_16750; sp->port.fifosize = CH357_FIFOSIZE_SET; sp->port.rx_trigger = CH357_TRIGGER_LEVEL_SET; } else if (sp->port.chip_flag == WCH_BOARD_CH358_4S1P || sp->port.chip_flag == WCH_BOARD_CH358_8S) { sp->port.type = PORT_SER_16750; sp->port.fifosize = CH358_FIFOSIZE_SET; sp->port.rx_trigger = CH358_TRIGGER_LEVEL_SET; } else if (sp->port.chip_flag == WCH_BOARD_CH359_16S) { sp->port.type = PORT_SER_16750; sp->port.fifosize = CH359_FIFOSIZE_SET; sp->port.rx_trigger = CH359_TRIGGER_LEVEL_SET; } else if (sp->port.chip_flag == WCH_BOARD_CH382_2S || sp->port.chip_flag == WCH_BOARD_CH382_2S1P) { sp->port.type = PORT_SER_16750; sp->port.fifosize = CH382_FIFOSIZE_SET; sp->port.rx_trigger = CH382_TRIGGER_LEVEL_SET; } else if (sp->port.chip_flag == WCH_BOARD_CH384_4S || sp->port.chip_flag == WCH_BOARD_CH384_4S1P) { sp->port.type = PORT_SER_16750; sp->port.fifosize = CH384_FIFOSIZE_SET; sp->port.rx_trigger = CH384_TRIGGER_LEVEL_SET; } else if (sp->port.chip_flag == WCH_BOARD_CH384_8S) { sp->port.type = PORT_SER_16750; sp->port.fifosize = CH358_FIFOSIZE_SET; sp->port.rx_trigger = CH358_TRIGGER_LEVEL_SET; } else if (sp->port.chip_flag == WCH_BOARD_CH384_28S) { sp->port.type = PORT_SER_16750; if (j >= 0 && j < 0x04) { sp->port.fifosize = CH384_FIFOSIZE_SET; sp->port.rx_trigger = CH384_TRIGGER_LEVEL_SET; } else { sp->port.fifosize = CH358_FIFOSIZE_SET; sp->port.rx_trigger = CH358_TRIGGER_LEVEL_SET; } } else if (sp->port.chip_flag == WCH_BOARD_CH365_32S) { sp->port.type = PORT_SER_16750; sp->port.fifosize = CH438_FIFOSIZE_SET; sp->port.rx_trigger = CH438_TRIGGER_LEVEL_SET; } else { sp->port.type = PORT_SER_16450; sp->port.fifosize = DEFAULT_FIFOSIZE; sp->port.rx_trigger = DEFAULT_TRIGGER_LEVEL; } if ((sb->pb_info.board_flag & BOARDFLAG_REMAP) == BOARDFLAG_REMAP) { sp->port.vector_mask = 0; sp->port.port_flag = PORTFLAG_REMAP; } else { sp->port.vector_mask = sb->vector_mask; sp->port.port_flag = PORTFLAG_NONE; } sp->port.setserial_flag = WCH_SER_BAUD_NOTSETSER; } sb->ser_isr = wch_ser_interrupt; } else { sb->ser_isr = NULL; } } return status; } #if WCH_DBG void wch_debug( void ) { #if WCH_DBG_BOARD struct wch_board *sb = NULL; int i; #endif #if WCH_DBG_SERPORT struct wch_ser_port *sp = NULL; int j; #endif #if WCH_DBG_BOARD printk("\n"); printk("======== board info ========\n"); for (i = 0; i < WCH_BOARDS_MAX; i++) { sb = &wch_board_table[i]; if (sb->board_enum != -1) { printk(" name : %s\n", sb->pb_info.board_name); printk(" board_enum : %d\n", sb->board_enum); printk(" board_number : %d\n", sb->board_number); printk(" irq : %d\n", sb->irq); printk(" vector_mask : 0x%x\n", sb->vector_mask); printk(" bar[0] : 0x%x\n", sb->bar_addr[0]); printk(" bar[1] : 0x%x\n", sb->bar_addr[1]); printk(" bar[2] : 0x%x\n", sb->bar_addr[2]); printk(" bar[3] : 0x%x\n", sb->bar_addr[3]); printk(" bar[4] : 0x%x\n", sb->bar_addr[4]); printk(" bar[5] : 0x%x\n", sb->bar_addr[5]); printk("----------------------------\n"); } } printk("============================\n"); printk("\n"); #endif #if WCH_DBG_SERPORT printk("\n"); printk("======== serial info ========\n"); for (j = 0; j < wch_ser_port_total_cnt; j++) { sp = &wch_ser_table[j]; if (sp->port.iobase) { printk(" number : %d\n", j); printk(" name : %s\n", sp->port.pb_info.board_name); printk(" iobase : 0x%x\n", sp->port.iobase); printk(" chip_iobase : 0x%x\n", sp->port.chip_iobase); printk(" irq : %d\n", sp->port.irq); printk(" vector : 0x%x\n", sp->port.vector); printk(" vector_mask : 0x%x\n", sp->port.vector_mask); printk(" chip_flag : 0x%x\n", sp->port.chip_flag); printk(" port_flag : 0x%x\n", sp->port.port_flag); printk("----------------------------\n"); } } printk("============================\n"); printk("\n"); #endif } #endif #if WCH_DBG_SERIAL void ch365_32s_test( void ) { struct wch_board *sb = NULL; int i; dbg_serial("\n"); dbg_serial("======== board info ========\n"); for (i = 0; i < WCH_BOARDS_MAX; i++) { sb = &wch_board_table[i]; if (sb->board_enum != -1) { dbg_serial(" ch438_1_uart0 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05)); dbg_serial(" ch438_1_uart1 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x08)); dbg_serial(" ch438_1_uart2 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x10)); dbg_serial(" ch438_1_uart3 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x18)); dbg_serial(" ch438_1_uart4 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x20)); dbg_serial(" ch438_1_uart5 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x28)); dbg_serial(" ch438_1_uart6 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x30)); dbg_serial(" ch438_1_uart7 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x38)); dbg_serial("----------------------------\n"); dbg_serial(" ch438_2_uart0 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x80)); dbg_serial(" ch438_2_uart1 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x08 + 0x80)); dbg_serial(" ch438_2_uart2 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x10 + 0x80)); dbg_serial(" ch438_2_uart3 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x18 + 0x80)); dbg_serial(" ch438_2_uart4 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x20 + 0x80)); dbg_serial(" ch438_2_uart5 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x28 + 0x80)); dbg_serial(" ch438_2_uart6 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x30 + 0x80)); dbg_serial(" ch438_2_uart7 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x38 + 0x80)); dbg_serial("----------------------------\n"); dbg_serial(" ch438_3_uart0 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x100)); dbg_serial(" ch438_3_uart1 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x08 + 0x100)); dbg_serial(" ch438_3_uart2 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x10 + 0x100)); dbg_serial(" ch438_3_uart3 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x18 + 0x100)); dbg_serial(" ch438_3_uart4 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x20 + 0x100)); dbg_serial(" ch438_3_uart5 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x28 + 0x100)); dbg_serial(" ch438_3_uart6 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x30 + 0x100)); dbg_serial(" ch438_3_uart7 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x38 + 0x100)); dbg_serial("----------------------------\n"); dbg_serial(" ch438_4_uart0 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x180)); dbg_serial(" ch438_4_uart1 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x08 + 0x180)); dbg_serial(" ch438_4_uart2 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x10 + 0x180)); dbg_serial(" ch438_4_uart3 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x18 + 0x180)); dbg_serial(" ch438_4_uart4 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x20 + 0x180)); dbg_serial(" ch438_4_uart5 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x28 + 0x180)); dbg_serial(" ch438_4_uart6 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x30 + 0x180)); dbg_serial(" ch438_4_uart7 LSR = %x\n", readb(sb->board_membase + 0x100 + 0x05 + 0x38 + 0x180)); dbg_serial("----------------------------\n"); } } dbg_serial("============================\n"); dbg_serial("\n"); } #endif int wch_register_irq( void ) { struct wch_board *sb = NULL; int status = 0; int i; unsigned int chip_iobase; #if WCH_DBG printk("%s : %s\n", __FILE__, __FUNCTION__); #endif for (i = 0; i < WCH_BOARDS_MAX; i++) { sb = &wch_board_table[i]; if (sb == NULL) { status = -ENXIO; printk("WCH Error: Board table pointer error !\n"); return status; } if (sb->board_enum > 0) { #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; } } if (ch365_32s) { outb(inb(sb->bar_addr[0] + 0xF8) & 0xFE, sb->bar_addr[0] + 0xF8); outb(((inb(sb->bar_addr[0] + 0xFA) & 0xFB) | 0x03), sb->bar_addr[0] + 0xFA); // set read/write plus width 240ns->120ns } if (((sb->board_flag & BOARDFLAG_CH384_8_PORTS) == BOARDFLAG_CH384_8_PORTS) || ((sb->board_flag & BOARDFLAG_CH384_28_PORTS) == BOARDFLAG_CH384_28_PORTS)) { chip_iobase = sb->bar_addr[0]; if (chip_iobase) { outb(inb(chip_iobase + 0xEB) | 0x02, chip_iobase + 0xEB); /* set read/write plus width 120ns->210ns */ outb(inb(chip_iobase + 0xFA) | 0x10, chip_iobase + 0xFA); } } } return status; } void wch_iounmap( void ) { struct wch_board *sb = NULL; int i; #if WCH_DBG printk("%s : %s\n", __FILE__, __FUNCTION__); #endif for (i = 0; i < WCH_BOARDS_MAX; i++) { sb = &wch_board_table[i]; if (sb->board_enum > 0) { iounmap(sb->board_membase); } } } void wch_release_irq( void ) { struct wch_board *sb = NULL; int i; unsigned int chip_iobase; #if WCH_DBG printk("%s : %s\n", __FILE__, __FUNCTION__); #endif for (i = 0; i < WCH_BOARDS_MAX; i++) { sb = &wch_board_table[i]; if (sb->board_enum > 0) { free_irq(sb->irq, sb); } if (ch365_32s) { outb(inb(sb->bar_addr[0] + 0xF8) | 0x01, sb->bar_addr[0] + 0xF8); } if (((sb->board_flag & BOARDFLAG_CH384_8_PORTS) == BOARDFLAG_CH384_8_PORTS) || ((sb->board_flag & BOARDFLAG_CH384_28_PORTS) == BOARDFLAG_CH384_28_PORTS)) { chip_iobase = sb->bar_addr[0]; if (chip_iobase) outb(inb(chip_iobase + 0xEB) & 0xFD, chip_iobase + 0xEB); } } } static struct ser_driver wch_ser_reg = { .dev_name = "ttyWCH", .major = WCH_TTY_MAJOR, .minor = 0, }; 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; } printk("------------------->pci board probe success\n"); status = wch_get_pci_board_conf(); if (status != 0) { goto step1_fail; } printk("------------------->pci board conf success\n"); status = wch_assign_resource(); if (status != 0) { goto step1_fail; } printk("------------------->pci assign success\n"); status = wch_ser_port_table_init(); if (status != 0) { goto step1_fail; } printk("------------------->ser port table init success\n"); status = wch_register_irq(); if (status != 0) { goto step1_fail; } printk("------------------->pci register irq success\n"); status = wch_ser_register_driver(&wch_ser_reg); if (status != 0) { goto step2_fail; } printk("------------------->ser register driver success\n"); status = wch_ser_register_ports(&wch_ser_reg); if (status != 0) { goto step3_fail; } #if WCH_DBG wch_debug(); // ch365_32s_test(); #endif printk("================================================================================\n"); return status; step3_fail: wch_ser_unregister_driver(&wch_ser_reg); step2_fail: wch_release_irq(); step1_fail: printk("WCH Error: Couldn't Loading WCH Multi-I/O Board Driver Module correctly,\n"); printk(" please reboot system and try again. If still can't loading driver,\n"); printk(" contact support.\n\n"); printk("================================================================================\n"); return status; } 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); printk("***********wch_ser_unregister_ports***************\n"); wch_ser_unregister_driver(&wch_ser_reg); printk("***********wch_ser_unregister_driver_success***********\n"); wch_iounmap(); wch_release_irq(); printk("WCH Info : Unload WCH Multi-I/O Board Driver Module Done.\n"); printk("================================================================================\n"); } module_init(wch_init); module_exit(wch_exit);