Oct 7, 2014

Qt5 GUI on Zedboard

Most Linux distributions (Ubuntu being the most popular right now) use the X windowing system for the display.  Since I don't need a full-blown graphical desktop on an embedded hardware controller but do need a sexy GUI application that runs in full screen mode, QT embedded is a good fit for my needs.  One can certainly cross-compile QT using any toolchain (as shown in this example), but since I have been creating my root file system in Buildroot, I want to modify the Buildroot config to pickup Qt as well.

Optional: minimal Zedboard Linux kernel config that works with Qt Embedded

Linux kernel and modules are complicated, and there are way too many modules even to count.  Being a minimalist, I wanted to whittle down even the kernel to the smallest size that supports running a Qt GUI.  The true minimal set is impossible to obtain: there are just too many dependencies, and my needs many change soon.  This is what I removed from the current working Zedboard kernel that boots from TFS, supports NFS rootfs:
  • Remove all SPI related modules, even the QSPI, which is the 1st stage boot device I want to use.  It's OK because Linux is NOT responsible for booting itself.
  • Remove most of the USB HID device drivers, except for Logtech and Microsoft
  • Remove everything I could see under IIO
    • CONFIG_IIO
  • Remove all LED related configs.
  • Remove HRTIMER
  • Removed KGDB--does NOT seem to work for debugging startup problems, which is where most the problems are
    • But leave CONFIG_DEBUG_INFO and CONFIG_FRAME_POINTER: still helps with JTAG debugging
  • Removed FTRACE related configs
Note that I2C still remains, in order to drive the ADV7511 HDMI display!

Buildroot config that includes Qt Embedded

