cancel
Showing results for 
Search instead for 
Did you mean: 

STM32MP1/STUSB1600 - DRP to DRP negotiation guidelines (no PD controller)

KChar.1
Senior

Hi there, 

I am working on an STM32MP157F-DK2 board and an STUSB1600 with hsotg gadget support and no PD controller. I am running a buildroot image with all the necessary drivers enabled including dwc2, SystemFS etc.  

The gadget is working as expected in most configurations. It can be hotplugged and get recognised with a high-speed unique address without any issues on several computers running windows or linux with a usb-c to usb-c cable . Recently I attempted to connect the board to a series of apple computers to test compatibility. I faced several unexpected behaviours during these tests. 

  • Apple Macbooks (Intel) will have a 50-50 chance of recognising the DK board as a peripheral. The rest of the times the DK is recognised as a charger and the MBP will start charging 
  • Apple Macbooks (M1) will still have a 50-50 chance of recognising the DK board as a peripheral but the rest of the gadget will not get recognised at all. 

  • Apple Mac Mini (M2) will not get recognised at all. The dwc2 otg driver will do several attempts always on address 1 until time-out. 

None of the above issues are present by using a usb-c to usb-A cable, usb Hub or usb-c to usb-A adapter. 

Following the Apple Accessory Design Guidelines it seems that any usb typec device is expected to work as long as it follows the Universal Serial Bus Type-C Cable and Connector Specification

I believe that a good hypothesis on the cause of the issue according to USB Type-C specification is hinted on

"In the special case of a DRP being attached to another DRP, an initialization protocol across the CC pins is used to establish the initial host-to-device relationship. Given no role-swapping intervention, the determination of which is DFP or UFP is random from the user’s perspective." 
Universal Serial Bus Type-C Cable and Connector Specification. Pg:34
More information on actual mechanisms on Pg: 200

It seems that the behaviour of the symptom is consistent with a random DRP to DRP negotiation between Apple machines and the DK board. In this particular application my goal is to create a mechanism that when the STUSB1600 detects a DRP or any DFP it always keeps the CC pins on UFP so the DK will always gets recognised as a peripheral in the case of a DRP to DRP connection. 

Following the stusb160x driver and the Device Tree Configuration guide I was not able to see an exposed entry or a direct modification that can account for such case. To my understanding all the dts entries on the typec connector will point to initial configurations but none of the available entries can achieve such behaviour.  I attach my current dts entries for both hsusbotg and stusb1600 below:

 

typec: stusb1600@28 {
       compatible = "st,stusb1600";
       reg = <0x28>;
       interrupts = <11 IRQ_TYPE_LEVEL_LOW>;
       interrupt-parent = <&gpioi>;
      
       vsys-supply = <&vdd_usb>;


       pinctrl-names = "default";
       pinctrl-0 = <&stusb1600_pins_a>;
       status = "okay";
       vdd-supply = <&vin>;


       typec_con: connector {
           compatible = "usb-c-connector";
           label = "USB-C";
           power-role = "dual";
           data-role = "dual";
           typec-power-opmode = "default"; //Advertise usb2 power capabilities (up to 500mV on PD negotiation)
              
           port {
               con_usbotg_hs_ep: endpoint {
                   remote-endpoint = <&usbotg_hs_ep>;
               };
           };
       };
   };

 

 

Board level

 

&usbotg_hs {
   compatible = "st,stm32mp15-hsotg", "snps,dwc2";
   phys = <&usbphyc_port1 0>;                                                /* 0: UTMI switch selects the OTG controller */
   phy-names = "usb2-phy";
   dr_mode = "peripheral";
   g-rx-fifo-size = <256>;
   g-np-tx-fifo-size = <32>;
   g-tx-fifo-size = <128 128 128 128 64 32 16 16>;
   usb-role-switch;
   role-switch-default-mode = "peripheral";                                                          /* see USB generic bindings [4] */
   status = "okay";                                                          /* enable OTG */
  
   port {
       usbotg_hs_ep: endpoint {
           remote-endpoint = <&con_usbotg_hs_ep>;                /* point the Type-C controller endpoint node */
       };
   };
};

 

 
I was wondering if I am missing a dts or software configuration that can handle DRP to DRP connections for the STUSB1600.

Any advice or ideas on this topic are highly appreciated. 

1 ACCEPTED SOLUTION

Accepted Solutions
KChar.1
Senior

Hey @Erwan SZYMANSKI and @NBALL , it has been a while but I was finally able to come up with a workaround based on your suggestions.

