Linux Driver for Silicon Laboratories WFx00 Series

Download the Linux Driver Source Code from Github

Loading and Probing

The WFx chip series can be connected via SPI or via SDIO.

SPI

You have to declare the WFx chip in your device tree.

Required properties:

Optional properties:

Please consult Documentation/devicetree/bindings/spi/spi-bus.txt for optional SPI connection related properties,

Example:

&spi1 {
  wfx {
    compatible = "silabs,wfx-spi";
    pinctrl-names = "default";
    pinctrl-0 = <&wfx_irq &wfx_gpios>;
    interrupts-extended = <&gpio 16 IRQ_TYPE_EDGE_RISING>;
    wakeup-gpios = <&gpio 12 GPIO_ACTIVE_HIGH>;
    reset-gpios = <&gpio 13 GPIO_ACTIVE_HIGH>;
    reg = <0>;
    spi-max-frequency = <42000000>;
  };
};

SDIO

The driver is able to detect a WFx chip on SDIO bus by matching its Vendor ID and Product ID. However, driver will only provide limited features in this case. Thus declaring Wfx chip in device tree is strongly recommended (and may become mandatory in the future).

Required properties:

In addition, it is recommended to declare a mmc-pwrseq on SDIO host above WFx. Without it, you may encounter issues with warm boot. mmc-pwrseq should be compatible with mmc-pwrseq-simple. Please consult Documentation/devicetree/bindings/mmc/mmc-pwrseq-simple.txt for more information.

Example:

/ {
  wfx_pwrseq: wfx_pwrseq {
    compatible = "mmc-pwrseq-simple";
    pinctrl-names = "default";
    pinctrl-0 = <&wfx_reset>;
    reset-gpios = <&gpio 13 GPIO_ACTIVE_LOW>;
  };
};

&mmc1 {
  mmc-pwrseq = <&wfx_pwrseq>;
  #address-size = <1>;
  #size = <0>;

  mmc@1 {
    compatible = "silabs,wfx-sdio";
    reg = <1>;
    pinctrl-names = "default";
    pinctrl-0 = <&wfx_wakeup>;
    wakeup-gpios = <&gpio 12 GPIO_ACTIVE_HIGH>;
  };
};

Note that #address-size and #size shoud already be defined in node mmc1, but it is rarely the case.

Common Properties

Some properties are recognized either by SPI and SDIO versions:

WFx driver also supports mac-address and local-mac-address as described in Documentation/devicetree/binding/net/ethernet.txt

How to Change MAC Address?

WFx follows standard rules related to MAC addresses under Linux. You can set them in device tree or once driver is loaded with:

$ ip link set wlan0 address 01:02:03:04:05:06

Advanced Driver Usage

How to Get Error Messages?

Run dmesg to display all message generated by WFx (or any drivers). Notice that dmesg -w allow to display driver messages as they arrive.

Reset Device Without Rebooting Target

In order to reset device, device tree should correctly declare reset-gpio (directly in device node for SPI and using mmc-pwrseq for SDIO). Once done, it is possible to dynamically bind and unbind device.

For spi, it is possible to just unbind the device:

$ ls /sys/bus/spi/drivers/wfx-spi/
...
/sys/bus/spi/drivers/wfx-spi/spi0.0
...
$ echo spi0.0 > /sys/bus/spi/drivers/wfx-spi/unbind
$ echo spi0.0 > /sys/bus/spi/drivers/wfx-spi/bind

For sdio, it is necessary to unbind the whole sdio bus:

$ ls /sys/bus/platform/drivers/mmc-bcm2835
...
/sys/bus/platform/drivers/mmc-bcm2835/3f300000.mmc
...
$ echo 3f300000.mmc > /sys/bus/platform/drivers/mmc-bcm2835/unbind
$ echo 3f300000.mmc > /sys/bus/platform/drivers/mmc-bcm2835/bind

Note: reloading WFx driver allows to reset chip on SPI bus, but it does not work on SDIO bus.

Using spidev Besides wfx-spi

It is possible to declare your device compatible with both silabs,wfx-spi and spidev (ie. using /dev/spi0.0). In this case, you will be able to use alternatively both drivers without rebooting or changing device tree.