From a minimal uClinux root file system as the baseline, I made the following changes:
  • Toolchain:
    • Buildroot toolchain
    • Kernel headers: pick something close to my kernel, which is the ADI kernel (version 3.15 when I downloaded it)
    • C library: glibc seems to be less finicky than uClibc, and it is also required for CUDA
    • Threading: Native POSIX (NPTL)
    • C++ support
    • tls (thread local storage) support
    • Build cross gdb for the host, AND select Python support.   Otherwise, you will see this message when trying to cross debug a Qt application:
    • Purge unwanted locales (except en_US)
    • Register toolchain within Eclipse Buildroot plug-in (to build my own programs in Eclipse)
  • Kernel
    • I have my own kernel (based on ADI's 3.15 kernel)
      • Local directory: /mnt/work/zed/kernel
      • Kernel configuration: defconfig (zynq_xcomm_adv7511_nfs)
      • Kernel binary format: uImage
      • Load address: 0x8000
    • Device tree support
      • Change the DTS file from zynq-zed to zynq-zed-adv7511.  While debugging why I am not seeing /dev/fb0, I found out that the ADV7511 device does not show up in /proc/device-tree (according to kernel/arch/arm/boo/dts/zyhnq-zed-adv7511.dts, I was looking for fpga-axi@0), and finally realized that the zedbord Buildroot config checked into buildroot does NOT display!
  • Target packages:
    • Debugging: just gdb (gdbesrver) for cross debugging.  May add oprofile and valgrind later.
    • Graphic libraries and applications:
      • directfb
        • Do NOT choose /dev/input/eventX input driver, because the latest CHIPIDEA USB driver enumerates the keyboard twice, leading to doubling of each keystroke (for example, typing "dir" yields "ddiirr").
        • compile keyboard input driver
        • compile PS2 mouse input driver (also supports USB mouse)
        • For me, no need for touchscreen support
      • FORGET about mesa3d-demos, which requires Xwindows
      • But DO consider the QT libraries in the future
        • qextserialport if the application needs to control serial devices
        • qwt: 2D plotting
      • FORGET about mesa3d, which allows OpenGL EGL and OpenGL ES packages.  Qt does not require OpenGL, and my unsexy Qt GUI can get by without 3D rendering
      • Do NOT select Qt--the Qt4 (Qt Embedded), because I want Qt5 (the newer release).  BUT if you do want it,
        • Note that only Qt standard is NOT available without an X-windows (which I do NOT want)
        • Compile and install Qt demos (with code)
        • Library type: static library, because I just want to copy 1 file to the target
        • Approve free license
        • Gui module
          • freetype2 support: Qt freetype2
          • JPEG support: system libjpeg
          • PNG support: system libpng
          • TIFF support: system libtiff
        • OpenGL ES v2.x support: does NOT build, at least for mesa on ARM
        • Uncheck Script Module
        • Graphics drivers: note that only linuxfb is selected (because I did not enable direcfb).  multiscreen driver looks interesting, but Zedboard can only driver 1 monitor.
        • Mouse drivers: linux input (to support USB mouse)
        • Keyboard drivers: linux input (to support USB mouse)
      • Qt5 (and only Qt5)
        • Approve free license
        • Compile and install examples (with code)
        • gui module
          • widgets module
          • Choose directfb platform, and uncheck OpenGL support, which is required for eglfs support
          • Default graphical platform: directfb
          • GIF, JPEG, PNG support
        • Do NOT check qt5imageformats (for TIFF support); it requires web support, which I do NOT want.
    • Libraries
      • Hardware handling: check libftdi (because I have experience integrating a distributed system over FTDI D2XX USB SDK), with C++ bindings (because I might use the Qt C++ API)
      • Graphics
        • tiff: in biotech, raw image files are saved in tiff
        • opencv, with tiff and turn off calib3d, legacy, ts, video, videostab modules.  Do NOT choose qtbackend support, which turns OFF Qt5
    • Networking applications
      • openssh: necessary for remote gdb debugging
      • ntp: if the host will be network enabled, it's nice to have accurate time
    • Shells and utilities:
      • Optional: logrotate: application WILL log, and it will have to be rotated.
      • file, screen: indispensable development tools
Note that I am still using EABI toolchain, rather than EABIhf (to try later).  I found that Qt takes a *long* time to build; the resulting rootfs.tar is 81 MB even after stripping out all other locales except for US-en.

1st try fails: adv7511 driver probe error -110 (ETIMEOUT)

When I change the DTS file from zynq_zed to zynq_zed_adv7511 in Buildroot config, I still don't have /dev/fb0, but at least the kernel initializes DRM, as you can see below:

xdevcfg f8007000.devcfg: ioremap 0xf8007000 to e0816000
[drm] Initialized drm 1.1.0 20060810
drivers/gpu/drm/adi_axi_hdmi/axi_hdmi_drv.c:axi_hdmi_platform_probe[176]
platform 70e00000.axi_hdmi: Driver axi-hdmi requests probe deferral
...
i2c /dev entries driver
...
adv7511: probe of 0-0039 failed with error -110

zynq-edac f8006000.ps7-ddrc: ecc not enabled
...
adv7511-hdmi-snd adv7511_hdmi_snd.3: ASoC: CODEC (null) not registered
platform adv7511_hdmi_snd.3: Driver adv7511-hdmi-snd requests probe deferral

zed-adau1761-snd zed_sound.4: adau-hifi <-> 77600000.axi-i2s mapping ok

It looks like the HDMI sound HW initialized OK, but the ADV7511 video driver has a problem, so I have to look inside the ADI supplied device driver in <kernel>/drivers/gpu/drm/i2c/adv7511_core.c.  It would appear that this is the right device driver for the <kernel>/arch/arm/boot/dts/zynq-zed-adv7511.dts, because the device properties specified in the DTS are parsed in the adv7511_parse_dt() function, as shown in the side by side comparison below:

zynq-zed-adv7511.dts adv7511@39 property static int adv7511_parse_dt ( struct device_node *np, struct adv7511_link_config *config)
compatible = "adi,adv7511";
reg = <0x39>;
adi,input-style = <0x02>;
adi,input-id = <0x01>;
adi,input-color-depth = <0x3>;
adi,sync-pulse = <0x03>;
adi,bit-justification = <0x01>;
adi,up-conversion = <0x00>;
adi,timing-generation-sequence = <0x00>;
adi,vsync-polarity = <0x02>;
adi,hsync-polarity = <0x02>;
adi,tdms-clock-inversion;
adi,clock-delay = <0x03>;
of_property_read_u32(np,
  "adi,input-id", &config->id);

config->sync_pulse =
  ADV7511_INPUT_SYNC_PULSE_NONE;

of_property_read_u32(np,
  "adi,sync-pulse",
   &config->sync_pulse);

...

config->gpio_pd =
  of_get_gpio(np, 0);
if (config->gpio_pd
    == -EPROBE_DEFER)
  return -EPROBE_DEFER;


return 0;

The DT parsing function is called from the ".probe" function adv7511_probe:

static const struct i2c_device_id adv7511_ids[] = {
        { "adv7511", 0 },
        {}
};

static struct drm_i2c_encoder_driver adv7511_driver = {
        .i2c_driver = {
                .driver = {
                        .name = "adv7511",
                },
                .id_table = adv7511_ids,
                .probe = adv7511_probe,
                .remove = adv7511_remove,
        },

        .encoder_init = adv7511_encoder_init,
};
static int __init adv7511_init(void)
{
  return drm_i2c_encoder_register(THIS_MODULE, &adv7511_driver);

}
module_init(adv7511_init);

According to the DTS, "adv7511" is a slave of the axi_hdmi@70e00000

zynq-zed-adv7511.dts axi_hdmi@70e00000 property<kernel>/drivers/gpu/drm/adi_hdmi_drv.c
axi_hdmi@70e00000 {
  compatible =
   "adi,axi-hdmi-tx-1.00.a";
  reg = <0x70e00000 0x10000>;
  encoder-slave = <&adv7511>;
  dmas = <&axi_vdma_0 0>;
  dma-names = "video";
  clocks = <&hdmi_clock>;
};
static struct platform_driver adv7511_encoder_driver = {
  .driver = {
    .name = "axi-hdmi",
    .owner = THIS_MODULE,
    .of_match_table = adv7511_encoder_of_match,
  },
  .probe = axi_hdmi_platform_probe,
  .remove = axi_hdmi_platform_remove,
};
module_platform_driver(adv7511_encoder_driver);

I know which function to read for why I saw " Driver axi-hdmi requests probe deferral" in dmesg earlier: on line 176 of the <kernel>/driveres/gpu/drm/adi_hdmi_drv.c, the clock is probably not ready yet:

174  private->hdmi_clock = devm_clk_get(&pdev->dev, NULL);
175  if (IS_ERR(private->hdmi_clock)) {
176    printk("%s:%s[%d]\n", __FILE__, __func__, __LINE__);
177    return -EPROBE_DEFER;
178  }
179
180  slave_node = of_parse_phandle(np, "encoder-slave", 0);
181  if (!slave_node)
182    return -EINVAL;
...
193  private->encoder_slave = of_find_i2c_device_by_node(slave_node);
194  of_node_put(slave_node);
195
196  if (!private->encoder_slave || !private->encoder_slave->dev.driver) {
197    printk("%s:%s[%d]\n", __FILE__, __func__, __LINE__);
198    return -EPROBE_DEFER;
199  }

Note that since the deferral happens even before parsing the "encode-slave" (the adv7511), the adv7511 driver will not even get initialized till later.  The clock being discussed is the <&hdmi_clock> in DTS But does it ever become ready?  I think so, because dmesg shows another axi-hdmi driver probe deferral a few moments later:
...
drivers/gpu/drm/adi_axi_hdmi/axi_hdmi_drv.c:axi_hdmi_platform_probe[197]
platform 70e00000.axi_hdmi: Driver axi-hdmi requests probe deferral
adv7511-hdmi-snd adv7511_hdmi_snd.3: ASoC: CODEC (null) not registered
platform adv7511_hdmi_snd.3: Driver adv7511-hdmi-snd requests probe deferral

If the axi-hdmi probe failed at checking the encoder slave (which is expected, since the slave probe died with error code -110--ETIMEOUT) a few lines below the clock checking, it must mean that the clock was OK the 2nd time around.  This time, the error is because the encoder_slave (the <&adv7511> in DTS) or the slave's device driver is NULL; since the slave has to be found through I2C bus search--HW operation--I would guess this is the problem.

Turn on I2C kernel driver debugging

To get a better visibility into the I2C discovery failure, I added kernel configs CONFIG_I2C_DEBUG_CORE=y and CONFIG_I2C_DEBUG_BUS=y in my defconfig zynq_zed_adv7511_nfs_defconfig.  Here is the modified dmesg, showing only new/different lines from the failure case (note that the I2C driver debugging is now on):

platform 70e00000.axi_hdmi: Driver axi-hdmi requests probe deferral
i2c-core: driver [adv7511] registered
i2c-core: driver [at24] registered
...
i2c-dev: adapter [xiic-i2c] registered as minor 0
i2c i2c-0: adapter [xiic-i2c] registered
i2c i2c-0: of_i2c: walking child nodes
i2c i2c-0: of_i2c: register /fpga-axi@0/i2c@41600000/adv7511@39
i2c 0-0039: uevent
adv7511 0-0039: probe
i2c i2c-0: master_xfer[0] W, addr=0x39, len=2
xiic-i2c 41600000.i2c: xiic_xfer entry SR: 0xc0
xiic-i2c 41600000.i2c: __xiic_start_xfer entry, msg: d7451c34,...
xiic-i2c 41600000.i2c: xiic_start_send entry, msg: d7451c34,len: 2
xiic-i2c 41600000.i2c: xiic_start_send entry, ISR: 0xd0, CR: 0x1... 
xiic-i2c 41600000.i2c: xiic_fill_tx_fifo entry, len: 2, fifo space: 15 
xiic-i2c 41600000.i2c: xiic_fill_tx_fifo TX STOP
adv7511: probe of 0-0039 failed with error -110
i2c i2c-0: client [adv7511] registered with bus id 0-0039
...
adau1761 0-003b: probe
i2c-core: driver [adau1761] registered
adv7511-hdmi-snd adv7511_hdmi_snd.3: ASoC: CODEC (null) not registered
platform adv7511_hdmi_snd.3: Driver adv7511-hdmi-snd requests platform adv7511_hdmi_snd.3: Driver adv7511-hdmi-snd requests
i2c i2c-0: master_xfer[0] W, addr=0x3b, len=2
i2c i2c-0: master_xfer[1] R, addr=0x3b, len=1
xiic-i2c 41600000.i2c: xiic_xfer entry SR: 0xc0
xiic-i2c 41600000.i2c: __xiic_start_xfer entry, msg: d7451d28,...
xiic-i2c 41600000.i2c: xiic_start_send entry, msg: d7451d28, len: 2
xiic-i2c 41600000.i2c: xiic_start_send entry, ISR: 0xd0, CR: 0x1
xiic-i2c 41600000.i2c: xiic_fill_tx_fifo entry, len: 2, ...
i2c i2c-0: master_xfer[0] W, addr=0x3b, len=3
xiic-i2c 41600000.i2c: xiic_xfer entry SR: 0x8c
...
zed-adau1761-snd zed_sound.4: adau-hifi <-> 77600000.axi-i2s mapping ok
i2c i2c-0: master_xfer[0] W, addr=0x3b, len=3
...
xiic-i2c 41600000.i2c: xiic_xfer entry SR: 0x8c
...
drivers/gpu/drm/adi_axi_hdmi/axi_hdmi_drv.c:axi_hdmi_platform_probe[197]

The difference between the success (adau1761) and the failure (adv7511) case is that the message to adau1761 (which also gets put into xiic_fill_tx_fifo?) is perhaps acknowledged, and more importantly the device driver can continue to the next message.  [But shouldn't even the successful case eventually come to exhaust the tx_fifo?]

The end result for the video and sound HDMI drivers are the same as before turning on I2C debug--I guess that's somewhat reassuring, but I am disappointed by no additional verbosity around where the I2C bus searches for ADV7511.  What the new dmesg log shows is that adv7511 probe begins, and the I2C transmission stops early, then the probe fails with -110 (ETIMEOUT).  The difference between TX STOP and sustained (at least for a few rounds) I2C transfer are the address (0x39 vs. 0x3B) and the message pointers (d7451c34 vs. d7451d28; but both messages are the same length--probably a handshake); in both cases, the master is the same (41600000.i2c: the parent of 39.adv7511 and 3b.adau1761 according to the DTS).

The "xiic_xfer entry SR" snippet above finally clues me into the function that is returning ETIMEOUT: <kernel>/drivers/i2c/busses/i2c-xiic.c, which is compatible with "xlnx,xps-iic-2.00.a" in the DTS.  The actual start of transfer is in xiic_start_xfer():

static void xiic_start_xfer(struct xiic_i2c *i2c)
{
  unsigned long flags;

  spin_lock_irqsave(&i2c->lock, flags);
  xiic_reinit(i2c);
  /* disable interrupts globally */
  xiic_setreg32(i2c, XIIC_DGIER_OFFSET, 0);
  spin_unlock_irqrestore(&i2c->lock, flags);

  __xiic_start_xfer(i2c);
  xiic_setreg32(i2c, XIIC_DGIER_OFFSET, XIIC_GINTR_ENABLE_MASK);
}

The actual start of message is defined in xiic_start_send, called from __xiic_start_xfer() above:

if (!(msg->flags & I2C_M_NOSTART)) {
  /* write the address */
  u16 data = ((msg->addr << 1) & 0xfe) | XIIC_WRITE_OPERATION |
                        XIIC_TX_DYN_START_MASK;
  if ((i2c->nmsgs == 1) && msg->len == 0)
    /* no data and last message -> add STOP */
    data |= XIIC_TX_DYN_STOP_MASK;

  xiic_setreg16(i2c, XIIC_DTR_REG_OFFSET, data);
}

Since the message size is 2, XIIC_TX_DYN_STOP_MASK will not be or'ed, so that the 16 bit message written to the data TX register should just be (0x39 << 1) | 0 | 0x0100 = 0x0172.  There is of course a lot of code between the above call of 16 bit register write on PS0 to when Zynq's I2C on-chip peripheral HW starts shaking the SDA/SCL.  But since the ADV7511 chip is sitting a couple of inches away from Zynq on the Zedboard, the ADV7511's SDA/SCL pins (pins 56 and 55) can only be shaken by the IO Zynq pins.  Back tracing ADV7511's SDA/SCL on the schematic, I find the other end of the 2 traces on BANK33 (VCC3V3), Y16/AA18, which appear in zed_system_constr.xdc in Vivado project:

set_property  -dict {PACKAGE_PIN  R7    IOSTANDARD LVCMOS33} [get_ports iic_scl]
set_property  -dict {PACKAGE_PIN  U7    IOSTANDARD LVCMOS33} [get_ports iic_sda]
set_property  -dict {PACKAGE_PIN  AA18  IOSTANDARD LVCMOS33 PULLTYPE PULLUP} [get_ports iic_mux_scl[1]]
set_property  -dict {PACKAGE_PIN  Y16   IOSTANDARD LVCMOS33 PULLTYPE PULLUP} [get_ports iic_mux_sda[1]]
set_property  -dict {PACKAGE_PIN  AB4   IOSTANDARD LVCMOS33 PULLTYPE PULLUP} [get_ports iic_mux_scl[0]]
set_property  -dict {PACKAGE_PIN  AB5   IOSTANDARD LVCMOS33 PULLTYPE PULLUP} [get_ports iic_mux_sda[0]]
So according to the constraint, the 1st output of the I2C mux is going to the ADAU1761 chip, and the 2nd output is going to the ADV7511 chip.  Behind IOBUFs that switch between output state and high impedance input state, sys_i2c_mixer should be handling the iic_scl inputs and outputs, as shown below in a portion of this Zynq design.
The mixer is a relative simple (2 VHDL files) logic that takes orders from AXI_IIC logic which emits the T (tristate) O (output) signals and takes the I (input) SCL/SDA signals.

Also in the top level module, iic_mux_scl The IO pins R7/U7 constrained to iic_scl/sda above are going to the FMC connector, and wired to iic_fmc_{csl|sda}_io in the top level module (system_top.v), which get connected to IIC_FMC_scl (part of IIC_FMC port) when I hop over an IOBU <--> axi_iic_fmc logic <--> axi_cpu_interconnect[M06_AXI] <--> M_AXI_GP0 in the design.

To debug the I2C messaging, I will begin by capturing the iic_mux_{scl|sda}_{i|o}[0,1] and the iic_mux_{scl|sda}_t wires: 10 wires total.  A chance to use my Hantek USB 16 bit logic analyzer!  But first, let's specify the IO constraints to the 2 Pmod connectors on the Zedboard (total of 4).  On the Zedboard schematic, JA1~4/7~10 are on the JA1 Pmod connector:
Similarly, JB1~4/7~10 are on the JB1 Pmod connector.  The Zynq IO pins to constrain can also be looked up in the schematic (3.3V bank 13) and added to the constraint file:

  • JA1 = Y11, JA2 = AA11, JA3 = Y10, JA4 = AA9, JA7 = AB11, JA8 = AB10, JA9 = AB9, JA10 = AA8
  • JB1 = W12, JB2 = W11, JB3 = V10, JB4 = W8, JB7 = V12, JB8 = W10, JB9 = V9, JB10 = V8
set_property -dict {PACKAGE_PIN Y11  IOSTANDARD LVCMOS33} [get_ports DebugA[0]]
set_property -dict {PACKAGE_PIN AA11 IOSTANDARD LVCMOS33} [get_ports DebugA[1]]
set_property -dict {PACKAGE_PIN Y10  IOSTANDARD LVCMOS33} [get_ports DebugA[2]]
set_property -dict {PACKAGE_PIN AA9  IOSTANDARD LVCMOS33} [get_ports DebugA[3]]
set_property -dict {PACKAGE_PIN AB11 IOSTANDARD LVCMOS33} [get_ports DebugA[4]]
set_property -dict {PACKAGE_PIN AB10 IOSTANDARD LVCMOS33} [get_ports DebugA[5]]
set_property -dict {PACKAGE_PIN AB9  IOSTANDARD LVCMOS33} [get_ports DebugA[6]]
set_property -dict {PACKAGE_PIN AA8  IOSTANDARD LVCMOS33} [get_ports DebugA[7]]

set_property -dict {PACKAGE_PIN W12  IOSTANDARD LVCMOS33} [get_ports DebugB[0]]
set_property -dict {PACKAGE_PIN W11  IOSTANDARD LVCMOS33} [get_ports DebugB[1]]
set_property -dict {PACKAGE_PIN V10  IOSTANDARD LVCMOS33} [get_ports DebugB[2]]
set_property -dict {PACKAGE_PIN W8   IOSTANDARD LVCMOS33} [get_ports DebugB[3]]
set_property -dict {PACKAGE_PIN V12  IOSTANDARD LVCMOS33} [get_ports DebugB[4]]
set_property -dict {PACKAGE_PIN W10  IOSTANDARD LVCMOS33} [get_ports DebugB[5]]
set_property -dict {PACKAGE_PIN V9   IOSTANDARD LVCMOS33} [get_ports DebugB[6]]
set_property -dict {PACKAGE_PIN V8   IOSTANDARD LVCMOS33} [get_ports DebugB[7]]

Then connect the DebugA/B wires at the top module.


  output[7:0] DebugA, DebugB;
  //Send to logic analyzer through Pmod JA and JB
  wire PLCLK0, PLnRST;
  assign DebugA[0] = PLnRST;            //channel0
  assign DebugA[1] = PLCLK0;            //channel1
  assign DebugA[2] = iic_mux_scl_t_s;   //channel2
  assign DebugA[3] = iic_mux_sda_t_s;   //channel3
  assign DebugA[4] = iic_mux_scl_o_s[0];//channel4
  assign DebugA[5] = iic_mux_sda_o_s[0];//channel5
  assign DebugA[6] = iic_mux_scl_i_s[0];//channel6
  assign DebugA[7] = iic_mux_sda_i_s[0];//channel7

  assign DebugB[0] = iic_mux_scl_o_s[1];//channel8
  assign DebugB[1] = iic_mux_sda_o_s[1];//channel9
  assign DebugB[2] = iic_mux_scl_i_s[1];//channel10
  assign DebugB[3] = iic_mux_sda_i_s[1];//channel11

The Pmod connectors are connected to the logic analyzer as shown in the picture below:

PLnRST and PLCLK0 are the 2 ports I created in the Zynq graphical configuration, as a sanity check when looking at the logic analyzer output, which looks like this:
FCLK_CLK0 is shaking at 500 us period when it should be a 100 MHz clock because of aliasing (this $99 16 channel logic analyzer can only sample up to 48 MHz--compare that to the $500 Saleae logic analyzer that samples at 500 MHz: you really do get what you pay for for the measurement tools).  When I expose the sdif_mclk at 12.8 MHz, the logic analyzer can barely keep up with it.  On the other hand, the Hantek DSO2250 250 MHz scope can show the 100 MHz PLCLK without a problem, showing that using the Pmod connector for logic analyzer debugging is a viable technique.  The biggest problem with the Hantek analyzer is inability to trigger.

Nevertheless, the INPUT SCL/SDA are bouncing, as you can see below (iic_mux_scl_i_s[1] in green and iic_mux_sda_i_s[1] in yellow):
and very shortly thereafter:

iic_mux_{sda|scl}_i_s are the OUTPUTs of the IOBUF (when Tristate is FALSE) and feeds the sys_i2c_mixer in the Zynq design, whereas iic_mux_{sda|scl}_o_s, which are the OUTPUTs of the sys_i2c_mixer show no activity.  But why would the I2C slave (ADV7511) respond with packets unless it received something from the master (sys_i2c_mixer)?

While thinking about whether/how to dig deeper into I2C, it occurred to me that SOME I2C communication is taking place, and it is unlikely that the messaging is broken in a bizarre way.  In my experience, things are usually broken in an OBVIOUS way (in hindsight).  So I started thinking about some OBVIOUS ways that I2C messaging to the ADV7511 could be broken, and the 1st thing that came to my mind were the master/slave modes and the address confusion.  According to the DTS (shown way above), the slave address is 0x39, so I was confused when reading ADV7511_Programming_Guide.pdf "Section 4 - Programming Tasks", which does NOT list 0x39 among the I2C addresses.  Instead, it says that if the PD/AD pin (pin 38 of the IC6 on the Zedboard) is tied to ground, then the I2C address is 0x72 (else 0x7A).  But on the same schematic, it says "I2C Address 0111001 R/W", so 0x39 must the right address.  And indeed, when I try changing the address to 0x72 or 0x7A, it still fails the same way.  So much for that guess.

When I exposed the wires from/to axi_iic_main to the scope and looked at the SCL in and out, I was surprised that there is absolutely no activity on the output from the axi_iic_main, which does NOT agree with the fact that the video and audio ICs should be I2C slaves.  Another surprise is that the signals are absolutely identical between the 1st and the 2nd inputs into the iic_mixer, as you can see below.
Even in the case of the working bare metal (no Linux) HDMI example, the output wires are complete, as you can see below:

Another bizarre thing is that the tristate (yellow in the capture below) is just the same as the scl, whereas I expected it to stay low the whole time the I2C master is transmitting.

When I read the raw HDMI code, I noticed that the iic_main is bypassed altogether!  For example, in SetVideoResolution(), the clkgen and HDMI video IP are controlled; but axi_iic_main is not involved.

Xil_Out32(CF_CLKGEN_BASEADDR + AXI_CLKGEN_V2_REG_DRP_CNTRL, 0x00);

Similarly, video is controlled through registers offset from either CFV_BASEADDR (axi_hdmi_core base address) or VDMA_BASE_ADDR (axi_hdmi_dma base address).  XPAR_AXI_IIC_MAIN_BASEADDR (the base address of axi_iic_main) is only accessed in XIic_Initialize() --> Xlic_LookupConfig() provided in the BSP, which is NOT used in the bare metal code.  So what is the role of the axi_iic_main in the Linux DRM driver?

I then wondered what the point of the i2c_mixer IP is anyway.  According to the reference design doc: "since axi_iic_main only has a single IIC port, an external multiplexer is required to connect multiple external IIC peripherals. The sys_i2C_mixer multiplexes the IIC ports of both the ADV7511 and the ADAU1761 back to the axi_iic_main IIC controller in the Zynq PL."  But I thought that I2C is NOT a 1-1 protocol like SPI!?

Hallelujah, Vivado 2013.4 generated bitstream works!

While googling what other people had to say about ADV7511 I2C communication, I stumbled on a forum question that said Vivado 2013.4 generated bitstream works for the Linaro Ubuntu demo, but Vivado 2014.2 generated bitstream does NOT, even though there are no obvious errors in connections between IPs.  So I downloaded 2013.4 to generate a bitstream, and created a BOOT.bin that has the same FSBL and uboot.elf, and beheld the 2 penguins on my monitor for the first time!

I then backed up the working 2013.4 folder, and fired up Vivado 2014.2 again, to let the auto-upgrade perform its magic, and got only 1 warning during the process:
 [IP_Flow 19-3298] Detected external port differences while upgrading IP 'system_sys_ps7_0'. These changes may impact your design.
In the upgrade log for IP 'system_sys_ps7_0', the above warning is elaborated: ports TTC0_CLKx_IN were removed.  But as you can see in the HW design connection for the PS7, this project does NOT use those ports anyway (TTC0_CLKx_IN are disconnected), so we are OK!
The updated HW design also works like the 2013.4 bitstream.

Back to the Qt example

With a working HW, back to the Qt demo application pathstroke.

# /usr/lib/qt/examples/widgets/painting/pathstroke/pathstroke
-plugin evdevmouse:/dev/input/event0 -plugin evdevkeyboard:/dev/input/event1



Zedboard HDMI output can drive demo application, and I can move the cursor!

BUT the keyboard is NOT responding, because the application does not use the keyboard.  When I tried the same arguments with the widgets/desktop/screenshot application, I saw that Qt was handling keyboard input.  I think the application is also not choosing the screen resolution correctly...

TODO: running QT application as non-root

The default /dev/fb0 permission (root:root 600) is too restrictive.  So far, I have been using the "dynamic using devtmpfs only" /dev management option in Buildroot, but reading the buildroot documentation, there doesn't seem to be a way to change the permission of devtmpfs files.  Instead, to change the permission of the device file /dev/fb0 through buildroo, there seem to be 2 ways:
  • Static using device table
  • Dynamic using mdev
  • There is actually also the eudev option (which is like the udev in the desktop world), but that requires rebuilding the toolchain with large file, so I don't want to consider it for now

If using static device table, Buildroot provides a way to pass the desired device permission to makedev command: <buildroot>/system/device_table.txt, which has the following format:

# See package/makedevs/README for details
#
# This device table is used to assign proper ownership and permissions
# on various files. It doesn't create any device file, as it is used
# in both static device configurations (where /dev/ is static) and in
# dynamic configurations (where devtmpfs, mdev or udev are used).
#
# <name> <type> <mode> <uid> <gid> <major> <minor> <start> <inc><count>
/dev d 755 0 0 - - - - -
/tmp d 1777 0 0 - - - - -
...
So I should add a new character device entry for /dev/fb0:

# <name> <type> <mode> <uid> <gid> <major> <minor> <start> <inc><count>
/dev/fb0 c 777 0 0 29 0 - - -

To force the root filesystem to rebuild, I deleted the .stamp_target_installed

$ find . -name \.stamp_target_installed -exec rm {} \;

My own Qt5 application

Running an example built by Buildroot is just a quick sanity check that SOME part of the QT infrastructure works.  Now the real work of writing an embedded Qt5 GUI for the Zedboard begins. About 10 years ago, Eclipse was the recommended Qt development environment, but now Eclipse is not officially supported any more; instead QtCreator supports cross-compiling!

Configure Qt Creator for cross compiling for Zed Buildroot

On Ubuntu, QtCreator is available as a Debian package

sudo apt-get install qtcreator

BUT, do NOT go this route; it seems unable to connect to the target!

Instead, get the latest Qt Creator stand-alone installer from the official download page.

$ chmod a+x qt-creator-opensource-linux-x86_64-3.3.2.run
$ ./qt-creator-opensource-linux-x86_64-3.3.2.run

The default extracts Qt Creator at ~/qtcreator-<version>.  I accepted this default.

Configure the target connection

After starting the Qt Creator, go to menu --> Tools --> Options --> Devices --> Add --> Generic Linux Device --> Start Wizard.  I set the connection name as shown below.
The next screen gives you a chance to start the target to be tested.  Boot the target now, and click Finish.  The connection test should succeed, as you can see here:

Configure Build & Run Kit for the target

A Qt Creator kit is a collection of tools and settings necessary to build, run, and debug programs for a given target. I added (menu --> Tools --> Options --> Kits --> Add) the zedbr2 kit with the following settings; unlike for the desktop (host and target are the same) kit, cross compilation requires sysroot, which is where the cross toolchain is hosted on the host system.  For the zedbr2 kit shown above, I also added the following ahead of time, by looking at the Makefile in the Qt analogclock example folder.
  • sysroot: /home/henry/work/zed/buildroot/output/host/usr/arm-buildroot-linux-gnueabi/sysroot
  • Qt version 5.4.0 for Embedded Linux location of qmake: /mnt/work/zed/buildroot/output/build/qtbase-5.4.0/bin/qmake, as you can see below.  Despite the warning (the yellow triangle below) that the qmake is NOT in a deployed folder, things seem to work OK.  More importantly, the cross compile does NOT work if I use the qmake that is in the buildroot/output/host/usr/bin (took me several hours to figure this out!)
  • Compiler path: /mnt/work/zed/buildroot/output/host/usr/bin/arm-buildroot-linux-gnueabi-g++
  • Debugger path: /mnt/work/zed/buildroot/output/host/usr/bin/arm-buildroot-linux-gnueabi-gdb
Do NOT ignore the red error sign next to the kit name if it appears.  A properly setup kit is necessary for even a semi working remote debugging (next section).

Hello world GUI

There are a new Qt books out there; I am reading "Application Development with Qt Creator" by Ray Rischpater at the moment.  I just follow the Hello world GUI example in that book.  Briefly, it puts a label, and a push button whose "click" slot (this is the Qt-terminology for a listener callback) just closes an application, like this:

void MainWindow::on_exitButton_clicked()
{
    QApplication::exit();
}

If the kit has been correctly set up, building the project is a snap.  The executable is emitted to the Qt Creator's workspace (I created mine in ~/work/zed/qt), in a new folder build-<project name>-<kit name>-<build config>.  In this case, the project name is "untitled", the kit name is "zedbr2", and the build config is "Debug", so I found my ELF file in build-untitled-zedbr2-Debug folder, as you can see here:

/mnt/work/zed/qt/build-untitled-zedbr2-Debug$ ls
main.o  mainwindow.o  Makefile  moc_mainwindow.cpp  moc_mainwindow.o  ui_mainwindow.h  untitled

Normally, I expect to click the "bug" button (or F5) to start debugging; the tool should copy the application to the target, start gdbserver for that application on a TCP port, and then make a gdb connection.  Alas, Qt Creator errors out while trying to stop an application.  I worked around by doing the above steps manually:

  1. Copy the ELF file to the target.  Since I run as root, I copied the untitled ELF file above to /root folder.
  2. Start the gdbserver on the target, with the desired argument.  For example (recall from the pathstroke demo earlier that the plugin argument was necessary for the Qt application to recognize the mouse and keyboard inputs):

    gdbserver localhost:1234 /root/untitled -plugin evdevmouse:/dev/input/event0
  3. In QtCreator, attach to the target's gdbserver (menu --> Debug --> Start Debugging --> Attach to Remote Debug Server), specifying the port and the ELF file, as you can see in this example:

Qt Creator debug window will then stop at main (note the "Break at main" checkbox above).  When hit the continue button (F5), I see my simple GUI:

I then set a breakpoint in the button clicked slot, and clicked the "Exit" button above, and behold: the breakpoint is hit!
Hitting F5 will exit the GUI.

Next step: learn Qt

I have avoided GUI all my career, but that's about to change: I signed up for a 4-day Qt training in May!

Appendix: fixing the missing clocks in AXI interfaces in HW design

Vivado keeps complaining about "missing clock in interface", referring to the AXI interfaces with clocks indeed unspecified.  It seems to be a harmless error This forum entry goes into the problem in more depth.

No comments:

Post a Comment