diff options
Diffstat (limited to 'drivers/usb/serial/cp210x.c')
-rw-r--r-- | drivers/usb/serial/cp210x.c | 421 |
1 files changed, 340 insertions, 81 deletions
diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 626a29d9aa58..c0777a374a88 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -239,11 +239,14 @@ MODULE_DEVICE_TABLE(usb, id_table); struct cp210x_serial_private { #ifdef CONFIG_GPIOLIB struct gpio_chip gc; - u8 config; - u8 gpio_mode; bool gpio_registered; + u8 gpio_pushpull; + u8 gpio_altfunc; + u8 gpio_input; #endif u8 partnum; + speed_t max_speed; + bool use_actual_rate; }; struct cp210x_port_private { @@ -356,6 +359,7 @@ static struct usb_serial_driver * const serial_drivers[] = { #define CONTROL_WRITE_RTS 0x0200 /* CP210X_VENDOR_SPECIFIC values */ +#define CP210X_READ_2NCONFIG 0x000E #define CP210X_READ_LATCH 0x00C2 #define CP210X_GET_PARTNUM 0x370B #define CP210X_GET_PORTCONFIG 0x370C @@ -369,6 +373,9 @@ static struct usb_serial_driver * const serial_drivers[] = { #define CP210X_PARTNUM_CP2104 0x04 #define CP210X_PARTNUM_CP2105 0x05 #define CP210X_PARTNUM_CP2108 0x08 +#define CP210X_PARTNUM_CP2102N_QFN28 0x20 +#define CP210X_PARTNUM_CP2102N_QFN24 0x21 +#define CP210X_PARTNUM_CP2102N_QFN20 0x22 #define CP210X_PARTNUM_UNKNOWN 0xFF /* CP210X_GET_COMM_STATUS returns these 0x13 bytes */ @@ -462,6 +469,12 @@ struct cp210x_config { #define CP2105_GPIO1_RXLED_MODE BIT(1) #define CP2105_GPIO1_RS485_MODE BIT(2) +/* CP2102N configuration array indices */ +#define CP210X_2NCONFIG_CONFIG_VERSION_IDX 2 +#define CP210X_2NCONFIG_GPIO_MODE_IDX 581 +#define CP210X_2NCONFIG_GPIO_RSTLATCH_IDX 587 +#define CP210X_2NCONFIG_GPIO_CONTROL_IDX 600 + /* CP210X_VENDOR_SPECIFIC, CP210X_WRITE_LATCH call writes these 0x2 bytes. */ struct cp210x_gpio_write { u8 mask; @@ -767,48 +780,6 @@ static int cp210x_get_line_ctl(struct usb_serial_port *port, u16 *ctl) return 0; } -/* - * cp210x_quantise_baudrate - * Quantises the baud rate as per AN205 Table 1 - */ -static unsigned int cp210x_quantise_baudrate(unsigned int baud) -{ - if (baud <= 300) - baud = 300; - else if (baud <= 600) baud = 600; - else if (baud <= 1200) baud = 1200; - else if (baud <= 1800) baud = 1800; - else if (baud <= 2400) baud = 2400; - else if (baud <= 4000) baud = 4000; - else if (baud <= 4803) baud = 4800; - else if (baud <= 7207) baud = 7200; - else if (baud <= 9612) baud = 9600; - else if (baud <= 14428) baud = 14400; - else if (baud <= 16062) baud = 16000; - else if (baud <= 19250) baud = 19200; - else if (baud <= 28912) baud = 28800; - else if (baud <= 38601) baud = 38400; - else if (baud <= 51558) baud = 51200; - else if (baud <= 56280) baud = 56000; - else if (baud <= 58053) baud = 57600; - else if (baud <= 64111) baud = 64000; - else if (baud <= 77608) baud = 76800; - else if (baud <= 117028) baud = 115200; - else if (baud <= 129347) baud = 128000; - else if (baud <= 156868) baud = 153600; - else if (baud <= 237832) baud = 230400; - else if (baud <= 254234) baud = 250000; - else if (baud <= 273066) baud = 256000; - else if (baud <= 491520) baud = 460800; - else if (baud <= 567138) baud = 500000; - else if (baud <= 670254) baud = 576000; - else if (baud < 1000000) - baud = 921600; - else if (baud > 2000000) - baud = 2000000; - return baud; -} - static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port) { int result; @@ -1028,6 +999,75 @@ static void cp210x_get_termios_port(struct usb_serial_port *port, *cflagp = cflag; } +struct cp210x_rate { + speed_t rate; + speed_t high; +}; + +static const struct cp210x_rate cp210x_an205_table1[] = { + { 300, 300 }, + { 600, 600 }, + { 1200, 1200 }, + { 1800, 1800 }, + { 2400, 2400 }, + { 4000, 4000 }, + { 4800, 4803 }, + { 7200, 7207 }, + { 9600, 9612 }, + { 14400, 14428 }, + { 16000, 16062 }, + { 19200, 19250 }, + { 28800, 28912 }, + { 38400, 38601 }, + { 51200, 51558 }, + { 56000, 56280 }, + { 57600, 58053 }, + { 64000, 64111 }, + { 76800, 77608 }, + { 115200, 117028 }, + { 128000, 129347 }, + { 153600, 156868 }, + { 230400, 237832 }, + { 250000, 254234 }, + { 256000, 273066 }, + { 460800, 491520 }, + { 500000, 567138 }, + { 576000, 670254 }, + { 921600, UINT_MAX } +}; + +/* + * Quantises the baud rate as per AN205 Table 1 + */ +static speed_t cp210x_get_an205_rate(speed_t baud) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cp210x_an205_table1); ++i) { + if (baud <= cp210x_an205_table1[i].high) + break; + } + + return cp210x_an205_table1[i].rate; +} + +static speed_t cp210x_get_actual_rate(struct usb_serial *serial, speed_t baud) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + unsigned int prescale = 1; + unsigned int div; + + baud = clamp(baud, 300u, priv->max_speed); + + if (baud <= 365) + prescale = 4; + + div = DIV_ROUND_CLOSEST(48000000, 2 * prescale * baud); + baud = 48000000 / (2 * prescale * div); + + return baud; +} + /* * CP2101 supports the following baud rates: * @@ -1057,16 +1097,24 @@ static void cp210x_get_termios_port(struct usb_serial_port *port, static void cp210x_change_speed(struct tty_struct *tty, struct usb_serial_port *port, struct ktermios *old_termios) { + struct usb_serial *serial = port->serial; + struct cp210x_serial_private *priv = usb_get_serial_data(serial); u32 baud; baud = tty->termios.c_ospeed; - /* This maps the requested rate to a rate valid on cp2102 or cp2103, - * or to an arbitrary rate in [1M,2M]. + /* + * This maps the requested rate to the actual rate, a valid rate on + * cp2102 or cp2103, or to an arbitrary rate in [1M, max_speed]. * * NOTE: B0 is not implemented. */ - baud = cp210x_quantise_baudrate(baud); + if (priv->use_actual_rate) + baud = cp210x_get_actual_rate(serial, baud); + else if (baud < 1000000) + baud = cp210x_get_an205_rate(baud); + else if (baud > priv->max_speed) + baud = priv->max_speed; dev_dbg(&port->dev, "%s - setting baud rate to %u\n", __func__, baud); if (cp210x_write_u32_reg(port, CP210X_SET_BAUDRATE, baud)) { @@ -1288,17 +1336,8 @@ static int cp210x_gpio_request(struct gpio_chip *gc, unsigned int offset) struct usb_serial *serial = gpiochip_get_data(gc); struct cp210x_serial_private *priv = usb_get_serial_data(serial); - switch (offset) { - case 0: - if (priv->config & CP2105_GPIO0_TXLED_MODE) - return -ENODEV; - break; - case 1: - if (priv->config & (CP2105_GPIO1_RXLED_MODE | - CP2105_GPIO1_RS485_MODE)) - return -ENODEV; - break; - } + if (priv->gpio_altfunc & BIT(offset)) + return -ENODEV; return 0; } @@ -1306,10 +1345,15 @@ static int cp210x_gpio_request(struct gpio_chip *gc, unsigned int offset) static int cp210x_gpio_get(struct gpio_chip *gc, unsigned int gpio) { struct usb_serial *serial = gpiochip_get_data(gc); + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + u8 req_type = REQTYPE_DEVICE_TO_HOST; int result; u8 buf; - result = cp210x_read_vendor_block(serial, REQTYPE_INTERFACE_TO_HOST, + if (priv->partnum == CP210X_PARTNUM_CP2105) + req_type = REQTYPE_INTERFACE_TO_HOST; + + result = cp210x_read_vendor_block(serial, req_type, CP210X_READ_LATCH, &buf, sizeof(buf)); if (result < 0) return result; @@ -1320,7 +1364,9 @@ static int cp210x_gpio_get(struct gpio_chip *gc, unsigned int gpio) static void cp210x_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value) { struct usb_serial *serial = gpiochip_get_data(gc); + struct cp210x_serial_private *priv = usb_get_serial_data(serial); struct cp210x_gpio_write buf; + int result; if (value == 1) buf.state = BIT(gpio); @@ -1329,25 +1375,68 @@ static void cp210x_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value) buf.mask = BIT(gpio); - cp210x_write_vendor_block(serial, REQTYPE_HOST_TO_INTERFACE, - CP210X_WRITE_LATCH, &buf, sizeof(buf)); + if (priv->partnum == CP210X_PARTNUM_CP2105) { + result = cp210x_write_vendor_block(serial, + REQTYPE_HOST_TO_INTERFACE, + CP210X_WRITE_LATCH, &buf, + sizeof(buf)); + } else { + u16 wIndex = buf.state << 8 | buf.mask; + + result = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + CP210X_VENDOR_SPECIFIC, + REQTYPE_HOST_TO_DEVICE, + CP210X_WRITE_LATCH, + wIndex, + NULL, 0, USB_CTRL_SET_TIMEOUT); + } + + if (result < 0) { + dev_err(&serial->interface->dev, "failed to set GPIO value: %d\n", + result); + } } static int cp210x_gpio_direction_get(struct gpio_chip *gc, unsigned int gpio) { - /* Hardware does not support an input mode */ - return 0; + struct usb_serial *serial = gpiochip_get_data(gc); + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + + return priv->gpio_input & BIT(gpio); } static int cp210x_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio) { - /* Hardware does not support an input mode */ - return -ENOTSUPP; + struct usb_serial *serial = gpiochip_get_data(gc); + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + + if (priv->partnum == CP210X_PARTNUM_CP2105) { + /* hardware does not support an input mode */ + return -ENOTSUPP; + } + + /* push-pull pins cannot be changed to be inputs */ + if (priv->gpio_pushpull & BIT(gpio)) + return -EINVAL; + + /* make sure to release pin if it is being driven low */ + cp210x_gpio_set(gc, gpio, 1); + + priv->gpio_input |= BIT(gpio); + + return 0; } static int cp210x_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio, int value) { + struct usb_serial *serial = gpiochip_get_data(gc); + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + + priv->gpio_input &= ~BIT(gpio); + cp210x_gpio_set(gc, gpio, value); + return 0; } @@ -1360,11 +1449,11 @@ static int cp210x_gpio_set_config(struct gpio_chip *gc, unsigned int gpio, /* Succeed only if in correct mode (this can't be set at runtime) */ if ((param == PIN_CONFIG_DRIVE_PUSH_PULL) && - (priv->gpio_mode & BIT(gpio))) + (priv->gpio_pushpull & BIT(gpio))) return 0; if ((param == PIN_CONFIG_DRIVE_OPEN_DRAIN) && - !(priv->gpio_mode & BIT(gpio))) + !(priv->gpio_pushpull & BIT(gpio))) return 0; return -ENOTSUPP; @@ -1377,12 +1466,13 @@ static int cp210x_gpio_set_config(struct gpio_chip *gc, unsigned int gpio, * this driver that provide GPIO do so in a way that does not impact other * signals and are thus expected to have very different initialisation. */ -static int cp2105_shared_gpio_init(struct usb_serial *serial) +static int cp2105_gpioconf_init(struct usb_serial *serial) { struct cp210x_serial_private *priv = usb_get_serial_data(serial); struct cp210x_pin_mode mode; struct cp210x_config config; u8 intf_num = cp210x_interface_num(serial); + u8 iface_config; int result; result = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST, @@ -1399,20 +1489,26 @@ static int cp2105_shared_gpio_init(struct usb_serial *serial) /* 2 banks of GPIO - One for the pins taken from each serial port */ if (intf_num == 0) { - if (mode.eci == CP210X_PIN_MODE_MODEM) + if (mode.eci == CP210X_PIN_MODE_MODEM) { + /* mark all GPIOs of this interface as reserved */ + priv->gpio_altfunc = 0xff; return 0; + } - priv->config = config.eci_cfg; - priv->gpio_mode = (u8)((le16_to_cpu(config.gpio_mode) & + iface_config = config.eci_cfg; + priv->gpio_pushpull = (u8)((le16_to_cpu(config.gpio_mode) & CP210X_ECI_GPIO_MODE_MASK) >> CP210X_ECI_GPIO_MODE_OFFSET); priv->gc.ngpio = 2; } else if (intf_num == 1) { - if (mode.sci == CP210X_PIN_MODE_MODEM) + if (mode.sci == CP210X_PIN_MODE_MODEM) { + /* mark all GPIOs of this interface as reserved */ + priv->gpio_altfunc = 0xff; return 0; + } - priv->config = config.sci_cfg; - priv->gpio_mode = (u8)((le16_to_cpu(config.gpio_mode) & + iface_config = config.sci_cfg; + priv->gpio_pushpull = (u8)((le16_to_cpu(config.gpio_mode) & CP210X_SCI_GPIO_MODE_MASK) >> CP210X_SCI_GPIO_MODE_OFFSET); priv->gc.ngpio = 3; @@ -1420,6 +1516,125 @@ static int cp2105_shared_gpio_init(struct usb_serial *serial) return -ENODEV; } + /* mark all pins which are not in GPIO mode */ + if (iface_config & CP2105_GPIO0_TXLED_MODE) /* GPIO 0 */ + priv->gpio_altfunc |= BIT(0); + if (iface_config & (CP2105_GPIO1_RXLED_MODE | /* GPIO 1 */ + CP2105_GPIO1_RS485_MODE)) + priv->gpio_altfunc |= BIT(1); + + /* driver implementation for CP2105 only supports outputs */ + priv->gpio_input = 0; + + return 0; +} + +static int cp2102n_gpioconf_init(struct usb_serial *serial) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + const u16 config_size = 0x02a6; + u8 gpio_rst_latch; + u8 config_version; + u8 gpio_pushpull; + u8 *config_buf; + u8 gpio_latch; + u8 gpio_ctrl; + int result; + u8 i; + + /* + * Retrieve device configuration from the device. + * The array received contains all customization settings done at the + * factory/manufacturer. Format of the array is documented at the + * time of writing at: + * https://www.silabs.com/community/interface/knowledge-base.entry.html/2017/03/31/cp2102n_setconfig-xsfa + */ + config_buf = kmalloc(config_size, GFP_KERNEL); + if (!config_buf) + return -ENOMEM; + + result = cp210x_read_vendor_block(serial, + REQTYPE_DEVICE_TO_HOST, + CP210X_READ_2NCONFIG, + config_buf, + config_size); + if (result < 0) { + kfree(config_buf); + return result; + } + + config_version = config_buf[CP210X_2NCONFIG_CONFIG_VERSION_IDX]; + gpio_pushpull = config_buf[CP210X_2NCONFIG_GPIO_MODE_IDX]; + gpio_ctrl = config_buf[CP210X_2NCONFIG_GPIO_CONTROL_IDX]; + gpio_rst_latch = config_buf[CP210X_2NCONFIG_GPIO_RSTLATCH_IDX]; + + kfree(config_buf); + + /* Make sure this is a config format we understand. */ + if (config_version != 0x01) + return -ENOTSUPP; + + /* + * We only support 4 GPIOs even on the QFN28 package, because + * config locations of GPIOs 4-6 determined using reverse + * engineering revealed conflicting offsets with other + * documented functions. So we'll just play it safe for now. + */ + priv->gc.ngpio = 4; + + /* + * Get default pin states after reset. Needed so we can determine + * the direction of an open-drain pin. + */ + gpio_latch = (gpio_rst_latch >> 3) & 0x0f; + + /* 0 indicates open-drain mode, 1 is push-pull */ + priv->gpio_pushpull = (gpio_pushpull >> 3) & 0x0f; + + /* 0 indicates GPIO mode, 1 is alternate function */ + priv->gpio_altfunc = (gpio_ctrl >> 2) & 0x0f; + + /* + * The CP2102N does not strictly has input and output pin modes, + * it only knows open-drain and push-pull modes which is set at + * factory. An open-drain pin can function both as an + * input or an output. We emulate input mode for open-drain pins + * by making sure they are not driven low, and we do not allow + * push-pull pins to be set as an input. + */ + for (i = 0; i < priv->gc.ngpio; ++i) { + /* + * Set direction to "input" iff pin is open-drain and reset + * value is 1. + */ + if (!(priv->gpio_pushpull & BIT(i)) && (gpio_latch & BIT(i))) + priv->gpio_input |= BIT(i); + } + + return 0; +} + +static int cp210x_gpio_init(struct usb_serial *serial) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + int result; + + switch (priv->partnum) { + case CP210X_PARTNUM_CP2105: + result = cp2105_gpioconf_init(serial); + break; + case CP210X_PARTNUM_CP2102N_QFN28: + case CP210X_PARTNUM_CP2102N_QFN24: + case CP210X_PARTNUM_CP2102N_QFN20: + result = cp2102n_gpioconf_init(serial); + break; + default: + return 0; + } + + if (result < 0) + return result; + priv->gc.label = "cp210x"; priv->gc.request = cp210x_gpio_request; priv->gc.get_direction = cp210x_gpio_direction_get; @@ -1452,7 +1667,7 @@ static void cp210x_gpio_remove(struct usb_serial *serial) #else -static int cp2105_shared_gpio_init(struct usb_serial *serial) +static int cp210x_gpio_init(struct usb_serial *serial) { return 0; } @@ -1497,6 +1712,50 @@ static int cp210x_port_remove(struct usb_serial_port *port) return 0; } +static void cp210x_init_max_speed(struct usb_serial *serial) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + bool use_actual_rate = false; + speed_t max; + + switch (priv->partnum) { + case CP210X_PARTNUM_CP2101: + max = 921600; + break; + case CP210X_PARTNUM_CP2102: + case CP210X_PARTNUM_CP2103: + max = 1000000; + break; + case CP210X_PARTNUM_CP2104: + use_actual_rate = true; + max = 2000000; + break; + case CP210X_PARTNUM_CP2108: + max = 2000000; + break; + case CP210X_PARTNUM_CP2105: + if (cp210x_interface_num(serial) == 0) { + use_actual_rate = true; + max = 2000000; /* ECI */ + } else { + max = 921600; /* SCI */ + } + break; + case CP210X_PARTNUM_CP2102N_QFN28: + case CP210X_PARTNUM_CP2102N_QFN24: + case CP210X_PARTNUM_CP2102N_QFN20: + use_actual_rate = true; + max = 3000000; + break; + default: + max = 2000000; + break; + } + + priv->max_speed = max; + priv->use_actual_rate = use_actual_rate; +} + static int cp210x_attach(struct usb_serial *serial) { int result; @@ -1517,12 +1776,12 @@ static int cp210x_attach(struct usb_serial *serial) usb_set_serial_data(serial, priv); - if (priv->partnum == CP210X_PARTNUM_CP2105) { - result = cp2105_shared_gpio_init(serial); - if (result < 0) { - dev_err(&serial->interface->dev, - "GPIO initialisation failed, continuing without GPIO support\n"); - } + cp210x_init_max_speed(serial); + + result = cp210x_gpio_init(serial); + if (result < 0) { + dev_err(&serial->interface->dev, "GPIO initialisation failed: %d\n", + result); } return 0; |