Message ID | 20200117044148.263556-1-joel@jms.id.au (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | USB: serial: Add vizzini driver for Exar USB2 serial adapters | expand |
On Fri, Jan 17, 2020 at 02:41:48PM +1000, Joel Stanley wrote: > From: Ben Hutchings <ben@decadent.org.uk> > > This driver supports the one, two and four port variants of the USB > serial adapters. These devices are present on the Digilent Atlys FPGA > board. > > This driver was written by Ben in 2015. Minor cleanups and fixes for the > 1410 by Joel. > > Signed-off-by: Ben Hutchings <ben@decadent.org.uk> > Signed-off-by: Joel Stanley <joel@jms.id.au> > --- > drivers/usb/serial/Kconfig | 9 + > drivers/usb/serial/Makefile | 1 + > drivers/usb/serial/vizzini.c | 370 +++++++++++++++++++++++++++++++++++ > 3 files changed, 380 insertions(+) > create mode 100644 drivers/usb/serial/vizzini.c > > diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig > index 25d7e0c36d38..167992ad1d05 100644 > --- a/drivers/usb/serial/Kconfig > +++ b/drivers/usb/serial/Kconfig > @@ -654,4 +654,13 @@ config USB_SERIAL_DEBUG > To compile this driver as a module, choose M here: the > module will be called usb-debug. > > +config USB_SERIAL_VIZZINI > + tristate "USB Exar XR21V141x 'Vizzini' Serial Driver" > + help > + Say Y here if you want to use the Exar XR21V1410/1412/141 > + USB 2 serial dapters. typo: adapters > + > + To compile this driver as a module, choose M here: the > + module will be called vizzini. > + > endif # USB_SERIAL > diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile > index 2d491e434f11..9f65a4863f1d 100644 > --- a/drivers/usb/serial/Makefile > +++ b/drivers/usb/serial/Makefile > @@ -59,6 +59,7 @@ obj-$(CONFIG_USB_SERIAL_WWAN) += usb_wwan.o > obj-$(CONFIG_USB_SERIAL_TI) += ti_usb_3410_5052.o > obj-$(CONFIG_USB_SERIAL_UPD78F0730) += upd78f0730.o > obj-$(CONFIG_USB_SERIAL_VISOR) += visor.o > +obj-$(CONFIG_USB_SERIAL_VIZZINI) += vizzini.o > obj-$(CONFIG_USB_SERIAL_WISHBONE) += wishbone-serial.o > obj-$(CONFIG_USB_SERIAL_WHITEHEAT) += whiteheat.o > obj-$(CONFIG_USB_SERIAL_XIRCOM) += keyspan_pda.o > diff --git a/drivers/usb/serial/vizzini.c b/drivers/usb/serial/vizzini.c > new file mode 100644 > index 000000000000..f4d1b9a75d8a > --- /dev/null > +++ b/drivers/usb/serial/vizzini.c > @@ -0,0 +1,370 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Based on vizzini driver: > + * > + * Copyright (c) 2013 Exar Corporation, Inc. > + * > + * Based on USB Serial "Simple" driver > + * > + * Copyright (C) 2001-2006,2008,2013 Greg Kroah-Hartman <greg@kroah.com> > + * Copyright (C) 2005 Arthur Huillet (ahuillet@users.sf.net) > + * Copyright (C) 2005 Thomas Hergenhahn <thomas.hergenhahn@suse.de> > + * Copyright (C) 2009 Outpost Embedded, LLC > + * Copyright (C) 2010 Zilogic Systems <code@zilogic.com> > + * Copyright (C) 2013 Wei Shuai <cpuwolf@gmail.com> > + * Copyright (C) 2013 Linux Foundation This looked weird, but I understand where it comes from now. With a calc_num_ports() callback, you can drop this bit (see below). > + */ > + > +#include <linux/kernel.h> > +#include <linux/tty.h> > +#include <linux/module.h> > +#include <linux/usb.h> > +#include <linux/usb/serial.h> > + > +#define XR_SET_REG 0 > + > +#define URM_REG_BLOCK 4 > +#define EPLOCALS_REG_BLOCK 0x66 > + > +#define MEM_EP_LOCALS_SIZE_S 3 > +#define MEM_EP_LOCALS_SIZE (1 << MEM_EP_LOCALS_SIZE_S) > + > +#define EP_WIDE_MODE 0x03 > + > +#define UART_GPIO_MODE 0x01a > + > +#define UART_GPIO_MODE_SEL_M 0x7 > +#define UART_GPIO_MODE_SEL_S 0 > +#define UART_GPIO_MODE_SEL 0x007 > + > +#define UART_GPIO_MODE_SEL_GPIO (0x0 << UART_GPIO_MODE_SEL_S) > +#define UART_GPIO_MODE_SEL_RTS_CTS (0x1 << UART_GPIO_MODE_SEL_S) > +#define UART_GPIO_MODE_SEL_DTR_DSR (0x2 << UART_GPIO_MODE_SEL_S) > + > +#define UART_ENABLE 0x003 > +#define UART_ENABLE_TX_M 0x1 > +#define UART_ENABLE_TX_S 0 Looks like several of these mask/shift defines are unused. > +#define UART_ENABLE_TX 0x001 > +#define UART_ENABLE_RX_M 0x1 > +#define UART_ENABLE_RX_S 1 > +#define UART_ENABLE_RX 0x002 > + > +#define UART_CLOCK_DIVISOR_0 0x004 > +#define UART_CLOCK_DIVISOR_1 0x005 > +#define UART_CLOCK_DIVISOR_2 0x006 > + > +#define UART_TX_CLOCK_MASK_0 0x007 > +#define UART_TX_CLOCK_MASK_1 0x008 > + > +#define UART_RX_CLOCK_MASK_0 0x009 > +#define UART_RX_CLOCK_MASK_1 0x00a > + > +#define UART_FORMAT 0x00b > + > +#define UART_FORMAT_SIZE_M 0xf > +#define UART_FORMAT_SIZE_S 0 > +#define UART_FORMAT_SIZE 0x00f Same here. > +#define UART_FORMAT_SIZE_7 (0x7 << UART_FORMAT_SIZE_S) > +#define UART_FORMAT_SIZE_8 (0x8 << UART_FORMAT_SIZE_S) > +#define UART_FORMAT_SIZE_9 (0x9 << UART_FORMAT_SIZE_S) > + > +#define UART_FORMAT_PARITY_M 0x7 > +#define UART_FORMAT_PARITY_S 4 > +#define UART_FORMAT_PARITY 0x070 > + > +#define UART_FORMAT_PARITY_NONE (0x0 << UART_FORMAT_PARITY_S) > +#define UART_FORMAT_PARITY_ODD (0x1 << UART_FORMAT_PARITY_S) > +#define UART_FORMAT_PARITY_EVEN (0x2 << UART_FORMAT_PARITY_S) > +#define UART_FORMAT_PARITY_1 (0x3 << UART_FORMAT_PARITY_S) > +#define UART_FORMAT_PARITY_0 (0x4 << UART_FORMAT_PARITY_S) > + > +#define UART_FORMAT_STOP_M 0x1 > +#define UART_FORMAT_STOP_S 7 > +#define UART_FORMAT_STOP 0x080 > + > +#define UART_FORMAT_STOP_1 (0x0 << UART_FORMAT_STOP_S) > +#define UART_FORMAT_STOP_2 (0x1 << UART_FORMAT_STOP_S) > + > +#define UART_FLOW 0x00c > + > +#define UART_FLOW_MODE_M 0x7 > +#define UART_FLOW_MODE_S 0 > +#define UART_FLOW_MODE 0x007 > + > +#define UART_FLOW_MODE_NONE (0x0 << UART_FLOW_MODE_S) > +#define UART_FLOW_MODE_HW (0x1 << UART_FLOW_MODE_S) > +#define UART_FLOW_MODE_SW (0x2 << UART_FLOW_MODE_S) > + > +#define UART_XON_CHAR 0x010 > +#define UART_XOFF_CHAR 0x011 > + > +#define URM_ENABLE_BASE 0x010 > +#define URM_ENABLE_0 0x010 > +#define URM_ENABLE_0_TX 0x001 > +#define URM_ENABLE_0_RX 0x002 > + > +struct vizzini_baud_rate { > + unsigned int tx; > + unsigned int rx0; > + unsigned int rx1; > +}; > + > +static const struct vizzini_baud_rate vizzini_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 vizzini_set_reg(struct usb_serial_port *port, > + unsigned int block, unsigned int regnum, > + unsigned int value) > +{ > + dev_dbg(&port->serial->dev->dev, "%s 0x%02x:0x%02x = 0x%02x\n", > + __func__, block, regnum, value); Use &port->dev instead. > + > + return usb_control_msg(port->serial->dev, > + usb_sndctrlpipe(port->serial->dev, 0), > + XR_SET_REG, > + USB_DIR_OUT | USB_TYPE_VENDOR, > + value, regnum | (block << 8), > + NULL, 0, > + 5000); Use USB_CTRL_SET_TIMEOUT Report errors? > +} > + > +static void vizzini_disable(struct usb_serial_port *port) > +{ > + unsigned int block = port->bulk_out_endpointAddress - 1; You should probably use port->port_number instead of relying on fixed endpoint addresses which you never verify. > + > + vizzini_set_reg(port, block, UART_ENABLE, 0); > + vizzini_set_reg(port, URM_REG_BLOCK, URM_ENABLE_BASE + block, 0); > +} > + > +static void vizzini_enable(struct usb_serial_port *port) > +{ > + unsigned int block = port->bulk_out_endpointAddress - 1; > > + vizzini_set_reg(port, URM_REG_BLOCK, URM_ENABLE_BASE + block, > + URM_ENABLE_0_TX); Could you add comment about why this sequence is necessary, if it really is? > + vizzini_set_reg(port, block, UART_ENABLE, > + UART_ENABLE_TX | UART_ENABLE_RX); > + vizzini_set_reg(port, URM_REG_BLOCK, URM_ENABLE_BASE + block, > + URM_ENABLE_0_TX | URM_ENABLE_0_RX); No error handling? > +} > + > +static void vizzini_set_baud_rate(struct usb_serial_port *port, > + unsigned int rate) > +{ > + int block = port->bulk_out_endpointAddress - 1; > + unsigned int divisor = 48000000 / rate; Use a define for the clock frequency? > + unsigned int i = ((32 * 48000000) / rate) & 0x1f; > + unsigned int tx_mask = vizzini_baud_rates[i].tx; > + unsigned int rx_mask = ((divisor & 1) ? vizzini_baud_rates[i].rx1 : > + vizzini_baud_rates[i].rx0); Don't do non-trivial initialisations at declaration. And please avoid the ternary operator. > + > + dev_dbg(&port->serial->dev->dev, > + "Setting baud rate to %d: i=%u div=%u tx=%03x rx=%03x\n", > + rate, i, divisor, tx_mask, rx_mask); > + > + vizzini_set_reg(port, block, UART_CLOCK_DIVISOR_0, > + (divisor >> 0) & 0xff); > + vizzini_set_reg(port, block, UART_CLOCK_DIVISOR_1, > + (divisor >> 8) & 0xff); > + vizzini_set_reg(port, block, UART_CLOCK_DIVISOR_2, > + (divisor >> 16) & 0xff); > + > + vizzini_set_reg(port, block, UART_TX_CLOCK_MASK_0, > + (tx_mask >> 0) & 0xff); > + vizzini_set_reg(port, block, UART_TX_CLOCK_MASK_1, > + (tx_mask >> 8) & 0xff); > + > + vizzini_set_reg(port, block, UART_RX_CLOCK_MASK_0, > + (rx_mask >> 0) & 0xff); > + vizzini_set_reg(port, block, UART_RX_CLOCK_MASK_1, > + (rx_mask >> 8) & 0xff); Error handling? Can you add some comment about how the masks are used? > +} > + > +static void vizzini_set_termios(struct tty_struct *tty, > + struct usb_serial_port *port, > + struct ktermios *termios_old) > +{ > + unsigned int cflag, block; > + speed_t rate; > + unsigned int format_size, format_parity, format_stop, flow, gpio_mode; One declaration per line please, at least unless they are clearly related (and stay under 80 cols). Looks like you can get away with just a fmt variable. > + > + cflag = tty->termios.c_cflag; > + > + block = port->bulk_out_endpointAddress - 1; > + > + vizzini_disable(port); Do you really need to disable the port? > + > + if ((cflag & CSIZE) == CS7) Use C_CSIZE() and friends. > + format_size = UART_FORMAT_SIZE_7; > + else if ((cflag & CSIZE) == CS5) > + /* Enabling 5-bit mode is really 9-bit mode! */ What does this mean? That you have redefined CS5? > + format_size = UART_FORMAT_SIZE_9; > + else > + format_size = UART_FORMAT_SIZE_8; You need to report back the settings actually used (update termios). > + > + if (cflag & PARENB) { C_PARENB() etc. > + if (cflag & PARODD) { > + if (cflag & CMSPAR) > + format_parity = UART_FORMAT_PARITY_1; > + else > + format_parity = UART_FORMAT_PARITY_ODD; > + } else { > + if (cflag & CMSPAR) > + format_parity = UART_FORMAT_PARITY_0; > + else > + format_parity = UART_FORMAT_PARITY_EVEN; > + } > + } else { > + format_parity = UART_FORMAT_PARITY_NONE; > + } > + > + if (cflag & CSTOPB) > + format_stop = UART_FORMAT_STOP_2; > + else > + format_stop = UART_FORMAT_STOP_1; > + > + vizzini_set_reg(port, block, UART_FORMAT, > + format_size | format_parity | format_stop); Error handling. > + > + 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; > + > + vizzini_set_reg(port, block, UART_XON_CHAR, start_char); > + vizzini_set_reg(port, block, UART_XOFF_CHAR, stop_char); > + } else { > + flow = UART_FLOW_MODE_NONE; > + gpio_mode = UART_GPIO_MODE_SEL_GPIO; > + } > + > + vizzini_set_reg(port, block, UART_FLOW, flow); > + vizzini_set_reg(port, block, UART_GPIO_MODE, gpio_mode); > + > + vizzini_set_reg(port, EPLOCALS_REG_BLOCK, > + (block * MEM_EP_LOCALS_SIZE) + EP_WIDE_MODE, > + format_size == UART_FORMAT_SIZE_9); What's this about? > + > + rate = tty_get_baud_rate(tty); > + if (rate) > + vizzini_set_baud_rate(port, rate); > + > + vizzini_enable(port); > +} > + > +static int vizzini_probe(struct usb_serial *serial, > + const struct usb_device_id *id) > +{ > + struct usb_host_interface *iface = serial->interface->cur_altsetting; > + int i; > + > + for (i = 0; i < iface->desc.bNumEndpoints; i++) { > + if (usb_endpoint_is_bulk_out(&iface->endpoint[i].desc)) > + return 0; > + } Set num_bulk_out (and num_bulk_in?) in struct usb_serial_driver and let core handle this for you, and you can drop probe() altogether. > + > + /* No bulk interfaces found */ > + dev_dbg(&serial->dev->dev, "Invalid interface, discarding\n"); > + > + return -ENODEV; > +} > + > +static const struct usb_device_id id_table[] = { > + { USB_DEVICE(0x04e2, 0x1410), }, > + { USB_DEVICE(0x04e2, 0x1412), }, > + { USB_DEVICE(0x04e2, 0x1414), }, > + { }, > +}; > +MODULE_DEVICE_TABLE(usb, id_table); > + > +static const struct usb_device_id vizzini_1410_id_table[] = { > + { USB_DEVICE(0x04e2, 0x1410), }, > + { }, > +}; > +static struct usb_serial_driver vizzini_1410_device = { > + .driver = { > + .owner = THIS_MODULE, > + .name = "vizzini_1410", > + }, > + .id_table = vizzini_1410_id_table, > + .num_ports = 1, > + .set_termios = vizzini_set_termios, > + .probe = vizzini_probe, > +}; > + > +static const struct usb_device_id vizzini_1412_id_table[] = { > + { USB_DEVICE(0x04e2, 0x1412), }, > + { }, > +}; > +static struct usb_serial_driver vizzini_1412_device = { > + .driver = { > + .owner = THIS_MODULE, > + .name = "vizzini_1412", > + }, > + .id_table = vizzini_1412_id_table, > + .num_ports = 2, > + .set_termios = vizzini_set_termios, > +}; > + > +static const struct usb_device_id vizzini_1414_id_table[] = { > + { USB_DEVICE(0x04e2, 0x1414), }, > + { }, > +}; > +static struct usb_serial_driver vizzini_1414_device = { > + .driver = { > + .owner = THIS_MODULE, > + .name = "vizzini_1414", > + }, > + .id_table = vizzini_1414_id_table, > + .num_ports = 4, > + .set_termios = vizzini_set_termios, > +}; > + > +static struct usb_serial_driver * const serial_drivers[] = { > + &vizzini_1410_device, > + &vizzini_1412_device, > + &vizzini_1414_device, > + NULL > +}; I think you should use a single struct usb_serial_driver for all three models and implement the calc_num_ports() callback instead. If you cannot determine the number of ports at runtime, you can encode it in the device-id table (driver_info). > + > +module_usb_serial_driver(serial_drivers, id_table); > + > +MODULE_LICENSE("GPL"); Johan
diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index 25d7e0c36d38..167992ad1d05 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -654,4 +654,13 @@ config USB_SERIAL_DEBUG To compile this driver as a module, choose M here: the module will be called usb-debug. +config USB_SERIAL_VIZZINI + tristate "USB Exar XR21V141x 'Vizzini' Serial Driver" + help + Say Y here if you want to use the Exar XR21V1410/1412/141 + USB 2 serial dapters. + + To compile this driver as a module, choose M here: the + module will be called vizzini. + endif # USB_SERIAL diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index 2d491e434f11..9f65a4863f1d 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_USB_SERIAL_WWAN) += usb_wwan.o obj-$(CONFIG_USB_SERIAL_TI) += ti_usb_3410_5052.o obj-$(CONFIG_USB_SERIAL_UPD78F0730) += upd78f0730.o obj-$(CONFIG_USB_SERIAL_VISOR) += visor.o +obj-$(CONFIG_USB_SERIAL_VIZZINI) += vizzini.o obj-$(CONFIG_USB_SERIAL_WISHBONE) += wishbone-serial.o obj-$(CONFIG_USB_SERIAL_WHITEHEAT) += whiteheat.o obj-$(CONFIG_USB_SERIAL_XIRCOM) += keyspan_pda.o diff --git a/drivers/usb/serial/vizzini.c b/drivers/usb/serial/vizzini.c new file mode 100644 index 000000000000..f4d1b9a75d8a --- /dev/null +++ b/drivers/usb/serial/vizzini.c @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Based on vizzini driver: + * + * Copyright (c) 2013 Exar Corporation, Inc. + * + * Based on USB Serial "Simple" driver + * + * Copyright (C) 2001-2006,2008,2013 Greg Kroah-Hartman <greg@kroah.com> + * Copyright (C) 2005 Arthur Huillet (ahuillet@users.sf.net) + * Copyright (C) 2005 Thomas Hergenhahn <thomas.hergenhahn@suse.de> + * Copyright (C) 2009 Outpost Embedded, LLC + * Copyright (C) 2010 Zilogic Systems <code@zilogic.com> + * Copyright (C) 2013 Wei Shuai <cpuwolf@gmail.com> + * Copyright (C) 2013 Linux Foundation + */ + +#include <linux/kernel.h> +#include <linux/tty.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/usb/serial.h> + +#define XR_SET_REG 0 + +#define URM_REG_BLOCK 4 +#define EPLOCALS_REG_BLOCK 0x66 + +#define MEM_EP_LOCALS_SIZE_S 3 +#define MEM_EP_LOCALS_SIZE (1 << MEM_EP_LOCALS_SIZE_S) + +#define EP_WIDE_MODE 0x03 + +#define UART_GPIO_MODE 0x01a + +#define UART_GPIO_MODE_SEL_M 0x7 +#define UART_GPIO_MODE_SEL_S 0 +#define UART_GPIO_MODE_SEL 0x007 + +#define UART_GPIO_MODE_SEL_GPIO (0x0 << UART_GPIO_MODE_SEL_S) +#define UART_GPIO_MODE_SEL_RTS_CTS (0x1 << UART_GPIO_MODE_SEL_S) +#define UART_GPIO_MODE_SEL_DTR_DSR (0x2 << UART_GPIO_MODE_SEL_S) + +#define UART_ENABLE 0x003 +#define UART_ENABLE_TX_M 0x1 +#define UART_ENABLE_TX_S 0 +#define UART_ENABLE_TX 0x001 +#define UART_ENABLE_RX_M 0x1 +#define UART_ENABLE_RX_S 1 +#define UART_ENABLE_RX 0x002 + +#define UART_CLOCK_DIVISOR_0 0x004 +#define UART_CLOCK_DIVISOR_1 0x005 +#define UART_CLOCK_DIVISOR_2 0x006 + +#define UART_TX_CLOCK_MASK_0 0x007 +#define UART_TX_CLOCK_MASK_1 0x008 + +#define UART_RX_CLOCK_MASK_0 0x009 +#define UART_RX_CLOCK_MASK_1 0x00a + +#define UART_FORMAT 0x00b + +#define UART_FORMAT_SIZE_M 0xf +#define UART_FORMAT_SIZE_S 0 +#define UART_FORMAT_SIZE 0x00f + +#define UART_FORMAT_SIZE_7 (0x7 << UART_FORMAT_SIZE_S) +#define UART_FORMAT_SIZE_8 (0x8 << UART_FORMAT_SIZE_S) +#define UART_FORMAT_SIZE_9 (0x9 << UART_FORMAT_SIZE_S) + +#define UART_FORMAT_PARITY_M 0x7 +#define UART_FORMAT_PARITY_S 4 +#define UART_FORMAT_PARITY 0x070 + +#define UART_FORMAT_PARITY_NONE (0x0 << UART_FORMAT_PARITY_S) +#define UART_FORMAT_PARITY_ODD (0x1 << UART_FORMAT_PARITY_S) +#define UART_FORMAT_PARITY_EVEN (0x2 << UART_FORMAT_PARITY_S) +#define UART_FORMAT_PARITY_1 (0x3 << UART_FORMAT_PARITY_S) +#define UART_FORMAT_PARITY_0 (0x4 << UART_FORMAT_PARITY_S) + +#define UART_FORMAT_STOP_M 0x1 +#define UART_FORMAT_STOP_S 7 +#define UART_FORMAT_STOP 0x080 + +#define UART_FORMAT_STOP_1 (0x0 << UART_FORMAT_STOP_S) +#define UART_FORMAT_STOP_2 (0x1 << UART_FORMAT_STOP_S) + +#define UART_FLOW 0x00c + +#define UART_FLOW_MODE_M 0x7 +#define UART_FLOW_MODE_S 0 +#define UART_FLOW_MODE 0x007 + +#define UART_FLOW_MODE_NONE (0x0 << UART_FLOW_MODE_S) +#define UART_FLOW_MODE_HW (0x1 << UART_FLOW_MODE_S) +#define UART_FLOW_MODE_SW (0x2 << UART_FLOW_MODE_S) + +#define UART_XON_CHAR 0x010 +#define UART_XOFF_CHAR 0x011 + +#define URM_ENABLE_BASE 0x010 +#define URM_ENABLE_0 0x010 +#define URM_ENABLE_0_TX 0x001 +#define URM_ENABLE_0_RX 0x002 + +struct vizzini_baud_rate { + unsigned int tx; + unsigned int rx0; + unsigned int rx1; +}; + +static const struct vizzini_baud_rate vizzini_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 vizzini_set_reg(struct usb_serial_port *port, + unsigned int block, unsigned int regnum, + unsigned int value) +{ + dev_dbg(&port->serial->dev->dev, "%s 0x%02x:0x%02x = 0x%02x\n", + __func__, block, regnum, value); + + return usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), + XR_SET_REG, + USB_DIR_OUT | USB_TYPE_VENDOR, + value, regnum | (block << 8), + NULL, 0, + 5000); +} + +static void vizzini_disable(struct usb_serial_port *port) +{ + unsigned int block = port->bulk_out_endpointAddress - 1; + + vizzini_set_reg(port, block, UART_ENABLE, 0); + vizzini_set_reg(port, URM_REG_BLOCK, URM_ENABLE_BASE + block, 0); +} + +static void vizzini_enable(struct usb_serial_port *port) +{ + unsigned int block = port->bulk_out_endpointAddress - 1; + + vizzini_set_reg(port, URM_REG_BLOCK, URM_ENABLE_BASE + block, + URM_ENABLE_0_TX); + vizzini_set_reg(port, block, UART_ENABLE, + UART_ENABLE_TX | UART_ENABLE_RX); + vizzini_set_reg(port, URM_REG_BLOCK, URM_ENABLE_BASE + block, + URM_ENABLE_0_TX | URM_ENABLE_0_RX); +} + +static void vizzini_set_baud_rate(struct usb_serial_port *port, + unsigned int rate) +{ + int block = port->bulk_out_endpointAddress - 1; + unsigned int divisor = 48000000 / rate; + unsigned int i = ((32 * 48000000) / rate) & 0x1f; + unsigned int tx_mask = vizzini_baud_rates[i].tx; + unsigned int rx_mask = ((divisor & 1) ? vizzini_baud_rates[i].rx1 : + vizzini_baud_rates[i].rx0); + + dev_dbg(&port->serial->dev->dev, + "Setting baud rate to %d: i=%u div=%u tx=%03x rx=%03x\n", + rate, i, divisor, tx_mask, rx_mask); + + vizzini_set_reg(port, block, UART_CLOCK_DIVISOR_0, + (divisor >> 0) & 0xff); + vizzini_set_reg(port, block, UART_CLOCK_DIVISOR_1, + (divisor >> 8) & 0xff); + vizzini_set_reg(port, block, UART_CLOCK_DIVISOR_2, + (divisor >> 16) & 0xff); + + vizzini_set_reg(port, block, UART_TX_CLOCK_MASK_0, + (tx_mask >> 0) & 0xff); + vizzini_set_reg(port, block, UART_TX_CLOCK_MASK_1, + (tx_mask >> 8) & 0xff); + + vizzini_set_reg(port, block, UART_RX_CLOCK_MASK_0, + (rx_mask >> 0) & 0xff); + vizzini_set_reg(port, block, UART_RX_CLOCK_MASK_1, + (rx_mask >> 8) & 0xff); +} + +static void vizzini_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + struct ktermios *termios_old) +{ + unsigned int cflag, block; + speed_t rate; + unsigned int format_size, format_parity, format_stop, flow, gpio_mode; + + cflag = tty->termios.c_cflag; + + block = port->bulk_out_endpointAddress - 1; + + vizzini_disable(port); + + if ((cflag & CSIZE) == CS7) + format_size = UART_FORMAT_SIZE_7; + else if ((cflag & CSIZE) == CS5) + /* Enabling 5-bit mode is really 9-bit mode! */ + format_size = UART_FORMAT_SIZE_9; + else + format_size = UART_FORMAT_SIZE_8; + + if (cflag & PARENB) { + if (cflag & PARODD) { + if (cflag & CMSPAR) + format_parity = UART_FORMAT_PARITY_1; + else + format_parity = UART_FORMAT_PARITY_ODD; + } else { + if (cflag & CMSPAR) + format_parity = UART_FORMAT_PARITY_0; + else + format_parity = UART_FORMAT_PARITY_EVEN; + } + } else { + format_parity = UART_FORMAT_PARITY_NONE; + } + + if (cflag & CSTOPB) + format_stop = UART_FORMAT_STOP_2; + else + format_stop = UART_FORMAT_STOP_1; + + vizzini_set_reg(port, block, UART_FORMAT, + format_size | format_parity | format_stop); + + 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; + + vizzini_set_reg(port, block, UART_XON_CHAR, start_char); + vizzini_set_reg(port, block, UART_XOFF_CHAR, stop_char); + } else { + flow = UART_FLOW_MODE_NONE; + gpio_mode = UART_GPIO_MODE_SEL_GPIO; + } + + vizzini_set_reg(port, block, UART_FLOW, flow); + vizzini_set_reg(port, block, UART_GPIO_MODE, gpio_mode); + + vizzini_set_reg(port, EPLOCALS_REG_BLOCK, + (block * MEM_EP_LOCALS_SIZE) + EP_WIDE_MODE, + format_size == UART_FORMAT_SIZE_9); + + rate = tty_get_baud_rate(tty); + if (rate) + vizzini_set_baud_rate(port, rate); + + vizzini_enable(port); +} + +static int vizzini_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + struct usb_host_interface *iface = serial->interface->cur_altsetting; + int i; + + for (i = 0; i < iface->desc.bNumEndpoints; i++) { + if (usb_endpoint_is_bulk_out(&iface->endpoint[i].desc)) + return 0; + } + + /* No bulk interfaces found */ + dev_dbg(&serial->dev->dev, "Invalid interface, discarding\n"); + + return -ENODEV; +} + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x04e2, 0x1410), }, + { USB_DEVICE(0x04e2, 0x1412), }, + { USB_DEVICE(0x04e2, 0x1414), }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static const struct usb_device_id vizzini_1410_id_table[] = { + { USB_DEVICE(0x04e2, 0x1410), }, + { }, +}; +static struct usb_serial_driver vizzini_1410_device = { + .driver = { + .owner = THIS_MODULE, + .name = "vizzini_1410", + }, + .id_table = vizzini_1410_id_table, + .num_ports = 1, + .set_termios = vizzini_set_termios, + .probe = vizzini_probe, +}; + +static const struct usb_device_id vizzini_1412_id_table[] = { + { USB_DEVICE(0x04e2, 0x1412), }, + { }, +}; +static struct usb_serial_driver vizzini_1412_device = { + .driver = { + .owner = THIS_MODULE, + .name = "vizzini_1412", + }, + .id_table = vizzini_1412_id_table, + .num_ports = 2, + .set_termios = vizzini_set_termios, +}; + +static const struct usb_device_id vizzini_1414_id_table[] = { + { USB_DEVICE(0x04e2, 0x1414), }, + { }, +}; +static struct usb_serial_driver vizzini_1414_device = { + .driver = { + .owner = THIS_MODULE, + .name = "vizzini_1414", + }, + .id_table = vizzini_1414_id_table, + .num_ports = 4, + .set_termios = vizzini_set_termios, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &vizzini_1410_device, + &vizzini_1412_device, + &vizzini_1414_device, + NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_LICENSE("GPL");