From patchwork Tue Jul 24 22:36:36 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Patong Yang X-Patchwork-Id: 10543261 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 278AD112B for ; Tue, 24 Jul 2018 22:36:48 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id EF83F29562 for ; Tue, 24 Jul 2018 22:36:47 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id E2CAC295B7; Tue, 24 Jul 2018 22:36:47 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.8 required=2.0 tests=BAYES_00,DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED,FREEMAIL_FROM,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI,T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 46F0529562 for ; Tue, 24 Jul 2018 22:36:44 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388783AbeGXXpV (ORCPT ); Tue, 24 Jul 2018 19:45:21 -0400 Received: from mail-yw0-f194.google.com ([209.85.161.194]:46389 "EHLO mail-yw0-f194.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388495AbeGXXpU (ORCPT ); Tue, 24 Jul 2018 19:45:20 -0400 Received: by mail-yw0-f194.google.com with SMTP id e23-v6so2158608ywe.13 for ; Tue, 24 Jul 2018 15:36:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=date:from:to:cc:subject:message-id:mime-version:content-disposition :user-agent; bh=Aj+l73hQRIAut4UhfUbZu8vW2NSJ3SB/4KX6xf3MP0k=; b=HB0Ej9FlWAkA0+6oi7JlPv0qLhDeHjYpn+mNFPnt8ZnpAk1dkkvALpWviYrZrbGYem wid6sJuOBSvjI1f2VEvg9xChqZqxgmKELAnBdYOLQ2vEjJLXBlbnzi8gC5FFea1aP8FG MhVILHFT34071/u7GcNaU/PAkVHQEMfnuaAKJzTY1rVhhVsK+dlD62Jg1nlVYR6qTFbh eIoIf1+GMbq6B9k1rslEstnCCqyEoMwbCcP1OHa5bIpl/mZnMyLHBBpd5+wprcTU8jqj XVD0u0bHRkntadU48uc0rNNbeN7PFfT5ciGF+mLi26uuaDk5QSo0+WxlJohHS0y46ePI hoGQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:mime-version :content-disposition:user-agent; bh=Aj+l73hQRIAut4UhfUbZu8vW2NSJ3SB/4KX6xf3MP0k=; b=oIk9KlYgYUFdzMJgyn4tOVsCfHYlr8IkObewZujWmFHZ7KPWEFQkMTR9rICwsfCks0 Y3yDmZ+Z26C60OK26KiR87EGDY0R/rZZUAJLWiSGXsy9JqXWBaKyF+tlJbw7wuLnNS8W gkjCqbNn+eOKqFOZ+neL0Tk/PGm/rESYxvNlqqhPBgaZuVcbzbrzV6Lkt56YfJpmBUM5 ykWORknDtdihByAS3ljyKTN2TNx0odylbbIFnKrpKOyC8Pk9kevGFF35AjA903s8DJlM UAmmbKQa4+69G8Ms6Z/6KD4iPwbY8NIqtVuiFfLjSmBIbktKvE2XcsA8SXdmB4f/Dr+q MScA== X-Gm-Message-State: AOUpUlFLdnVeqzPcKO5P/eo9ykRuzakpkbrgyjHWk1XY3XfU3s8ZxEev yZcvfJSYwqUQISlHpQtnfYeTKLwH X-Google-Smtp-Source: AAOMgpexe5ssbnGGFh4zKehyW0prZcaEPBjYcgOaUsG1e8x/beH+8q6tOeqFAipW3WODuvS7qrwJeg== X-Received: by 2002:a0d:f3c7:: with SMTP id c190-v6mr9779033ywf.98.1532471799013; Tue, 24 Jul 2018 15:36:39 -0700 (PDT) Received: from linux1804-desktop (gw.exar.com. [204.154.183.70]) by smtp.gmail.com with ESMTPSA id e22-v6sm8595021ywe.37.2018.07.24.15.36.37 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Tue, 24 Jul 2018 15:36:38 -0700 (PDT) Date: Tue, 24 Jul 2018 15:36:36 -0700 From: Patong Yang To: linux-usb@vger.kernel.org Cc: johan@kernel.org, pyang@maxlinear.com, gregkh@linuxfoundation.org Subject: [PATCH] Driver for MaxLinear/Exar USB (UART) Serial Adapters Message-ID: <20180724223636.GA17270@linux1804-desktop> MIME-Version: 1.0 Content-Disposition: inline User-Agent: Mutt/1.9.4 (2018-02-28) Sender: linux-usb-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP The original driver/patch was submitted on April 4, 2018. This is the second version based on the feedback received on the original patch. v2: Removed custom IOCTLs, as suggested by Greg KH Using standard Linux GPIO APIs, as suggested by Greg KH Removed file reads/writes as suggested by Greg KH Signed-off-by: Patong Yang --- drivers/usb/serial/xrusb_serial.c | 2380 +++++++++++++++++++++++++++++ drivers/usb/serial/xrusb_serial.h | 234 +++ 2 files changed, 2614 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..707c470d32db --- /dev/null +++ b/drivers/usb/serial/xrusb_serial.c @@ -0,0 +1,2380 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * xrusb_serial.c + * + * Copyright (c) 2018 Patong Yang + * + * USB Serial Driver based on the cdc-acm.c driver for the + * MaxLinear/Exar USB UARTs/Serial adapters + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "linux/version.h" +#include "xrusb_serial.h" + +#define DRIVER_AUTHOR "Patong Yang " +#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 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); + +/* + * 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); + 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; +} + +int xrusb_set_reg_ext(struct xrusb *xrusb, int channel, int regnum, int value) +{ + int result; + 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) { + 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) { + 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; +} + + +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; +} + + +int xrusb_get_reg_ext(struct xrusb *xrusb, int channel, + int regnum, short *value) +{ + int result; + 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) { + 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 | (channel << 8), // index + dmadata, // data + 2, // size + 5000); // timeout + memcpy(value, dmadata, 2); + } else if ((xrusb->DeviceProduct & 0xfff0) == 0x1410) { + 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 Error:%d\n", + __func__, result); + } + kfree(dmadata); + return result; +} + +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, + UART_GPIO_CLR_DTR); + } else { + xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set, + UART_GPIO_SET_DTR); + } + + if (control & XRUSB_CTRL_RTS) { + xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr, + UART_GPIO_CLR_RTS); + } else { + xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set, + UART_GPIO_SET_RTS); + } + } else { + // Use CDC command for XR21B14xx and XR2280x + 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; + } + + // Mode configured in BIOS for Caracalla + // Do nothing for xrusb_set_flow_mode + if (xrusb->found_smbios_caracalla_config) + return 0; + + 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); + } + } else {// Use CDC command for XR21B14xx and XR2280x + 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_reg_ext(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_reg_ext(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_reg_ext(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_reg_ext(xrusb, + XR21V141x_URM_REG_BLOCK, + XR21V141x_URM_RX_FIFO_RESET + channel, + 0xff); + rv |= xrusb_set_reg_ext(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_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_reg_ext(xrusb, + XR21V141x_UART_CUSTOM_BLOCK, + channel*8 + XR21V141X_WIDE_MODE_REG, + wide_mode); + } + + xrusb_enable(xrusb); + 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; +} + +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, + UART_GPIO_CLR_DTR); + } else { + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_set, + UART_GPIO_SET_DTR); + } + + if (newctrl & XRUSB_CTRL_RTS) { + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_clr, + UART_GPIO_CLR_RTS); + } else { + xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_set, + UART_GPIO_SET_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_rs485_delay = 0xc0b; + 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; + + 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 |= 0x300; + + 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, 0x28); + xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set, + UART_GPIO_SET_DTR | UART_GPIO_SET_RTS); + + 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; + + int rv; + int status = urb->status; + unsigned char *p; + + switch (status) { + case 0: + p = (unsigned char *)(urb->transfer_buffer); + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + + return; + default: + break; + } + + rv = usb_submit_urb(urb, GFP_ATOMIC); + if (rv && rv != -EPERM && rv != -ENODEV) { + 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; + + res = usb_submit_urb(xrusb->read_urbs[index], mem_flags); + if (res) { + if (res != -EPERM && res != -ENODEV) { + 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 (xrusb->uart_type == 0) + return; + 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); + 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; + + 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); + + 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; + + mutex_lock(&xrusb->mutex); + if (xrusb->disconnected) + goto disconnected; + + rv = usb_autopm_get_interface(xrusb->control); + if (rv) + goto error_get_interface; + + 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); + +#ifdef CONFIG_GPIOLIB + if (xrusb->rv_gpio_created == 0) + gpiochip_remove(&xrusb->xr_gpio); +#endif + 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; + + 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; + + tty_port_put(&xrusb->port); +} + +static void xrusb_tty_hangup(struct tty_struct *tty) +{ + struct xrusb *xrusb = tty->driver_data; + + tty_port_hangup(&xrusb->port); +} + +static void xrusb_tty_close(struct tty_struct *tty, struct file *filp) +{ + struct xrusb *xrusb = tty->driver_data; + + 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 (xrusb->uart_type == 0) + return -EIO; + + if (!count) + return 0; + + 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; + 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.type = xrusb->uart_type; + 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; + } + xrusb->uart_type = new_serial.type; + mutex_unlock(&xrusb->port.mutex); + return rv; +} + +/* Enable or disable the rs485 support */ +void xr_config_rs485(struct xrusb *xrusb, struct serial_rs485 *rs485conf) +{ + int rv; + short reg_value; + + xrusb->rs485 = *rs485conf; + rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_mode, ®_value); + + if (rs485conf->flags & SER_RS485_ENABLED) { + if ((rs485conf->delay_rts_after_send) < 0xf) { + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_rs485_delay, + rs485conf->delay_rts_after_send); + } else { + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_rs485_delay, + 0xf); // max value is 0xf + } + + if (rs485conf->flags & SER_RS485_RTS_ON_SEND) { + /* Set logical level for RTS pin equal to 1 when sending: */ + reg_value &= 0xfff0; + reg_value |= 0xb; + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_mode, + reg_value); + } else { + /*set logical level for RTS pin equal to 0 when sending: */ + reg_value &= 0xfff0; + reg_value |= 0x3; + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_mode, + reg_value); + } + + } else { + /* RS-485/422 RTS direction control output disabled */ + rv = xrusb_set_reg(xrusb, + xrusb->reg_map.uart_gpio_mode, + reg_value & 0xfff0); + } + +} + +static int xrusb_tty_ioctl(struct tty_struct *tty, unsigned int cmd, + unsigned long arg) +{ + struct xrusb *xrusb = tty->driver_data; + int rv = -ENOIOCTLCMD; + struct serial_rs485 rs485conf; + + 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 TIOCSRS485: + if (copy_from_user(&rs485conf, (struct serial_rs485 *) arg, + sizeof(rs485conf))) { + return -EFAULT; + } + xr_config_rs485(xrusb, &rs485conf); + rv = 0; + break; + case TIOCGRS485: + if (copy_to_user((struct serial_rs485 *) arg, &(xrusb->rs485), + sizeof(rs485conf))) { + 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; +} + +#ifdef CONFIG_GPIOLIB +static int xrusb_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct xrusb *xrusb = container_of(chip, struct xrusb, xr_gpio); + int rv; + short gpio_status; + + rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_status, + &gpio_status); + if (gpio_status&(1 << offset)) + return 1; + else + return 0; +} + +static void xrusb_gpio_set(struct gpio_chip *chip, unsigned int offset, int val) +{ + struct xrusb *xrusb = container_of(chip, struct xrusb, xr_gpio); + int rv, tmp; + + tmp = 1 << offset; + if (val) + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set, tmp); + else + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr, tmp); +} + +static int xrusb_gpio_dir_input(struct gpio_chip *chip, unsigned int offset) +{ + int rv; + short dir_value; + struct xrusb *xrusb = container_of(chip, struct xrusb, xr_gpio); + + rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_dir, &dir_value); + dir_value &= ~(1 << offset); + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_dir, (int)dir_value); + return 0; +} + +static int xrusb_gpio_dir_output(struct gpio_chip *chip, + unsigned int offset, int val) +{ + int rv; + short tmp; + struct xrusb *xrusb = container_of(chip, struct xrusb, xr_gpio); + + rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_dir, &tmp); + tmp |= (1 << offset); + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_dir, (int)tmp); + + if (offset > 7) { + rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_mode, &tmp); + tmp &= ~(1 << offset); + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode, + (int)tmp); + } + return 0; +} +#endif + +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; +#ifdef CONFIG_GPIOLIB + int gpiochip_base; +#endif + + /* 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); + } + + /* 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]; + } + + 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 (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; + + t = epread; + epread = epwrite; + epwrite = t; + } + + xrusb = kzalloc(sizeof(struct xrusb), GFP_KERNEL); + if (xrusb == NULL) + 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->uart_type = 4; /*#define PORT_16550A 4*/ + xrusb->channel = 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; + + 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; + } +#ifdef CONFIG_GPIOLIB + /* Setup GPIO cotroller */ + gpiochip_base = xrusb->dev->portnum*100 + xrusb->channel*10; + if (gpiochip_base > 502) { + // base must be 502 or less + gpiochip_base = 0; + } + + xrusb->xr_gpio.owner = THIS_MODULE; + xrusb->xr_gpio.label = dev_name(&control_interface->dev); + xrusb->xr_gpio.direction_input = xrusb_gpio_dir_input; + xrusb->xr_gpio.get = xrusb_gpio_get; + xrusb->xr_gpio.direction_output = xrusb_gpio_dir_output; + xrusb->xr_gpio.set = xrusb_gpio_set; + xrusb->xr_gpio.base = gpiochip_base; + xrusb->xr_gpio.ngpio = 10; + xrusb->xr_gpio.can_sleep = 1; + + rv = gpiochip_add(&xrusb->xr_gpio); + + if (rv != 0) { + // gpiochip numbers not available, start from 0 + xrusb->xr_gpio.base = 0; + } + + while (rv != 0) { + xrusb->xr_gpio.base += 10; + + if (xrusb->xr_gpio.base > 502) { + // max gpio number = 512 + // we ran out of gpios?? + break; + } + rv = gpiochip_add(&xrusb->xr_gpio); + } + xrusb->rv_gpio_created = rv; + if (rv == 0) { + dev_dbg(&xrusb->control->dev, "gpiochip%d added", + xrusb->xr_gpio.base); + } else { + dev_dbg(&xrusb->control->dev, "failed to add gpiochip\n"); + } + +#endif + + 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; + unsigned short dir_tmp, mode_tmp, state_tmp; + int rv; + + 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); + + //Save the register values before suspend + rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_dir, &dir_tmp); + xrusb->gpio_dir_saved = dir_tmp; + rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_status, &state_tmp); + xrusb->gpio_state_saved = state_tmp; + rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_mode, &mode_tmp); + xrusb->gpio_mode_saved = mode_tmp; + 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; + unsigned short gpio_state, gpio_dir, gpio_mode; + + 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); + } + gpio_dir = xrusb->gpio_dir_saved; + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_dir, gpio_dir); + + gpio_mode = xrusb->gpio_mode_saved; + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode, gpio_mode); + + gpio_state = xrusb->gpio_state_saved; + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set, gpio_state); + rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr, ~gpio_state); +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"); + pr_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..7b743d3da68a --- /dev/null +++ b/drivers/usb/serial/xrusb_serial.h @@ -0,0 +1,234 @@ +/* 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 { + 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; + int uart_type; + unsigned short DeviceVendor; + unsigned short DeviceProduct; + struct serial_rs485 rs485; /* rs485 settings */ + int found_smbios_caracalla_config; /* Modes pre-programmed in BIOS for Caracalla board */ + + struct reg_addr_map reg_map; + #ifdef CONFIG_GPIOLIB + struct gpio_chip xr_gpio; + int rv_gpio_created; + #endif + unsigned short gpio_dir_saved; + unsigned short gpio_state_saved; + unsigned short gpio_mode_saved; +}; + +#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 + +#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 Manager Registers */ +#define XR21V141x_URM_REG_BLOCK 4 +#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 XR21V141x_UART_CUSTOM_BLOCK 0x66 + +#define UART_ENABLE_TX 1 +#define UART_ENABLE_RX 2 + +#define UART_GPIO_CLR_DTR 0x8 +#define UART_GPIO_SET_DTR 0x8 +#define UART_GPIO_CLR_RTS 0x20 +#define UART_GPIO_SET_RTS 0x20 + +#define LOOPBACK_ENABLE_TX_RX 1 +#define LOOPBACK_ENABLE_RTS_CTS 2 +#define LOOPBACK_ENABLE_DTR_DSR 4 + +#define UART_FLOW_MODE_NONE 0x0 +#define UART_FLOW_MODE_HW 0x1 +#define UART_FLOW_MODE_SW 0x2 + +#define UART_GPIO_MODE_SEL_GPIO 0x0 +#define UART_GPIO_MODE_SEL_RTS_CTS 0x1 + +#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