Enable USB networking

Hello old friend

Sorry for the big delay between posts. I got caught up with some other work.

In fact I had most of the work done 3 months ago, and there were some finishing touches left, but when I resumed on my journey I figured I can’t even run the mainline kernel as I did in the last post, which was pretty embarrassing…

Luckily I was able to dig up the old boot.img along with it initfs and dtb. Also luckily I had IKCONFIG enabled which meant I can do ./scripts/extract-ikconfig kernel and get a working kconfig from the old kernel. I also found out I had a few errors in my DTS (I guess I did refactoring and messed up the published file). So I was back on track after 3 hours of head-bashing!

So remember kids:

  • refactor (clean up) your code after you make some progress
  • test your cleaned-up code to make sure it still works
  • save kconfig to a known location
  • keep a log of what you’ve tried and what happened


USB is one of the more complicated nodes in the DT. Worse — it has dependencies, i.e needs clocks, regulators, smmu-s etc. These were needed to get mine running:

  1. GCC (Global Clock Controller) driver + dt node. The driver is more or less copy-pasted from downstream drivers/clk/qcom/gcc-bengal.c onto drivers/clk/gcc-sm6115.c. And then remove/comment out the bits that don’t compile like some props not (yet) present on mainline, and pray! patch
  2. GDSC power domains. These are simple on/off switches that are described under arch/arm64/boot/dts/vendor/20882/bengal-gdsc.dtsi in Downstream kernel and are placed in the gcc driver on mainline, like this sdm845. patch
  3. RPMCC clock driver. This is a separate group of clocks that live in drivers/clk/qcom/clk-smd-rpm.c (both DS and ML). patch
  4. APCS IPC compat. This is a one liner.
  5. Add dts nodes for all of the above, including smmu and of course usb

Noteworthy Hacks and Pointers

grep -EInr pattern dir

Keep in mind that mainlining is basically Copy-Paste for Intermediate Users. With that said you need to figure out what to copy and where from. grep is your best friend so make sure you are grepping constantly in mainline for similar drivers/code, and from downstream for the original code. In many cases it is helpful to find other device’s downstream bits (most notably DT, sometimes code), to compare how they got mainlined and follow a similar trajectory.

ignore unused

Especially early on clocks that are not specified in the DT (i.e most clocks, as your DT starts empty), should not be stopped. Another way to look at it is — you just told your kernel about the presence of 150 clocks (in the driver that you sneakily copy-pasted) and then you specify 3 clocks in the USB node. The kernel looks at this and says, “oooh let’s save some power by turning off those 147 clocks” which leads to phone turning off (in the best-case scenario, without sparks or bangs). Because of course (some of) those 147 are doing critical work, we just haven’t bothered specifying which (yet).

To do this, add clk_ignore_unused pd_ignore_unused to the chosen { bootargs = "..." } property in the device.dts file (or add them to your kernel command line via mkbootimg). Thanks Konrad.

rpm voter clocks

There is a new type of clock introduced in 2020(-ish) under drivers/clk/qcom/clk-smd-rpm.c. DS devs named them voter clocks, the code is located under drivers/clk/qcom/clk-voter.[ch]. The general idea is that I didn’t initially port them, because I thought they weren’t that needed, but it turned out my USB PHY does need a voter clock 🙁

Konrad again to the rescue hinted I can just replace the voter clock with &xo_board (basically a global board clock that is immutable). Essentially this is a way to provide a dummy clock where you can’t be bothered to provide the actual clock. In cases where the actual clock is already configured and running as expected by the component — all is well. Otherwise — stuff won’t work.

In this particular case it is relatively easy to actually add support for the voter clocks, but it is useful to know that you can always chicken-out and give xo_board instead.

♫ let me ♫ regulate you♫

Or not. All things need power, and regulators normally provide. The issue is that mainline regulator driver requires some non-trivial work. If you feel above all this you can just ignore all supply attributes and hope for the best. The situation is similar to the ignore unused mentioned above — if the regulator happens to be configured properly stuff would work.

usb weirdness

I’ll list a bunch of things here, because I’m too lazy to explain it all in detail:

  • to get it to work, you need the main usb node (compat qcom,dwc3), inside it another one (compat snps,dwc3) and a separate hs (high speed) phy with compat qcom,sm4250-qusb2-phy
  • be careful with address on the qcom,dwc3 one, because DS and ML differ. Generally you should take the DS reg of the inner node and add 0xf8800 to it, with length 0x400. If you burn your phone you haven’t heard this from me, otherwise give me all the credit!
  • for me I had to add an iommu (the kernel was crashing in a dwc3 irq without iommu). On DS the iommu is attached on the outher qcom.dwc3 node, on ML it is attached on the inner snps node.
  • If you only enable the hsphy, you need qcom,select-utmi-as-pipe-clk in the outher node.
  • The phy might need some driver code. If your device is recent you can try to quickly fry it (or quickly get it to work, 50:50) by using qcom,msm8996-qusb2-phy compat. Alternatively you can stare at the qcom,qusb-phy-init-seq, figure out that it is value, reg-address pairs described under qusb2_phy_init_tbl arrays in ML and go from there. But that is sooo boring.
  • one of the phy registers (named tune2_efuse_addr in my DS), is specified using nvmem-cells as a property under a qcom,qfprom. The idea is that some memory locations are actually non-volatile (stay after reboot), and keep some tuning parameters set from the factory. You can find the qfprom node in DS and note that it’s base address 0x1b40000 is similar to the reg address in DS 0x1b40258, so you can add a qfprom inner node with just the offset 0x258. Then you might notice that the 0x19-th bit is the 2nd bit of the 3rd byte, so 0x258 becomes 0x25b, and the bit attribute 1 instead of 0x19. If you get it wrong you’d never know, so don’t worry too much about it.