Re: [PATCH] USB: serial: cp210x: Implement GPIO support for CP2102N

From: Karoly Pados
Date: Wed Jun 20 2018 - 05:45:21 EST


OK, though if you allow me a few comments and explanations:


> You're gonna need to unify a lot of this with the current implementation
> for cp2105 however.
>
This also relates to your later points about unifying the cp2105 an cp2102n:
The implementation for the cp2102n ended up pretty different. Both in initializaiton
as well as normal operation, partially due to the better config/input/alt.function
support for the cp2102n. At this point I though it is much more readable if I just create
separate code hierarchies than having to pollute everything with ifs and switch-cases.

And maybe most importantly, I cannot test on the cp2105, so I cannot check if I've broken
anything by mistake, and since it is the odd bird anyway among most devices,
I though it is pretty logical not to touch its code but instead create a new skeleton for
others.

Of course you're the boss and whatever you say I will do, but I wanted to explain my reasons
(or at least let you know my arguments).

> I was going to comment on the odd coding style above, when I noticed
> that you've copied this implementation from a silabs forum post. Not
> good at all (as there was no indication of any license).
>
Not quite. It is not from a forum post, but from a SiLabs Knowledge Base article
(https://www.silabs.com/community/interface/knowledge-base.entry.html/2017/06/12/fletcher_checksumfo-TeDF)
That article states explicitly that the code was taken from Wikipedia, so it is CC-SA, which is to
the best of my knowledge 1) compatible with GPL, and 2) does not require attribution if the original material
is missing it, and it does. So AFAICT we are clear on the licensing front.

Do you still want me to remove the checksum check?

> So for cp2105 we decided against implementing an input mode. We at least
> need to be consistent here, so I suggest starting with dropping those
>
Again, you're the boss, but I do find this odd. These are different devices and thankfully now
there'd be better support for the cp2102n, and you're asking me to remove input support for
consistency reasons. To remove a feature for one device just because it wasn't correctly implemented
in another and wanting to stay consistent with the missing support is very odd for me tbh.
I know I'm in no position, but please let me ask you once to please reconsider this.

The way it was done for the cp2105 where we make everything and everyone on the system
think that a pin is "out" but in reality is an input is quirky to say the least, while
at the same time it also does not make sure that an input pin (which is of course not marked as such)
is really an input. Why amputate the cp2102n like this if it has been already implemented correctly?
If we wanted to stay consistent with half-developed and incomplete drivers then there'd be no
improvements to Linux.

> Please be more specific here; what do you mean by not corresponding to
> reality.
>
It means that I've found GPIO4-6 occupying bits which were documented doing completely other things,
and those other things occupying reserved bits even though they were documented occupying the places
where GPIOs 4-6 were.

> No need to check for this in every callback; move to
> cp210x_gpio_request().
>
Wouldn't this prevent the disabled GPIOs from being created in the first place?
I very much want to create them in every case, because otherwise it becomes a PITA
for both the driver and consumers:
- For the driver a PITA, because the disabled GPIOs can be any and non-continuous,
so the module would need to dynamically and arbitrarily remap logical GPIOs to hardware GPIO numbers.
- For consumers a PITA, because it would mean GPIOs can change names depending on the current configuration
of the device. On one device GPIO.3 would be called GPIO.2, on another GPIO.1 etc. My intention
here is that applications should be able to use the real ID of the GPIO and expect that GPIO.3 (base+offset 3)
is actually GPIO.3 and not something else.

Best,
Karoly

June 20, 2018 10:25 AM, "Johan Hovold" <johan@xxxxxxxxxx> wrote:

> On Sun, Jun 17, 2018 at 08:25:03PM +0200, Karoly Pados wrote:
>
>> 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.
>
> Thanks for the patch. Nice to see this finally be implemented for
> further device types.
>
> You're gonna need to unify a lot of this with the current implementation
> for cp2105 however.
>
>> 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@xxxxxxxxxxxxx RTSCTS hardware flow
>> - * control thanks to Munir Nassar nassarmu@xxxxxxxxxxxxx
>> + * control thanks to Munir Nassar nassarmu@xxxxxxxxxxxxxx
>> + * GPIO support for CP2102N thanks to Karoly Pados.
>
> No need to thank yourself here. This was added by the original author
> (who appears to have partly been developing this driver out-of-tree) to
> credit contributers.
>
> The git logs will keep a record of your contributions.
>
>> *
>> */
>>
>> @@ -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)))
>
> These are in fact not cp2102n specific. If they are at all needed in the
> end they should be implemented as generic static functions.
>
>> +
>> 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
>
> Keep the defines sorted by value.
>
>> /* 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))
>
> So reuse the static helper I mentioned in my comments to the baudrate
> patch.
>
>> +
>> +#define CP210X_2NCONFIG_GPIO_CONTROL_IDX 600
>> +#define CP210X_2NCONFIG_GPIO_MODE_IDX 581
>> +#define CP210X_2NCONFIG_GPIO_RSTLATCH_IDX 587
>
> Sort by value.
>
>> +
>> /* 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));
>> +}
>
> This should be handled in cp210x_gpio_get() by making the control
> request type depend on the device type.
>
> Make the cp2102n behaviour the default as it is used also by some legacy
> devices, and explicitly check for the cp2105 odd bird. We'll deal with
> cp2108, which 16 gpios and hence uses 16-bit mask, when we get there.
>
>> +
>> +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;
>> + }
>
> No need to check for this in every callback; move to
> cp210x_gpio_request().
>
>> +
>> + 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,
>
> AFAICT this will work also on big-endian machines, but the implicit
> endian conversions involved makes my head spin. ;)
>
> It may be better to just encode the masks explicitly for the default
> (non-cp2105) case that use wIndex:
>
> buf.state << 8 | buf.mask
>
> when merging this with cp210x_gpio_set.
>
>> + 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);
>> +}
>
> So for cp2105 we decided against implementing an input mode. We at least
> need to be consistent here, so I suggest starting with dropping those
> bits of the implementation and we can have that discussion again and,
> depending on the outcome, possibly enable it for all cp210x devices
> later.
>
>> +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;
>> +}
>
> I was going to comment on the odd coding style above, when I noticed
> that you've copied this implementation from a silabs forum post. Not
> good at all (as there was no indication of any license).
>
> Please remove.
>
> Fortunately, the below checksum is redundant so no need for you to
> reimplement this yourself.
>
>> +
>> +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);
>
> Style nit: Move longer declarations towards the top of the function
> (reverse xmas style).
>
>> +
>> + /* 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-xs
>> a
>> + */
>
> Multi-line comments should use the following format
>
> /*
> * blah...
> */
>
>> + 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");
>> + }
>
> So just drop this for now.
>
>> +
>> + 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.
>> + */
>
> Please be more specific here; what do you mean by not corresponding to
> reality.
>
>> + 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);
>> + }
>> + }
>
> So this can be simplified a bit by deferring implementing an input mode.
>
>> +
>> + 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;
>
> And this whole function should be merged with the cp2105 implementation.
> Retrieving and parsing the configuration is obviously going to remain
> type specific (and live in their own functions), but the above
> initialisation and registration can be shared.
>
>> +
>> + 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,
>
> And this all should be hidden away in a common cp210x_gpio_init()
> function.
>
> Thanks,
> Johan