|  | /* | 
|  | * spcp8x5 USB to serial adaptor driver | 
|  | * | 
|  | * Copyright (C) 2010-2013 Johan Hovold ([email protected]) | 
|  | * Copyright (C) 2006 Linxb ([email protected]) | 
|  | * Copyright (C) 2006 S1 Corp. | 
|  | * | 
|  | * Original driver for 2.6.10 pl2303 driver by | 
|  | *   Greg Kroah-Hartman ([email protected]) | 
|  | * Changes for 2.6.20 by Harald Klein <[email protected]> | 
|  | * | 
|  | *	This program is free software; you can redistribute it and/or modify | 
|  | *	it under the terms of the GNU General Public License as published by | 
|  | *	the Free Software Foundation; either version 2 of the License, or | 
|  | *	(at your option) any later version. | 
|  | */ | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/errno.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/tty.h> | 
|  | #include <linux/tty_driver.h> | 
|  | #include <linux/tty_flip.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/spinlock.h> | 
|  | #include <linux/usb.h> | 
|  | #include <linux/usb/serial.h> | 
|  |  | 
|  | #define DRIVER_DESC	"SPCP8x5 USB to serial adaptor driver" | 
|  |  | 
|  | #define SPCP825_QUIRK_NO_UART_STATUS	0x01 | 
|  | #define SPCP825_QUIRK_NO_WORK_MODE	0x02 | 
|  |  | 
|  | #define SPCP8x5_007_VID		0x04FC | 
|  | #define SPCP8x5_007_PID		0x0201 | 
|  | #define SPCP8x5_008_VID		0x04fc | 
|  | #define SPCP8x5_008_PID		0x0235 | 
|  | #define SPCP8x5_PHILIPS_VID	0x0471 | 
|  | #define SPCP8x5_PHILIPS_PID	0x081e | 
|  | #define SPCP8x5_INTERMATIC_VID	0x04FC | 
|  | #define SPCP8x5_INTERMATIC_PID	0x0204 | 
|  | #define SPCP8x5_835_VID		0x04fc | 
|  | #define SPCP8x5_835_PID		0x0231 | 
|  |  | 
|  | static const struct usb_device_id id_table[] = { | 
|  | { USB_DEVICE(SPCP8x5_PHILIPS_VID , SPCP8x5_PHILIPS_PID)}, | 
|  | { USB_DEVICE(SPCP8x5_INTERMATIC_VID, SPCP8x5_INTERMATIC_PID)}, | 
|  | { USB_DEVICE(SPCP8x5_835_VID, SPCP8x5_835_PID)}, | 
|  | { USB_DEVICE(SPCP8x5_008_VID, SPCP8x5_008_PID)}, | 
|  | { USB_DEVICE(SPCP8x5_007_VID, SPCP8x5_007_PID), | 
|  | .driver_info = SPCP825_QUIRK_NO_UART_STATUS | | 
|  | SPCP825_QUIRK_NO_WORK_MODE }, | 
|  | { }					/* Terminating entry */ | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(usb, id_table); | 
|  |  | 
|  | struct spcp8x5_usb_ctrl_arg { | 
|  | u8	type; | 
|  | u8	cmd; | 
|  | u8	cmd_type; | 
|  | u16	value; | 
|  | u16	index; | 
|  | u16	length; | 
|  | }; | 
|  |  | 
|  |  | 
|  | /* spcp8x5 spec register define */ | 
|  | #define MCR_CONTROL_LINE_RTS		0x02 | 
|  | #define MCR_CONTROL_LINE_DTR		0x01 | 
|  | #define MCR_DTR				0x01 | 
|  | #define MCR_RTS				0x02 | 
|  |  | 
|  | #define MSR_STATUS_LINE_DCD		0x80 | 
|  | #define MSR_STATUS_LINE_RI		0x40 | 
|  | #define MSR_STATUS_LINE_DSR		0x20 | 
|  | #define MSR_STATUS_LINE_CTS		0x10 | 
|  |  | 
|  | /* verdor command here , we should define myself */ | 
|  | #define SET_DEFAULT			0x40 | 
|  | #define SET_DEFAULT_TYPE		0x20 | 
|  |  | 
|  | #define SET_UART_FORMAT			0x40 | 
|  | #define SET_UART_FORMAT_TYPE		0x21 | 
|  | #define SET_UART_FORMAT_SIZE_5		0x00 | 
|  | #define SET_UART_FORMAT_SIZE_6		0x01 | 
|  | #define SET_UART_FORMAT_SIZE_7		0x02 | 
|  | #define SET_UART_FORMAT_SIZE_8		0x03 | 
|  | #define SET_UART_FORMAT_STOP_1		0x00 | 
|  | #define SET_UART_FORMAT_STOP_2		0x04 | 
|  | #define SET_UART_FORMAT_PAR_NONE	0x00 | 
|  | #define SET_UART_FORMAT_PAR_ODD		0x10 | 
|  | #define SET_UART_FORMAT_PAR_EVEN	0x30 | 
|  | #define SET_UART_FORMAT_PAR_MASK	0xD0 | 
|  | #define SET_UART_FORMAT_PAR_SPACE	0x90 | 
|  |  | 
|  | #define GET_UART_STATUS_TYPE		0xc0 | 
|  | #define GET_UART_STATUS			0x22 | 
|  | #define GET_UART_STATUS_MSR		0x06 | 
|  |  | 
|  | #define SET_UART_STATUS			0x40 | 
|  | #define SET_UART_STATUS_TYPE		0x23 | 
|  | #define SET_UART_STATUS_MCR		0x0004 | 
|  | #define SET_UART_STATUS_MCR_DTR		0x01 | 
|  | #define SET_UART_STATUS_MCR_RTS		0x02 | 
|  | #define SET_UART_STATUS_MCR_LOOP	0x10 | 
|  |  | 
|  | #define SET_WORKING_MODE		0x40 | 
|  | #define SET_WORKING_MODE_TYPE		0x24 | 
|  | #define SET_WORKING_MODE_U2C		0x00 | 
|  | #define SET_WORKING_MODE_RS485		0x01 | 
|  | #define SET_WORKING_MODE_PDMA		0x02 | 
|  | #define SET_WORKING_MODE_SPP		0x03 | 
|  |  | 
|  | #define SET_FLOWCTL_CHAR		0x40 | 
|  | #define SET_FLOWCTL_CHAR_TYPE		0x25 | 
|  |  | 
|  | #define GET_VERSION			0xc0 | 
|  | #define GET_VERSION_TYPE		0x26 | 
|  |  | 
|  | #define SET_REGISTER			0x40 | 
|  | #define SET_REGISTER_TYPE		0x27 | 
|  |  | 
|  | #define	GET_REGISTER			0xc0 | 
|  | #define GET_REGISTER_TYPE		0x28 | 
|  |  | 
|  | #define SET_RAM				0x40 | 
|  | #define SET_RAM_TYPE			0x31 | 
|  |  | 
|  | #define GET_RAM				0xc0 | 
|  | #define GET_RAM_TYPE			0x32 | 
|  |  | 
|  | /* how come ??? */ | 
|  | #define UART_STATE			0x08 | 
|  | #define UART_STATE_TRANSIENT_MASK	0x75 | 
|  | #define UART_DCD			0x01 | 
|  | #define UART_DSR			0x02 | 
|  | #define UART_BREAK_ERROR		0x04 | 
|  | #define UART_RING			0x08 | 
|  | #define UART_FRAME_ERROR		0x10 | 
|  | #define UART_PARITY_ERROR		0x20 | 
|  | #define UART_OVERRUN_ERROR		0x40 | 
|  | #define UART_CTS			0x80 | 
|  |  | 
|  | struct spcp8x5_private { | 
|  | unsigned		quirks; | 
|  | spinlock_t		lock; | 
|  | u8			line_control; | 
|  | }; | 
|  |  | 
|  | static int spcp8x5_probe(struct usb_serial *serial, | 
|  | const struct usb_device_id *id) | 
|  | { | 
|  | usb_set_serial_data(serial, (void *)id); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int spcp8x5_port_probe(struct usb_serial_port *port) | 
|  | { | 
|  | const struct usb_device_id *id = usb_get_serial_data(port->serial); | 
|  | struct spcp8x5_private *priv; | 
|  |  | 
|  | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | 
|  | if (!priv) | 
|  | return -ENOMEM; | 
|  |  | 
|  | spin_lock_init(&priv->lock); | 
|  | priv->quirks = id->driver_info; | 
|  |  | 
|  | usb_set_serial_port_data(port, priv); | 
|  |  | 
|  | port->port.drain_delay = 256; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int spcp8x5_port_remove(struct usb_serial_port *port) | 
|  | { | 
|  | struct spcp8x5_private *priv; | 
|  |  | 
|  | priv = usb_get_serial_port_data(port); | 
|  | kfree(priv); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int spcp8x5_set_ctrl_line(struct usb_serial_port *port, u8 mcr) | 
|  | { | 
|  | struct spcp8x5_private *priv = usb_get_serial_port_data(port); | 
|  | struct usb_device *dev = port->serial->dev; | 
|  | int retval; | 
|  |  | 
|  | if (priv->quirks & SPCP825_QUIRK_NO_UART_STATUS) | 
|  | return -EPERM; | 
|  |  | 
|  | retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), | 
|  | SET_UART_STATUS_TYPE, SET_UART_STATUS, | 
|  | mcr, 0x04, NULL, 0, 100); | 
|  | if (retval != 0) { | 
|  | dev_err(&port->dev, "failed to set control lines: %d\n", | 
|  | retval); | 
|  | } | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | static int spcp8x5_get_msr(struct usb_serial_port *port, u8 *status) | 
|  | { | 
|  | struct spcp8x5_private *priv = usb_get_serial_port_data(port); | 
|  | struct usb_device *dev = port->serial->dev; | 
|  | u8 *buf; | 
|  | int ret; | 
|  |  | 
|  | if (priv->quirks & SPCP825_QUIRK_NO_UART_STATUS) | 
|  | return -EPERM; | 
|  |  | 
|  | buf = kzalloc(1, GFP_KERNEL); | 
|  | if (!buf) | 
|  | return -ENOMEM; | 
|  |  | 
|  | ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), | 
|  | GET_UART_STATUS, GET_UART_STATUS_TYPE, | 
|  | 0, GET_UART_STATUS_MSR, buf, 1, 100); | 
|  | if (ret < 0) | 
|  | dev_err(&port->dev, "failed to get modem status: %d\n", ret); | 
|  |  | 
|  | dev_dbg(&port->dev, "0xc0:0x22:0:6  %d - 0x02%x\n", ret, *buf); | 
|  | *status = *buf; | 
|  | kfree(buf); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void spcp8x5_set_work_mode(struct usb_serial_port *port, u16 value, | 
|  | u16 index) | 
|  | { | 
|  | struct spcp8x5_private *priv = usb_get_serial_port_data(port); | 
|  | struct usb_device *dev = port->serial->dev; | 
|  | int ret; | 
|  |  | 
|  | if (priv->quirks & SPCP825_QUIRK_NO_WORK_MODE) | 
|  | return; | 
|  |  | 
|  | ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), | 
|  | SET_WORKING_MODE_TYPE, SET_WORKING_MODE, | 
|  | value, index, NULL, 0, 100); | 
|  | dev_dbg(&port->dev, "value = %#x , index = %#x\n", value, index); | 
|  | if (ret < 0) | 
|  | dev_err(&port->dev, "failed to set work mode: %d\n", ret); | 
|  | } | 
|  |  | 
|  | static int spcp8x5_carrier_raised(struct usb_serial_port *port) | 
|  | { | 
|  | u8 msr; | 
|  | int ret; | 
|  |  | 
|  | ret = spcp8x5_get_msr(port, &msr); | 
|  | if (ret || msr & MSR_STATUS_LINE_DCD) | 
|  | return 1; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void spcp8x5_dtr_rts(struct usb_serial_port *port, int on) | 
|  | { | 
|  | struct spcp8x5_private *priv = usb_get_serial_port_data(port); | 
|  | unsigned long flags; | 
|  | u8 control; | 
|  |  | 
|  | spin_lock_irqsave(&priv->lock, flags); | 
|  | if (on) | 
|  | priv->line_control = MCR_CONTROL_LINE_DTR | 
|  | | MCR_CONTROL_LINE_RTS; | 
|  | else | 
|  | priv->line_control &= ~ (MCR_CONTROL_LINE_DTR | 
|  | | MCR_CONTROL_LINE_RTS); | 
|  | control = priv->line_control; | 
|  | spin_unlock_irqrestore(&priv->lock, flags); | 
|  | spcp8x5_set_ctrl_line(port, control); | 
|  | } | 
|  |  | 
|  | static void spcp8x5_init_termios(struct tty_struct *tty) | 
|  | { | 
|  | tty->termios = tty_std_termios; | 
|  | tty->termios.c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL; | 
|  | tty->termios.c_ispeed = 115200; | 
|  | tty->termios.c_ospeed = 115200; | 
|  | } | 
|  |  | 
|  | static void spcp8x5_set_termios(struct tty_struct *tty, | 
|  | struct usb_serial_port *port, struct ktermios *old_termios) | 
|  | { | 
|  | struct usb_serial *serial = port->serial; | 
|  | struct spcp8x5_private *priv = usb_get_serial_port_data(port); | 
|  | unsigned long flags; | 
|  | unsigned int cflag = tty->termios.c_cflag; | 
|  | unsigned short uartdata; | 
|  | unsigned char buf[2] = {0, 0}; | 
|  | int baud; | 
|  | int i; | 
|  | u8 control; | 
|  |  | 
|  | /* check that they really want us to change something */ | 
|  | if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios)) | 
|  | return; | 
|  |  | 
|  | /* set DTR/RTS active */ | 
|  | spin_lock_irqsave(&priv->lock, flags); | 
|  | control = priv->line_control; | 
|  | if (old_termios && (old_termios->c_cflag & CBAUD) == B0) { | 
|  | priv->line_control |= MCR_DTR; | 
|  | if (!(old_termios->c_cflag & CRTSCTS)) | 
|  | priv->line_control |= MCR_RTS; | 
|  | } | 
|  | if (control != priv->line_control) { | 
|  | control = priv->line_control; | 
|  | spin_unlock_irqrestore(&priv->lock, flags); | 
|  | spcp8x5_set_ctrl_line(port, control); | 
|  | } else { | 
|  | spin_unlock_irqrestore(&priv->lock, flags); | 
|  | } | 
|  |  | 
|  | /* Set Baud Rate */ | 
|  | baud = tty_get_baud_rate(tty); | 
|  | switch (baud) { | 
|  | case 300:	buf[0] = 0x00;	break; | 
|  | case 600:	buf[0] = 0x01;	break; | 
|  | case 1200:	buf[0] = 0x02;	break; | 
|  | case 2400:	buf[0] = 0x03;	break; | 
|  | case 4800:	buf[0] = 0x04;	break; | 
|  | case 9600:	buf[0] = 0x05;	break; | 
|  | case 19200:	buf[0] = 0x07;	break; | 
|  | case 38400:	buf[0] = 0x09;	break; | 
|  | case 57600:	buf[0] = 0x0a;	break; | 
|  | case 115200:	buf[0] = 0x0b;	break; | 
|  | case 230400:	buf[0] = 0x0c;	break; | 
|  | case 460800:	buf[0] = 0x0d;	break; | 
|  | case 921600:	buf[0] = 0x0e;	break; | 
|  | /*	case 1200000:	buf[0] = 0x0f;	break; */ | 
|  | /*	case 2400000:	buf[0] = 0x10;	break; */ | 
|  | case 3000000:	buf[0] = 0x11;	break; | 
|  | /*	case 6000000:	buf[0] = 0x12;	break; */ | 
|  | case 0: | 
|  | case 1000000: | 
|  | buf[0] = 0x0b;	break; | 
|  | default: | 
|  | dev_err(&port->dev, "unsupported baudrate, using 9600\n"); | 
|  | } | 
|  |  | 
|  | /* Set Data Length : 00:5bit, 01:6bit, 10:7bit, 11:8bit */ | 
|  | switch (cflag & CSIZE) { | 
|  | case CS5: | 
|  | buf[1] |= SET_UART_FORMAT_SIZE_5; | 
|  | break; | 
|  | case CS6: | 
|  | buf[1] |= SET_UART_FORMAT_SIZE_6; | 
|  | break; | 
|  | case CS7: | 
|  | buf[1] |= SET_UART_FORMAT_SIZE_7; | 
|  | break; | 
|  | default: | 
|  | case CS8: | 
|  | buf[1] |= SET_UART_FORMAT_SIZE_8; | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* Set Stop bit2 : 0:1bit 1:2bit */ | 
|  | buf[1] |= (cflag & CSTOPB) ? SET_UART_FORMAT_STOP_2 : | 
|  | SET_UART_FORMAT_STOP_1; | 
|  |  | 
|  | /* Set Parity bit3-4 01:Odd 11:Even */ | 
|  | if (cflag & PARENB) { | 
|  | buf[1] |= (cflag & PARODD) ? | 
|  | SET_UART_FORMAT_PAR_ODD : SET_UART_FORMAT_PAR_EVEN ; | 
|  | } else { | 
|  | buf[1] |= SET_UART_FORMAT_PAR_NONE; | 
|  | } | 
|  | uartdata = buf[0] | buf[1]<<8; | 
|  |  | 
|  | i = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), | 
|  | SET_UART_FORMAT_TYPE, SET_UART_FORMAT, | 
|  | uartdata, 0, NULL, 0, 100); | 
|  | if (i < 0) | 
|  | dev_err(&port->dev, "Set UART format %#x failed (error = %d)\n", | 
|  | uartdata, i); | 
|  | dev_dbg(&port->dev, "0x21:0x40:0:0  %d\n", i); | 
|  |  | 
|  | if (cflag & CRTSCTS) { | 
|  | /* enable hardware flow control */ | 
|  | spcp8x5_set_work_mode(port, 0x000a, SET_WORKING_MODE_U2C); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int spcp8x5_open(struct tty_struct *tty, struct usb_serial_port *port) | 
|  | { | 
|  | struct usb_serial *serial = port->serial; | 
|  | struct spcp8x5_private *priv = usb_get_serial_port_data(port); | 
|  | int ret; | 
|  |  | 
|  | usb_clear_halt(serial->dev, port->write_urb->pipe); | 
|  | usb_clear_halt(serial->dev, port->read_urb->pipe); | 
|  |  | 
|  | ret = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), | 
|  | 0x09, 0x00, | 
|  | 0x01, 0x00, NULL, 0x00, 100); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | spcp8x5_set_ctrl_line(port, priv->line_control); | 
|  |  | 
|  | if (tty) | 
|  | spcp8x5_set_termios(tty, port, NULL); | 
|  |  | 
|  | return usb_serial_generic_open(tty, port); | 
|  | } | 
|  |  | 
|  | static int spcp8x5_tiocmset(struct tty_struct *tty, | 
|  | unsigned int set, unsigned int clear) | 
|  | { | 
|  | struct usb_serial_port *port = tty->driver_data; | 
|  | struct spcp8x5_private *priv = usb_get_serial_port_data(port); | 
|  | unsigned long flags; | 
|  | u8 control; | 
|  |  | 
|  | spin_lock_irqsave(&priv->lock, flags); | 
|  | if (set & TIOCM_RTS) | 
|  | priv->line_control |= MCR_RTS; | 
|  | if (set & TIOCM_DTR) | 
|  | priv->line_control |= MCR_DTR; | 
|  | if (clear & TIOCM_RTS) | 
|  | priv->line_control &= ~MCR_RTS; | 
|  | if (clear & TIOCM_DTR) | 
|  | priv->line_control &= ~MCR_DTR; | 
|  | control = priv->line_control; | 
|  | spin_unlock_irqrestore(&priv->lock, flags); | 
|  |  | 
|  | return spcp8x5_set_ctrl_line(port, control); | 
|  | } | 
|  |  | 
|  | static int spcp8x5_tiocmget(struct tty_struct *tty) | 
|  | { | 
|  | struct usb_serial_port *port = tty->driver_data; | 
|  | struct spcp8x5_private *priv = usb_get_serial_port_data(port); | 
|  | unsigned long flags; | 
|  | unsigned int mcr; | 
|  | u8 status; | 
|  | unsigned int result; | 
|  |  | 
|  | result = spcp8x5_get_msr(port, &status); | 
|  | if (result) | 
|  | return result; | 
|  |  | 
|  | spin_lock_irqsave(&priv->lock, flags); | 
|  | mcr = priv->line_control; | 
|  | spin_unlock_irqrestore(&priv->lock, flags); | 
|  |  | 
|  | result = ((mcr & MCR_DTR)			? TIOCM_DTR : 0) | 
|  | | ((mcr & MCR_RTS)			? TIOCM_RTS : 0) | 
|  | | ((status & MSR_STATUS_LINE_CTS)	? TIOCM_CTS : 0) | 
|  | | ((status & MSR_STATUS_LINE_DSR)	? TIOCM_DSR : 0) | 
|  | | ((status & MSR_STATUS_LINE_RI)	? TIOCM_RI  : 0) | 
|  | | ((status & MSR_STATUS_LINE_DCD)	? TIOCM_CD  : 0); | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | static struct usb_serial_driver spcp8x5_device = { | 
|  | .driver = { | 
|  | .owner =	THIS_MODULE, | 
|  | .name =		"SPCP8x5", | 
|  | }, | 
|  | .id_table		= id_table, | 
|  | .num_ports		= 1, | 
|  | .open			= spcp8x5_open, | 
|  | .dtr_rts		= spcp8x5_dtr_rts, | 
|  | .carrier_raised		= spcp8x5_carrier_raised, | 
|  | .set_termios		= spcp8x5_set_termios, | 
|  | .init_termios		= spcp8x5_init_termios, | 
|  | .tiocmget		= spcp8x5_tiocmget, | 
|  | .tiocmset		= spcp8x5_tiocmset, | 
|  | .probe			= spcp8x5_probe, | 
|  | .port_probe		= spcp8x5_port_probe, | 
|  | .port_remove		= spcp8x5_port_remove, | 
|  | }; | 
|  |  | 
|  | static struct usb_serial_driver * const serial_drivers[] = { | 
|  | &spcp8x5_device, NULL | 
|  | }; | 
|  |  | 
|  | module_usb_serial_driver(serial_drivers, id_table); | 
|  |  | 
|  | MODULE_DESCRIPTION(DRIVER_DESC); | 
|  | MODULE_LICENSE("GPL"); |