diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 0eae4ba..3e0b558 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -23,14 +23,22 @@ #include #include #include +#include #define DRIVER_DESC "Silicon Labs CP210x RS232 serial adaptor driver" +#define CP210x_MAX_DEVICE_STRLEN 256 +#define CP210x_MAX_PRODUCT_STRLEN 126 +#define CP210x_MAX_SERIAL_STRLEN 63 +#define CP210x_MAX_MAXPOWER 250 + + /* * Function Prototypes */ static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *); static void cp210x_close(struct usb_serial_port *); +static int cp210x_ioctl(struct tty_struct *, struct file *, unsigned long); static void cp210x_get_termios(struct tty_struct *, struct usb_serial_port *); static void cp210x_get_termios_port(struct usb_serial_port *port, unsigned int *cflagp, unsigned int *baudp); @@ -198,6 +206,7 @@ static struct usb_serial_driver cp210x_device = { .bulk_out_size = 256, .open = cp210x_open, .close = cp210x_close, + .ioctl = cp210x_ioctl, .break_ctl = cp210x_break_ctl, .set_termios = cp210x_set_termios, .tiocmget = cp210x_tiocmget, @@ -286,6 +295,185 @@ static struct usb_serial_driver * const serial_drivers[] = { #define CONTROL_WRITE_DTR 0x0100 #define CONTROL_WRITE_RTS 0x0200 + + +/* + * cp210x_ctlmsg + * A generic usb control message interface. + * Returns the actual size of the data read or written within the message, 0 + * if no data were read or written, or a negative value to indicate an error. + */ +static int cp210x_ctlmsg(struct usb_serial_port *port, u8 request, + u8 requestype, u16 value, u16 index, void *data, u16 size) +{ + struct usb_serial *serial = port->serial; + u8 *tbuf; + int ret; + + if (!(tbuf = kmalloc(size, GFP_KERNEL))) + return -ENOMEM; + if (requestype & 0x80) { + ret = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), request, + requestype, value, index, tbuf, size, 300); + if (ret > 0 && size) + memcpy(data, tbuf, size); + } else { + if (size) + memcpy(tbuf, data, size); + ret = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), request, + requestype, value, index, tbuf, size, 300); + } + kfree(tbuf); + if (ret < 0 && ret != -EPIPE) { + dev_err(&port->dev, "cp210x: control failed cmd rqt %u " + "rq %u len %u ret %d\n", requestype, request, size, ret); + } + return ret; +} + + +static int cp210x_reset(struct usb_serial_port *port) +{ + /* Is this better than usb_device_reset? It may be. Once a client issues + * the reset ioctl, it must disconnect and reconnect, since the USB + * connections are torn down. We also ignore the error return, since + * the part resets and doesn't send one... + */ + cp210x_ctlmsg(port, 0xff, 0x40, 0x0008, 0x00, 0, 0); + + return 0; +} + +// // cp210x_get_partnum + +static inline int cp210x_setu16(struct usb_serial_port *port, int cmd, + unsigned int value) +{ + return cp210x_ctlmsg(port, 0xff, 0x40, 0x3700 | (cmd & 0xff), + value, 0, 0); +} + + +// make_usb_string +/* + * cp210x_setstr + * + * Set a USB string descriptor using proprietary cp210x control messages. + * Return the number of characters actually written. + */ +static int cp210x_setstr(struct usb_serial_port *port, int cmd, uint8_t *usbstr,size_t len) +{ + int ret = cp210x_ctlmsg(port, 0xff, 0x40, 0x3700 | (cmd & 0xff), 0, + usbstr, len); + dbg("%s - cmd 0x%02x len %d ret %d", __FUNCTION__, cmd, len, ret); + printk(KERN_ERR "%s - cmd 0x%02x len %d ret %d %x %x %x %x %x %x", __FUNCTION__, cmd, len, ret, + usbstr[0],usbstr[1],usbstr[2],usbstr[3],usbstr[4],usbstr[5]); + return ret; +} + + + +/* Set all gpio simultaneously */ +static int cp210x_gpioset(struct usb_serial_port *port, int gpio) +{ + dbg("%s - port %d, gpio = 0x%.2x", __FUNCTION__, port->number, gpio); + + return cp210x_ctlmsg(port, 0xff, 0x40, 0x37e1, + ((uint16_t)gpio << 8) | CP_GPIO_MASK, 0, 0); +} + +/* Set select gpio bits */ +static int cp210x_gpiosetb(struct usb_serial_port *port, int set, int clear) +{ + u16 gpio = 0; + + gpio |= set | clear; + gpio |= set << 8; + + dbg("%s - port %d, gpiob = 0x%.4x", __FUNCTION__, port->number, gpio); + + /* FIXME: how about REQTYPE_HOST_TO_DEVICE instead of 0x40? */ + return cp210x_ctlmsg(port, 0xff, 0x40, 0x37e1, gpio, 0, 0); +} + +static int cp210x_gpioget(struct usb_serial_port *port, int *gpio_ret) +{ + int ret; + u8 gpio; + + dbg("%s - port %d", __FUNCTION__, port->number); + + /* FIXME: how about REQTYPE_DEVICE_TO_HOST instead of 0xc0? */ + ret = cp210x_ctlmsg(port, 0xff, 0xc0, 0x00c2, 0, &gpio, 1); + + dbg("%s - gpio = 0x%.2x (%d)", __FUNCTION__, gpio, ret); + + *gpio_ret=gpio; + + return (ret == 1) ? 0 : -EPROTO; +} + +static int cp210x_portconfset(struct usb_serial_port *port, + struct cp210x_port_config *config) +{ + struct cp210x_port_config be_config; + int ret; + + dbg("%s", __FUNCTION__); + + memset(&be_config, 0, sizeof(be_config)); + + be_config.reset.mode = cpu_to_be16(config->reset.mode); + be_config.reset.low_power = 0; // cpu_to_be16(config->reset.low_power); + be_config.reset.latch = cpu_to_be16(config->reset.latch); + + be_config.suspend.mode = cpu_to_be16(config->suspend.mode); + be_config.suspend.low_power = 0; // cpu_to_be16(config->suspend.low_power); + be_config.suspend.latch = cpu_to_be16(config->suspend.latch); + + be_config.enhanced_fxn = config->enhanced_fxn; + + + ret = cp210x_ctlmsg(port, 0xff, 0x40, 0x370c, 0, &be_config, sizeof(be_config)); + if (ret == sizeof(be_config)) + return 0; + else if (ret >= 0) + return -EPROTO; + else + return ret; +} + +static int cp210x_portconfget(struct usb_serial_port *port, + struct cp210x_port_config *config) +{ + int ret; + + dbg("%s", __FUNCTION__); + + ret = cp210x_ctlmsg(port, 0xff, 0xc0, 0x370c, 0, config, sizeof(*config)); + + if (ret != sizeof(*config)) + return (ret >= 0) ? -EPROTO : ret; + + /* Words from cp2103 are MSB */ + + config->reset.mode = be16_to_cpu(config->reset.mode); + config->reset.low_power = be16_to_cpu(config->reset.low_power); + config->reset.latch = be16_to_cpu(config->reset.latch); + + config->suspend.mode = be16_to_cpu(config->suspend.mode); + config->suspend.low_power = be16_to_cpu(config->suspend.low_power); + config->suspend.latch = be16_to_cpu(config->suspend.latch); + + /* apparently not implemented yet */ + config->reset.low_power = 0; + config->suspend.low_power = 0; + + return 0; +} + + + /* * cp210x_get_config * Reads from the CP210x configuration registers @@ -469,6 +657,163 @@ static void cp210x_close(struct usb_serial_port *port) cp210x_set_config_single(port, CP210X_IFC_ENABLE, UART_DISABLE); } +static int cp210x_string_ioctl(struct usb_serial_port *port, unsigned long arg, int cmd,size_t max_len) +{ + uint8_t buf[CP210x_MAX_STRLEN]; + size_t len; + + if (copy_from_user(buf,(uint8_t *)arg,1)) + return -EFAULT; + + len=buf[0]; + + if (len>max_len) + return -EMSGSIZE; + + if (copy_from_user(buf,(uint8_t *)arg,len)) + return -EFAULT; + + if (len && cp210x_setstr(port, cmd, buf, len) != len) + return -EPROTO; + + return 0; +} + + +static int cp210x_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + struct cp210x_port_config config; + int iarg; + int ret = -ENOIOCTLCMD; + + dbg("%s (%d) cmd = 0x%04x", __FUNCTION__, port->number, cmd); + + switch (cmd) { + case CPIOC_GPIOGET: + /*if (cp210x_get_partnum(port) != CP210x_CP2103_VERSION) break; */ + ret = cp210x_gpioget(port, &iarg); + + if (ret) + break; + + if (copy_to_user((int *)arg, &iarg, sizeof(iarg))) + ret = -EFAULT; + + break; + + case CPIOC_GPIOSET: + /*if (cp210x_get_partnum(port) != CP210x_CP2103_VERSION) break; */ + + if (copy_from_user(&iarg,(int *)arg, sizeof(iarg))) { + ret = -EFAULT; + break; + } + + ret = cp210x_gpioset(port, iarg); + + break; + + case CPIOC_GPIOBIS: + /*if (cp210x_get_partnum(port) != CP210x_CP2103_VERSION) break; */ + + if (copy_from_user(&iarg, (int *)arg, sizeof(iarg))) { + ret = -EFAULT; + break; + } + + ret = cp210x_gpiosetb(port, iarg, 0); + + break; + + case CPIOC_GPIOBIC: + /*if (cp210x_get_partnum(port) != CP210x_CP2103_VERSION) break; */ + + if (copy_from_user(&iarg, (int *)arg, sizeof(iarg))) { + ret = -EFAULT; + break; + } + + ret = cp210x_gpiosetb(port, 0, iarg); + + break; + + case CPIOC_DEVICERESET: + ret = cp210x_reset(port); + break; + + case CPIOC_PORTCONFGET: + ret = cp210x_portconfget(port, &config); + + if (ret) break; + + if (copy_to_user((struct cp210x_port_state *)arg, &config, sizeof(config))) + ret = -EFAULT; + + break; + + case CPIOC_PORTCONFSET: + if (copy_from_user(&config, (struct cp210x_port_state *)arg, sizeof(config))) { + ret = -EFAULT; + break; + } + + ret = cp210x_portconfset(port, &config); + break; + + case CPIOC_SETVID: + if (copy_from_user(&iarg, (int *)arg,sizeof(iarg))) { + ret = -EFAULT; + break; + } + + ret = cp210x_setu16(port, 0x01, iarg); + + break; + + case CPIOC_SETPID: + if (copy_from_user(&iarg, (int *)arg,sizeof(iarg))) { + ret = -EFAULT; + break; + } + + ret = cp210x_setu16(port, 0x02, iarg); + + break; + + case CPIOC_SETMFG: + ret = cp210x_string_ioctl(port, arg, 0x0, CP210x_MAX_MFG_STRLEN); + break; + + case CPIOC_SETPRODUCT: + ret = cp210x_string_ioctl(port, arg, 0x3, CP210x_MAX_PRODUCT_STRLEN); + + break; + + case CPIOC_SETSERIAL: + ret = cp210x_string_ioctl(port, arg, 0x3, CP210x_MAX_SERIAL_STRLEN); + break; + + case CPIOC_SETDEVVER: + if (copy_from_user(&iarg, (int *)arg,sizeof(iarg))) { + ret = -EFAULT; + break; + } + + ret = cp210x_setu16(port, 0x07, iarg); + + break; + default: + dbg("%s not supported = 0x%04x", __FUNCTION__, cmd); + break; + } + + return ret; +} + + + + /* * cp210x_get_termios * Reads the baud rate, data bits, parity, stop bits and flow control mode diff --git a/include/linux/cp210x.h b/include/linux/cp210x.h new file mode 100644 index 0000000..39faf96 --- /dev/null +++ b/include/linux/cp210x.h @@ -0,0 +1,100 @@ +/* + * + */ +#ifndef _CPIO210X_H +#define _CPIO210X_H + +#define CP210X_IOCTL_BASE 'C' + +//struct watchdog_info { +// __u32 options; /* Options the card/driver supports */ +// __u32 firmware_version; /* Firmware version of the card */ +// __u8 identity[32]; /* Identity of the board */ +//}; + +struct cp210x_port_state { + uint16_t mode; /* push-pull = 1, open-drain = 0 */ + uint16_t low_power; /* 1 = ground the pin, and disable */ + uint16_t latch; +} __attribute__((packed)); + +/* LOPWR MODE LATCH */ +/* 0 0 0 Pin drivers are totem pole, and inital config sinks current to GND */ +/* 0 0 1 Pin drivers are totem pole, and inital config sources current from VIO */ +/* 0 1 0 Pin drivers are open drain, and inital config sinks current to GND */ +/* 0 1 1 Pin drivers are open drain, and inital config leaves pin HI-Z (you want this for inputs) */ +/* 1 X X Pin drivers are disabled, and pin sinks current to GND */ + + +struct cp210x_port_config { + struct cp210x_port_state reset; + struct cp210x_port_state suspend; + uint8_t enhanced_fxn; +} __attribute__((packed)); + + +#define CP_PIN_RI (1 << 0) +#define CP_PIN_DCD (1 << 1) +#define CP_PIN_DTR (1 << 2) +#define CP_PIN_DSR (1 << 3) +#define CP_PIN_TXD (1 << 4) +#define CP_PIN_RXD (1 << 5) +#define CP_PIN_RTS (1 << 6) +#define CP_PIN_CTS (1 << 7) +#define CP_PIN_GPIO_0 (1 << 8) +#define CP_PIN_GPIO_1 (1 << 9) +#define CP_PIN_GPIO_2 (1 << 10) +#define CP_PIN_GPIO_3 (1 << 11) +#define CP_PIN_UNUSED_1 (1 << 12) +#define CP_PIN_UNUSED_2 (1 << 13) +#define CP_PIN_GPIO_SUSPEND (1 << 14) +#define CP_PIN_GPIO_NSUSPEND (1 << 15) + +#define CP_INPUT_PINS (CP_PIN_RXD|CP_PIN_CTS|CP_PIN_DSR|CP_PIN_RI|CP_PIN_DCD) + + +#define CP_EFXN_GPIO_0_IS_TXLED (1 << 0) +#define CP_EFXN_GPIO_1_IS_RXLED (1 << 1) +#define CP_EFXN_GPIO_2_IS_RS485_TX (1 << 2) +#define CP_EFXN_UNUSED_1 (1 << 3) /* Set to zero */ +#define CP_EFXN_ENABLE_WPU (1 << 4) +#define CP_EFXN_UNUSED_2 (1 << 5) /* Set to zero */ +#define CP_EFXN_SERIAL_AUTOOFF (1 << 6) +#define CP_EFXN_GPIOL_AUTOOFF (1 << 7) + + + + + + +#define CPIOC_GPIOGET _IOR(CP210X_IOCTL_BASE, 0, int) +#define CPIOC_GPIOSET _IOW(CP210X_IOCTL_BASE, 1, int) +#define CPIOC_GPIOBIC _IOW(CP210X_IOCTL_BASE, 2, int) +#define CPIOC_GPIOBIS _IOW(CP210X_IOCTL_BASE, 3, int) + +#define CPIOC_DEVICERESET _IO(CP210X_IOCTL_BASE,4) +#define CPIOC_PORTCONFGET _IOR(CP210X_IOCTL_BASE, 5, struct cp210x_port_config) +#define CPIOC_PORTCONFSET _IOW(CP210X_IOCTL_BASE, 6, struct cp210x_port_config) + +#define CPIOC_SETVID _IOW(CP210X_IOCTL_BASE,7, int) +#define CPIOC_SETPID _IOW(CP210X_IOCTL_BASE,8, int) +#define CPIOC_SETMFG _IOW(CP210X_IOCTL_BASE,9, char *) +#define CPIOC_SETPRODUCT _IOW(CP210X_IOCTL_BASE,10, char *) +#define CPIOC_SETSERIAL _IOW(CP210X_IOCTL_BASE,11, char *) +#define CPIOC_SETDEVVER _IOW(CP210X_IOCTL_BASE,12, int) + +/* CP2103 GPIO */ +#define CP_GPIO_0 0x01 +#define CP_GPIO_1 0x02 +#define CP_GPIO_2 0x04 +#define CP_GPIO_3 0x08 +#define CP_GPIO_MASK (CP_GPIO_0|CP_GPIO_1|CP_GPIO_2|CP_GPIO_3) + + +#define CP210x_MAX_MFG_STRLEN 128 +#define CP210x_MAX_PRODUCT_STRLEN 126 +#define CP210x_MAX_SERIAL_STRLEN 63 +#define CP210x_MAX_MAXPOWER 250 + +#endif /* _CPIO210X_H */ +