On the New Apple devices issue the suggested PHY tuning indeed increases chances for successful connections at about 80% according to internal testing but there is still about 20% chance that you will get a bind failure.

[   96.502077] cdc_ncm 3-1:1.0: bind() failure
[   96.531547] cdc_ncm 3-1:1.2: bind() failure


On older Apple laptops with DRP ports the issue is exactly as described by  @NBALL and USB-C Specifications. There is a 50-50 chance for the MP1 to be a SINK or a SOURCE.

After applying the new PHY tuning I attempted to resolve both issues with the same mechanism. I started by following @NBALL suggestion to modify the stusb160x driver to reset the connection and force a SINK role if 0x0E registry is SINK. Unfortunately at this point I realised that in our board we do not have a dedicated 5V regulator for stusb1600 but we are routing 5V directly from the 5V input similar to the DK2 board. Due to this I could not force reset the connection when the connected device was a SINK. To work around this issue I used register 0x23 to SW reset the stusb1600 after forcing the role to SINK. I exposed a SysFS entry that when is written with "1" it will force the power role to SINK and the data role to "PERIPHERAL" and then reset by using registry 0x23.

static ssize_t force_roles_store(struct device *dev,
                 struct device_attribute *attr,
                 const char *buf, size_t count)
{
    struct stusb160x *chip = dev_get_drvdata(dev);
    u32 role;
    int ret;

    if (sscanf(buf, "%u", &role) != 1)
        return -EINVAL;

    if (role == 1) {
        // Force the power role to SINK and the data role to DEVICE
        typec_set_pwr_role(chip->port, TYPEC_SINK);
        typec_set_data_role(chip->port, TYPEC_DEVICE);


        // Write to the SW RESET register to reset the chip
        ret = regmap_write(chip->regmap, STUSB160X_RESET_CTRL, STUSB160X_SW_RESET_EN);
        if (ret)
            return ret;


        // Wait for the reset to take effect
        msleep(475);

        // Write to the SW RESET register again to clear the reset
        ret = regmap_write(chip->regmap, STUSB160X_RESET_CTRL, 0);
        if (ret)
            return ret;
    }

    return count;
}



Then I used a udev rule to check for attach and detach connections.

# /etc/udev/rules.d/99-usb.rules
ACTION=="add", SUBSYSTEM=="typec", DEVPATH=="/devices/platform/soc/5c002000.i2c/i2c-2/2-0028/typec/port0/port0-partner", RUN+="/bin/rlq_usb_attach"
ACTION=="remove", SUBSYSTEM=="typec", DEVPATH=="/devices/platform/soc/5c002000.i2c/i2c-2/2-0028/typec/port0/port0-partner", RUN+="/bin/rlq_usb_dettach"


With udev rules I am able to probe device information from the corresponding scripts as below:

# Assemble the DEVNAME
DEVNAME="/dev/bus/usb/003/$(ls /dev/bus/usb/003/ | sort -n | tail -1)"

# Get the ID_MODEL of the USB device
PRODUCT=$(udevadm info --query=property --name=$DEVNAME | grep ID_MODEL= | cut -d'=' -f2)

and I can force the power role to SINK with:

    echo 1 > /sys/bus/i2c/devices/i2c-2/2-0028/force_roles

and the data role with

echo "host" > /sys/devices/platform/soc/49000000.usb-otg/usb_role/49000000.usb-otg-role-switch/role

My thinking is that in this way I can get enough information on the connected device to find solutions for different edge cases. In the above example if a device is a Macbook with a USB-C charger I will force the roles to sink and peripheral but if a device is an Apple Product with an M chip I will check for bind failures and just force reset the connection until I establish one.

The maximum time to establish a connection until now is below 2seconds with every device I tested including M chips, older Apple computers and other peripherals.

Although this solution still looks a bit complicated it works according to my specs at the moment but please let me know if there any ideas for improvement :) 

View solution in original post

9 REPLIES 9
Erwan SZYMANSKI
ST Employee

Hello @KChar.1,

I am afraid you are facing now an issue that is more than tricky right now. We passed months and months to debug a such USB-C issue like the one you have now.

So, let me summarize you our researches in few words :

  • New Apple devices (such as iPhone 12 or more / Apple MAC with M1 chip or newer) now integrates an eUSB2 to USB2 repeater that modify a bit the signal is formed on USB lanes.
  • To explain it briefly, the first bit formed by the repeater, that is supposed to be the first bit of the SYNC pattern seen by STM32MP15 PHY-C, has a random duration. It is not "out of spec" but in the gray zone.
  • Whatever, our PHY-C does not always recognize this first bit as a real bit if the frequency is too quick, and the result is that the packet is dropped, and note that we are talking about picoseconds delta for a packet passing or not passing.

