Pretty much what the title says. No other functions/devices touched. Tested on CP2102N-QFN28. Limitation: Even though the QFN28 package has 7 GPIOs, this patch allows to control only the first 4. What I've found using reverse engineering regarding the other 3 pins collides both with reality and with other documented functions, so I did not dare to just use my findings because I cannot test on other packages. Instead, I decided to play it safe and only support 4 GPIOs. Signed-off-by: Karoly Pados <pados@xxxxxxxx> --- drivers/usb/serial/cp210x.c | 259 +++++++++++++++++++++++++++++++++++- 1 file changed, 257 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 793b86252c46..adb450185ce8 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -6,7 +6,8 @@ * * Support to set flow control line levels using TIOCMGET and TIOCMSET * thanks to Karl Hiramoto karl@xxxxxxxxxxxx. RTSCTS hardware flow - * control thanks to Munir Nassar nassarmu@xxxxxxxxxxxxx + * control thanks to Munir Nassar nassarmu@xxxxxxxxxxxxx. + * GPIO support for CP2102N thanks to Karoly Pados. * */ @@ -229,11 +230,16 @@ struct cp210x_serial_private { struct gpio_chip gc; u8 config; u8 gpio_mode; + u8 gpio_control; + u8 gpio_dir; bool gpio_registered; #endif u8 partnum; }; +#define CP2102N_PIN_OPENDRAIN(var, pin) (!((var) & BIT(pin))) +#define CP2102N_PIN_GPIO(var, pin) (!((var) & BIT(pin))) + struct cp210x_port_private { __u8 bInterfaceNumber; bool has_swapped_line_ctl; @@ -349,6 +355,7 @@ static struct usb_serial_driver * const serial_drivers[] = { #define CP210X_GET_PORTCONFIG 0x370C #define CP210X_GET_DEVICEMODE 0x3711 #define CP210X_WRITE_LATCH 0x37E1 +#define CP210X_READ_2NCONFIG 0x000E /* Part number definitions */ #define CP210X_PARTNUM_CP2101 0x01 @@ -362,6 +369,14 @@ static struct usb_serial_driver * const serial_drivers[] = { #define CP210X_PARTNUM_CP2102N_QFN20 0x22 #define CP210X_PARTNUM_UNKNOWN 0xFF +#define IS_CP2102N(partnum) (((partnum) == CP210X_PARTNUM_CP2102N_QFN28) || \ + ((partnum) == CP210X_PARTNUM_CP2102N_QFN24) || \ + ((partnum) == CP210X_PARTNUM_CP2102N_QFN20)) + +#define CP210X_2NCONFIG_GPIO_CONTROL_IDX 600 +#define CP210X_2NCONFIG_GPIO_MODE_IDX 581 +#define CP210X_2NCONFIG_GPIO_RSTLATCH_IDX 587 + /* CP210X_GET_COMM_STATUS returns these 0x13 bytes */ struct cp210x_comm_status { __le32 ulErrors; @@ -1455,6 +1470,235 @@ static void cp210x_gpio_remove(struct usb_serial *serial) } } +static int cp2102n_gpio_get(struct gpio_chip *gc, unsigned int gpio) +{ + struct usb_serial *serial = gpiochip_get_data(gc); + int result; + u8 buf; + + result = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST, + CP210X_READ_LATCH, &buf, sizeof(buf)); + if (result < 0) + return result; + + return !!(buf & BIT(gpio)); +} + +static void cp2102n_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value) +{ + int result; + struct cp210x_gpio_write buf; + struct usb_serial *serial = gpiochip_get_data(gc); + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + + /* Ignore request if pin is not in our control */ + if (!CP2102N_PIN_GPIO(priv->gpio_control, gpio)) { + dev_warn(&serial->interface->dev, + "Cannot control GPIO with active alternate function.\n"); + return; + } + + buf.state = (value == 1) ? BIT(gpio) : 0; + buf.mask = BIT(gpio); + + result = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + CP210X_VENDOR_SPECIFIC, + REQTYPE_HOST_TO_DEVICE, + CP210X_WRITE_LATCH, + *(u16 *)&buf, + NULL, 0, USB_CTRL_SET_TIMEOUT); + + if (result < 0) { + dev_err(&serial->interface->dev, + "Failed to set GPIO value.\n"); + } +} + +static int cp2102n_gpio_direction_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); + + return priv->gpio_dir & BIT(gpio); +} + +static int cp2102n_gpio_direction_input(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); + + /* Return an error if pin is not in our control */ + if (!CP2102N_PIN_GPIO(priv->gpio_control, gpio)) { + dev_warn(&serial->interface->dev, + "Cannot control GPIO with active alternate function.\n"); + return -EPERM; + } + + /* Push-pull pins cannot be changed to be inputs */ + if (!CP2102N_PIN_OPENDRAIN(priv->gpio_mode, gpio)) { + dev_warn(&serial->interface->dev, + "Cannot change direction of a push-pull GPIO to input.\n"); + return -EPERM; + } + + /* Make sure to release pin if it is being driven low */ + cp2102n_gpio_set(gc, gpio, 1); + + /* Note pin direction to ourselves */ + priv->gpio_dir |= BIT(gpio); + + return 0; +} + +static int cp2102n_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); + + /* Return an error if pin is not in our control */ + if (!CP2102N_PIN_GPIO(priv->gpio_control, gpio)) { + dev_warn(&serial->interface->dev, + "Cannot control GPIO with active alternate function.\n"); + return -EPERM; + } + + /* Note pin direction to ourselves */ + priv->gpio_dir &= ~BIT(gpio); + + /* Set requested initial output value */ + cp2102n_gpio_set(gc, gpio, value); + + return 0; +} + +static u16 fletcher16(u8 *data, u16 bytes) +{ + u16 sum1 = 0xff, sum2 = 0xff; + u16 tlen; + + while (bytes) { + tlen = bytes >= 20 ? 20 : bytes; + bytes -= tlen; + do { + sum2 += sum1 += *data++; + } while (--tlen); + sum1 = (sum1 & 0xff) + (sum1 >> 8); + sum2 = (sum2 & 0xff) + (sum2 >> 8); + } + /* Second reduction step to reduce sums to 8 bits */ + sum1 = (sum1 & 0xff) + (sum1 >> 8); + sum2 = (sum2 & 0xff) + (sum2 >> 8); + return sum2 << 8 | sum1; +} + +static int cp2102n_gpio_init(struct usb_serial *serial) +{ + const u16 CONFIG_SIZE = 0x02A6; + int result; + u16 config_csum; + u8 *config_buf; + u8 gpio_ctrl; + u8 gpio_mode; + u8 gpio_latch; + u8 gpio_rst_latch; + u8 i; + bool config_valid = true; + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + + /* 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) + return result; + + /* Check configuration for validity. + * The last two bytes of the array contain a fletcher16 + * checksum of all the bytes before, which we can validate. + */ + config_csum = fletcher16(config_buf, CONFIG_SIZE - 2); + if (((config_csum & 0xFF) != config_buf[CONFIG_SIZE - 1]) || + ((config_csum >> 8) != config_buf[CONFIG_SIZE - 2])) { + config_valid = false; + dev_err(&serial->interface->dev, + "Corrupted device configuration received\n"); + } + + gpio_mode = 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); + + if (!config_valid) + return -EIO; + + /* We only support 4 GPIOs even on the QFN28 package because + * documentation about the CP2102N GetConfig Array + * does not seem to correspond to reality on this device. + */ + 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_mode = (gpio_mode >> 3) & 0x0F; + + /* 0 indicates GPIO mode, 1 is alternate function */ + priv->gpio_control = (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 (CP2102N_PIN_OPENDRAIN(priv->gpio_mode, i) && + (gpio_latch & BIT(i))) { + priv->gpio_dir |= BIT(i); + } + } + + priv->gc.label = "cp2102n"; + priv->gc.get_direction = cp2102n_gpio_direction_get; + priv->gc.direction_input = cp2102n_gpio_direction_input; + priv->gc.direction_output = cp2102n_gpio_direction_output; + priv->gc.get = cp2102n_gpio_get; + priv->gc.set = cp2102n_gpio_set; + priv->gc.owner = THIS_MODULE; + priv->gc.parent = &serial->interface->dev; + priv->gc.base = -1; + priv->gc.can_sleep = true; + + result = gpiochip_add_data(&priv->gc, serial); + if (!result) + priv->gpio_registered = true; + + return result; +} + #else static int cp2105_shared_gpio_init(struct usb_serial *serial) @@ -1462,6 +1706,11 @@ static int cp2105_shared_gpio_init(struct usb_serial *serial) return 0; } +static int cp2102n_gpio_init(struct usb_serial *serial) +{ + return 0; +} + static void cp210x_gpio_remove(struct usb_serial *serial) { /* Nothing to do */ @@ -1522,7 +1771,13 @@ static int cp210x_attach(struct usb_serial *serial) usb_set_serial_data(serial, priv); - if (priv->partnum == CP210X_PARTNUM_CP2105) { + if (IS_CP2102N(priv->partnum)) { + result = cp2102n_gpio_init(serial); + if (result < 0) { + dev_err(&serial->interface->dev, + "GPIO initialisation failed, continuing without GPIO support\n"); + } + } else if (priv->partnum == CP210X_PARTNUM_CP2105) { result = cp2105_shared_gpio_init(serial); if (result < 0) { dev_err(&serial->interface->dev, -- 2.17.1 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html