| /* |
| * USB Moxa UPORT 11x0 Serial Driver |
| * |
| * Copyright (C) 2007 MOXA Technologies Co., Ltd. |
| * Copyright (C) 2015 Mathieu Othacehe <m.othacehe@gmail.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * |
| * Supports the following Moxa USB to serial converters: |
| * UPort 1110, 1 port RS-232 USB to Serial Hub. |
| * UPort 1130, 1 port RS-422/485 USB to Serial Hub. |
| * UPort 1130I, 1 port RS-422/485 USB to Serial Hub with isolation |
| * protection. |
| * UPort 1150, 1 port RS-232/422/485 USB to Serial Hub. |
| * UPort 1150I, 1 port RS-232/422/485 USB to Serial Hub with isolation |
| * protection. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/firmware.h> |
| #include <linux/jiffies.h> |
| #include <linux/serial.h> |
| #include <linux/serial_reg.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/mutex.h> |
| #include <linux/tty.h> |
| #include <linux/tty_driver.h> |
| #include <linux/tty_flip.h> |
| #include <linux/uaccess.h> |
| #include <linux/usb.h> |
| #include <linux/usb/serial.h> |
| |
| /* Vendor and product ids */ |
| #define MXU1_VENDOR_ID 0x110a |
| #define MXU1_1110_PRODUCT_ID 0x1110 |
| #define MXU1_1130_PRODUCT_ID 0x1130 |
| #define MXU1_1150_PRODUCT_ID 0x1150 |
| #define MXU1_1151_PRODUCT_ID 0x1151 |
| #define MXU1_1131_PRODUCT_ID 0x1131 |
| |
| /* Commands */ |
| #define MXU1_GET_VERSION 0x01 |
| #define MXU1_GET_PORT_STATUS 0x02 |
| #define MXU1_GET_PORT_DEV_INFO 0x03 |
| #define MXU1_GET_CONFIG 0x04 |
| #define MXU1_SET_CONFIG 0x05 |
| #define MXU1_OPEN_PORT 0x06 |
| #define MXU1_CLOSE_PORT 0x07 |
| #define MXU1_START_PORT 0x08 |
| #define MXU1_STOP_PORT 0x09 |
| #define MXU1_TEST_PORT 0x0A |
| #define MXU1_PURGE_PORT 0x0B |
| #define MXU1_RESET_EXT_DEVICE 0x0C |
| #define MXU1_GET_OUTQUEUE 0x0D |
| #define MXU1_WRITE_DATA 0x80 |
| #define MXU1_READ_DATA 0x81 |
| #define MXU1_REQ_TYPE_CLASS 0x82 |
| |
| /* Module identifiers */ |
| #define MXU1_I2C_PORT 0x01 |
| #define MXU1_IEEE1284_PORT 0x02 |
| #define MXU1_UART1_PORT 0x03 |
| #define MXU1_UART2_PORT 0x04 |
| #define MXU1_RAM_PORT 0x05 |
| |
| /* Modem status */ |
| #define MXU1_MSR_DELTA_CTS 0x01 |
| #define MXU1_MSR_DELTA_DSR 0x02 |
| #define MXU1_MSR_DELTA_RI 0x04 |
| #define MXU1_MSR_DELTA_CD 0x08 |
| #define MXU1_MSR_CTS 0x10 |
| #define MXU1_MSR_DSR 0x20 |
| #define MXU1_MSR_RI 0x40 |
| #define MXU1_MSR_CD 0x80 |
| #define MXU1_MSR_DELTA_MASK 0x0F |
| #define MXU1_MSR_MASK 0xF0 |
| |
| /* Line status */ |
| #define MXU1_LSR_OVERRUN_ERROR 0x01 |
| #define MXU1_LSR_PARITY_ERROR 0x02 |
| #define MXU1_LSR_FRAMING_ERROR 0x04 |
| #define MXU1_LSR_BREAK 0x08 |
| #define MXU1_LSR_ERROR 0x0F |
| #define MXU1_LSR_RX_FULL 0x10 |
| #define MXU1_LSR_TX_EMPTY 0x20 |
| |
| /* Modem control */ |
| #define MXU1_MCR_LOOP 0x04 |
| #define MXU1_MCR_DTR 0x10 |
| #define MXU1_MCR_RTS 0x20 |
| |
| /* Mask settings */ |
| #define MXU1_UART_ENABLE_RTS_IN 0x0001 |
| #define MXU1_UART_DISABLE_RTS 0x0002 |
| #define MXU1_UART_ENABLE_PARITY_CHECKING 0x0008 |
| #define MXU1_UART_ENABLE_DSR_OUT 0x0010 |
| #define MXU1_UART_ENABLE_CTS_OUT 0x0020 |
| #define MXU1_UART_ENABLE_X_OUT 0x0040 |
| #define MXU1_UART_ENABLE_XA_OUT 0x0080 |
| #define MXU1_UART_ENABLE_X_IN 0x0100 |
| #define MXU1_UART_ENABLE_DTR_IN 0x0800 |
| #define MXU1_UART_DISABLE_DTR 0x1000 |
| #define MXU1_UART_ENABLE_MS_INTS 0x2000 |
| #define MXU1_UART_ENABLE_AUTO_START_DMA 0x4000 |
| #define MXU1_UART_SEND_BREAK_SIGNAL 0x8000 |
| |
| /* Parity */ |
| #define MXU1_UART_NO_PARITY 0x00 |
| #define MXU1_UART_ODD_PARITY 0x01 |
| #define MXU1_UART_EVEN_PARITY 0x02 |
| #define MXU1_UART_MARK_PARITY 0x03 |
| #define MXU1_UART_SPACE_PARITY 0x04 |
| |
| /* Stop bits */ |
| #define MXU1_UART_1_STOP_BITS 0x00 |
| #define MXU1_UART_1_5_STOP_BITS 0x01 |
| #define MXU1_UART_2_STOP_BITS 0x02 |
| |
| /* Bits per character */ |
| #define MXU1_UART_5_DATA_BITS 0x00 |
| #define MXU1_UART_6_DATA_BITS 0x01 |
| #define MXU1_UART_7_DATA_BITS 0x02 |
| #define MXU1_UART_8_DATA_BITS 0x03 |
| |
| /* Operation modes */ |
| #define MXU1_UART_232 0x00 |
| #define MXU1_UART_485_RECEIVER_DISABLED 0x01 |
| #define MXU1_UART_485_RECEIVER_ENABLED 0x02 |
| |
| /* Pipe transfer mode and timeout */ |
| #define MXU1_PIPE_MODE_CONTINUOUS 0x01 |
| #define MXU1_PIPE_MODE_MASK 0x03 |
| #define MXU1_PIPE_TIMEOUT_MASK 0x7C |
| #define MXU1_PIPE_TIMEOUT_ENABLE 0x80 |
| |
| /* Config struct */ |
| struct mxu1_uart_config { |
| __be16 wBaudRate; |
| __be16 wFlags; |
| u8 bDataBits; |
| u8 bParity; |
| u8 bStopBits; |
| char cXon; |
| char cXoff; |
| u8 bUartMode; |
| } __packed; |
| |
| /* Purge modes */ |
| #define MXU1_PURGE_OUTPUT 0x00 |
| #define MXU1_PURGE_INPUT 0x80 |
| |
| /* Read/Write data */ |
| #define MXU1_RW_DATA_ADDR_SFR 0x10 |
| #define MXU1_RW_DATA_ADDR_IDATA 0x20 |
| #define MXU1_RW_DATA_ADDR_XDATA 0x30 |
| #define MXU1_RW_DATA_ADDR_CODE 0x40 |
| #define MXU1_RW_DATA_ADDR_GPIO 0x50 |
| #define MXU1_RW_DATA_ADDR_I2C 0x60 |
| #define MXU1_RW_DATA_ADDR_FLASH 0x70 |
| #define MXU1_RW_DATA_ADDR_DSP 0x80 |
| |
| #define MXU1_RW_DATA_UNSPECIFIED 0x00 |
| #define MXU1_RW_DATA_BYTE 0x01 |
| #define MXU1_RW_DATA_WORD 0x02 |
| #define MXU1_RW_DATA_DOUBLE_WORD 0x04 |
| |
| struct mxu1_write_data_bytes { |
| u8 bAddrType; |
| u8 bDataType; |
| u8 bDataCounter; |
| __be16 wBaseAddrHi; |
| __be16 wBaseAddrLo; |
| u8 bData[0]; |
| } __packed; |
| |
| /* Interrupt codes */ |
| #define MXU1_CODE_HARDWARE_ERROR 0xFF |
| #define MXU1_CODE_DATA_ERROR 0x03 |
| #define MXU1_CODE_MODEM_STATUS 0x04 |
| |
| static inline int mxu1_get_func_from_code(unsigned char code) |
| { |
| return code & 0x0f; |
| } |
| |
| /* Download firmware max packet size */ |
| #define MXU1_DOWNLOAD_MAX_PACKET_SIZE 64 |
| |
| /* Firmware image header */ |
| struct mxu1_firmware_header { |
| __le16 wLength; |
| u8 bCheckSum; |
| } __packed; |
| |
| #define MXU1_UART_BASE_ADDR 0xFFA0 |
| #define MXU1_UART_OFFSET_MCR 0x0004 |
| |
| #define MXU1_BAUD_BASE 923077 |
| |
| #define MXU1_TRANSFER_TIMEOUT 2 |
| #define MXU1_DOWNLOAD_TIMEOUT 1000 |
| #define MXU1_DEFAULT_CLOSING_WAIT 4000 /* in .01 secs */ |
| |
| struct mxu1_port { |
| u8 msr; |
| u8 mcr; |
| u8 uart_mode; |
| spinlock_t spinlock; /* Protects msr */ |
| struct mutex mutex; /* Protects mcr */ |
| bool send_break; |
| }; |
| |
| struct mxu1_device { |
| u16 mxd_model; |
| }; |
| |
| static const struct usb_device_id mxu1_idtable[] = { |
| { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1110_PRODUCT_ID) }, |
| { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1130_PRODUCT_ID) }, |
| { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1150_PRODUCT_ID) }, |
| { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1151_PRODUCT_ID) }, |
| { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1131_PRODUCT_ID) }, |
| { } |
| }; |
| |
| MODULE_DEVICE_TABLE(usb, mxu1_idtable); |
| |
| /* Write the given buffer out to the control pipe. */ |
| static int mxu1_send_ctrl_data_urb(struct usb_serial *serial, |
| u8 request, |
| u16 value, u16 index, |
| void *data, size_t size) |
| { |
| int status; |
| |
| status = usb_control_msg(serial->dev, |
| usb_sndctrlpipe(serial->dev, 0), |
| request, |
| (USB_DIR_OUT | USB_TYPE_VENDOR | |
| USB_RECIP_DEVICE), value, index, |
| data, size, |
| USB_CTRL_SET_TIMEOUT); |
| if (status < 0) { |
| dev_err(&serial->interface->dev, |
| "%s - usb_control_msg failed: %d\n", |
| __func__, status); |
| return status; |
| } |
| |
| if (status != size) { |
| dev_err(&serial->interface->dev, |
| "%s - short write (%d / %zd)\n", |
| __func__, status, size); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| /* Send a vendor request without any data */ |
| static int mxu1_send_ctrl_urb(struct usb_serial *serial, |
| u8 request, u16 value, u16 index) |
| { |
| return mxu1_send_ctrl_data_urb(serial, request, value, index, |
| NULL, 0); |
| } |
| |
| static int mxu1_download_firmware(struct usb_serial *serial, |
| const struct firmware *fw_p) |
| { |
| int status = 0; |
| int buffer_size; |
| int pos; |
| int len; |
| int done; |
| u8 cs = 0; |
| u8 *buffer; |
| struct usb_device *dev = serial->dev; |
| struct mxu1_firmware_header *header; |
| unsigned int pipe; |
| |
| pipe = usb_sndbulkpipe(dev, serial->port[0]->bulk_out_endpointAddress); |
| |
| buffer_size = fw_p->size + sizeof(*header); |
| buffer = kmalloc(buffer_size, GFP_KERNEL); |
| if (!buffer) |
| return -ENOMEM; |
| |
| memcpy(buffer, fw_p->data, fw_p->size); |
| memset(buffer + fw_p->size, 0xff, buffer_size - fw_p->size); |
| |
| for (pos = sizeof(*header); pos < buffer_size; pos++) |
| cs = (u8)(cs + buffer[pos]); |
| |
| header = (struct mxu1_firmware_header *)buffer; |
| header->wLength = cpu_to_le16(buffer_size - sizeof(*header)); |
| header->bCheckSum = cs; |
| |
| dev_dbg(&dev->dev, "%s - downloading firmware\n", __func__); |
| |
| for (pos = 0; pos < buffer_size; pos += done) { |
| len = min(buffer_size - pos, MXU1_DOWNLOAD_MAX_PACKET_SIZE); |
| |
| status = usb_bulk_msg(dev, pipe, buffer + pos, len, &done, |
| MXU1_DOWNLOAD_TIMEOUT); |
| if (status) |
| break; |
| } |
| |
| kfree(buffer); |
| |
| if (status) { |
| dev_err(&dev->dev, "failed to download firmware: %d\n", status); |
| return status; |
| } |
| |
| msleep_interruptible(100); |
| usb_reset_device(dev); |
| |
| dev_dbg(&dev->dev, "%s - download successful\n", __func__); |
| |
| return 0; |
| } |
| |
| static int mxu1_port_probe(struct usb_serial_port *port) |
| { |
| struct mxu1_port *mxport; |
| struct mxu1_device *mxdev; |
| |
| if (!port->interrupt_in_urb) { |
| dev_err(&port->dev, "no interrupt urb\n"); |
| return -ENODEV; |
| } |
| |
| mxport = kzalloc(sizeof(struct mxu1_port), GFP_KERNEL); |
| if (!mxport) |
| return -ENOMEM; |
| |
| spin_lock_init(&mxport->spinlock); |
| mutex_init(&mxport->mutex); |
| |
| mxdev = usb_get_serial_data(port->serial); |
| |
| switch (mxdev->mxd_model) { |
| case MXU1_1110_PRODUCT_ID: |
| case MXU1_1150_PRODUCT_ID: |
| case MXU1_1151_PRODUCT_ID: |
| mxport->uart_mode = MXU1_UART_232; |
| break; |
| case MXU1_1130_PRODUCT_ID: |
| case MXU1_1131_PRODUCT_ID: |
| mxport->uart_mode = MXU1_UART_485_RECEIVER_DISABLED; |
| break; |
| } |
| |
| usb_set_serial_port_data(port, mxport); |
| |
| port->port.closing_wait = |
| msecs_to_jiffies(MXU1_DEFAULT_CLOSING_WAIT * 10); |
| port->port.drain_delay = 1; |
| |
| return 0; |
| } |
| |
| static int mxu1_port_remove(struct usb_serial_port *port) |
| { |
| struct mxu1_port *mxport; |
| |
| mxport = usb_get_serial_port_data(port); |
| kfree(mxport); |
| |
| return 0; |
| } |
| |
| static int mxu1_startup(struct usb_serial *serial) |
| { |
| struct mxu1_device *mxdev; |
| struct usb_device *dev = serial->dev; |
| struct usb_host_interface *cur_altsetting; |
| char fw_name[32]; |
| const struct firmware *fw_p = NULL; |
| int err; |
| |
| dev_dbg(&serial->interface->dev, "%s - product 0x%04X, num configurations %d, configuration value %d\n", |
| __func__, le16_to_cpu(dev->descriptor.idProduct), |
| dev->descriptor.bNumConfigurations, |
| dev->actconfig->desc.bConfigurationValue); |
| |
| /* create device structure */ |
| mxdev = kzalloc(sizeof(struct mxu1_device), GFP_KERNEL); |
| if (!mxdev) |
| return -ENOMEM; |
| |
| usb_set_serial_data(serial, mxdev); |
| |
| mxdev->mxd_model = le16_to_cpu(dev->descriptor.idProduct); |
| |
| cur_altsetting = serial->interface->cur_altsetting; |
| |
| /* if we have only 1 configuration, download firmware */ |
| if (cur_altsetting->desc.bNumEndpoints == 1) { |
| |
| snprintf(fw_name, |
| sizeof(fw_name), |
| "moxa/moxa-%04x.fw", |
| mxdev->mxd_model); |
| |
| err = request_firmware(&fw_p, fw_name, &serial->interface->dev); |
| if (err) { |
| dev_err(&serial->interface->dev, "failed to request firmware: %d\n", |
| err); |
| goto err_free_mxdev; |
| } |
| |
| err = mxu1_download_firmware(serial, fw_p); |
| if (err) |
| goto err_release_firmware; |
| |
| /* device is being reset */ |
| err = -ENODEV; |
| goto err_release_firmware; |
| } |
| |
| return 0; |
| |
| err_release_firmware: |
| release_firmware(fw_p); |
| err_free_mxdev: |
| kfree(mxdev); |
| |
| return err; |
| } |
| |
| static void mxu1_release(struct usb_serial *serial) |
| { |
| struct mxu1_device *mxdev; |
| |
| mxdev = usb_get_serial_data(serial); |
| kfree(mxdev); |
| } |
| |
| static int mxu1_write_byte(struct usb_serial_port *port, u32 addr, |
| u8 mask, u8 byte) |
| { |
| int status; |
| size_t size; |
| struct mxu1_write_data_bytes *data; |
| |
| dev_dbg(&port->dev, "%s - addr 0x%08X, mask 0x%02X, byte 0x%02X\n", |
| __func__, addr, mask, byte); |
| |
| size = sizeof(struct mxu1_write_data_bytes) + 2; |
| data = kzalloc(size, GFP_KERNEL); |
| if (!data) |
| return -ENOMEM; |
| |
| data->bAddrType = MXU1_RW_DATA_ADDR_XDATA; |
| data->bDataType = MXU1_RW_DATA_BYTE; |
| data->bDataCounter = 1; |
| data->wBaseAddrHi = cpu_to_be16(addr >> 16); |
| data->wBaseAddrLo = cpu_to_be16(addr); |
| data->bData[0] = mask; |
| data->bData[1] = byte; |
| |
| status = mxu1_send_ctrl_data_urb(port->serial, MXU1_WRITE_DATA, 0, |
| MXU1_RAM_PORT, data, size); |
| if (status < 0) |
| dev_err(&port->dev, "%s - failed: %d\n", __func__, status); |
| |
| kfree(data); |
| |
| return status; |
| } |
| |
| static int mxu1_set_mcr(struct usb_serial_port *port, unsigned int mcr) |
| { |
| int status; |
| |
| status = mxu1_write_byte(port, |
| MXU1_UART_BASE_ADDR + MXU1_UART_OFFSET_MCR, |
| MXU1_MCR_RTS | MXU1_MCR_DTR | MXU1_MCR_LOOP, |
| mcr); |
| return status; |
| } |
| |
| static void mxu1_set_termios(struct tty_struct *tty, |
| struct usb_serial_port *port, |
| struct ktermios *old_termios) |
| { |
| struct mxu1_port *mxport = usb_get_serial_port_data(port); |
| struct mxu1_uart_config *config; |
| tcflag_t cflag, iflag; |
| speed_t baud; |
| int status; |
| unsigned int mcr; |
| |
| cflag = tty->termios.c_cflag; |
| iflag = tty->termios.c_iflag; |
| |
| if (old_termios && |
| !tty_termios_hw_change(&tty->termios, old_termios) && |
| tty->termios.c_iflag == old_termios->c_iflag) { |
| dev_dbg(&port->dev, "%s - nothing to change\n", __func__); |
| return; |
| } |
| |
| dev_dbg(&port->dev, |
| "%s - cflag 0x%08x, iflag 0x%08x\n", __func__, cflag, iflag); |
| |
| if (old_termios) { |
| dev_dbg(&port->dev, "%s - old cflag 0x%08x, old iflag 0x%08x\n", |
| __func__, |
| old_termios->c_cflag, |
| old_termios->c_iflag); |
| } |
| |
| config = kzalloc(sizeof(*config), GFP_KERNEL); |
| if (!config) |
| return; |
| |
| /* these flags must be set */ |
| config->wFlags |= MXU1_UART_ENABLE_MS_INTS; |
| config->wFlags |= MXU1_UART_ENABLE_AUTO_START_DMA; |
| if (mxport->send_break) |
| config->wFlags |= MXU1_UART_SEND_BREAK_SIGNAL; |
| config->bUartMode = mxport->uart_mode; |
| |
| switch (C_CSIZE(tty)) { |
| case CS5: |
| config->bDataBits = MXU1_UART_5_DATA_BITS; |
| break; |
| case CS6: |
| config->bDataBits = MXU1_UART_6_DATA_BITS; |
| break; |
| case CS7: |
| config->bDataBits = MXU1_UART_7_DATA_BITS; |
| break; |
| default: |
| case CS8: |
| config->bDataBits = MXU1_UART_8_DATA_BITS; |
| break; |
| } |
| |
| if (C_PARENB(tty)) { |
| config->wFlags |= MXU1_UART_ENABLE_PARITY_CHECKING; |
| if (C_CMSPAR(tty)) { |
| if (C_PARODD(tty)) |
| config->bParity = MXU1_UART_MARK_PARITY; |
| else |
| config->bParity = MXU1_UART_SPACE_PARITY; |
| } else { |
| if (C_PARODD(tty)) |
| config->bParity = MXU1_UART_ODD_PARITY; |
| else |
| config->bParity = MXU1_UART_EVEN_PARITY; |
| } |
| } else { |
| config->bParity = MXU1_UART_NO_PARITY; |
| } |
| |
| if (C_CSTOPB(tty)) |
| config->bStopBits = MXU1_UART_2_STOP_BITS; |
| else |
| config->bStopBits = MXU1_UART_1_STOP_BITS; |
| |
| if (C_CRTSCTS(tty)) { |
| /* RTS flow control must be off to drop RTS for baud rate B0 */ |
| if (C_BAUD(tty) != B0) |
| config->wFlags |= MXU1_UART_ENABLE_RTS_IN; |
| config->wFlags |= MXU1_UART_ENABLE_CTS_OUT; |
| } |
| |
| if (I_IXOFF(tty) || I_IXON(tty)) { |
| config->cXon = START_CHAR(tty); |
| config->cXoff = STOP_CHAR(tty); |
| |
| if (I_IXOFF(tty)) |
| config->wFlags |= MXU1_UART_ENABLE_X_IN; |
| |
| if (I_IXON(tty)) |
| config->wFlags |= MXU1_UART_ENABLE_X_OUT; |
| } |
| |
| baud = tty_get_baud_rate(tty); |
| if (!baud) |
| baud = 9600; |
| config->wBaudRate = MXU1_BAUD_BASE / baud; |
| |
| dev_dbg(&port->dev, "%s - BaudRate=%d, wBaudRate=%d, wFlags=0x%04X, bDataBits=%d, bParity=%d, bStopBits=%d, cXon=%d, cXoff=%d, bUartMode=%d\n", |
| __func__, baud, config->wBaudRate, config->wFlags, |
| config->bDataBits, config->bParity, config->bStopBits, |
| config->cXon, config->cXoff, config->bUartMode); |
| |
| cpu_to_be16s(&config->wBaudRate); |
| cpu_to_be16s(&config->wFlags); |
| |
| status = mxu1_send_ctrl_data_urb(port->serial, MXU1_SET_CONFIG, 0, |
| MXU1_UART1_PORT, config, |
| sizeof(*config)); |
| if (status) |
| dev_err(&port->dev, "cannot set config: %d\n", status); |
| |
| mutex_lock(&mxport->mutex); |
| mcr = mxport->mcr; |
| |
| if (C_BAUD(tty) == B0) |
| mcr &= ~(MXU1_MCR_DTR | MXU1_MCR_RTS); |
| else if (old_termios && (old_termios->c_cflag & CBAUD) == B0) |
| mcr |= MXU1_MCR_DTR | MXU1_MCR_RTS; |
| |
| status = mxu1_set_mcr(port, mcr); |
| if (status) |
| dev_err(&port->dev, "cannot set modem control: %d\n", status); |
| else |
| mxport->mcr = mcr; |
| |
| mutex_unlock(&mxport->mutex); |
| |
| kfree(config); |
| } |
| |
| static int mxu1_get_serial_info(struct usb_serial_port *port, |
| struct serial_struct __user *ret_arg) |
| { |
| struct serial_struct ret_serial; |
| unsigned cwait; |
| |
| if (!ret_arg) |
| return -EFAULT; |
| |
| cwait = port->port.closing_wait; |
| if (cwait != ASYNC_CLOSING_WAIT_NONE) |
| cwait = jiffies_to_msecs(cwait) / 10; |
| |
| memset(&ret_serial, 0, sizeof(ret_serial)); |
| |
| ret_serial.type = PORT_16550A; |
| ret_serial.line = port->minor; |
| ret_serial.port = 0; |
| ret_serial.xmit_fifo_size = port->bulk_out_size; |
| ret_serial.baud_base = MXU1_BAUD_BASE; |
| ret_serial.close_delay = 5*HZ; |
| ret_serial.closing_wait = cwait; |
| |
| if (copy_to_user(ret_arg, &ret_serial, sizeof(*ret_arg))) |
| return -EFAULT; |
| |
| return 0; |
| } |
| |
| |
| static int mxu1_set_serial_info(struct usb_serial_port *port, |
| struct serial_struct __user *new_arg) |
| { |
| struct serial_struct new_serial; |
| unsigned cwait; |
| |
| if (copy_from_user(&new_serial, new_arg, sizeof(new_serial))) |
| return -EFAULT; |
| |
| cwait = new_serial.closing_wait; |
| if (cwait != ASYNC_CLOSING_WAIT_NONE) |
| cwait = msecs_to_jiffies(10 * new_serial.closing_wait); |
| |
| port->port.closing_wait = cwait; |
| |
| return 0; |
| } |
| |
| static int mxu1_ioctl(struct tty_struct *tty, |
| unsigned int cmd, unsigned long arg) |
| { |
| struct usb_serial_port *port = tty->driver_data; |
| |
| switch (cmd) { |
| case TIOCGSERIAL: |
| return mxu1_get_serial_info(port, |
| (struct serial_struct __user *)arg); |
| case TIOCSSERIAL: |
| return mxu1_set_serial_info(port, |
| (struct serial_struct __user *)arg); |
| } |
| |
| return -ENOIOCTLCMD; |
| } |
| |
| static int mxu1_tiocmget(struct tty_struct *tty) |
| { |
| struct usb_serial_port *port = tty->driver_data; |
| struct mxu1_port *mxport = usb_get_serial_port_data(port); |
| unsigned int result; |
| unsigned int msr; |
| unsigned int mcr; |
| unsigned long flags; |
| |
| mutex_lock(&mxport->mutex); |
| spin_lock_irqsave(&mxport->spinlock, flags); |
| |
| msr = mxport->msr; |
| mcr = mxport->mcr; |
| |
| spin_unlock_irqrestore(&mxport->spinlock, flags); |
| mutex_unlock(&mxport->mutex); |
| |
| result = ((mcr & MXU1_MCR_DTR) ? TIOCM_DTR : 0) | |
| ((mcr & MXU1_MCR_RTS) ? TIOCM_RTS : 0) | |
| ((mcr & MXU1_MCR_LOOP) ? TIOCM_LOOP : 0) | |
| ((msr & MXU1_MSR_CTS) ? TIOCM_CTS : 0) | |
| ((msr & MXU1_MSR_CD) ? TIOCM_CAR : 0) | |
| ((msr & MXU1_MSR_RI) ? TIOCM_RI : 0) | |
| ((msr & MXU1_MSR_DSR) ? TIOCM_DSR : 0); |
| |
| dev_dbg(&port->dev, "%s - 0x%04X\n", __func__, result); |
| |
| return result; |
| } |
| |
| static int mxu1_tiocmset(struct tty_struct *tty, |
| unsigned int set, unsigned int clear) |
| { |
| struct usb_serial_port *port = tty->driver_data; |
| struct mxu1_port *mxport = usb_get_serial_port_data(port); |
| int err; |
| unsigned int mcr; |
| |
| mutex_lock(&mxport->mutex); |
| mcr = mxport->mcr; |
| |
| if (set & TIOCM_RTS) |
| mcr |= MXU1_MCR_RTS; |
| if (set & TIOCM_DTR) |
| mcr |= MXU1_MCR_DTR; |
| if (set & TIOCM_LOOP) |
| mcr |= MXU1_MCR_LOOP; |
| |
| if (clear & TIOCM_RTS) |
| mcr &= ~MXU1_MCR_RTS; |
| if (clear & TIOCM_DTR) |
| mcr &= ~MXU1_MCR_DTR; |
| if (clear & TIOCM_LOOP) |
| mcr &= ~MXU1_MCR_LOOP; |
| |
| err = mxu1_set_mcr(port, mcr); |
| if (!err) |
| mxport->mcr = mcr; |
| |
| mutex_unlock(&mxport->mutex); |
| |
| return err; |
| } |
| |
| static void mxu1_break(struct tty_struct *tty, int break_state) |
| { |
| struct usb_serial_port *port = tty->driver_data; |
| struct mxu1_port *mxport = usb_get_serial_port_data(port); |
| |
| if (break_state == -1) |
| mxport->send_break = true; |
| else |
| mxport->send_break = false; |
| |
| mxu1_set_termios(tty, port, NULL); |
| } |
| |
| static int mxu1_open(struct tty_struct *tty, struct usb_serial_port *port) |
| { |
| struct mxu1_port *mxport = usb_get_serial_port_data(port); |
| struct usb_serial *serial = port->serial; |
| int status; |
| u16 open_settings; |
| |
| open_settings = (MXU1_PIPE_MODE_CONTINUOUS | |
| MXU1_PIPE_TIMEOUT_ENABLE | |
| (MXU1_TRANSFER_TIMEOUT << 2)); |
| |
| mxport->msr = 0; |
| |
| status = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); |
| if (status) { |
| dev_err(&port->dev, "failed to submit interrupt urb: %d\n", |
| status); |
| return status; |
| } |
| |
| if (tty) |
| mxu1_set_termios(tty, port, NULL); |
| |
| status = mxu1_send_ctrl_urb(serial, MXU1_OPEN_PORT, |
| open_settings, MXU1_UART1_PORT); |
| if (status) { |
| dev_err(&port->dev, "cannot send open command: %d\n", status); |
| goto unlink_int_urb; |
| } |
| |
| status = mxu1_send_ctrl_urb(serial, MXU1_START_PORT, |
| 0, MXU1_UART1_PORT); |
| if (status) { |
| dev_err(&port->dev, "cannot send start command: %d\n", status); |
| goto unlink_int_urb; |
| } |
| |
| status = mxu1_send_ctrl_urb(serial, MXU1_PURGE_PORT, |
| MXU1_PURGE_INPUT, MXU1_UART1_PORT); |
| if (status) { |
| dev_err(&port->dev, "cannot clear input buffers: %d\n", |
| status); |
| |
| goto unlink_int_urb; |
| } |
| |
| status = mxu1_send_ctrl_urb(serial, MXU1_PURGE_PORT, |
| MXU1_PURGE_OUTPUT, MXU1_UART1_PORT); |
| if (status) { |
| dev_err(&port->dev, "cannot clear output buffers: %d\n", |
| status); |
| |
| goto unlink_int_urb; |
| } |
| |
| /* |
| * reset the data toggle on the bulk endpoints to work around bug in |
| * host controllers where things get out of sync some times |
| */ |
| usb_clear_halt(serial->dev, port->write_urb->pipe); |
| usb_clear_halt(serial->dev, port->read_urb->pipe); |
| |
| if (tty) |
| mxu1_set_termios(tty, port, NULL); |
| |
| status = mxu1_send_ctrl_urb(serial, MXU1_OPEN_PORT, |
| open_settings, MXU1_UART1_PORT); |
| if (status) { |
| dev_err(&port->dev, "cannot send open command: %d\n", status); |
| goto unlink_int_urb; |
| } |
| |
| status = mxu1_send_ctrl_urb(serial, MXU1_START_PORT, |
| 0, MXU1_UART1_PORT); |
| if (status) { |
| dev_err(&port->dev, "cannot send start command: %d\n", status); |
| goto unlink_int_urb; |
| } |
| |
| status = usb_serial_generic_open(tty, port); |
| if (status) |
| goto unlink_int_urb; |
| |
| return 0; |
| |
| unlink_int_urb: |
| usb_kill_urb(port->interrupt_in_urb); |
| |
| return status; |
| } |
| |
| static void mxu1_close(struct usb_serial_port *port) |
| { |
| int status; |
| |
| usb_serial_generic_close(port); |
| usb_kill_urb(port->interrupt_in_urb); |
| |
| status = mxu1_send_ctrl_urb(port->serial, MXU1_CLOSE_PORT, |
| 0, MXU1_UART1_PORT); |
| if (status) { |
| dev_err(&port->dev, "failed to send close port command: %d\n", |
| status); |
| } |
| } |
| |
| static void mxu1_handle_new_msr(struct usb_serial_port *port, u8 msr) |
| { |
| struct mxu1_port *mxport = usb_get_serial_port_data(port); |
| struct async_icount *icount; |
| unsigned long flags; |
| |
| dev_dbg(&port->dev, "%s - msr 0x%02X\n", __func__, msr); |
| |
| spin_lock_irqsave(&mxport->spinlock, flags); |
| mxport->msr = msr & MXU1_MSR_MASK; |
| spin_unlock_irqrestore(&mxport->spinlock, flags); |
| |
| if (msr & MXU1_MSR_DELTA_MASK) { |
| icount = &port->icount; |
| if (msr & MXU1_MSR_DELTA_CTS) |
| icount->cts++; |
| if (msr & MXU1_MSR_DELTA_DSR) |
| icount->dsr++; |
| if (msr & MXU1_MSR_DELTA_CD) |
| icount->dcd++; |
| if (msr & MXU1_MSR_DELTA_RI) |
| icount->rng++; |
| |
| wake_up_interruptible(&port->port.delta_msr_wait); |
| } |
| } |
| |
| static void mxu1_interrupt_callback(struct urb *urb) |
| { |
| struct usb_serial_port *port = urb->context; |
| unsigned char *data = urb->transfer_buffer; |
| int length = urb->actual_length; |
| int function; |
| int status; |
| u8 msr; |
| |
| switch (urb->status) { |
| case 0: |
| break; |
| case -ECONNRESET: |
| case -ENOENT: |
| case -ESHUTDOWN: |
| dev_dbg(&port->dev, "%s - urb shutting down: %d\n", |
| __func__, urb->status); |
| return; |
| default: |
| dev_dbg(&port->dev, "%s - nonzero urb status: %d\n", |
| __func__, urb->status); |
| goto exit; |
| } |
| |
| if (length != 2) { |
| dev_dbg(&port->dev, "%s - bad packet size: %d\n", |
| __func__, length); |
| goto exit; |
| } |
| |
| if (data[0] == MXU1_CODE_HARDWARE_ERROR) { |
| dev_err(&port->dev, "hardware error: %d\n", data[1]); |
| goto exit; |
| } |
| |
| function = mxu1_get_func_from_code(data[0]); |
| |
| dev_dbg(&port->dev, "%s - function %d, data 0x%02X\n", |
| __func__, function, data[1]); |
| |
| switch (function) { |
| case MXU1_CODE_DATA_ERROR: |
| dev_dbg(&port->dev, "%s - DATA ERROR, data 0x%02X\n", |
| __func__, data[1]); |
| break; |
| |
| case MXU1_CODE_MODEM_STATUS: |
| msr = data[1]; |
| mxu1_handle_new_msr(port, msr); |
| break; |
| |
| default: |
| dev_err(&port->dev, "unknown interrupt code: 0x%02X\n", |
| data[1]); |
| break; |
| } |
| |
| exit: |
| status = usb_submit_urb(urb, GFP_ATOMIC); |
| if (status) { |
| dev_err(&port->dev, "resubmit interrupt urb failed: %d\n", |
| status); |
| } |
| } |
| |
| static struct usb_serial_driver mxu11x0_device = { |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = "mxu11x0", |
| }, |
| .description = "MOXA UPort 11x0", |
| .id_table = mxu1_idtable, |
| .num_ports = 1, |
| .port_probe = mxu1_port_probe, |
| .port_remove = mxu1_port_remove, |
| .attach = mxu1_startup, |
| .release = mxu1_release, |
| .open = mxu1_open, |
| .close = mxu1_close, |
| .ioctl = mxu1_ioctl, |
| .set_termios = mxu1_set_termios, |
| .tiocmget = mxu1_tiocmget, |
| .tiocmset = mxu1_tiocmset, |
| .tiocmiwait = usb_serial_generic_tiocmiwait, |
| .get_icount = usb_serial_generic_get_icount, |
| .break_ctl = mxu1_break, |
| .read_int_callback = mxu1_interrupt_callback, |
| }; |
| |
| static struct usb_serial_driver *const serial_drivers[] = { |
| &mxu11x0_device, NULL |
| }; |
| |
| module_usb_serial_driver(serial_drivers, mxu1_idtable); |
| |
| MODULE_AUTHOR("Mathieu Othacehe <m.othacehe@gmail.com>"); |
| MODULE_DESCRIPTION("MOXA UPort 11x0 USB to Serial Hub Driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_FIRMWARE("moxa/moxa-1110.fw"); |
| MODULE_FIRMWARE("moxa/moxa-1130.fw"); |
| MODULE_FIRMWARE("moxa/moxa-1131.fw"); |
| MODULE_FIRMWARE("moxa/moxa-1150.fw"); |
| MODULE_FIRMWARE("moxa/moxa-1151.fw"); |