So, now let's talk about what interest you, is there any solution ?
Well, difficult to say. We have a workaround that has much better result than the default behavior, it is composed of :

  • 1 SW modification
  • 1 HW modification

Both modifications are here to slow down a very little bit the speed of the first bit seen by our PHY-C, and then consider the USB packet as a valid packet.

SW modif :

Change the PHY-C tuning, by changing the following registers with given values :

- Register 0x5A00610C --> value: 0x01A76BEC
- Register 0x5A00620C --> value: 0x01A76BEC

HW modif :

Adding a coil of 27nH between the connector and the SoC on D+ and D- USB lines. In the case of STM32MP157F-DK2 board, this is between the SoC and the U29 component (ESD protection).

You can first make a try with the SW modif only, to see if it improves your results. This is far from being an easy topic, and it deals with the very basic HW USB signal, and the way a PHY can understand this last one.
Kind regards,
Erwan.

In order to give better visibility on the answered topics, please click on 'Accept as Solution' on the reply which solved your issue or answered your question.

Hi @Erwan SZYMANSKI ,

Thanks a lot for the very detailed and quick reply. I will move forward tryin both your suggestions and keep you posted for any updates. 

By checking the USBPHYC documentation I was not able to locate the provided registers. I was wondering if there are any available examples or documentation. 

 

NBALL
ST Employee

Hello  KChar.1,
"None of the above issues are present by using a usb-c to usb-A cable, usb Hub or usb-c to usb-A adapter. ": when using those cable, power and data role are forced by the cable.
When using TypeC to TypeC cable, as STUSB1600 is DRP and devices mentioned are also DRP (i. e CC pin toggle to be part of time Source/DRP and part of time SInk/UFP), there is 50% of chance to connect as Sink/UFP and 50% as Source/DFP.
As the is TypeC only (and not PD) there is no protocol messaging to swap power or data role.
To force the role, 'manual force' needs to be done by forcing system disconnection and setting stusb1600 to either source/dfp or sink/ufp.
Best regards

Nathalie

@KChar.1 ,
In the Reference Manual, you have a chapter named "USBPHYC register" with a base address of 0x5A006000

The exact registers we modify are so USBPHYC_TUNE1 and USBPHYC_TUNE2.

2 solutions: for your tests, you can use a tool such as devmem2 to modify registers just after the boot and begin your USB negotiation. The other way is to modify the device tree nodes usbphyc_port0 and usbphyc_port1 as described in stm32mp15xx-dkx.dtsi. You have to check the YAML documentation in parallel of the Reference Manual, to make match each property to the ones activated by 0x01A76BEC value. 

I advice to first test with devmem2, just to see if it solves the case when you see your timeout. @NBALL answered about 50/50 cases (thanks).

Board $> devmem2 0x5A00610C w 0x01A76BEC
Board $> devmem2 0x5A00620C w 0x01A76BEC

Kind regards,
Erwan.

In order to give better visibility on the answered topics, please click on 'Accept as Solution' on the reply which solved your issue or answered your question.
KChar.1
Senior

@Erwan SZYMANSKI ,

Thank you so much for providing more details. Changing the registers already improved the issue on Apple Silicon machines. Based on several tests today I can see that the device is detected stably more than 90% of the times.

Hey @NBALL,
Thanks so much for the explanation. Indeed this is consistent with my original hypothesis and the Universal Serial Bus Type-C Cable and Connector Specification.

I experience this issue with all intel Macbooks. 

I am wondering whether it is possible to read from the stusb1600 registers when a connection has been established with a DRP port on the other end. If this is possible, can a forced disconnection and a switch to sink/ufp be established through user-space or the stusb160x driver?
  

@KChar.1 ,
I am 95% sure that Linux expose a sysfs entry or something like this to force a role switch. I already saw iPhone communicating by USB-C with STM32MP15 (a kind of car-play application), and the STM32MP15 clearly asked for a role switch at user space level.

Please let me know if you find it online or if you miss the information, I'll take a better look at it if you do not find it.

Regards,
Erwan.

In order to give better visibility on the answered topics, please click on 'Accept as Solution' on the reply which solved your issue or answered your question.

Hello
By reading @x0E STUSB1600, you can get current power role after connection. But you cannot know if the other device is single role or DRP. This would need PD functionality.
So when connection is so that stusb1600 is source while you wish to be sink, you can force the role and see if a connection is setup again. If counter part was sink only, there will be no connection (sink to sink is not a valid connection). Timeout of 475ms after restarting the new role can be used to decide to be back to initial role for example. (475 = 200+275 which corresponds to, as per spec, tccdebounce max + tVbusOn max).