In order to avoid automatic handling of device by spidev, we suggest to blacklist spidev:

echo blacklist spidev > /etc/modprobe.d/silabs.conf

You can load spidev later with:

modprobe spidev

Next, it is possible to bind/unbind device from/to wfx/spidev driver.

To make /dev/spi0.0 appear:

echo spi0.0 > /sys/bus/spi/drivers/wfx-spi/unbind
echo spi0.0 > /sys/bus/spi/drivers/spidev/bind

To make wlan0 appear:

echo spi0.0 > /sys/bus/spi/drivers/spidev/unbind
echo spi0.0 > /sys/bus/spi/drivers/wfx-spi/bind

Controlling gpio Manually

When device is not binded, you can control the reset gpio manually:

echo spi0.0 > /sys/bus/spi/drivers/wfx-spi/unbind
echo 13 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio13/direction
echo 0 > /sys/class/gpio/gpio13/value
echo 1 > /sys/class/gpio/gpio13/value

The WFx driver normally takes over the reset gpio on device binding. However, it is possible to change this behavior by passing gpio-reset=-1 as parameter:

modprobe wfx gpio-reset=-1

It is also possible to change this parameter without unloading the driver with:

echo -1 > /sys/module/wfx/parameters/gpio_reset

However, you have to unbind/bind device in order to take into account any new value.

Of course, you can also remove reset-gpio attribute from device tree.

Loading PDS

You send PDS fragment to chip using /sys/kernel/debug/ieee80211/phy*/wfx/send_pds

You can directly execute pds_compress on this file:

pds_compress YOUR.pds.in /sys/kernel/debug/ieee80211/phy*/wfx/send_pds

Enable/Disable UAPSD

For some tests, UAPSD is required. You can enable it with:

echo 0xF > /sys/kernel/debug/ieee80211/phy1/netdev:wlan0/uapsd_queue

Obviously, you can disable UAPSD with:

echo 0 > /sys/kernel/debug/ieee80211/phy1/netdev:wlan0/uapsd_queue

Debugging

How to Enable dev_dbg()?

By default, traces defined with dev_dbg() are not displayed. The easiest way to enable them is to add #define DEBUG on top of files you want to trace. It is also possible to enable all traces by setting the Makefile variable ccflags-y to -DDEBUG but it is insane since it enables too much traces.

It is possible to dynamically enable traces. This way allows to enable/disable each trace. For example:

echo 'file sta.c line 1603 +p' > /sys/kernel/debug/dynamic_debug/control

You can also pass dyndbg=p to modprobe to enable messages during module loading.

All features of dynamic debug are described in Documentation/dynamic-debug-howto.txt.

Of course, you need to run dmesg (or even better dmesg -w) to display messages.

How to Trace Events?

Tracers allow to trace plenty of useful events from kernel. It is described in Documentation/trace/events.txt.

You can get a list of tracepoints implemented by WFx with:

$ ls /sys/kernel/debug/tracing/events/wfx/

Thus, you can trace all bus transfers with:

$ echo 1 > /sys/kernel/debug/tracing/events/wfx/io_read/enable
$ echo 1 > /sys/kernel/debug/tracing/events/wfx/io_read32/enable
$ echo 1 > /sys/kernel/debug/tracing/events/wfx/io_write/enable
$ echo 1 > /sys/kernel/debug/tracing/events/wfx/io_write32/enable
$ cat /sys/kernel/debug/tracing/trace_pipe
kworker/2:0H-23    [002] .... 429125.079545: io_write: QUEUE: 10 04 06 28 02 10 04 04 00 00 c4...
kworker/2:0H-23    [002] .... 429125.079597: io_read32: CONTROL: 00003004
kworker/2:0H-23    [002] .... 429125.079631: io_read: QUEUE: 08 00 06 20 00 00 00 00 00 30 (10 bytes)
kworker/2:0H-23    [002] .... 429125.079685: io_read32: CONTROL: 00003000
kworker/2:0H-23    [002] .... 429125.079719: io_read32: CONFIG: 01070000
kworker/2:0H-23    [002] .... 429125.079749: io_write32: CONFIG: 01070000
<Ctrl+C>

