lkml.org 
[lkml]   [2018]   [Jun]   [17]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH] USB: serial: cp210x: Implement GPIO support for CP2102N
    Date
    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@pados.hu>
    ---
    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@hiramoto.org. RTSCTS hardware flow
    - * control thanks to Munir Nassar nassarmu@real-time.com
    + * control thanks to Munir Nassar nassarmu@real-time.com.
    + * 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
    \
     
     \ /
      Last update: 2018-06-17 20:25    [W:2.941 / U:0.028 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site