Best regards

Nathalie

 

KChar.1
Senior

Hi @Erwan SZYMANSKI,
 
Awesome! Thanks for this. I could not find something with a quick look but I will take a deeper look around to see whether a role exchange can be forced via sysfs.  @NBALL , this is a great suggestion! Highly appreciated! I will try to implement this logic as soon as I figure out an efficient way to force role changes from usr-space. 
 

KChar.1
Senior

Hey @Erwan SZYMANSKI and @NBALL , it has been a while but I was finally able to come up with a workaround based on your suggestions.

On the New Apple devices issue the suggested PHY tuning indeed increases chances for successful connections at about 80% according to internal testing but there is still about 20% chance that you will get a bind failure.

[   96.502077] cdc_ncm 3-1:1.0: bind() failure
[   96.531547] cdc_ncm 3-1:1.2: bind() failure


On older Apple laptops with DRP ports the issue is exactly as described by  @NBALL and USB-C Specifications. There is a 50-50 chance for the MP1 to be a SINK or a SOURCE.

After applying the new PHY tuning I attempted to resolve both issues with the same mechanism. I started by following @NBALL suggestion to modify the stusb160x driver to reset the connection and force a SINK role if 0x0E registry is SINK. Unfortunately at this point I realised that in our board we do not have a dedicated 5V regulator for stusb1600 but we are routing 5V directly from the 5V input similar to the DK2 board. Due to this I could not force reset the connection when the connected device was a SINK. To work around this issue I used register 0x23 to SW reset the stusb1600 after forcing the role to SINK. I exposed a SysFS entry that when is written with "1" it will force the power role to SINK and the data role to "PERIPHERAL" and then reset by using registry 0x23.

static ssize_t force_roles_store(struct device *dev,
                 struct device_attribute *attr,
                 const char *buf, size_t count)
{
    struct stusb160x *chip = dev_get_drvdata(dev);
    u32 role;
    int ret;

    if (sscanf(buf, "%u", &role) != 1)
        return -EINVAL;

    if (role == 1) {
        // Force the power role to SINK and the data role to DEVICE
        typec_set_pwr_role(chip->port, TYPEC_SINK);
        typec_set_data_role(chip->port, TYPEC_DEVICE);


        // Write to the SW RESET register to reset the chip
        ret = regmap_write(chip->regmap, STUSB160X_RESET_CTRL, STUSB160X_SW_RESET_EN);
        if (ret)
            return ret;


        // Wait for the reset to take effect
        msleep(475);

        // Write to the SW RESET register again to clear the reset
        ret = regmap_write(chip->regmap, STUSB160X_RESET_CTRL, 0);
        if (ret)
            return ret;
    }

    return count;
}



Then I used a udev rule to check for attach and detach connections.

# /etc/udev/rules.d/99-usb.rules
ACTION=="add", SUBSYSTEM=="typec", DEVPATH=="/devices/platform/soc/5c002000.i2c/i2c-2/2-0028/typec/port0/port0-partner", RUN+="/bin/rlq_usb_attach"
ACTION=="remove", SUBSYSTEM=="typec", DEVPATH=="/devices/platform/soc/5c002000.i2c/i2c-2/2-0028/typec/port0/port0-partner", RUN+="/bin/rlq_usb_dettach"


With udev rules I am able to probe device information from the corresponding scripts as below:

# Assemble the DEVNAME
DEVNAME="/dev/bus/usb/003/$(ls /dev/bus/usb/003/ | sort -n | tail -1)"

# Get the ID_MODEL of the USB device
PRODUCT=$(udevadm info --query=property --name=$DEVNAME | grep ID_MODEL= | cut -d'=' -f2)

and I can force the power role to SINK with:

    echo 1 > /sys/bus/i2c/devices/i2c-2/2-0028/force_roles

and the data role with

echo "host" > /sys/devices/platform/soc/49000000.usb-otg/usb_role/49000000.usb-otg-role-switch/role

My thinking is that in this way I can get enough information on the connected device to find solutions for different edge cases. In the above example if a device is a Macbook with a USB-C charger I will force the roles to sink and peripheral but if a device is an Apple Product with an M chip I will check for bind failures and just force reset the connection until I establish one.

The maximum time to establish a connection until now is below 2seconds with every device I tested including M chips, older Apple computers and other peripherals.

Although this solution still looks a bit complicated it works according to my specs at the moment but please let me know if there any ideas for improvement :)