Notice, the tee command can replace a serie of echo X > file:

$ echo 1 | tee /sys/kernel/debug/tracing/events/wfx/io_*/enable

Disable traces with:

$ echo 0 > /sys/kernel/debug/tracing/events/enable

It can be more convenient to follow higher level HIF messages:

$ echo 1 | tee /sys/kernel/debug/tracing/events/wfx/wsm_*/enable
$ cat /sys/kernel/debug/tracing/trace_pipe
kworker/2:0H-23    [002] .... 429125.079556: wsm_send: 40:WRITE_MIB_REQ/TEMPLATE_FRAME: 00 00 c4 00...
kworker/2:0H-23    [002] .... 429125.079636: wsm_recv: 32:WRITE_MIB_CNF: 00 00 00 00 (8 bytes)
kworker/2:0H-23    [002] .... 429125.079834: wsm_send: 48:WRITE_MIB_REQ/RX_FILTER: 08 00 00 00 40 00 00 00 (16 bytes)
kworker/2:0H-23    [002] .... 429125.079915: wsm_recv: 40:WRITE_MIB_CNF: 00 00 00 00 (8 bytes)
kworker/2:0H-23    [002] .... 429125.080412: wsm_send: 56:START_SCAN_REQ: 00 00 02 00 00 00 00 00 02...
<Ctrl+C>

It can also be convenient to trace IRQs associated to the WFx chip. In this case, we want to trace all IRQs and add a filter to only show IRQs related to WFx:

$ echo 'name == "wfx"' >  /sys/kernel/debug/tracing/events/irq/irq_handler_entry/filter
$ echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_entry/enable
$ cat /sys/kernel/debug/tracing/trace_pipe
      <idle>-0     [000] d.h. 429585.854353: irq_handler_entry: irq=167 name=wfx
kworker/2:0H-23    [002] .... 429585.854353: wsm_send: 16:START_SCAN_REQ: 00 00 02 00 00 00 00 00 02...
kworker/2:0H-23    [002] .... 429585.854413: wsm_recv: 8:START_SCAN_CNF: 00 00 00 00 (8 bytes)
      <idle>-0     [000] d.h. 429585.859621: irq_handler_entry: irq=167 name=wfx
kworker/2:0H-23    [002] .... 429585.859942: wsm_recv: 20:RX_IND: 00 00 00 00 01 00 00 6c 80 00 04 00...

Another exemple would be to also trace wake-up gpio:

$ echo 1 > /sys/kernel/debug/tracing/events/gpio/gpio_value/enable
$ echo 'gpio == 12' > /sys/kernel/debug/tracing/events/gpio/gpio_value/filter
$ cat  /sys/kernel/debug/tracing/trace_pipe
kworker/3:0H-21    [003] ...1   342.741704: gpio_value: 12 set 1
       <...>-1172  [000] d.h2   342.743710: irq_handler_entry: irq=182 name=wfx
        spi0-182   [000] d.h3   342.745488: irq_handler_entry: irq=182 name=wfx
kworker/3:0H-21    [003] ...1   342.749736: gpio_value: 12 set 0
kworker/2:0H-23    [002] ....   342.854353: wsm_send: 16:START_SCAN_REQ: 00 00 02 00 00 00 00 00 02...
kworker/2:0H-23    [002] ....   342.854413: wsm_recv: 8:START_SCAN_CNF: 00 00 00 00 (8 bytes)

It is also possible to trace requests from mac80211 stack to WFx driver:

$ echo 1 | tee /sys/kernel/debug/tracing/events/mac80211/drv_*/enable
$ cat /sys/kernel/debug/tracing/trace_pipe
wpa_supplicant-156 [002] .... 429945.348883: drv_hw_scan: phy0 vif:wlan0(2)
 ksoftirqd/0-9     [000] d.H. 429945.349256: irq_handler_entry: irq=167 name=wfx
kworker/2:0H-23    [002] .... 429945.349360: wsm_send: 48:WRITE_MIB_REQ/TEMPLATE_FRAME: 00 09 c4 00 40...
kworker/2:0H-23    [002] .... 429945.349425: wsm_recv: 16:WRITE_MIB_CNF: 00 00 00 00 (8 bytes)
        sshd-5400  [000] d.h. 429945.349595: irq_handler_entry: irq=167 name=wfx
