The driver is based on the CDC-ACM driver. In addition to supporting the features of the MaxLinear/Exar USB UART devices, the driver also has support for 2 other functions per customer requirements: - Specific entries are checked in the BIOS to detect if the board is a "Caracalla" board before enabling specific modes in the MaxLinear/Exar USB UARTs. The smbios code is based on the example at: https://sourceforge.net/projects/smbios/ - When specific IOCTLs are called by a user-space application, a port_config file is created for the /dev/ttyXRUSB device at a specific USB tree location, and some configuration data is stored. The driver checks for the port_config file when the driver is loaded for each port and loads the configuration settings if there is a port_config file for the USB tree location. Signed-off-by: Patong Yang <patong.mxl@xxxxxxxxx> --- drivers/usb/serial/xrusb_serial.c | 3285 +++++++++++++++++++++++++++++++++++++ drivers/usb/serial/xrusb_serial.h | 448 +++++ 2 files changed, 3733 insertions(+) create mode 100644 drivers/usb/serial/xrusb_serial.c create mode 100644 drivers/usb/serial/xrusb_serial.h diff --git a/drivers/usb/serial/xrusb_serial.c b/drivers/usb/serial/xrusb_serial.c new file mode 100644 index 000000000000..16a5bcff9103 --- /dev/null +++ b/drivers/usb/serial/xrusb_serial.c @@ -0,0 +1,3285 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * xrusb_serial.c + * + * Copyright (c) 2018 Patong Yang <patong.mxl@xxxxxxxxx> + * + * USB Serial Driver based on the cdc-acm.c driver for the + * MaxLinear/Exar USB UARTs/Serial adapters + */ + + +#undef DEBUG +#undef VERBOSE_DEBUG + +#include <linux/kernel.h> +#include <linux/sched/signal.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/log2.h> +#include <linux/serial.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> +#include <linux/usb.h> +#include <linux/usb/cdc.h> +#include <linux/dmi.h> +#include <asm/byteorder.h> +#include <asm/unaligned.h> +#include <linux/idr.h> +#include <linux/list.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <linux/fs.h> +#include <linux/ioctl.h> + +#include "linux/version.h" +#include "xrusb_serial.h" + +#define DRIVER_AUTHOR "Patong Yang <patong.mxl@xxxxxxxxx>" +#define DRIVER_DESC "MaxLinear/Exar USB UART (serial port) driver" + +static struct usb_driver xrusb_driver; +static struct tty_driver *xrusb_tty_driver; +static struct xrusb *xrusb_table[XRUSB_TTY_MINORS]; +static struct file *port_param_file[32]; +static struct reg_addr_map xr2280x_reg_map; +static struct reg_addr_map xr21b1411_reg_map; +static struct reg_addr_map xr21v141x_reg_map; +static struct reg_addr_map xr21b142x_reg_map; + +static DEFINE_MUTEX(xrusb_table_lock); + +/* + * SMBIOS code snippets below borrowed from + * https://sourceforge.net/projects/smbios/ + * + * Checking SMBIOS values to determine if this is Caracalla board + * where modes are programmed in the BIOS + * + */ + +unsigned char smbios_check_entry_point (void *addr) +{ + unsigned char *i; + unsigned char checksum = 0; + unsigned char length; + + length = ((struct smbios_entry_point_struct *) addr)-> + entry_point_length; + /* calculate checksum for entry point structure (should be 0) */ + for (i = (unsigned char *) addr; + i < (unsigned char *) addr + length; i++) + checksum += *i; + return checksum; +} + +struct smbios_entry_point_struct *smbios_find_entry_point (void *base) +{ + struct smbios_entry_point_struct *entry_point = 0; + unsigned int *temp; + + /* search for the magic dword on paragraph boundaries */ + for (temp = base; + !entry_point && temp < (unsigned int *) base + BIOS_MAP_LENGTH; + temp += 4) { + + if (*temp == SMBIOS_MAGIC_DWORD) { + /* check if entry point valid (build checksum) */ + if (!(smbios_check_entry_point (temp))) { + entry_point = (struct + smbios_entry_point_struct *) temp; + + // fix display of Bios version string + // SMBios version is known as 2.1, 2.2, + // 2.3 and 2.3.1, never as 2.01 (JB) + SM_BIOS_DEBUG("SM-BIOS V%d.%d entry point ", + entry_point->major_version, + entry_point->minor_version); + SM_BIOS_DEBUG("found at 0x%x\n", + (unsigned int) temp); + } + } + } + return entry_point; +} + +int smbios_check_caracalla_config(unsigned char *config0, + unsigned char *config1) +{ + + int i; + int result = -1; + unsigned char *p; + + smbios_base = ioremap(BIOS_START_ADDRESS, BIOS_MAP_LENGTH); + + if (!smbios_base) { + SM_BIOS_DEBUG("ioremap() for entry point failed\n"); + result = -ENXIO; + return result; + } + + smbios_entry_point = smbios_find_entry_point (smbios_base); + if (!smbios_entry_point) { + SM_BIOS_DEBUG("SM-BIOS entry point not found\n"); + iounmap(smbios_base); + result = -ENXIO; + return result; + } + /* + * for SM-BIOS: + * check if Pointer to DMI structures exist. + * intermediate_string (_DMI_) is not '\0' terminated, + * so strncmp() with sizeof(DMI_STRING) - 1 is needed. + */ + if (smbios_entry_point) { + if (strncmp((char *) &(smbios_entry_point->intermediate_string), + DMI_STRING, sizeof(DMI_STRING) - 1)) + SM_BIOS_DEBUG("Pointer to DMI struct not found!\n"); + } + + /* + * map the SM-BIOS structures physical address range. + * the 'real' smbios_structures_base contains the starting + * address, where the instances of dmi structures are located. + */ + if (smbios_entry_point) { + smbios_structures_base = + ioremap(smbios_entry_point->struct_table_address, + (unsigned long)smbios_entry_point->struct_table_length); + if (!(smbios_structures_base)) { + SM_BIOS_DEBUG("ioremap() for struct table failed\n"); + iounmap(smbios_base); + result = -ENXIO; + return result; + } + } + SM_BIOS_DEBUG("smbios_structures_base to 0x%p length %d ", + smbios_structures_base, + smbios_entry_point->struct_table_length); + SM_BIOS_DEBUG(" no_of_structures:%d\n", + smbios_entry_point->no_of_structures); + + p = (unsigned char *)smbios_structures_base; + for (i = 0; i < smbios_entry_point->struct_table_length; i++) { + if ((p[i] == 0xc0) && (p[i+1] == 0x06)) { + SM_BIOS_DEBUG("Found 0xc0 at offset:%d 0x%02x 0x%02x ", + i, p[i], p[i+1]); + SM_BIOS_DEBUG("0x%02x 0x%02x 0x%02x 0x%02x\n\t", + p[i+2], p[i+3], p[i+4], p[i+5]); + *config0 = p[i+4]; + *config1 = p[i+5]; + result = 0; + break; + } + } + iounmap(smbios_structures_base); + iounmap(smbios_base); + return result; +} + +/* + * Look up an XRUSB structure by index. If found and not disconnected, + * increment its refcount and return it with its mutex held. + */ + +static struct xrusb *xrusb_get_by_index(unsigned int index) +{ + struct xrusb *xrusb; + + mutex_lock(&xrusb_table_lock); + xrusb = xrusb_table[index]; + if (xrusb) { + mutex_lock(&xrusb->mutex); + if (xrusb->disconnected) { + mutex_unlock(&xrusb->mutex); + xrusb = NULL; + } else { + tty_port_get(&xrusb->port); + mutex_unlock(&xrusb->mutex); + } + } + mutex_unlock(&xrusb_table_lock); + return xrusb; +} + +/* + * Try to find an available minor number and if found, associate it with + * 'xrusb'. + */ +static int xrusb_alloc_minor(struct xrusb *xrusb) +{ + int minor; + + mutex_lock(&xrusb_table_lock); + for (minor = 0; minor < XRUSB_TTY_MINORS; minor++) { + if (!xrusb_table[minor]) { + xrusb_table[minor] = xrusb; + break; + } + } + mutex_unlock(&xrusb_table_lock); + + return minor; +} + +/* Release the minor number associated with 'xrusb'. */ +static void xrusb_release_minor(struct xrusb *xrusb) +{ + mutex_lock(&xrusb_table_lock); + xrusb_table[xrusb->minor] = NULL; + mutex_unlock(&xrusb_table_lock); +} + +/* + * Functions for XRUSB control messages. + */ + +static int xrusb_ctrl_msg(struct xrusb *xrusb, + int request, int value, void *buf, int len) +{ + int rv = usb_control_msg(xrusb->dev, + usb_sndctrlpipe(xrusb->dev, 0), + request, + USB_RT_XRUSB, + value, + xrusb->control->altsetting[0].desc.bInterfaceNumber, + buf, + len, + 5000); + dev_dbg(&xrusb->control->dev, + "%s - rq 0x%02x, val %#x, len %#x, result %d\n", + __func__, request, value, len, rv); + return rv < 0 ? rv : 0; +} + +int xrusb_set_reg(struct xrusb *xrusb, int regnum, int value) +{ + int result; + int channel = 0; + int XR2280xaddr = XR2280x_FUNC_MGR_OFFSET + regnum; + + if ((xrusb->DeviceProduct & 0xfff0) == 0x1400) { + result = usb_control_msg(xrusb->dev, // usb device + usb_sndctrlpipe(xrusb->dev, 0), // endpoint pipe + XRUSB_SET_XR2280X, // request + USB_DIR_OUT | USB_TYPE_VENDOR, // request_type + value, // request value + XR2280xaddr, // index + NULL, // data + 0, // size + 5000); // timeout + } else if ((xrusb->DeviceProduct & 0xfff0) == 0x1420) { + channel = (xrusb->channel - 4)*2; + result = usb_control_msg(xrusb->dev, // usb device + usb_sndctrlpipe(xrusb->dev, 0), // endpoint pipe + XRUSB_SET_XR21B142X, // request + USB_DIR_OUT | USB_TYPE_VENDOR | 1, // request_type + value, // request value + regnum | (channel << 8), // index + NULL, // data + 0, // size + 5000); // timeout + } else if (xrusb->DeviceProduct == 0x1411) { + result = usb_control_msg(xrusb->dev, // usb device + usb_sndctrlpipe(xrusb->dev, 0), // endpoint pipe + XRUSB_SET_XR21B1411, // request + USB_DIR_OUT | USB_TYPE_VENDOR, // request_type + value, // request value + regnum, // index + NULL, // data + 0, // size + 5000); // timeout + } else if ((xrusb->DeviceProduct & 0xfff0) == 0x1410) { + if (xrusb->channel) + channel = xrusb->channel - 1; + result = usb_control_msg(xrusb->dev, // usb device + usb_sndctrlpipe(xrusb->dev, 0), // endpoint pipe + XRUSB_SET_XR21V141X, // request + USB_DIR_OUT | USB_TYPE_VENDOR, // request_type + value, // request value + regnum | (channel << 8), // index + NULL, // data + 0, // size + 5000); // timeout + } else + result = -1; + + if (result < 0) + dev_err(&xrusb->control->dev, + "%s Error:%d\n", __func__, result); + + return result; +} + +/* Write to UART Block registers in XR21V141x */ +int xrusb_set_block_reg(struct xrusb *xrusb, int block, int regnum, int value) +{ + int result; + + result = usb_control_msg(xrusb->dev, // usb device + usb_sndctrlpipe(xrusb->dev, 0), // endpoint pipe + XRUSB_SET_XR21V141X, // request + USB_DIR_OUT | USB_TYPE_VENDOR, // request_type + value, // request value + regnum | (block << 8), // index + NULL, // data + 0, // size + 5000); // timeout + + if (result < 0) + dev_err(&xrusb->control->dev, "%s Error:%d\n", + __func__, result); + + return result; +} + +int xrusb_get_reg(struct xrusb *xrusb, int regnum, short *value) +{ + int result; + int channel = 0; + int XR2280xaddr = XR2280x_FUNC_MGR_OFFSET + regnum; + void *dmadata = kmalloc(2, GFP_KERNEL); + + if (!dmadata) + return -ENOMEM; + + if ((xrusb->DeviceProduct & 0xfff0) == 0x1400) { + result = usb_control_msg(xrusb->dev, // usb device + usb_rcvctrlpipe(xrusb->dev, 0), // endpoint pipe + XRUSB_GET_XR2280X, // request + USB_DIR_IN | USB_TYPE_VENDOR, // request_type + 0, // request value + XR2280xaddr, // index + dmadata, // data + 2, // size + 5000); // timeout + memcpy(value, dmadata, 2); + } else if ((xrusb->DeviceProduct & 0xfff0) == 0x1420) { + channel = (xrusb->channel - 4)*2; + result = usb_control_msg(xrusb->dev, // usb device + usb_rcvctrlpipe(xrusb->dev, 0), // endpoint pipe + XRUSB_GET_XR21B142X, // request + USB_DIR_IN | USB_TYPE_VENDOR | 1, // request_type + 0, // request value + regnum | (channel << 8), // index + dmadata, // data + 2, // size + 5000); // timeout + memcpy(value, dmadata, 2); + } else if (xrusb->DeviceProduct == 0x1411) { + result = usb_control_msg(xrusb->dev, // usb device + usb_rcvctrlpipe(xrusb->dev, 0), // endpoint pipe + XRUSB_GET_XR21B1411, // request + USB_DIR_IN | USB_TYPE_VENDOR, // request_type + 0, // request value + regnum, // index + dmadata, // data + 2, // size + 5000); // timeout + memcpy(value, dmadata, 2); + } else if ((xrusb->DeviceProduct & 0xfff0) == 0x1410) { + if (xrusb->channel) + channel = xrusb->channel - 1; + result = usb_control_msg(xrusb->dev, // usb device + usb_rcvctrlpipe(xrusb->dev, 0), // endpoint pipe + XRUSB_GET_XR21V141X, // request + USB_DIR_IN | USB_TYPE_VENDOR, // request_type + 0, // request value + regnum | (channel << 8), // index + dmadata, // data + 1, // size + 5000); // timeout + memcpy(value, dmadata, 1); + } else + result = -1; + + if (result < 0) { + dev_err(&xrusb->control->dev, + "%s channel:%d Reg 0x%x Error:%d\n", + __func__, channel, regnum, result); + } + kfree(dmadata); + return result; +} + +/* Read from UART Block registers in XR21V141x */ +int xrusb_get_block_reg(struct xrusb *xrusb, int block, + int regnum, short *value) +{ + int result; + void *dmadata = kmalloc(2, GFP_KERNEL); + + if (!dmadata) + return -ENOMEM; + + result = usb_control_msg(xrusb->dev, // usb device + usb_rcvctrlpipe(xrusb->dev, 0), // endpoint pipe + XRUSB_GET_XR21V141X, // request + USB_DIR_IN | USB_TYPE_VENDOR, // request_type + 0, // request value + regnum | (block << 8), // index + dmadata, // data + 1, // size + 5000); // timeout + memcpy(value, dmadata, 1); + + if (result < 0) { + dev_err(&xrusb->control->dev, "%s Error:%d\n", + __func__, result); + } + kfree(dmadata); + return result; +} + +int check_portparamfile(struct xrusb *xrusb) +{ + char filename[256]; + int level_num, port_num, channel_num, config_num; + + memset(filename, 0, sizeof(filename)); + + level_num = xrusb->dev->level; + port_num = xrusb->dev->portnum; + channel_num = xrusb->channel % 4; + + // config_num is a unique number identifying USB level, USB port + // and UART/serial port channel. Driver assumes that devices are + // plugged into a single USB hub at each level. + config_num = (level_num * 100) + (port_num * 10) + channel_num; + + sprintf(filename, "/etc/port_config%d", config_num); + + port_param_file[config_num] = + filp_open(filename, O_RDWR | O_LARGEFILE, 0600); + if (IS_ERR(port_param_file[config_num])) { + xrusb->have_txcvr_config = 0; + return 0; + } + filp_close(port_param_file[config_num], NULL); + xrusb->have_txcvr_config = 1; + return 1; +} + +int load_portparamfile(struct xrusb *xrusb) +{ + int length; + loff_t offset = 0; + mm_segment_t old_fs; + char filename[256]; + int level_num, port_num, channel_num, config_num; + + memset(filename, 0, sizeof(filename)); + + level_num = xrusb->dev->level; + port_num = xrusb->dev->portnum; + channel_num = xrusb->channel % 4; + + // config_num is a unique number identifying USB level, USB port + // and UART/serial port channel. Driver assumes that devices are + // plugged into a single USB hub at each level. + config_num = (level_num * 100) + (port_num * 10) + channel_num; + + sprintf(filename, "/etc/port_config%d", config_num); + + port_param_file[config_num] = + filp_open(filename, O_RDWR | O_LARGEFILE, 0600); + if (IS_ERR(port_param_file[config_num])) { + dev_err(&xrusb->control->dev, "filp_open failed\n"); + return PTR_ERR(port_param_file[config_num]); + } + old_fs = get_fs(); + set_fs(KERNEL_DS); + length = vfs_read(port_param_file[config_num], + (char *) &(xrusb->port_setting), + sizeof(struct xrusb_setting), + &offset); + //dev_err(&xrusb->control->dev, "vfs_read :%d\n", length); + set_fs(old_fs); + filp_close(port_param_file[config_num], NULL); + return 0; +} + +int update_portparamfile(struct xrusb *xrusb) +{ + int length; + loff_t offset = 0; + mm_segment_t old_fs; + char filename[256]; + int level_num, port_num, channel_num, config_num; + + memset(filename, 0, sizeof(filename)); + + level_num = xrusb->dev->level; + port_num = xrusb->dev->portnum; + channel_num = xrusb->channel % 4; + + // config_num is a unique number identifying USB level, USB port + // and UART/serial port channel. Driver assumes that devices are + // plugged into a single USB hub at each level. + config_num = (level_num * 100) + (port_num * 10) + channel_num; + + sprintf(filename, "/etc/port_config%d", config_num); + port_param_file[config_num] = + filp_open(filename, O_CREAT | O_RDWR | O_LARGEFILE, 0600); + if (IS_ERR(port_param_file[config_num])) { + dev_err(&xrusb->control->dev, "filp_open failed\n"); + return PTR_ERR(port_param_file[config_num]); + } + old_fs = get_fs(); + set_fs(KERNEL_DS); + length = vfs_write(port_param_file[config_num], + (char *) &(xrusb->port_setting), + sizeof(struct xrusb_setting), + &offset); + if (length != sizeof(struct xrusb_setting)) { + dev_err(&xrusb->control->dev, "vfs_write failed :%d\n", length); + set_fs(old_fs); + filp_close(port_param_file[config_num], NULL); + return -1; + } + //dev_err(&xrusb->control->dev, "successful vfs_write :%d\n", length); + set_fs(old_fs); + filp_close(port_param_file[config_num], NULL); + xrusb->have_txcvr_config = 1; + return 0; +} + +static int set_txcvr_mode(struct xrusb *xrusb, unsigned int mode) +{ + unsigned short tmp_value; + int rv; + unsigned int mode1_mask, mode0_mask; + + mode1_mask = (1<<(xrusb->port_setting.txcvr_mode1_pin)); + mode0_mask = (1<<(xrusb->port_setting.txcvr_mode0_pin)); + tmp_value = 0; + + dev_err(&xrusb->control->dev, "Ch %d: ", (xrusb->channel % 4)); + + switch (mode) { + case 0: + dev_err(&xrusb->control->dev, "Transceiver Mode = LOOPBACK\n"); + tmp_value &= ~mode0_mask; + tmp_value &= ~mode1_mask; + break; + case 1: + dev_err(&xrusb->control->dev, "Transceiver Mode = RS232\n"); + tmp_value |= mode0_mask; + tmp_value &= ~mode1_mask; + break; + case 2: + dev_err(&xrusb->control->dev, + "Transceiver Mode = RS485 Half-Duplex\n"); + tmp_value &= ~mode0_mask; + tmp_value |= mode1_mask; + break; + case 3: + dev_err(&xrusb->control->dev, + "Transceiver Mode = RS485/RS422 Full-Duplex\n"); + tmp_value |= mode0_mask; + tmp_value |= mode1_mask; + break; + default: + dev_err(&xrusb->control->dev, + "Invalid Parameter. No mode change.\n"); + break; + } + + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set, + (int)tmp_value); + if (rv < 0) + return -EFAULT; + + tmp_value = ~tmp_value; + tmp_value &= (mode1_mask | mode0_mask); + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr, + (int)tmp_value); + if (rv < 0) + return -EFAULT; + return rv; +} + +static int set_rs485_pin(struct xrusb *xrusb, unsigned int option) +{ + short reg_value; + int rv; + + rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_mode, ®_value); + if (rv < 0) + return -EFAULT; + + //dev_err(&xrusb->control->dev, "set_rs485_pin option = %d\n", option); + + switch (option) { + case 0: // disable RS485 pin + reg_value &= (GPIO9_RXT | GPIO8_TXT); + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode, + (int)reg_value); + if (rv < 0) + return -EFAULT; + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_flow, + UART_FLOW_MODE_NONE); + if (rv < 0) + return -EFAULT; + break; + case 5: // enable GPIO5/RTS as active-high RS-485 control output + reg_value = UART_GPIO_MODE_DIR_RTS_HI; + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode, + (int)reg_value); + if (rv < 0) + return -EFAULT; + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_flow, + UART_FLOW_MODE_NONE); + if (rv < 0) + return -EFAULT; + break; + case 7: // Enable GPIO7/XEN as active-high RS-485 control output + reg_value = UART_GPIO_MODE_DIR_XEN_HI; + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode, + (int)reg_value); + if (rv < 0) + return -EFAULT; + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_flow, + UART_FLOW_MODE_NONE); + if (rv < 0) + return -EFAULT; + break; + default: // Invalid option + return -EFAULT; + } + + return 0; +} + +static int set_txcvr_term_mode(struct xrusb *xrusb, unsigned int option) +{ + unsigned int term_mask; + int rv; + + term_mask = (1<<(xrusb->port_setting.txcvr_term_pin)); + + if (option) { //Set GPIO pin High + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set, + term_mask); + if (rv < 0) + return -EFAULT; + } else { //Set GPIO pin Low + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr, + term_mask); + if (rv < 0) + return -EFAULT; + } + return 0; +} +static int set_txcvr_slew_mode(struct xrusb *xrusb, unsigned int option) +{ + int rv; + unsigned int slew_mask; + + slew_mask = (1<<(xrusb->port_setting.txcvr_slew_pin)); + if (option) { //Slew pin is high + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set, + slew_mask); + if (rv < 0) + return -EFAULT; + } else { // Slew pin is low + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr, + slew_mask); + if (rv < 0) + return -EFAULT; + } + return 0; +} + +int init_portparam(struct xrusb *xrusb) +{ + int rv; + + if (xrusb->port_setting.txcvr_mode < 2) + rv = set_rs485_pin(xrusb, 0); + else + rv = set_rs485_pin(xrusb, xrusb->port_setting.txcvr_dir_pin); + if (rv < 0) + return -EFAULT; + + rv = set_txcvr_mode(xrusb, xrusb->port_setting.txcvr_mode); + if (rv < 0) + return -EFAULT; + + rv = set_txcvr_term_mode(xrusb, + xrusb->port_setting.term_mode); + if (rv < 0) + return -EFAULT; + + rv = set_txcvr_slew_mode(xrusb, xrusb->port_setting.slew_mode); + if (rv < 0) + return -EFAULT; + + return rv; +} + +int xrusb_gpio_dir_out(struct xrusb *xrusb, int gpio_mask) +{ + int rv = 0; + short reg_value; + + rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_dir, ®_value); + if (rv < 0) + return -EFAULT; + + reg_value |= gpio_mask; + + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_dir, reg_value); + if (rv < 0) + return -EFAULT; + + return rv; +} + +int xrusb_gpio_dir_in(struct xrusb *xrusb, int gpio_mask) +{ + int rv = 0; + short reg_value; + + rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_dir, ®_value); + if (rv < 0) + return -EFAULT; + + gpio_mask = ~gpio_mask; + reg_value &= gpio_mask; + + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_dir, reg_value); + if (rv < 0) + return -EFAULT; + + return rv; +} + +struct xr21v141x_baud_rate { + unsigned int tx; + unsigned int rx0; + unsigned int rx1; }; + +static struct xr21v141x_baud_rate xr21v141x_baud_rates[] = { + { 0x000, 0x000, 0x000 }, + { 0x000, 0x000, 0x000 }, + { 0x100, 0x000, 0x100 }, + { 0x020, 0x400, 0x020 }, + { 0x010, 0x100, 0x010 }, + { 0x208, 0x040, 0x208 }, + { 0x104, 0x820, 0x108 }, + { 0x844, 0x210, 0x884 }, + { 0x444, 0x110, 0x444 }, + { 0x122, 0x888, 0x224 }, + { 0x912, 0x448, 0x924 }, + { 0x492, 0x248, 0x492 }, + { 0x252, 0x928, 0x292 }, + { 0X94A, 0X4A4, 0XA52 }, + { 0X52A, 0XAA4, 0X54A }, + { 0XAAA, 0x954, 0X4AA }, + { 0XAAA, 0x554, 0XAAA }, + { 0x555, 0XAD4, 0X5AA }, + { 0XB55, 0XAB4, 0X55A }, + { 0X6B5, 0X5AC, 0XB56 }, + { 0X5B5, 0XD6C, 0X6D6 }, + { 0XB6D, 0XB6A, 0XDB6 }, + { 0X76D, 0X6DA, 0XBB6 }, + { 0XEDD, 0XDDA, 0X76E }, + { 0XDDD, 0XBBA, 0XEEE }, + { 0X7BB, 0XF7A, 0XDDE }, + { 0XF7B, 0XEF6, 0X7DE }, + { 0XDF7, 0XBF6, 0XF7E }, + { 0X7F7, 0XFEE, 0XEFE }, + { 0XFDF, 0XFBE, 0X7FE }, + { 0XF7F, 0XEFE, 0XFFE }, + { 0XFFF, 0XFFE, 0XFFD }, +}; + + +static int xr21v141x_set_baud_rate(struct xrusb *xrusb, unsigned int rate) +{ + unsigned int divisor = 48000000 / rate; + unsigned int i = ((32 * 48000000) / rate) & 0x1f; + unsigned int tx_mask = xr21v141x_baud_rates[i].tx; + unsigned int rx_mask = (divisor & 1) ? + xr21v141x_baud_rates[i].rx1 : + xr21v141x_baud_rates[i].rx0; + + xrusb_set_reg(xrusb, XR21V141X_CLOCK_DIVISOR_0, (divisor >> 0) & 0xff); + xrusb_set_reg(xrusb, XR21V141X_CLOCK_DIVISOR_1, (divisor >> 8) & 0xff); + xrusb_set_reg(xrusb, XR21V141X_CLOCK_DIVISOR_2, (divisor >> 16) & 0xff); + xrusb_set_reg(xrusb, XR21V141X_TX_CLOCK_MASK_0, (tx_mask >> 0) & 0xff); + xrusb_set_reg(xrusb, XR21V141X_TX_CLOCK_MASK_1, (tx_mask >> 8) & 0xff); + xrusb_set_reg(xrusb, XR21V141X_RX_CLOCK_MASK_0, (rx_mask >> 0) & 0xff); + xrusb_set_reg(xrusb, XR21V141X_RX_CLOCK_MASK_1, (rx_mask >> 8) & 0xff); + + return 0; +} + +/* devices aren't required to support these requests. + * the cdc xrusb descriptor tells whether they do... + */ +int xrusb_set_control(struct xrusb *xrusb, unsigned int control) +{ + int rv = 0; + + // Use custom vendor request for XR21V1410/12/14 + if ((xrusb->DeviceProduct & 0x1411) == 0x1410) { + if (control & XRUSB_CTRL_DTR) + xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr, + GPIO3_DTR); + else + xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set, + GPIO3_DTR); + + if (control & XRUSB_CTRL_RTS) + xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr, + GPIO5_RTS); + else + xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set, + GPIO5_RTS); + } + // Use CDC command for XR21B14xx and XR2280x + else + rv = xrusb_ctrl_msg(xrusb, + USB_CDC_REQ_SET_CONTROL_LINE_STATE, + control, + NULL, + 0); + + return rv; +} + +int xrusb_set_line(struct xrusb *xrusb, struct usb_cdc_line_coding *line) +{ + int rv = 0; + unsigned int data_size, data_parity, data_stop, format; + + // Use custom vendor request for XR21V1410/12/14 + if ((xrusb->DeviceProduct & 0x1411) == 0x1410) { + xr21v141x_set_baud_rate(xrusb, line->dwDTERate); + data_size = line->bDataBits; + data_parity = line->bParityType; + data_stop = line->bCharFormat; + format = data_size | (data_parity << 4) | (data_stop << 7); + xrusb_set_reg(xrusb, xrusb->reg_map.uart_format, format); + } + // Use CDC command for XR21B14xx and XR2280x + else + rv = xrusb_ctrl_msg(xrusb, + USB_CDC_REQ_SET_LINE_CODING, + 0, + line, + sizeof *(line)); + + return rv; +} + +int xrusb_set_flow_mode(struct xrusb *xrusb, + struct tty_struct *tty, unsigned int cflag) +{ + unsigned int flow; + unsigned int gpio_mode; + short gpio_mode_reg; + + xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_mode, &gpio_mode_reg); + gpio_mode = gpio_mode_reg; + if (cflag & CRTSCTS) { + flow = UART_FLOW_MODE_HW; + gpio_mode |= UART_GPIO_MODE_SEL_RTS_CTS; + } else if (I_IXOFF(tty) || I_IXON(tty)) { + unsigned char start_char = START_CHAR(tty); + unsigned char stop_char = STOP_CHAR(tty); + + flow = UART_FLOW_MODE_SW; + gpio_mode |= UART_GPIO_MODE_SEL_GPIO; + + xrusb_set_reg(xrusb, xrusb->reg_map.uart_xon_char, start_char); + xrusb_set_reg(xrusb, xrusb->reg_map.uart_xoff_char, stop_char); + } else { + flow = UART_FLOW_MODE_NONE; + gpio_mode |= UART_GPIO_MODE_SEL_GPIO; + } + + // Mode configured in BIOS for Caracalla + // Do nothing for set_flow_mode + if (xrusb->found_smbios_caracalla_config) + return 0; + + // If mode configured as RS485 or RS422 by application + // and stored in /etc/port_config file + if (xrusb->have_txcvr_config) { + xrusb_gpio_dir_out(xrusb, + GPIO9_RXT | GPIO8_TXT | GPIO7_XEN | GPIO6_CLK); + gpio_mode &= 0x00FF; // disable TXT and RXT + } + if (xrusb->port_setting.txcvr_mode < 2) { + xrusb_set_reg(xrusb, xrusb->reg_map.uart_flow, flow); + xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode, gpio_mode); + } + return 0; +} + +int xrusb_send_break(struct xrusb *xrusb, int state) +{ + int rv = 0; + + // Use custom vendor request for XR21V1410/12/14 + if ((xrusb->DeviceProduct & 0x1411) == 0x1410) { + if (state) + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_tx_break, + 0xffff); + else + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_tx_break, + 0); + } + // Use CDC command for XR21B14xx and XR2280x + else + rv = xrusb_ctrl_msg(xrusb, + USB_CDC_REQ_SEND_BREAK, + state, + NULL, + 0); + + return rv; +} + +int xrusb_enable(struct xrusb *xrusb) +{ + int rv = 0; + int channel = xrusb->channel; + + if (channel) + channel--; + + if ((xrusb->DeviceProduct & 0x1411) == 0x1410) { + rv = xrusb_set_block_reg(xrusb, + XR21V141x_URM_REG_BLOCK, + XR21V141x_URM_FIFO_ENABLE_REG + channel, + XR21V141x_URM_ENABLE_TX_FIFO); + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_enable, + UART_ENABLE_TX | UART_ENABLE_RX); + rv = xrusb_set_block_reg(xrusb, + XR21V141x_URM_REG_BLOCK, + XR21V141x_URM_FIFO_ENABLE_REG + channel, + XR21V141x_URM_ENABLE_TX_FIFO | + XR21V141x_URM_ENABLE_RX_FIFO); + } else + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_enable, + UART_ENABLE_TX | UART_ENABLE_RX); + + return rv; +} + +int xrusb_disable(struct xrusb *xrusb) +{ + int rv = 0; + int channel = xrusb->channel; + + if (channel) + channel--; + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_enable, 0); + + if ((xrusb->DeviceProduct & 0x1411) == 0x1410) { + rv = xrusb_set_block_reg(xrusb, + XR21V141x_URM_REG_BLOCK, + XR21V141x_URM_FIFO_ENABLE_REG + channel, 0); + } + + return rv; +} + +int xrusb_fifo_reset(struct xrusb *xrusb) +{ + int rv = 0; + int channel = xrusb->channel; + + if (channel) + channel--; + if ((xrusb->DeviceProduct & 0x1411) == 0x1410) { + rv = xrusb_set_block_reg(xrusb, + XR21V141x_URM_REG_BLOCK, + XR21V141x_URM_RX_FIFO_RESET + channel, + 0xff); + rv |= xrusb_set_block_reg(xrusb, + XR21V141x_URM_REG_BLOCK, + XR21V141x_URM_TX_FIFO_RESET + channel, + 0xff); + } else + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_enable, + UART_ENABLE_TX | UART_ENABLE_RX); + + return rv; +} + +int xrusb_set_loopback(struct xrusb *xrusb, int channel) +{ + int rv = 0; + + xrusb_disable(xrusb); + + if ((xrusb->DeviceProduct & 0x1411) == 0x1410) { + switch (channel) { + case 0: + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_loopback, + XR21V141x_LOOP_ENABLE_A); + break; + case 1: + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_loopback, + XR21V141x_LOOP_ENABLE_B); + break; + case 2: + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_loopback, + XR21V141x_LOOP_ENABLE_C); + break; + case 3: + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_loopback, + XR21V141x_LOOP_ENABLE_D); + break; + default: + break; + } + } else + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_loopback, + LOOP_ENABLE); + + xrusb_enable(xrusb); + return rv; +} + +int xrusb_set_wide_mode(struct xrusb *xrusb, int wide_mode) +{ + int rv = 0; + int channel = xrusb->channel; + + xrusb_disable(xrusb); + + if ((xrusb->DeviceProduct & 0xFFF0) == 0x1400) { + xrusb_set_reg(xrusb, XR2280X_TX_WIDE_MODE_REG, wide_mode); + xrusb_set_reg(xrusb, XR2280X_RX_WIDE_MODE_REG, wide_mode); + } else if ((xrusb->DeviceProduct & 0xFFF0) == 0x1420) { + xrusb_set_reg(xrusb, XR21B142X_TX_WIDE_MODE_REG, wide_mode); + xrusb_set_reg(xrusb, XR21B142X_RX_WIDE_MODE_REG, wide_mode); + } else if (xrusb->DeviceProduct == 0x1411) { + xrusb_set_reg(xrusb, XR21B1411_WIDE_MODE_REG, wide_mode); + } else if ((xrusb->DeviceProduct & 0xFFF0) == 0x1410) { + if (channel) + channel--; + xrusb_set_block_reg(xrusb, + XR21V141x_UART_CUSTOM_BLOCK, + channel*8 + XR21V141X_WIDE_MODE_REG, + wide_mode); + } + + xrusb_enable(xrusb); + return rv; +} + +int xrusb_disable_txt_rxt(struct xrusb *xrusb) +{ + int rv = 0; + short reg_value; + + rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_mode, ®_value); + if (rv < 0) + return -EFAULT; + + reg_value &= 0xFF; + + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode, reg_value); + if (rv < 0) + return -EFAULT; + + return rv; +} + +static int xrusb_tiocmget(struct xrusb *xrusb) + +{ + short data; + int result; + + result = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_status, &data); + + if (result) + return ((data & 0x8) ? 0 : TIOCM_DTR) | + ((data & 0x20) ? 0 : TIOCM_RTS) | + ((data & 0x4) ? 0 : TIOCM_DSR) | + ((data & 0x1) ? 0 : TIOCM_RI) | + ((data & 0x2) ? 0 : TIOCM_CD) | + ((data & 0x10) ? 0 : TIOCM_CTS); + else + return -EFAULT; +} + +static int xrusb_tiocmset(struct xrusb *xrusb, + unsigned int set, unsigned int clear) +{ + + unsigned int newctrl = 0; + + newctrl = xrusb->ctrlout; + set = (set & TIOCM_DTR ? XRUSB_CTRL_DTR : 0) | + (set & TIOCM_RTS ? XRUSB_CTRL_RTS : 0); + clear = (clear & TIOCM_DTR ? XRUSB_CTRL_DTR : 0) | + (clear & TIOCM_RTS ? XRUSB_CTRL_RTS : 0); + newctrl = (newctrl & ~clear) | set; + + if (xrusb->ctrlout == newctrl) + return 0; + + xrusb->ctrlout = newctrl; + + if (newctrl & XRUSB_CTRL_DTR) + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_clr, + GPIO3_DTR); + else + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_set, + GPIO3_DTR); + + if (newctrl & XRUSB_CTRL_RTS) + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_clr, + GPIO5_RTS); + else + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_set, + GPIO5_RTS); + + return 0; +} + +static void init_xr2280x_reg_map(void) +{ + xr2280x_reg_map.uart_enable = 0x40; + xr2280x_reg_map.uart_flow = 0x46; + xr2280x_reg_map.uart_xon_char = 0x47; + xr2280x_reg_map.uart_xoff_char = 0x48; + xr2280x_reg_map.uart_tx_break = 0x4a; + xr2280x_reg_map.uart_rs485_delay = 0x4b; + xr2280x_reg_map.uart_gpio_mode = 0x4c; + xr2280x_reg_map.uart_gpio_dir = 0x4d; + xr2280x_reg_map.uart_gpio_set = 0x4e; + xr2280x_reg_map.uart_gpio_clr = 0x4f; + xr2280x_reg_map.uart_gpio_status = 0x50; + xr2280x_reg_map.uart_gpio_int_mask = 0x51; + xr2280x_reg_map.uart_customized_int = 0x52; + xr2280x_reg_map.uart_gpio_pull_up_enable = 0x54; + xr2280x_reg_map.uart_gpio_pull_down_enable = 0x55; + xr2280x_reg_map.uart_loopback = 0x56; + xr2280x_reg_map.uart_low_latency = 0x66; + xr2280x_reg_map.uart_custom_driver = 0x81; +} + +static void init_xr21b1411_reg_map(void) +{ + xr21b1411_reg_map.uart_enable = 0xc00; + xr21b1411_reg_map.uart_flow = 0xc06; + xr21b1411_reg_map.uart_xon_char = 0xc07; + xr21b1411_reg_map.uart_xoff_char = 0xc08; + xr21b1411_reg_map.uart_tx_break = 0xc0a; + xr21b1411_reg_map.uart_gpio_mode = 0xc0c; + xr21b1411_reg_map.uart_gpio_dir = 0xc0d; + xr21b1411_reg_map.uart_gpio_set = 0xc0e; + xr21b1411_reg_map.uart_gpio_clr = 0xc0f; + xr21b1411_reg_map.uart_gpio_status = 0xc10; + xr21b1411_reg_map.uart_gpio_int_mask = 0xc11; + xr21b1411_reg_map.uart_customized_int = 0xc12; + xr21b1411_reg_map.uart_gpio_pull_up_enable = 0xc14; + xr21b1411_reg_map.uart_gpio_pull_down_enable = 0xc15; + xr21b1411_reg_map.uart_loopback = 0xc16; + xr21b1411_reg_map.uart_low_latency = 0xcc2; + xr21b1411_reg_map.uart_custom_driver = 0x20d; +} + +static void init_xr21v141x_reg_map(void) +{ + xr21v141x_reg_map.uart_enable = 0x03; + xr21v141x_reg_map.uart_format = 0x0b; + xr21v141x_reg_map.uart_flow = 0x0c; + xr21v141x_reg_map.uart_xon_char = 0x10; + xr21v141x_reg_map.uart_xoff_char = 0x11; + xr21v141x_reg_map.uart_loopback = 0x12; + xr21v141x_reg_map.uart_tx_break = 0x14; + xr21v141x_reg_map.uart_rs485_delay = 0x15; + xr21v141x_reg_map.uart_gpio_mode = 0x1a; + xr21v141x_reg_map.uart_gpio_dir = 0x1b; + xr21v141x_reg_map.uart_gpio_int_mask = 0x1c; + xr21v141x_reg_map.uart_gpio_set = 0x1d; + xr21v141x_reg_map.uart_gpio_clr = 0x1e; + xr21v141x_reg_map.uart_gpio_status = 0x1f; +} + +static void init_xr21b142x_reg_map(void) +{ + xr21b142x_reg_map.uart_enable = 0x00; + xr21b142x_reg_map.uart_flow = 0x06; + xr21b142x_reg_map.uart_xon_char = 0x07; + xr21b142x_reg_map.uart_xoff_char = 0x08; + xr21b142x_reg_map.uart_tx_break = 0x0a; + xr21b142x_reg_map.uart_rs485_delay = 0x0b; + xr21b142x_reg_map.uart_gpio_mode = 0x0c; + xr21b142x_reg_map.uart_gpio_dir = 0x0d; + xr21b142x_reg_map.uart_gpio_set = 0x0e; + xr21b142x_reg_map.uart_gpio_clr = 0x0f; + xr21b142x_reg_map.uart_gpio_status = 0x10; + xr21b142x_reg_map.uart_gpio_int_mask = 0x11; + xr21b142x_reg_map.uart_customized_int = 0x12; + xr21b142x_reg_map.uart_gpio_open_drain = 0x13; + xr21b142x_reg_map.uart_gpio_pull_up_enable = 0x14; + xr21b142x_reg_map.uart_gpio_pull_down_enable = 0x15; + xr21b142x_reg_map.uart_loopback = 0x16; + xr21b142x_reg_map.uart_custom_driver = 0x60; + xr21b142x_reg_map.uart_low_latency = 0x46; +} + +int xrusb_reg_init(struct xrusb *xrusb) +{ + int rv = 0, gpio_mode = 0; + unsigned char ch1_caracalla_config = 0xff; + unsigned char ch2_caracalla_config = 0xff; + + init_xr2280x_reg_map(); + init_xr21b142x_reg_map(); + init_xr21b1411_reg_map(); + init_xr21v141x_reg_map(); + + if ((xrusb->DeviceProduct & 0xfff0) == 0x1400) + memcpy(&(xrusb->reg_map), &xr2280x_reg_map, + sizeof(struct reg_addr_map)); + else if ((xrusb->DeviceProduct & 0xFFF0) == 0x1420) + memcpy(&(xrusb->reg_map), &xr21b142x_reg_map, + sizeof(struct reg_addr_map)); + else if (xrusb->DeviceProduct == 0x1411) + memcpy(&(xrusb->reg_map), &xr21b1411_reg_map, + sizeof(struct reg_addr_map)); + else if ((xrusb->DeviceProduct & 0xfff0) == 0x1410) + memcpy(&(xrusb->reg_map), &xr21v141x_reg_map, + sizeof(struct reg_addr_map)); + else + rv = -1; + + if (xrusb->reg_map.uart_custom_driver) + xrusb_set_reg(xrusb, xrusb->reg_map.uart_custom_driver, 1); + + // Enable TXT and RXT function for XR21B1420/22/24 + if ((xrusb->DeviceProduct & 0xfff0) == 0x1420) + gpio_mode |= (GPIO9_RXT | GPIO8_TXT); + + xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode, gpio_mode); + + // Enable RTS and DTR as outputs and high (de-asserted) + xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_dir, + GPIO5_RTS | GPIO3_DTR); + xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set, + GPIO5_RTS | GPIO3_DTR); + + // Check for Caracalla board using XR21V1412 (2-ch) + xrusb->found_smbios_caracalla_config = 0; + if (smbios_check_caracalla_config(&ch1_caracalla_config, + &ch2_caracalla_config) == 0) { + if (xrusb->channel == 1) { + xrusb->found_smbios_caracalla_config = 1; + xrusb->channel_config = ch1_caracalla_config; + switch (ch1_caracalla_config) { + case 1: // RS-232 Mode + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_mode, 0); + break; + case 2: // RS-485 Half-Duplex, active high RTS + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_mode, + UART_GPIO_MODE_DIR_RTS_HI); + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_flow, 0); + break; + case 3: // RS-485/422 Full-Duplex, active high RTS + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_mode, + UART_GPIO_MODE_DIR_RTS_HI); + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_flow, 0); + break; + default: // No Caracalla BIOS setting found + xrusb->found_smbios_caracalla_config = 0; + break; + } + } else if (xrusb->channel == 2) { + xrusb->found_smbios_caracalla_config = 1; + xrusb->channel_config = ch2_caracalla_config; + switch (ch2_caracalla_config) { + case 1: // RS-232 Mode + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_mode, 0); + break; + case 2: // RS-485 Half-Duplex, active high RTS + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_mode, + UART_GPIO_MODE_DIR_RTS_HI); + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_flow, 0); + break; + case 3: // RS-485/422 Full-Duplex, active high RTS + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_mode, + UART_GPIO_MODE_DIR_RTS_HI); + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_flow, 0); + break; + default: // No Caracalla BIOS setting found + xrusb->found_smbios_caracalla_config = 0; + break; + } + } + } + if (xrusb->found_smbios_caracalla_config) + return rv; + + // Check for previously saved port_config files in /etc/ + // for designs using XR21B142x using GPIO9-6 for transceiver control + if (check_portparamfile(xrusb)) { + xrusb_gpio_dir_out(xrusb, + GPIO9_RXT | GPIO8_TXT | GPIO7_XEN | GPIO6_CLK); + load_portparamfile(xrusb); + init_portparam(xrusb); + } + return rv; +} + +/* + * Write buffer management. + * All of these assume proper locks taken by the caller. + */ + +static int xrusb_wb_alloc(struct xrusb *xrusb) +{ + int i, wbn; + struct xrusb_wb *wb; + + wbn = 0; + i = 0; + for (;;) { + wb = &xrusb->wb[wbn]; + if (!wb->use) { + wb->use = 1; + return wbn; + } + wbn = (wbn + 1) % XRUSB_NW; + if (++i >= XRUSB_NW) + return -1; + } +} + +static int xrusb_wb_is_avail(struct xrusb *xrusb) +{ + int i, n; + unsigned long flags; + + n = XRUSB_NW; + spin_lock_irqsave(&xrusb->write_lock, flags); + for (i = 0; i < XRUSB_NW; i++) + n -= xrusb->wb[i].use; + spin_unlock_irqrestore(&xrusb->write_lock, flags); + return n; +} + +/* + * Finish write. Caller must hold xrusb->write_lock + */ +static void xrusb_write_done(struct xrusb *xrusb, + struct xrusb_wb *wb) +{ + wb->use = 0; + xrusb->transmitting--; + usb_autopm_put_interface_async(xrusb->control); +} + +/* + * Poke write. + * + * the caller is responsible for locking + */ + +static int xrusb_start_wb(struct xrusb *xrusb, struct xrusb_wb *wb) +{ + int rc; + + xrusb->transmitting++; + + wb->urb->transfer_buffer = wb->buf; + wb->urb->transfer_dma = wb->dmah; + wb->urb->transfer_buffer_length = wb->len; + wb->urb->dev = xrusb->dev; + + rc = usb_submit_urb(wb->urb, GFP_ATOMIC); + if (rc < 0) { + dev_err(&xrusb->data->dev, + "%s - usb_submit_urb(write bulk) failed: %d\n", + __func__, rc); + xrusb_write_done(xrusb, wb); + } + return rc; +} + +/* + * attributes exported through sysfs + */ + +static ssize_t bmCapabilities_show +(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct xrusb *xrusb = usb_get_intfdata(intf); + + return sprintf(buf, "%d", xrusb->ctrl_caps); +} +static DEVICE_ATTR_RO(bmCapabilities); + +/* + * Interrupt handlers for various XRUSB device responses + */ + +/* control interface reports status changes with "interrupt" transfers */ +static void xrusb_ctrl_irq(struct urb *urb) +{ + struct xrusb *xrusb = urb->context; + struct usb_cdc_notification *dr = urb->transfer_buffer; + + unsigned char *data; + int newctrl; + int rv; + int status = urb->status; + int i; + unsigned char *p; + + switch (status) { + case 0: + p = (unsigned char *)(urb->transfer_buffer); + for (i = 0; i < urb->actual_length; i++) + dev_dbg(&xrusb->control->dev, "0x%02x\n", p[i]); + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&xrusb->control->dev, + "%s - urb shutting down with status: %d\n", __func__, + status); + return; + default: + dev_dbg(&xrusb->control->dev, + "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + usb_mark_last_busy(xrusb->dev); + + data = (unsigned char *)(dr + 1); + switch (dr->bNotificationType) { + case USB_CDC_NOTIFY_NETWORK_CONNECTION: + dev_dbg(&xrusb->control->dev, "%s - network connection: %d\n", + __func__, dr->wValue); + break; + + case USB_CDC_NOTIFY_SERIAL_STATE: + + newctrl = get_unaligned_le16(data); + if (!xrusb->clocal && + (xrusb->ctrlin & ~newctrl & XRUSB_CTRL_DCD)) { + + dev_dbg(&xrusb->control->dev, + "%s - calling hangup\n", + __func__); + tty_port_tty_hangup(&xrusb->port, false); + } + + xrusb->ctrlin = newctrl; + break; + + default: + dev_dbg(&xrusb->control->dev, + "%s - unknown notification %d received: index %d ", + __func__, dr->bNotificationType, dr->wIndex); + dev_dbg(&xrusb->control->dev, "len %d data0 %d data1 %d\n", + dr->wLength, data[0], data[1]); + break; + } +exit: + rv = usb_submit_urb(urb, GFP_ATOMIC); + if (rv) + dev_err(&xrusb->control->dev, + "%s - usb_submit_urb failed: %d\n", __func__, rv); +} + +static int xrusb_submit_read_urb(struct xrusb *xrusb, + int index, gfp_t mem_flags) +{ + int res; + + if (!test_and_clear_bit(index, &xrusb->read_urbs_free)) + return 0; + + //dev_vdbg(&xrusb->data->dev, "%s - urb %d\n", __func__, index); + + res = usb_submit_urb(xrusb->read_urbs[index], mem_flags); + if (res) { + if (res != -EPERM) { + dev_err(&xrusb->data->dev, + "%s - usb_submit_urb failed: %d\n", + __func__, res); + } + set_bit(index, &xrusb->read_urbs_free); + return res; + } + + return 0; +} + +static int xrusb_submit_read_urbs(struct xrusb *xrusb, + gfp_t mem_flags) +{ + int res; + int i; + + for (i = 0; i < xrusb->rx_buflimit; ++i) { + res = xrusb_submit_read_urb(xrusb, i, mem_flags); + if (res) + return res; + } + + return 0; +} + +static void xrusb_process_read_urb(struct xrusb *xrusb, struct urb *urb) +{ + int wide_mode = xrusb->wide_mode; + int have_extra_byte; + int length; + + if (!urb->actual_length) + return; + + if (wide_mode) { + char *dp = urb->transfer_buffer; + int i, ch, ch_flags; + + length = urb->actual_length; + length = length + (xrusb->have_extra_byte ? 1 : 0); + have_extra_byte = (wide_mode && (length & 1)); + length = (wide_mode) ? (length / 2) : length; + for (i = 0; i < length; ++i) { + char tty_flag; + + if (i == 0) { + if (xrusb->have_extra_byte) + ch = xrusb->extra_byte; + else + ch = *dp++; + } else + ch = *dp++; + + ch_flags = *dp++; + if (ch_flags & WIDE_MODE_PARITY) + tty_flag = TTY_PARITY; + else if (ch_flags & WIDE_MODE_BREAK) + tty_flag = TTY_BREAK; + else if (ch_flags & WIDE_MODE_FRAME) + tty_flag = TTY_FRAME; + else if (ch_flags & WIDE_MODE_OVERRUN) + tty_flag = TTY_OVERRUN; + else + tty_flag = TTY_NORMAL; + + tty_insert_flip_char(&xrusb->port, + ch, tty_flag); + tty_flip_buffer_push(&xrusb->port); + } + } else { + tty_insert_flip_string(&xrusb->port, + urb->transfer_buffer, urb->actual_length); + tty_flip_buffer_push(&xrusb->port); + } +} + +static void xrusb_read_bulk_callback(struct urb *urb) +{ + struct xrusb_rb *rb = urb->context; + struct xrusb *xrusb = rb->instance; + unsigned long flags; + + set_bit(rb->index, &xrusb->read_urbs_free); + + if (!xrusb->dev) { + dev_dbg(&xrusb->data->dev, + "%s - disconnected\n", __func__); + return; + } + usb_mark_last_busy(xrusb->dev); + + if (urb->status) { + dev_dbg(&xrusb->data->dev, + "%s - non-zero urb status: %d\n", + __func__, urb->status); + return; + } + xrusb_process_read_urb(xrusb, urb); + + /* throttle device if requested by tty */ + spin_lock_irqsave(&xrusb->read_lock, flags); + xrusb->throttled = xrusb->throttle_req; + if (!xrusb->throttled && !xrusb->susp_count) { + spin_unlock_irqrestore(&xrusb->read_lock, flags); + xrusb_submit_read_urb(xrusb, + rb->index, GFP_ATOMIC); + } else { + spin_unlock_irqrestore(&xrusb->read_lock, flags); + } +} + +/* data interface wrote those outgoing bytes */ +static void xrusb_write_bulk(struct urb *urb) +{ + struct xrusb_wb *wb = urb->context; + struct xrusb *xrusb = wb->instance; + unsigned long flags; + + spin_lock_irqsave(&xrusb->write_lock, flags); + xrusb_write_done(xrusb, wb); + spin_unlock_irqrestore(&xrusb->write_lock, flags); + schedule_work(&xrusb->work); +} + +static void xrusb_softint(struct work_struct *work) +{ + struct xrusb *xrusb = container_of(work, struct xrusb, work); + + tty_port_tty_wakeup(&xrusb->port); +} + +/* + * TTY handlers + */ + +static int xrusb_tty_install(struct tty_driver *driver, + struct tty_struct *tty) +{ + struct xrusb *xrusb; + int rv; + + dev_dbg(tty->dev, "%s\n", __func__); + + xrusb = xrusb_get_by_index(tty->index); + if (!xrusb) + return -ENODEV; + + rv = tty_standard_install(driver, tty); + if (rv) + goto error_init_termios; + + tty->driver_data = xrusb; + + return 0; + +error_init_termios: + tty_port_put(&xrusb->port); + return rv; +} + +static int xrusb_tty_open(struct tty_struct *tty, struct file *filp) +{ + struct xrusb *xrusb = tty->driver_data; + int result; + + result = xrusb_fifo_reset(xrusb); + dev_dbg(tty->dev, "%s\n", __func__); + + return tty_port_open(&xrusb->port, tty, filp); +} + +static int xrusb_port_activate(struct tty_port *port, struct tty_struct *tty) +{ + struct xrusb *xrusb = container_of(port, struct xrusb, port); + int rv = -ENODEV; + + dev_dbg(&xrusb->control->dev, "%s\n", __func__); + + mutex_lock(&xrusb->mutex); + if (xrusb->disconnected) + goto disconnected; + + rv = usb_autopm_get_interface(xrusb->control); + if (rv) + goto error_get_interface; + + /* + * FIXME: Why do we need this? Allocating 64K of physically contiguous + * memory is really nasty... + */ + set_bit(TTY_NO_WRITE_SPLIT, &tty->flags); + xrusb->control->needs_remote_wakeup = 1; + + xrusb->ctrlurb->dev = xrusb->dev; + if (usb_submit_urb(xrusb->ctrlurb, GFP_KERNEL)) { + dev_err(&xrusb->control->dev, + "%s - usb_submit_urb(ctrl irq) failed\n", __func__); + goto error_submit_urb; + } + + xrusb->ctrlout = XRUSB_CTRL_DTR | + XRUSB_CTRL_RTS; + if (xrusb_set_control(xrusb, xrusb->ctrlout) < 0 + && (xrusb->ctrl_caps & USB_CDC_CAP_LINE)) + goto error_set_control; + + usb_autopm_put_interface(xrusb->control); + + /* + * Unthrottle device in case the TTY was closed while throttled. + */ + spin_lock_irq(&xrusb->read_lock); + xrusb->throttled = 0; + xrusb->throttle_req = 0; + spin_unlock_irq(&xrusb->read_lock); + + if (xrusb_submit_read_urbs(xrusb, GFP_KERNEL)) + goto error_submit_read_urbs; + + mutex_unlock(&xrusb->mutex); + + return 0; + +error_submit_read_urbs: + xrusb->ctrlout = 0; + xrusb_set_control(xrusb, xrusb->ctrlout); +error_set_control: + usb_kill_urb(xrusb->ctrlurb); +error_submit_urb: + usb_autopm_put_interface(xrusb->control); +error_get_interface: +disconnected: + mutex_unlock(&xrusb->mutex); + return rv; +} + +static void xrusb_port_destruct(struct tty_port *port) +{ + struct xrusb *xrusb = container_of(port, struct xrusb, port); + + //dev_dbg(&xrusb->control->dev, "%s\n", __func__); + + xrusb_release_minor(xrusb); + usb_put_intf(xrusb->control); + kfree(xrusb); +} + +static void xrusb_port_shutdown(struct tty_port *port) +{ + struct xrusb *xrusb = container_of(port, struct xrusb, port); + int i; + + //dev_dbg(&xrusb->control->dev, "%s\n", __func__); + + mutex_lock(&xrusb->mutex); + if (!xrusb->disconnected) { + usb_autopm_get_interface(xrusb->control); + xrusb_set_control(xrusb, + xrusb->ctrlout = 0); + usb_kill_urb(xrusb->ctrlurb); + for (i = 0; i < XRUSB_NW; i++) + usb_kill_urb(xrusb->wb[i].urb); + for (i = 0; i < xrusb->rx_buflimit; i++) + usb_kill_urb(xrusb->read_urbs[i]); + xrusb->control->needs_remote_wakeup = 0; + usb_autopm_put_interface(xrusb->control); + } + mutex_unlock(&xrusb->mutex); +} + +static void xrusb_tty_cleanup(struct tty_struct *tty) +{ + struct xrusb *xrusb = tty->driver_data; + //dev_dbg(&xrusb->control->dev, "%s\n", __func__); + tty_port_put(&xrusb->port); +} + +static void xrusb_tty_hangup(struct tty_struct *tty) +{ + struct xrusb *xrusb = tty->driver_data; + //dev_dbg(&xrusb->control->dev, "%s\n", __func__); + tty_port_hangup(&xrusb->port); +} + +static void xrusb_tty_close(struct tty_struct *tty, struct file *filp) +{ + struct xrusb *xrusb = tty->driver_data; + //dev_dbg(&xrusb->control->dev, "%s\n", __func__); + tty_port_close(&xrusb->port, tty, filp); +} + +static int xrusb_tty_write(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + struct xrusb *xrusb = tty->driver_data; + int stat; + unsigned long flags; + int wbn; + struct xrusb_wb *wb; + + if (!count) + return 0; + + //dev_vdbg(&xrusb->data->dev, "%s - count %d\n", __func__, count); + + spin_lock_irqsave(&xrusb->write_lock, flags); + wbn = xrusb_wb_alloc(xrusb); + if (wbn < 0) { + spin_unlock_irqrestore(&xrusb->write_lock, flags); + return 0; + } + wb = &xrusb->wb[wbn]; + + if (!xrusb->dev) { + wb->use = 0; + spin_unlock_irqrestore(&xrusb->write_lock, flags); + return -ENODEV; + } + + count = (count > xrusb->writesize) ? xrusb->writesize : count; + //dev_vdbg(&xrusb->data->dev, "%s - write %d\n", __func__, count); + memcpy(wb->buf, buf, count); + wb->len = count; + + usb_autopm_get_interface_async(xrusb->control); + if (xrusb->susp_count) { + if (!xrusb->delayed_wb) + xrusb->delayed_wb = wb; + else + usb_autopm_put_interface_async(xrusb->control); + spin_unlock_irqrestore(&xrusb->write_lock, flags); + return count; /* A white lie */ + } + usb_mark_last_busy(xrusb->dev); + + stat = xrusb_start_wb(xrusb, wb); + spin_unlock_irqrestore(&xrusb->write_lock, flags); + + if (stat < 0) + return stat; + return count; +} + +static int xrusb_tty_write_room(struct tty_struct *tty) +{ + struct xrusb *xrusb = tty->driver_data; + /* + * Do not let the line discipline to know that we have a reserve, + * or it might get too enthusiastic. + */ + return xrusb_wb_is_avail(xrusb) ? + xrusb->writesize : 0; +} + +static int xrusb_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct xrusb *xrusb = tty->driver_data; + /* + * if the device was unplugged then any remaining characters fell out + * of the connector ;) + */ + if (xrusb->disconnected) + return 0; + /* + * This is inaccurate (overcounts), but it works. + */ + return (XRUSB_NW - xrusb_wb_is_avail(xrusb)) * + xrusb->writesize; +} + +static void xrusb_tty_throttle(struct tty_struct *tty) +{ + struct xrusb *xrusb = tty->driver_data; + + spin_lock_irq(&xrusb->read_lock); + xrusb->throttle_req = 1; + spin_unlock_irq(&xrusb->read_lock); +} + +static void xrusb_tty_unthrottle(struct tty_struct *tty) +{ + struct xrusb *xrusb = tty->driver_data; + unsigned int was_throttled; + + spin_lock_irq(&xrusb->read_lock); + was_throttled = xrusb->throttled; + xrusb->throttled = 0; + xrusb->throttle_req = 0; + spin_unlock_irq(&xrusb->read_lock); + + if (was_throttled) + xrusb_submit_read_urbs(xrusb, GFP_KERNEL); +} + +static int xrusb_tty_break_ctl(struct tty_struct *tty, int state) +{ + struct xrusb *xrusb = tty->driver_data; + int rv; + + rv = xrusb_send_break(xrusb, state ? 0xffff : 0); + if (rv < 0) + dev_err(&xrusb->control->dev, "%s - send break failed\n", + __func__); + return rv; +} + +static int xrusb_tty_tiocmget(struct tty_struct *tty) +{ + struct xrusb *xrusb = tty->driver_data; + + return xrusb_tiocmget(xrusb); +} + +static int xrusb_tty_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct xrusb *xrusb = tty->driver_data; + + return xrusb_tiocmset(xrusb, set, clear); +} + +static int get_serial_info(struct xrusb *xrusb, + struct serial_struct __user *info) +{ + struct serial_struct tmp; + + if (!info) + return -EINVAL; + + memset(&tmp, 0, sizeof(tmp)); + tmp.flags = ASYNC_LOW_LATENCY; + tmp.xmit_fifo_size = xrusb->writesize; + tmp.baud_base = le32_to_cpu(xrusb->line.dwDTERate); + tmp.close_delay = xrusb->port.close_delay / 10; + tmp.closing_wait = xrusb->port.closing_wait == + ASYNC_CLOSING_WAIT_NONE ? ASYNC_CLOSING_WAIT_NONE : + xrusb->port.closing_wait / 10; + + if (copy_to_user(info, &tmp, sizeof(tmp))) + return -EFAULT; + else + return 0; +} + +static int set_serial_info(struct xrusb *xrusb, + struct serial_struct __user *newinfo) +{ + struct serial_struct new_serial; + unsigned int closing_wait, close_delay; + int rv = 0; + + if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) + return -EFAULT; + + close_delay = new_serial.close_delay * 10; + closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10; + + mutex_lock(&xrusb->port.mutex); + + if (!capable(CAP_SYS_ADMIN)) { + if ((close_delay != xrusb->port.close_delay) || + (closing_wait != xrusb->port.closing_wait)) + rv = -EPERM; + else + rv = -EOPNOTSUPP; + } else { + xrusb->port.close_delay = close_delay; + xrusb->port.closing_wait = closing_wait; + } + + mutex_unlock(&xrusb->port.mutex); + return rv; +} + +static int xrusb_tty_ioctl(struct tty_struct *tty, unsigned int cmd, + unsigned long arg) +{ + struct xrusb *xrusb = tty->driver_data; + int rv = -ENOIOCTLCMD; + unsigned int channel, param_rw; + int baud_rate = 0; + struct usb_cdc_line_coding newline; + short reg_value; + + channel = xrusb->channel; + switch (cmd) { + case TIOCGSERIAL: /* gets serial port data */ + rv = get_serial_info(xrusb, + (struct serial_struct __user *) arg); + break; + case TIOCSSERIAL: + rv = set_serial_info(xrusb, + (struct serial_struct __user *) arg); + break; + case XRUSB_SET_BAUD: + if (get_user(baud_rate, (int __user *)arg)) { + dev_dbg(&xrusb->control->dev, + "get_user error\n"); + return -EFAULT; + } + xrusb->line.dwDTERate = baud_rate; + memcpy(&newline, &(xrusb->line), + sizeof(struct usb_cdc_line_coding)); + xrusb_disable(xrusb); + rv = xrusb_set_line(xrusb, &newline); + xrusb_enable(xrusb); + break; + case XRUSB_LOOPBACK: + rv = xrusb_set_loopback(xrusb, channel); + if (rv < 0) + return -EFAULT; + break; + case XRUSB_SET_GPIO_MODE: + if (get_user(param_rw, (int __user *)arg)) { + dev_dbg(&xrusb->control->dev, + "get_user error\n"); + return -EFAULT; + } + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_mode, param_rw); + if (rv < 0) + return -EFAULT; + rv = 0; + break; + case XRUSB_GET_GPIO_MODE: + rv = xrusb_get_reg(xrusb, + xrusb->reg_map.uart_gpio_mode, ®_value); + if (rv < 0) + return -EFAULT; + if (put_user(reg_value, (int __user *)arg)) { + dev_err(&xrusb->control->dev, + "put_user error\n"); + return -EFAULT; + } + rv = 0; + break; + case XRUSB_SET_GPIO_DIR: + if (get_user(param_rw, (int __user *)arg)) { + dev_dbg(&xrusb->control->dev, + "get_user error\n"); + return -EFAULT; + } + + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_dir, + (int)param_rw); + if (rv < 0) + return -EFAULT; + rv = 0; + break; + case XRUSB_GET_GPIO_DIR: + rv = xrusb_get_reg(xrusb, + xrusb->reg_map.uart_gpio_dir, + ®_value); + if (rv < 0) + return -EFAULT; + if (put_user(reg_value, (int __user *)arg)) { + dev_err(&xrusb->control->dev, + "put_user error\n"); + return -EFAULT; + } + rv = 0; + break; + case XRUSB_SET_GPIO_STATE: + if (get_user(param_rw, (int __user *)arg)) { + dev_dbg(&xrusb->control->dev, + "get_user error\n"); + return -EFAULT; + } + + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_set, + (int)param_rw); + + if (rv < 0) + return -EFAULT; + rv = 0; + break; + case XRUSB_CLEAR_GPIO_STATE: + if (get_user(param_rw, (int __user *)arg)) { + dev_dbg(&xrusb->control->dev, + "get_user error\n"); + return -EFAULT; + } + + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_clr, + (int)param_rw); + + if (rv < 0) + return -EFAULT; + rv = 0; + break; + case XRUSB_GET_GPIO_STATE: + rv = xrusb_get_reg(xrusb, + xrusb->reg_map.uart_gpio_status, + ®_value); + if (rv < 0) + return -EFAULT; + if (put_user(reg_value, (int __user *)arg)) { + dev_err(&xrusb->control->dev, + "put_user error\n"); + return -EFAULT; + } + rv = 0; + break; + case XRUSB_SET_GPIO_INT_MASK: + if (get_user(param_rw, (int __user *)arg)) { + dev_dbg(&xrusb->control->dev, + "get_user error\n"); + return -EFAULT; + } + + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_int_mask, + (int)param_rw); + if (rv < 0) + return -EFAULT; + rv = 0; + break; + case XRUSB_GET_GPIO_INT_MASK: + rv = xrusb_get_reg(xrusb, + xrusb->reg_map.uart_gpio_int_mask, + ®_value); + if (put_user(reg_value, (int __user *)arg)) { + dev_err(&xrusb->control->dev, + "put_user error\n"); + return -EFAULT; + } + rv = 0; + break; + case XRUSB_SET_GPIO_PULL_UP: + if (get_user(param_rw, (int __user *)arg)) { + dev_dbg(&xrusb->control->dev, + "get_user error\n"); + return -EFAULT; + } + + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_pull_up_enable, + (int)param_rw); + if (rv < 0) + return -EFAULT; + rv = 0; + break; + case XRUSB_GET_GPIO_PULL_UP: + rv = xrusb_get_reg(xrusb, + xrusb->reg_map.uart_gpio_pull_up_enable, + ®_value); + if (put_user(reg_value, (int __user *)arg)) { + dev_err(&xrusb->control->dev, + "Cannot put user result\n"); + return -EFAULT; + } + rv = 0; + break; + case XRUSB_SET_GPIO_PULL_DOWN: + if (get_user(param_rw, (int __user *)arg)) { + dev_dbg(&xrusb->control->dev, + "get_user error\n"); + return -EFAULT; + } + + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_pull_down_enable, + (int)param_rw); + if (rv < 0) + return -EFAULT; + rv = 0; + break; + case XRUSB_GET_GPIO_PULL_DOWN: + rv = xrusb_get_reg(xrusb, + xrusb->reg_map.uart_gpio_pull_down_enable, + ®_value); + if (put_user(reg_value, (int __user *)arg)) { + dev_err(&xrusb->control->dev, + "Cannot put user result\n"); + return -EFAULT; + } + rv = 0; + break; + case XRUSB_SET_GPIO_OPEN_DRAIN: + if (get_user(param_rw, (int __user *)arg)) { + dev_dbg(&xrusb->control->dev, + "get_user error\n"); + return -EFAULT; + } + + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_open_drain, + (int)param_rw); + if (rv < 0) + return -EFAULT; + rv = 0; + break; + case XRUSB_GET_GPIO_OPEN_DRAIN: + rv = xrusb_get_reg(xrusb, + xrusb->reg_map.uart_gpio_open_drain, + ®_value); + if (put_user(reg_value, (int __user *)arg)) { + dev_err(&xrusb->control->dev, + "Cannot put user result\n"); + return -EFAULT; + } + rv = 0; + break; + + case XRUSB_WIDE_MODE: + if (get_user(param_rw, (int __user *)arg)) { + dev_dbg(&xrusb->control->dev, + "get_user error\n"); + return -EFAULT; + } + if (param_rw) + xrusb->wide_mode = 1; // enable wide mode + else + xrusb->wide_mode = 0; // disable wide mode + rv = xrusb_set_wide_mode(xrusb, + xrusb->wide_mode); + if (rv < 0) + return -EFAULT; + rv = 0; + break; + case XRUSB_LOW_LATENCY_MODE: + if (get_user(param_rw, (int __user *)arg)) { + dev_dbg(&xrusb->control->dev, + "get_user error\n"); + return -EFAULT; + } + if (param_rw) { + // enable low latency mode + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_low_latency, 1); + if (rv < 0) + return -EFAULT; + + } else { + // disable low latency mode + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_low_latency, 0); + if (rv < 0) + return -EFAULT; + } + xrusb->port_setting.low_latency_mode = param_rw; + rv = 0; + break; + case XRUSB_GET_TXCVR_MODE: + if (xrusb->have_txcvr_config) { + if (copy_to_user((void __user *)arg, + (void *)&(xrusb->port_setting), + sizeof(struct xrusb_setting))) { + return -EFAULT; + } + rv = 0; + } + break; + case XRUSB_SET_TXCVR_MODE: + if (copy_from_user((void *)&(xrusb->port_setting), + (void __user *)arg, sizeof(struct xrusb_setting))) + return -EFAULT; + + rv = xrusb_disable_txt_rxt(xrusb); + if (rv < 0) + return -EFAULT; + + // Enable GPIOs 9-6 as outputs + rv = xrusb_gpio_dir_out(xrusb, 0x3c0); + if (rv < 0) + return -EFAULT; + + rv = set_txcvr_mode(xrusb, + xrusb->port_setting.txcvr_mode); + if (rv < 0) + return -EFAULT; + + rv = update_portparamfile(xrusb); + if (rv < 0) + return -EFAULT; + rv = 0; + break; + case XRUSB_SET_TERM_MODE: + if (copy_from_user((void *)&(xrusb->port_setting), + (void __user *)arg, sizeof(struct xrusb_setting))) + return -EFAULT; + + rv = xrusb_disable_txt_rxt(xrusb); + if (rv < 0) + return -EFAULT; + + // Enable GPIOs 9-6 as outputs + rv = xrusb_gpio_dir_out(xrusb, 0x3c0); + if (rv < 0) + return -EFAULT; + + rv = set_txcvr_term_mode(xrusb, + xrusb->port_setting.term_mode); + if (rv < 0) + return -EFAULT; + + rv = update_portparamfile(xrusb); + if (rv < 0) + return -EFAULT; + rv = 0; + break; + case XRUSB_SET_SLEW_MODE: + if (copy_from_user((void *)&(xrusb->port_setting), + (void __user *)arg, sizeof(struct xrusb_setting))) + return -EFAULT; + + rv = xrusb_disable_txt_rxt(xrusb); + if (rv < 0) + return -EFAULT; + + // Enable GPIOs 9-6 as outputs + rv = xrusb_gpio_dir_out(xrusb, 0x3c0); + if (rv < 0) + return -EFAULT; + + rv = set_txcvr_slew_mode(xrusb, + xrusb->port_setting.slew_mode); + if (rv < 0) + return -EFAULT; + + rv = update_portparamfile(xrusb); + if (rv < 0) + return -EFAULT; + rv = 0; + break; + case XRUSB_SET_RS485_PIN: + if (copy_from_user((void *)&(xrusb->port_setting), + (void __user *)arg, sizeof(struct xrusb_setting))) + return -EFAULT; + + if (xrusb->port_setting.txcvr_mode < 2) + rv = set_rs485_pin(xrusb, 0); + else + rv = set_rs485_pin(xrusb, + xrusb->port_setting.txcvr_dir_pin); + + if (rv < 0) + return -EFAULT; + + rv = update_portparamfile(xrusb); + if (rv < 0) + return -EFAULT; + rv = 0; + break; + case XRUSB_ENABLE_RS485_LOW: + rv = xrusb_get_reg(xrusb, + xrusb->reg_map.uart_gpio_mode, ®_value); + if (rv < 0) + return -EFAULT; + reg_value &= 0x3f7; // bit-3 = 0 for active low + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode, + (int)reg_value); + if (rv < 0) + return -EFAULT; + rv = 0; + break; + case XRUSB_RS485_DELAY: + if (get_user(param_rw, (int __user *)arg)) { + dev_dbg(&xrusb->control->dev, "get_user error\n"); + return -EFAULT; + } + if (param_rw) { + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_rs485_delay, 15); + if (rv < 0) + return -EFAULT; + } else { + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_rs485_delay, 0); + if (rv < 0) + return -EFAULT; + } + rv = 0; + break; + case XRUSB_ENABLE_DTRDSR_FLOW: + if (get_user(param_rw, (int __user *)arg)) { + dev_dbg(&xrusb->control->dev, "get_user error\n"); + return -EFAULT; + } + // Value = Read GPIO_MODE register & 0x300; + // GPIO_MODE = Value | 0x02; + // FLOW = 0x1; + rv = xrusb_get_reg(xrusb, + xrusb->reg_map.uart_gpio_mode, ®_value); + if (rv < 0) + return -EFAULT; + reg_value &= 0x300; + reg_value |= 0x02; + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_mode, + (int)reg_value); + if (rv < 0) + return -EFAULT; + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_flow, + UART_FLOW_MODE_HW); + if (rv < 0) + return -EFAULT; + rv = 0; + break; + case XRUSB_DISABLE_FLOW_CONTROL: + if (get_user(param_rw, (int __user *)arg)) { + dev_dbg(&xrusb->control->dev, "get_user error\n"); + return -EFAULT; + } + // Value = Read GPIO_MODE register & 0x300; + // GPIO_MODE = Value; + // FLOW = 0x00; + rv = xrusb_get_reg(xrusb, + xrusb->reg_map.uart_gpio_mode, ®_value); + if (rv < 0) + return -EFAULT; + reg_value &= 0x300; + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_mode, + (int)reg_value); + if (rv < 0) + return -EFAULT; + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_flow, + UART_FLOW_MODE_NONE); + if (rv < 0) + return -EFAULT; + break; + case XRUSB_GET_FLOW_CONTROL: + rv = xrusb_get_reg(xrusb, + xrusb->reg_map.uart_flow, ®_value); + if (put_user(reg_value, (int __user *)arg)) { + dev_err(&xrusb->control->dev, "put_user error\n"); + return -EFAULT; + } + rv = 0; + break; + } + return rv; +} + +static void xrusb_tty_set_termios(struct tty_struct *tty, + struct ktermios *termios_old) +{ + struct xrusb *xrusb = tty->driver_data; + struct ktermios *termios = &tty->termios; + unsigned int cflag = termios->c_cflag; + struct usb_cdc_line_coding newline; + int newctrl = xrusb->ctrlout; + + xrusb_disable(xrusb); + newline.dwDTERate = cpu_to_le32(tty_get_baud_rate(tty)); + newline.bCharFormat = termios->c_cflag & CSTOPB ? 1 : 0; + newline.bParityType = termios->c_cflag & PARENB ? + (termios->c_cflag & PARODD ? 1 : 2) + + (termios->c_cflag & CMSPAR ? 2 : 0) : 0; + xrusb->wide_mode = 0; + switch (termios->c_cflag & CSIZE) { + case CS5:/*using CS5 replace of the 9 bit data mode*/ + newline.bDataBits = 9; + xrusb->wide_mode = 1; + break; + case CS6: + newline.bDataBits = 6; + break; + case CS7: + newline.bDataBits = 7; + break; + case CS8: + default: + newline.bDataBits = 8; + break; + } + /* FIXME: Needs to clear unsupported bits in the termios */ + xrusb->clocal = ((termios->c_cflag & CLOCAL) != 0); + + if (!newline.dwDTERate) { + newline.dwDTERate = xrusb->line.dwDTERate; + newctrl &= ~XRUSB_CTRL_DTR; + } else + newctrl |= XRUSB_CTRL_DTR; + + if (newctrl != xrusb->ctrlout) + xrusb_set_control(xrusb, + xrusb->ctrlout = newctrl); + + xrusb_set_flow_mode(xrusb, tty, cflag); + if (xrusb->wide_mode) + xrusb_set_wide_mode(xrusb, 1); + else if (!xrusb->wide_mode) + xrusb_set_wide_mode(xrusb, 0); + + if (memcmp(&xrusb->line, &newline, sizeof(newline))) { + memcpy(&xrusb->line, &newline, sizeof(newline)); + xrusb_set_line(xrusb, &xrusb->line); + } + xrusb_enable(xrusb); +} + +static const struct tty_port_operations xrusb_port_ops = { + .shutdown = xrusb_port_shutdown, + .activate = xrusb_port_activate, + .destruct = xrusb_port_destruct, +}; + +/* + * USB probe and disconnect routines. + */ + +/* Little helpers: write/read buffers free */ +static void xrusb_write_buffers_free(struct xrusb *xrusb) +{ + int i; + struct xrusb_wb *wb; + + for (wb = &xrusb->wb[0], i = 0; i < XRUSB_NW; i++, wb++) + usb_free_coherent(xrusb->dev, + xrusb->writesize, wb->buf, wb->dmah); +} + +static void xrusb_read_buffers_free(struct xrusb *xrusb) +{ + int i; + + for (i = 0; i < xrusb->rx_buflimit; i++) + usb_free_coherent(xrusb->dev, xrusb->readsize, + xrusb->read_buffers[i].base, + xrusb->read_buffers[i].dma); +} + +/* Little helper: write buffers allocate */ +static int xrusb_write_buffers_alloc(struct xrusb *xrusb) +{ + int i; + struct xrusb_wb *wb; + + for (wb = &xrusb->wb[0], i = 0; i < XRUSB_NW; i++, wb++) { + wb->buf = usb_alloc_coherent(xrusb->dev, + xrusb->writesize, GFP_KERNEL, + &wb->dmah); + if (!wb->buf) { + while (i != 0) { + --i; + --wb; + usb_free_coherent(xrusb->dev, + xrusb->writesize, + wb->buf, wb->dmah); + } + return -ENOMEM; + } + } + return 0; +} + +static int xrusb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_cdc_union_desc *union_header = NULL; + unsigned char *buffer = intf->altsetting->extra; + int buflen = intf->altsetting->extralen; + struct usb_interface *control_interface; + struct usb_interface *data_interface; + struct usb_endpoint_descriptor *epctrl = NULL; + struct usb_endpoint_descriptor *epread = NULL; + struct usb_endpoint_descriptor *epwrite = NULL; + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct xrusb *xrusb; + int minor; + int ctrlsize, readsize; + u8 *buf; + u8 ac_management_function = 0; + u8 call_management_function = 0; + int call_interface_num = -1; + int data_interface_num = -1; + unsigned long quirks; + int num_rx_buf; + int i; + int combined_interfaces = 0; + struct device *tty_dev; + int rv = -ENOMEM; + + /* normal quirks */ + quirks = (unsigned long)id->driver_info; + + if (quirks == IGNORE_DEVICE) + return -ENODEV; + + num_rx_buf = (quirks == SINGLE_RX_URB) ? 1 : XRUSB_NR; + + /* handle quirks deadly to normal probing*/ + if (quirks == NO_UNION_NORMAL) { + data_interface = usb_ifnum_to_if(usb_dev, 1); + control_interface = usb_ifnum_to_if(usb_dev, 0); + goto skip_normal_probe; + } + + /* normal probing*/ + if (!buffer) { + dev_err(&intf->dev, "Weird descriptor references\n"); + return -EINVAL; + } + + if (!buflen) { + if (intf->cur_altsetting->endpoint && + intf->cur_altsetting->endpoint->extralen && + intf->cur_altsetting->endpoint->extra) { + dev_dbg(&intf->dev, + "Seeking extra descriptors on endpoint\n"); + buflen = intf->cur_altsetting->endpoint->extralen; + buffer = intf->cur_altsetting->endpoint->extra; + } else { + dev_err(&intf->dev, + "Zero length descriptor references\n"); + return -EINVAL; + } + } + + while (buflen > 0) { + if (buffer[1] != USB_DT_CS_INTERFACE) { + dev_err(&intf->dev, "skipping garbage\n"); + goto next_desc; + } + + switch (buffer[2]) { + case USB_CDC_UNION_TYPE: /* we've found it */ + if (union_header) { + dev_err(&intf->dev, "> 1 union descriptor"); + dev_err(&intf->dev, "skipping ...\n"); + goto next_desc; + } + union_header = (struct usb_cdc_union_desc *)buffer; + break; + case USB_CDC_HEADER_TYPE: /* maybe check version */ + break; /* for now we ignore it */ + case USB_CDC_ACM_TYPE: + ac_management_function = buffer[3]; + break; + case USB_CDC_CALL_MANAGEMENT_TYPE: + call_management_function = buffer[3]; + call_interface_num = buffer[4]; + //if ((quirks & NOT_A_MODEM) == 0 && + // (call_management_function & 3) != 3) + // dev_err(&intf->dev, "This device cannot + // do calls on its own. It is + // not a modem.\n"); + break; + default: + /* there are LOTS more CDC descriptors that + * could legitimately be found here. + */ + dev_dbg(&intf->dev, "Ignoring descriptor: "); + dev_dbg(&intf->dev, "type %02x, length %d\n", + buffer[2], buffer[0]); + break; + } +next_desc: + buflen -= buffer[0]; + buffer += buffer[0]; + } + + if (!union_header) { + if (call_interface_num > 0) { + dev_dbg(&intf->dev, "No union descriptor, using"); + dev_dbg(&intf->dev, " call management descriptor\n"); + /* quirks for Droids MuIn LCD */ + if (quirks & NO_DATA_INTERFACE) + data_interface = usb_ifnum_to_if(usb_dev, 0); + else + data_interface = usb_ifnum_to_if(usb_dev, + (data_interface_num = + call_interface_num)); + control_interface = intf; + } else { + if (intf->cur_altsetting->desc.bNumEndpoints != 3) { + dev_dbg(&intf->dev, "No union descriptor,"); + dev_dbg(&intf->dev, "giving up\n"); + return -ENODEV; + } else { + dev_warn(&intf->dev, "No union descriptor, "); + dev_warn(&intf->dev, "testing for castrated device\n"); + combined_interfaces = 1; + control_interface = data_interface = intf; + goto look_for_collapsed_interface; + } + } + } else { + control_interface = usb_ifnum_to_if(usb_dev, + union_header->bMasterInterface0); + data_interface = usb_ifnum_to_if(usb_dev, + (data_interface_num = union_header->bSlaveInterface0)); + if (!control_interface || !data_interface) { + dev_dbg(&intf->dev, "no interfaces\n"); + return -ENODEV; + } + } + + if (data_interface_num != call_interface_num) { + dev_dbg(&intf->dev, "Separate call control interface. "); + dev_dbg(&intf->dev, "That is not fully supported.\n"); + } + + if (control_interface == data_interface) { + /* some broken devices designed for windows work this way */ + dev_warn(&intf->dev, "Control and data interfaces"); + dev_warn(&intf->dev, "are not separated!\n"); + combined_interfaces = 1; + /* a popular other OS doesn't use it */ + quirks |= NO_CAP_LINE; + if (data_interface->cur_altsetting->desc.bNumEndpoints != 3) { + dev_err(&intf->dev, "This needs exactly 3 endpoints\n"); + return -EINVAL; + } +look_for_collapsed_interface: + for (i = 0; i < 3; i++) { + struct usb_endpoint_descriptor *ep; + + ep = &data_interface->cur_altsetting->endpoint[i].desc; + + if (usb_endpoint_is_int_in(ep)) + epctrl = ep; + else if (usb_endpoint_is_bulk_out(ep)) + epwrite = ep; + else if (usb_endpoint_is_bulk_in(ep)) + epread = ep; + else + return -EINVAL; + } + if (!epctrl || !epread || !epwrite) + return -ENODEV; + else + goto made_compressed_probe; + } + +skip_normal_probe: + + /*workaround for switched interfaces */ + if (data_interface->cur_altsetting->desc.bInterfaceClass + != CDC_DATA_INTERFACE_TYPE) { + if (control_interface->cur_altsetting->desc.bInterfaceClass + == CDC_DATA_INTERFACE_TYPE) { + struct usb_interface *t; + + dev_dbg(&intf->dev, + "Your device has switched interfaces.\n"); + t = control_interface; + control_interface = data_interface; + data_interface = t; + } else { + return -EINVAL; + } + } + + /* Accept probe requests only for the control interface */ + if (!combined_interfaces && intf != control_interface) + return -ENODEV; + + if (!combined_interfaces && usb_interface_claimed(data_interface)) { + /* valid in this context */ + dev_dbg(&intf->dev, "The data interface isn't available\n"); + return -EBUSY; + } + + + if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 || + control_interface->cur_altsetting->desc.bNumEndpoints == 0) + return -EINVAL; + + epctrl = &control_interface->cur_altsetting->endpoint[0].desc; + epread = &data_interface->cur_altsetting->endpoint[0].desc; + epwrite = &data_interface->cur_altsetting->endpoint[1].desc; + + + /* workaround for switched endpoints */ + if (!usb_endpoint_dir_in(epread)) { + /* descriptors are swapped */ + struct usb_endpoint_descriptor *t; + + dev_dbg(&intf->dev, + "The data interface has switched endpoints\n"); + t = epread; + epread = epwrite; + epwrite = t; + } +made_compressed_probe: + dev_dbg(&intf->dev, "interfaces are valid\n"); + + xrusb = kzalloc(sizeof(struct xrusb), GFP_KERNEL); + if (xrusb == NULL) { + dev_err(&intf->dev, "out of memory (xrusb kzalloc)\n"); + goto alloc_fail; + } + + minor = xrusb_alloc_minor(xrusb); + if (minor == XRUSB_TTY_MINORS) { + dev_err(&intf->dev, "no more free xrusb devices\n"); + kfree(xrusb); + return -ENODEV; + } + + ctrlsize = usb_endpoint_maxp(epctrl); + readsize = usb_endpoint_maxp(epread) * + (quirks == SINGLE_RX_URB ? 1 : 2); + xrusb->combined_interfaces = combined_interfaces; + xrusb->writesize = usb_endpoint_maxp(epwrite) * 20; + xrusb->control = control_interface; + xrusb->data = data_interface; + xrusb->minor = minor; + xrusb->dev = usb_dev; + xrusb->ctrl_caps = ac_management_function; + if (quirks & NO_CAP_LINE) + xrusb->ctrl_caps &= ~USB_CDC_CAP_LINE; + xrusb->ctrlsize = ctrlsize; + xrusb->readsize = readsize; + xrusb->rx_buflimit = num_rx_buf; + INIT_WORK(&xrusb->work, xrusb_softint); + spin_lock_init(&xrusb->write_lock); + spin_lock_init(&xrusb->read_lock); + mutex_init(&xrusb->mutex); + xrusb->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress); + xrusb->is_int_ep = usb_endpoint_xfer_int(epread); + if (xrusb->is_int_ep) + xrusb->bInterval = epread->bInterval; + tty_port_init(&xrusb->port); + xrusb->port.ops = &xrusb_port_ops; + xrusb->DeviceVendor = id->idVendor; + xrusb->DeviceProduct = id->idProduct; + + xrusb->channel = epwrite->bEndpointAddress; + //dev_err(&intf->dev, "epwrite->bEndpointAddress =%d\n", + // epwrite->bEndpointAddress); + + buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL, + &xrusb->ctrl_dma); + if (!buf) { + dev_err(&intf->dev, "out of memory (ctrl buffer alloc)\n"); + goto alloc_fail2; + } + xrusb->ctrl_buffer = buf; + + if (xrusb_write_buffers_alloc(xrusb) < 0) { + dev_err(&intf->dev, "out of memory (write buffer alloc)\n"); + goto alloc_fail4; + } + + xrusb->ctrlurb = usb_alloc_urb(0, GFP_KERNEL); + if (!xrusb->ctrlurb) { + dev_err(&intf->dev, "out of memory (ctrlurb kmalloc)\n"); + goto alloc_fail5; + } + for (i = 0; i < num_rx_buf; i++) { + struct xrusb_rb *rb = &(xrusb->read_buffers[i]); + struct urb *urb; + + rb->base = usb_alloc_coherent(xrusb->dev, readsize, GFP_KERNEL, + &rb->dma); + if (!rb->base) { + dev_err(&intf->dev, "out of memory "); + dev_err(&intf->dev, "(read bufs usb_alloc_coherent)\n"); + goto alloc_fail6; + } + rb->index = i; + rb->instance = xrusb; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + dev_err(&intf->dev, "out of memory "); + dev_err(&intf->dev, "(read bufs usb_alloc_urb)\n"); + goto alloc_fail6; + } + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb->transfer_dma = rb->dma; + if (xrusb->is_int_ep) { + usb_fill_int_urb(urb, xrusb->dev, + xrusb->rx_endpoint, + rb->base, + xrusb->readsize, + xrusb_read_bulk_callback, rb, + xrusb->bInterval); + } else { + usb_fill_bulk_urb(urb, xrusb->dev, + xrusb->rx_endpoint, + rb->base, + xrusb->readsize, + xrusb_read_bulk_callback, rb); + } + + xrusb->read_urbs[i] = urb; + __set_bit(i, &xrusb->read_urbs_free); + } + for (i = 0; i < XRUSB_NW; i++) { + struct xrusb_wb *snd = &(xrusb->wb[i]); + + snd->urb = usb_alloc_urb(0, GFP_KERNEL); + if (snd->urb == NULL) { + dev_err(&intf->dev, "out of memory "); + dev_err(&intf->dev, "(write urbs usb_alloc_urb)\n"); + goto alloc_fail7; + } + + if (usb_endpoint_xfer_int(epwrite)) + usb_fill_int_urb(snd->urb, + usb_dev, + usb_sndintpipe(usb_dev, + epwrite->bEndpointAddress), + NULL, + xrusb->writesize, + xrusb_write_bulk, + snd, + epwrite->bInterval); + else + usb_fill_bulk_urb(snd->urb, + usb_dev, + usb_sndbulkpipe(usb_dev, + epwrite->bEndpointAddress), + NULL, + xrusb->writesize, + xrusb_write_bulk, + snd); + snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + snd->instance = xrusb; + } + + usb_set_intfdata(intf, xrusb); + + i = device_create_file(&intf->dev, &dev_attr_bmCapabilities); + if (i < 0) + goto alloc_fail7; + + usb_fill_int_urb(xrusb->ctrlurb, usb_dev, + usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress), + xrusb->ctrl_buffer, ctrlsize, xrusb_ctrl_irq, xrusb, + /* works around buggy devices */ + epctrl->bInterval ? epctrl->bInterval : 0xff); + xrusb->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + xrusb->ctrlurb->transfer_dma = xrusb->ctrl_dma; + + //dev_info(&intf->dev, "ttyXRUSB%d: USB XRUSB device\n", minor); + + xrusb_reg_init(xrusb); + + xrusb_set_control(xrusb, xrusb->ctrlout); + + xrusb->line.dwDTERate = cpu_to_le32(9600); + xrusb->line.bDataBits = 8; + xrusb_set_line(xrusb, &xrusb->line); + + usb_driver_claim_interface(&xrusb_driver, data_interface, xrusb); + usb_set_intfdata(data_interface, xrusb); + + usb_get_intf(control_interface); + + tty_dev = tty_port_register_device(&xrusb->port, + xrusb_tty_driver, minor, &control_interface->dev); + + if (IS_ERR(tty_dev)) { + rv = PTR_ERR(tty_dev); + goto alloc_fail8; + } + + return 0; + +alloc_fail8: + device_remove_file(&xrusb->control->dev, &dev_attr_bmCapabilities); +alloc_fail7: + usb_set_intfdata(intf, NULL); + for (i = 0; i < XRUSB_NW; i++) + usb_free_urb(xrusb->wb[i].urb); +alloc_fail6: + for (i = 0; i < num_rx_buf; i++) + usb_free_urb(xrusb->read_urbs[i]); + xrusb_read_buffers_free(xrusb); + usb_free_urb(xrusb->ctrlurb); +alloc_fail5: + xrusb_write_buffers_free(xrusb); +alloc_fail4: + usb_free_coherent(usb_dev, ctrlsize, + xrusb->ctrl_buffer, xrusb->ctrl_dma); +alloc_fail2: + xrusb_release_minor(xrusb); + kfree(xrusb); +alloc_fail: + return rv; +} + +static void stop_data_traffic(struct xrusb *xrusb) +{ + int i; + + dev_dbg(&xrusb->control->dev, "%s\n", __func__); + + usb_kill_urb(xrusb->ctrlurb); + for (i = 0; i < XRUSB_NW; i++) + usb_kill_urb(xrusb->wb[i].urb); + for (i = 0; i < xrusb->rx_buflimit; i++) + usb_kill_urb(xrusb->read_urbs[i]); + + cancel_work_sync(&xrusb->work); +} + +static void xrusb_disconnect(struct usb_interface *intf) +{ + struct xrusb *xrusb = usb_get_intfdata(intf); + struct tty_struct *tty; + + /* sibling interface is already cleaning up */ + if (!xrusb) + return; + + mutex_lock(&xrusb->mutex); + xrusb->disconnected = true; + + device_remove_file(&xrusb->control->dev, + &dev_attr_bmCapabilities); + usb_set_intfdata(xrusb->control, NULL); + usb_set_intfdata(xrusb->data, NULL); + mutex_unlock(&xrusb->mutex); + + tty = tty_port_tty_get(&xrusb->port); + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } + + stop_data_traffic(xrusb); + + tty_unregister_device(xrusb_tty_driver, xrusb->minor); + + xrusb_write_buffers_free(xrusb); + usb_free_coherent(xrusb->dev, xrusb->ctrlsize, + xrusb->ctrl_buffer, xrusb->ctrl_dma); + xrusb_read_buffers_free(xrusb); + + if (!xrusb->combined_interfaces) + usb_driver_release_interface(&xrusb_driver, + intf == xrusb->control ? + xrusb->data : xrusb->control); + + tty_port_put(&xrusb->port); +} + +#ifdef CONFIG_PM +static int xrusb_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct xrusb *xrusb = usb_get_intfdata(intf); + int cnt; + + if (PMSG_IS_AUTO(message)) { + int b; + + spin_lock_irq(&xrusb->write_lock); + b = xrusb->transmitting; + spin_unlock_irq(&xrusb->write_lock); + if (b) + return -EBUSY; + } + + spin_lock_irq(&xrusb->read_lock); + spin_lock(&xrusb->write_lock); + cnt = xrusb->susp_count++; + spin_unlock(&xrusb->write_lock); + spin_unlock_irq(&xrusb->read_lock); + + if (cnt) + return 0; + + if (test_bit(ASYNCB_INITIALIZED, &xrusb->port.flags)) + stop_data_traffic(xrusb); + + return 0; +} + +static int xrusb_resume(struct usb_interface *intf) +{ + struct xrusb *xrusb = usb_get_intfdata(intf); + struct xrusb_wb *wb; + int rv = 0; + int cnt; + + xrusb_reg_init(xrusb); + + spin_lock_irq(&xrusb->read_lock); + xrusb->susp_count -= 1; + cnt = xrusb->susp_count; + spin_unlock_irq(&xrusb->read_lock); + + if (cnt) + return 0; + + if (test_bit(ASYNCB_INITIALIZED, &xrusb->port.flags)) { + rv = usb_submit_urb(xrusb->ctrlurb, GFP_NOIO); + + spin_lock_irq(&xrusb->write_lock); + if (xrusb->delayed_wb) { + wb = xrusb->delayed_wb; + xrusb->delayed_wb = NULL; + spin_unlock_irq(&xrusb->write_lock); + xrusb_start_wb(xrusb, wb); + } else { + spin_unlock_irq(&xrusb->write_lock); + } + + /* + * delayed error checking because we must + * do the write path at all cost + */ + if (rv < 0) + goto err_out; + + rv = xrusb_submit_read_urbs(xrusb, GFP_NOIO); + } + +err_out: + return rv; +} + +static int xrusb_reset_resume(struct usb_interface *intf) +{ + struct xrusb *xrusb = usb_get_intfdata(intf); + + if (test_bit(ASYNCB_INITIALIZED, &xrusb->port.flags)) + tty_port_tty_hangup(&xrusb->port, false); + return xrusb_resume(intf); +} + +#endif /* CONFIG_PM */ + +/* + * USB driver structure. + */ +static const struct usb_device_id xrusb_ids[] = { + { USB_DEVICE(0x04e2, 0x1410)}, + { USB_DEVICE(0x04e2, 0x1411)}, + { USB_DEVICE(0x04e2, 0x1412)}, + { USB_DEVICE(0x04e2, 0x1414)}, + { USB_DEVICE(0x04e2, 0x1420)}, + { USB_DEVICE(0x04e2, 0x1422)}, + { USB_DEVICE(0x04e2, 0x1424)}, + { USB_DEVICE(0x04e2, 0x1400)}, // XR2280x ch A + { USB_DEVICE(0x04e2, 0x1401)}, // XR2280x ch B + { USB_DEVICE(0x04e2, 0x1402)}, // XR2280x ch C + { USB_DEVICE(0x04e2, 0x1403)}, // XR2280x ch D + { } +}; + +MODULE_DEVICE_TABLE(usb, xrusb_ids); + +static struct usb_driver xrusb_driver = { + .name = "xrusb_serial", + .probe = xrusb_probe, + .disconnect = xrusb_disconnect, +#ifdef CONFIG_PM + .suspend = xrusb_suspend, + .resume = xrusb_resume, + .reset_resume = xrusb_reset_resume, +#endif + .id_table = xrusb_ids, +#ifdef CONFIG_PM + .supports_autosuspend = 1, +#endif + .disable_hub_initiated_lpm = 1, +}; + +/* + * TTY driver structures. + */ + +static const struct tty_operations xrusb_ops = { + .install = xrusb_tty_install, + .open = xrusb_tty_open, + .close = xrusb_tty_close, + .cleanup = xrusb_tty_cleanup, + .hangup = xrusb_tty_hangup, + .write = xrusb_tty_write, + .write_room = xrusb_tty_write_room, + .ioctl = xrusb_tty_ioctl, + .throttle = xrusb_tty_throttle, + .unthrottle = xrusb_tty_unthrottle, + .chars_in_buffer = xrusb_tty_chars_in_buffer, + .break_ctl = xrusb_tty_break_ctl, + .set_termios = xrusb_tty_set_termios, + .tiocmget = xrusb_tty_tiocmget, + .tiocmset = xrusb_tty_tiocmset, +}; + +/* + * Init / exit. + */ + +static int __init xrusb_init(void) +{ + int rv; + + xrusb_tty_driver = alloc_tty_driver(XRUSB_TTY_MINORS); + if (!xrusb_tty_driver) + return -ENOMEM; + xrusb_tty_driver->driver_name = "xrusb", + xrusb_tty_driver->name = "ttyXRUSB", + xrusb_tty_driver->major = XRUSB_TTY_MAJOR, + xrusb_tty_driver->minor_start = 0, + xrusb_tty_driver->type = TTY_DRIVER_TYPE_SERIAL, + xrusb_tty_driver->subtype = SERIAL_TYPE_NORMAL, + xrusb_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + xrusb_tty_driver->init_termios = tty_std_termios; + xrusb_tty_driver->init_termios.c_cflag = B9600 | CS8 | + CREAD | HUPCL | CLOCAL; + tty_set_operations(xrusb_tty_driver, &xrusb_ops); + + rv = tty_register_driver(xrusb_tty_driver); + if (rv) { + put_tty_driver(xrusb_tty_driver); + return rv; + } + + rv = usb_register(&xrusb_driver); + if (rv) { + tty_unregister_driver(xrusb_tty_driver); + put_tty_driver(xrusb_tty_driver); + return rv; + } + + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n"); + + return 0; +} + +static void __exit xrusb_exit(void) +{ + usb_deregister(&xrusb_driver); + tty_unregister_driver(xrusb_tty_driver); + put_tty_driver(xrusb_tty_driver); +} + +module_init(xrusb_init); +module_exit(xrusb_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_CHARDEV_MAJOR(XRUSB_TTY_MAJOR); diff --git a/drivers/usb/serial/xrusb_serial.h b/drivers/usb/serial/xrusb_serial.h new file mode 100644 index 000000000000..c4b214b3e576 --- /dev/null +++ b/drivers/usb/serial/xrusb_serial.h @@ -0,0 +1,448 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Includes for xrusb_serial.c + */ + +/* + * CMSPAR, some architectures can't have space and mark parity. + */ + +#ifndef CMSPAR +#define CMSPAR 0 +#endif + +/* + * Major and minor numbers. + */ + +#define XRUSB_TTY_MAJOR 266 +#define XRUSB_TTY_MINORS 256 + +/* + * Requests. + */ + +#define USB_RT_XRUSB (USB_TYPE_CLASS | USB_RECIP_INTERFACE) + +/* + * Output control lines. + */ + +#define XRUSB_CTRL_DTR 0x01 +#define XRUSB_CTRL_RTS 0x02 + +/* + * Input control lines and line errors. + */ + +#define XRUSB_CTRL_DCD 0x01 +#define XRUSB_CTRL_DSR 0x02 +#define XRUSB_CTRL_BRK 0x04 +#define XRUSB_CTRL_RI 0x08 + +#define XRUSB_CTRL_FRAMING 0x10 +#define XRUSB_CTRL_PARITY 0x20 +#define XRUSB_CTRL_OVERRUN 0x40 + +/* + * Internal driver structures. + */ + +/* + * The only reason to have several buffers is to accommodate assumptions + * in line disciplines. They ask for empty space amount, receive our URB size, + * and proceed to issue several 1-character writes, assuming they will fit. + * The very first write takes a complete URB. Fortunately, this only happens + * when processing onlcr, so we only need 2 buffers. These values must be + * powers of 2. + */ + +#define XRUSB_NW 16 +#define XRUSB_NR 16 + +#define WIDE_MODE_PARITY 0x1 +#define WIDE_MODE_BREAK 0x2 +#define WIDE_MODE_FRAME 0x4 +#define WIDE_MODE_OVERRUN 0x8 + +struct xrusb_wb { + unsigned char *buf; + dma_addr_t dmah; + int len; + int use; + struct urb *urb; + struct xrusb *instance; +}; + +struct xrusb_rb { + int size; + unsigned char *base; + dma_addr_t dma; + int index; + struct xrusb *instance; +}; + +struct reg_addr_map { + unsigned int uart_enable; + unsigned int uart_format; + unsigned int uart_flow; + unsigned int uart_xon_char; + unsigned int uart_xoff_char; + unsigned int uart_tx_break; + unsigned int uart_rs485_delay; + unsigned int uart_gpio_mode; + unsigned int uart_gpio_dir; + unsigned int uart_gpio_set; + unsigned int uart_gpio_clr; + unsigned int uart_gpio_status; + unsigned int uart_gpio_int_mask; + unsigned int uart_customized_int; + unsigned int uart_gpio_open_drain; + unsigned int uart_gpio_pull_up_enable; + unsigned int uart_gpio_pull_down_enable; + unsigned int uart_loopback; + unsigned int uart_low_latency; + unsigned int uart_custom_driver; +}; + +struct xrusb_setting { + unsigned int txcvr_mode1_pin; + unsigned int txcvr_mode0_pin; + unsigned int txcvr_dir_pin; + unsigned int txcvr_term_pin; + unsigned int txcvr_slew_pin; + unsigned int txcvr_mode; + unsigned int term_mode; + unsigned int slew_mode; + unsigned int low_latency_mode; +}; + +struct xrusb { + struct usb_device *dev; /* the corresponding usb device */ + struct usb_interface *control; /* control interface */ + struct usb_interface *data; /* data interface */ + struct tty_port port; /* our tty port data */ + struct urb *ctrlurb; /* urbs */ + u8 *ctrl_buffer; /* buffers of urbs */ + dma_addr_t ctrl_dma; /* dma handles of buffers */ + struct xrusb_wb wb[XRUSB_NW]; + unsigned long read_urbs_free; + struct urb *read_urbs[XRUSB_NR]; + struct xrusb_rb read_buffers[XRUSB_NR]; + struct xrusb_wb *putbuffer; + int rx_buflimit; + int rx_endpoint; + spinlock_t read_lock; + int transmitting; + spinlock_t write_lock; + struct mutex mutex; + bool disconnected; + struct usb_cdc_line_coding line; /* bits, stop, parity */ + struct work_struct work; /* work queue entry for line discipline waking up */ + unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ + unsigned int ctrlout; /* output control lines (DTR, RTS) */ + unsigned int writesize; /* max packet size for the output bulk endpoint */ + unsigned int readsize, ctrlsize; /* buffer sizes for freeing */ + unsigned int minor; /* xrusb minor number */ + unsigned char clocal; /* termios CLOCAL */ + unsigned int ctrl_caps; /* control capabilities from the class specific header */ + unsigned int susp_count; /* number of suspended interfaces */ + unsigned int combined_interfaces:1; /* control and data collapsed */ + unsigned int is_int_ep:1; /* interrupt endpoints contrary to spec used */ + unsigned int throttled:1; /* actually throttled */ + unsigned int throttle_req:1; /* throttle requested */ + u8 bInterval; + + struct xrusb_wb *delayed_wb; /* write queued for a device about to be woken */ + unsigned int channel; + int wide_mode; /* USB: wide mode, TTY: flags per character */ + int have_extra_byte; + int extra_byte; + + unsigned short DeviceVendor; + unsigned short DeviceProduct; + + int found_smbios_caracalla_config; /* Modes pre-programmed in BIOS for Caracalla board */ + unsigned char channel_config; /* Mode setting for each channel */ + + struct reg_addr_map reg_map; + struct xrusb_setting port_setting; + unsigned char have_txcvr_config; +}; + +/* + * SMBIOS code snippets below borrowed from + * https://sourceforge.net/projects/smbios/ + * + */ + +#ifndef __SM_BIOS_H__ +#define __SM_BIOS_H__ + +#ifdef _EN_DEBUG_ +#define SM_BIOS_DEBUG(fmt, args...) printk(KERN_DEBUG "smbios: " fmt, ## args) +#else +#define SM_BIOS_DEBUG(fmt, args...) /* not debugging: nothing */ +#endif + +/* + * Magic numbers + */ + +/** start address of BIOS segment to scanned for SM-BIOS and DMI-BIOS */ +#define BIOS_START_ADDRESS 0xF0000 +/** length of the scanned BIOS area for SM-BIOS and DMI-BIOS */ +#define BIOS_MAP_LENGTH 0x10000 +/** magic 4 bytes to identify SM-BIOS entry point, paragraph boundary */ +#define SMBIOS_MAGIC_DWORD 0x5F4D535F /* anchor string "_SM_" */ +/** identifier for SM-BIOS structures within SM-BIOS entry point */ +#define DMI_STRING "_DMI_" + +/* + * Structures + */ + +/** SM-BIOS entry point structure + * the SMBIOS Entry Point structure described below can be located by + * application software by searching for the anchor string on paragraph + * (16 byte) boundaries within the physical memory address range 000F0000h to + * 000FFFFFh. This entry point encapsulates an intermediate anchor string + * that is used by some existing DMI browsers. + * + * @note While the SMBIOS Major and Minor Versions (offsets 06h and 07h) + * currently duplicate the information present in the SMBIOS BCD Revision + * (offset 1Dh), they provide a path for future growth in this specification. + * The BCD Revision, for example, provides only a single digit for each of + * the major and minor version numbers. + */ +struct smbios_entry_point_struct { + /** "_SM_", specified as four ASCII characters (5F 53 4D 5F) */ + __u32 anchor_string; + /** checksum of the Entry Point Structure (EPS). This value, when added to + * all other bytes in the EPS, will result in the value 00h (using 8 bit + * addition calculations). Values in the EPS are summed starting at offset + * 00h, for Entry Point Length bytes. + */ + __u8 entry_point_checksum; + /** Length of the Entry Point Structure, starting with the Anchor String + * field, in bytes, currently 1Fh. + */ + __u8 entry_point_length; + /** identifies the major version of this specification implemented in + * the table structures, e.g. the value will be 0Ah for revision 10.22 + * and 02h for revision 2.1 + */ + __u8 major_version; + /** identifies the minor version of this specification implemented in + * the table structures, e.g. the value will be 16h for revision 10.22 + * and 01h for revision 2.1 + */ + __u8 minor_version; + /** size of the largest SMBIOS structure, in bytes, and encompasses the + * structure's formatted area and text strings. This is the value returned + * as StructureSize from the Plug-n-Play 'Get SMBIOS Information' function + */ + __u16 max_struct_size; + /** identifies the EPS revision implemented in this structure and identifies + * the formatting of offsets 0Bh to 0Fh, one of: + * 00h Entry Point based on SMBIOS 2.1 definition; formatted area is + * reserved and set to all 00h. + * 01h-FFh reserved for assignment via this specification + */ + __u8 revision; + /** the value present in the Entry Point Revision field defines the + * interpretation to be placed upon these5 bytes. + */ + __u8 formated_area[5]; + /** "_DMI_", specified as five ASCII characters (5F 44 4D 49 5F) */ + __u8 intermediate_string[5]; + /** checksum of the Intermediate Entry Point Structure (IEPS). This value, + * when added to all other bytes in the IEPS, will result in the value 00h + * (using 8 bit addition calculations). Values in the IEPS are summed + * starting at offset 10h, for 0Fh bytes + */ + __u8 intermediate_checksum; + /** the 32 bit physical starting address of the read-only SMBIOS Structure + * Table, that can start at any 32 bit address. This area contains all of the + * SMBIOS structures fully packed together. These structures can then be + * parsed to produce exactly the same format as that returned from a 'Get + * SMBIOS Structure' function call. + */ + __u16 struct_table_length; + __u32 struct_table_address; + __u16 no_of_structures; + __u8 bcd_revision; +} __attribute__ ((packed)); + +/** SM-BIOS structure header */ +struct smbios_struct { + __u8 type; + __u8 length; + __u16 handle; + __u8 subtype; +/* ... other fields are structure dependend ... */ +} __attribute__ ((packed)); + +extern void *smbios_structures_base; /* base of SMBIOS raw structures */ +extern unsigned char smbios_types_with_subtypes[]; +extern char smbios_version_string[32]; /* e.g. V2.31 */ + +/* + * Functions + */ + +/* for the description see the implementation file */ +struct smbios_entry_point_struct *smbios_find_entry_point(void *base); +struct dmibios_entry_point_struct *dmibios_find_entry_point(void *base); +unsigned char smbios_check_entry_point(void *addr); +int smbios_type_has_subtype(unsigned char type); +int smbios_get_struct_length(struct smbios_struct *struct_ptr); +int dmibios_get_struct_length(struct smbios_struct *struct_ptr); +unsigned int smbios_get_readable_name_ext(char *readable_name, struct smbios_struct *struct_ptr); +unsigned int smbios_get_readable_name(char *readable_name, struct smbios_struct *struct_ptr); +int smbios_check_if_have_exar_config(unsigned char *config0, unsigned char *config1); + + +#endif /* __BIOS_H__ */ + +static void *smbios_base; +struct smbios_entry_point_struct *smbios_entry_point; /** SM-BIOS entry point structure */ +void *smbios_structures_base; + +#define CDC_DATA_INTERFACE_TYPE 0x0a + +/* constants describing various quirks and errors */ +#define NO_UNION_NORMAL 1 +#define SINGLE_RX_URB 2 +#define NO_CAP_LINE 4 +#define NOT_A_MODEM 8 +#define NO_DATA_INTERFACE 16 +#define IGNORE_DEVICE 32 + +/* USB Requests */ +#define XRUSB_GET_CHIP_ID 0xFF +#define XRUSB_SET_XR2280X 5 +#define XRUSB_GET_XR2280X 5 +#define XRUSB_SET_XR21B142X 0 +#define XRUSB_GET_XR21B142X 0 +#define XRUSB_SET_XR21V141X 0 +#define XRUSB_GET_XR21V141X 1 +#define XRUSB_SET_XR21B1411 0 +#define XRUSB_GET_XR21B1411 1 + +/* XR21V141x Baud Rate Generator Registers */ +#define XR21V141X_CLOCK_DIVISOR_0 0x004 +#define XR21V141X_CLOCK_DIVISOR_1 0x005 +#define XR21V141X_CLOCK_DIVISOR_2 0x006 +#define XR21V141X_TX_CLOCK_MASK_0 0x007 +#define XR21V141X_TX_CLOCK_MASK_1 0x008 +#define XR21V141X_RX_CLOCK_MASK_0 0x009 +#define XR21V141X_RX_CLOCK_MASK_1 0x00a + +/* XR21V141x UART Block Registers */ +#define XR21V141x_URM_REG_BLOCK 4 +#define XR21V141x_UART_CUSTOM_BLOCK 0x66 + +/* XR21V141x UART Manager Register values */ +#define XR21V141x_URM_FIFO_ENABLE_REG 0x10 +#define XR21V141x_URM_ENABLE_TX_FIFO 0x1 +#define XR21V141x_URM_ENABLE_RX_FIFO 0x2 +#define XR21V141x_URM_RX_FIFO_RESET 0x18 +#define XR21V141x_URM_TX_FIFO_RESET 0x1C + +#define UART_ENABLE_TX 1 +#define UART_ENABLE_RX 2 + +/* GPIOs */ +#define GPIO9_RXT 0x200 +#define GPIO8_TXT 0x100 +#define GPIO7_XEN 0x80 +#define GPIO6_CLK 0x40 +#define GPIO5_RTS 0x20 +#define GPIO4_CTS 0x10 +#define GPIO3_DTR 0x8 +#define GPIO2_DSR 0x4 +#define GPIO1_CD 0x2 +#define GPIO0_RI 0x1 + +/* Loopback Register Values */ + +#define LOOP_ENABLE 0x7 +#define XR21V141x_LOOP_ENABLE_A 0x40 +#define XR21V141x_LOOP_ENABLE_B 0x41 +#define XR21V141x_LOOP_ENABLE_C 0x42 +#define XR21V141x_LOOP_ENABLE_D 0x43 + +/* FLOW_MODE Register Values */ + +#define UART_FLOW_MODE_NONE 0x0 +#define UART_FLOW_MODE_HW 0x1 +#define UART_FLOW_MODE_SW 0x2 + +/* GPIO_MODE Register Values */ + +#define UART_GPIO_MODE_SEL_GPIO 0x0 +#define UART_GPIO_MODE_SEL_RTS_CTS 0x1 +#define UART_GPIO_MODE_DIR_RTS_HI 0xb +#define UART_GPIO_MODE_DIR_RTS_LOW 0x3 + +/* Additional GPIO_MODE Register Values - XR21B142x */ + +#define UART_GPIO_MODE_DIR_XEN_HI 0x38 +#define UART_GPIO_MODE_DIR_XEN_LOW 0x30 +#define UART_GPIO_MODE_RXT 0x200 +#define UART_GPIO_MODE_TXT 0x100 + +/* Randomly located registers */ +#define XR21V141X_WIDE_MODE_REG 3 + +#define XR21B1411_UART_ENABLE 0xC00 +#define XR21B1411_WIDE_MODE_REG 0xD02 + +#define XR21B142x_UART_ENABLE 0x00 +#define XR21B142X_TX_WIDE_MODE_REG 0x42 +#define XR21B142X_RX_WIDE_MODE_REG 0x45 + +#define XR2280X_TX_WIDE_MODE_REG 0x62 +#define XR2280X_RX_WIDE_MODE_REG 0x65 + +#define XR2280x_FUNC_MGR_OFFSET 0x40 + +#define XRUSB_IOC_MAGIC 'v' + +#define XRUSB_ENABLE _IO(XRUSB_IOC_MAGIC, 3) +#define XRUSB_SET_BAUD _IO(XRUSB_IOC_MAGIC, 4) +#define XRUSB_LOOPBACK _IO(XRUSB_IOC_MAGIC, 5) + +#define XRUSB_SET_GPIO_MODE _IO(XRUSB_IOC_MAGIC, 7) +#define XRUSB_GET_GPIO_MODE _IO(XRUSB_IOC_MAGIC, 8) +#define XRUSB_SET_GPIO_STATE _IO(XRUSB_IOC_MAGIC, 9) +#define XRUSB_GET_GPIO_STATE _IO(XRUSB_IOC_MAGIC, 10) +#define XRUSB_CLEAR_GPIO_STATE _IO(XRUSB_IOC_MAGIC, 11) +#define XRUSB_SET_GPIO_INT_MASK _IO(XRUSB_IOC_MAGIC, 12) +#define XRUSB_GET_GPIO_INT_MASK _IO(XRUSB_IOC_MAGIC, 13) +#define XRUSB_SET_GPIO_PULL_UP _IO(XRUSB_IOC_MAGIC, 14) +#define XRUSB_GET_GPIO_PULL_UP _IO(XRUSB_IOC_MAGIC, 15) +#define XRUSB_SET_GPIO_PULL_DOWN _IO(XRUSB_IOC_MAGIC, 16) +#define XRUSB_GET_GPIO_PULL_DOWN _IO(XRUSB_IOC_MAGIC, 17) +#define XRUSB_SET_GPIO_OPEN_DRAIN _IO(XRUSB_IOC_MAGIC, 18) +#define XRUSB_GET_GPIO_OPEN_DRAIN _IO(XRUSB_IOC_MAGIC, 19) + +#define XRUSB_WIDE_MODE _IO(XRUSB_IOC_MAGIC, 20) +#define XRUSB_MULTIDROP_MODE _IO(XRUSB_IOC_MAGIC, 21) +#define XRUSB_LOW_LATENCY_MODE _IO(XRUSB_IOC_MAGIC, 22) +#define XRUSB_SET_RS485_PIN _IO(XRUSB_IOC_MAGIC, 23) +#define XRUSB_RS485_PIN_OPTION _IO(XRUSB_IOC_MAGIC, 25) +#define XRUSB_RS485_DELAY _IO(XRUSB_IOC_MAGIC, 26) +#define XRUSB_ENABLE_RS485_LOW _IO(XRUSB_IOC_MAGIC, 27) + +#define XRUSB_ENABLE_DTRDSR_FLOW _IO(XRUSB_IOC_MAGIC, 28) +#define XRUSB_DISABLE_FLOW_CONTROL _IO(XRUSB_IOC_MAGIC, 29) +#define XRUSB_GET_FLOW_CONTROL _IO(XRUSB_IOC_MAGIC, 30) +#define XRUSB_SET_GPIO_DIR _IO(XRUSB_IOC_MAGIC, 31) +#define XRUSB_GET_GPIO_DIR _IO(XRUSB_IOC_MAGIC, 32) +#define XRUSB_SET_TXCVR_MODE _IO(XRUSB_IOC_MAGIC, 33) +#define XRUSB_GET_TXCVR_MODE _IO(XRUSB_IOC_MAGIC, 34) +#define XRUSB_SET_TERM_MODE _IO(XRUSB_IOC_MAGIC, 35) +#define XRUSB_SET_SLEW_MODE _IO(XRUSB_IOC_MAGIC, 36) +#define XRUSB_SET_GPIO_MAP_TABLE _IO(XRUSB_IOC_MAGIC, 37) -- 2.14.1 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html