kworker/2:0H-23    [002] .... 429945.349596: wsm_send: 56:WRITE_MIB_REQ/RX_FILTER: 08 00 00 00 40 00 00...
kworker/2:0H-23    [002] .... 429945.349637: wsm_recv: 24:WRITE_MIB_CNF: 00 00 00 00 (8 bytes)
wpa_supplicant-156 [002] .... 429945.349752: drv_return_int: phy0 - 0
kworker/2:0H-23    [002] .... 429945.349867: wsm_send: 0:START_SCAN_REQ: 00 00 02 00 00 00 00 00 02 64...
      <idle>-0     [000] d.h. 429945.701657: irq_handler_entry: irq=167 name=wfx
kworker/2:0H-23    [002] .... 429945.701732: wsm_recv: 8:SCAN_CMPL_IND: 00 00 00 00 00 0d 00 00 (12 bytes)
wpa_supplicant-156 [002] .... 429945.702647: drv_get_survey: phy0 idx:0
wpa_supplicant-156 [002] .... 429945.702664: drv_return_int: phy0 - -95

Note that there is no real way to trace event during module loading. The best solution is to defer probing, setup traces and finally request probe manually:

$ modprobe wfx async_probe
$ echo 1 | tee /sys/kernel/debug/tracing/events/wfx/wsm_*/enable
$ cat /sys/kernel/debug/tracing/trace_pipe &
$ echo spi0.0 > /sys/bus/spi/drivers/wfx-spi/bind

(see also this patch)

Also note that perf command provides an alternative way to access to tracepoints.

How to Use gdb

Before to start, you need to install gdb-multiarch:

apt-get install gdb-multiarch

Also add set auto-load safe-path / to your ~/.gdbinit:

echo 'set auto-load safe-path /' >> ~/.gdbinit

Make sure you have write access to serial port (usually /dev/ttyUSB0). If not, run:

adduser YOUR_USER dialout

then reboot your work station.

Next, follow these steps:

  1. Connect uart between your workstation and your target
  2. Make sure uart is not used by WFx chip (check your PDS)
  3. Make sure uart is not used as console by host. Check /proc/cmdline does not contains console=ttyAMA0,115200. Remove 'console=serial0,115200' from /boot/cmdline.txt if necessary.
  4. Make sure you have compiled/deployed your own kernel (it is possible to not compile kernel yourself, it simplifies things a lot)
  5. Connect to your target using ssh
  6. Configure kgdb to use serial line:

     $ echo ttyAMA0,115200 > /sys/module/kgdboc/parameters/kgdboc
  7. Load WFx module

     $ modprobe wfx
  8. Break kernel execution:

     $ echo g > /proc/sysrq-trigger
  9. Run gdb on vmlinux file from your build directory

     $ gdb-multiarch vmlinux
  10. Under gdb, connect to target

    (gdb) set remotebaud 115200
    (gdb) target remote /dev/ttyUSB0
    Remote debugging using /dev/ttyUSB0
    kgdb_breakpoint () at kernel/debug/debug_core.c:1071
    1071            arch_kgdb_breakpoint();
  11. Load symbols from all loaded modules (note that lx-symbols does not recognize ~)

     (gdb) lx-symbols /home/jerome/wfx/wfx_linux_driver
     loading vmlinux
     scanning for modules in /home/jerome/wfx/wfx_linux_driver
     scanning for modules in /home/jerome/wfx/wfx_linux_kernel
     loading @0x7f111000: /home/jerome/wfx/wfx_linux_driver/wfx.ko
     loading @0x7f10a000: /home/jerome/wfx/wfx_linux_kernel/drivers/spi/spidev.ko
     [...]
  1. Place a breakpoint:

    (gdb) b wfx_scan_start
    Breakpoint 1 at 0x7f136664: file /home/jerome/wfx/wfx_linux_driver/scan.c, line 32.
  2. Continue execution:

    (gdb) c
  3. End with:

    (gdb) detach

Note that it is possible to use it inside eclipse, however, process is far from being easy and many things must be done manually.

Architecture