AD9467 and AXI DMA

Hey Guys,

I'm getting some strange behaviour using AXI DMA to retrieve continuous samples from the AD9467 on the Zedboard.

Environment

-------------------

     HW: ZedBoard Rev D, AD9467

     Linux source: xcomm_zynq branch of the ADI Linux sources ( https://github.com/analogdevicesinc/linux.git )

     Device Tree: zynq-zed-adv7511-ad9467-fmc-250ebz.dts

Build process

--------------------

$ make make zynq_xcomm_adv7511_defconfig

$ make UIMAGE_LOADADDR=0x8000 uImage

$ make zynq-zed-adv7511-ad9467-fmc-250ebz.dtb

The current iomem map is:

zbconsole:~/projects/axidma # cat /proc/iomem
00000000-18ffffff : System RAM
  00008000-005f3cfb : Kernel code
  00628000-006833e7 : Kernel data
41600000-4160ffff : /fpga-axi@0/i2c@41600000
44a00000-44a0ffff : /fpga-axi@0/cf-ad9467-core-lpc@44A00000
44a30000-44a3ffff : /fpga-axi@0/rx-dmac@44A30000
70e00000-70e0ffff : /fpga-axi@0/axi_hdmi@70e00000
75c00000-75c00fff : /fpga-axi@0/axi-spdif-tx@0x75c00000
77600000-77600fff : /fpga-axi@0/axi-i2s@0x77600000
79000000-7900ffff : /fpga-axi@0/axi-clkgen@79000000
e0001000-e0001fff : xuartps
e0002000-e0002fff : /amba@0/usb@e0002000
  e0002000-e0002fff : /amba@0/usb@e0002000
e0006000-e0006fff : /amba@0/spi@e0006000
e000a000-e000afff : /amba@0/gpio@e000a000
e000b000-e000bfff : /amba@0/eth@e000b000
e000d000-e000dfff : /amba@0/qspi@e000d000
e0100000-e0100fff : mmc0
f8003000-f8003fff : /amba@0/ps7-dma@f8003000
  f8003000-f8003fff : /amba@0/ps7-dma@f8003000
f8007000-f80070ff : /amba@0/devcfg@f8007000
f8007100-f800711f : /amba@0/xadc@f8007100
f800c000-f800cfff : /amba@0/ps7-ocm@f800c000
fffc0000-ffffffff : f800c000.ps7-ocm

Device tree entry for ADC & AXI DMA:

&fpga_axi {

    rx_dma: rx-dmac@44A30000 {

        compatible = "adi,axi-dmac-1.00.a";

        reg = <0x44A30000 0x10000>;

        #dma-cells = <1>;

        interrupts = <0 57 0>;

        clocks = <&clkc 16>;

        dma-channel {

            adi,type = <0>;

        };

    };

    cf_ad9467_core_0: cf-ad9467-core-lpc@44A00000 {

        compatible = "xlnx,cf-ad9467-core-1.00.a";

        reg = <0x44A00000 0x10000>;

        dmas = <&rx_dma 0>;

        dma-names = "rx";

        spibus-connected = <&adc_ad9467>;

        xlnx,dphase-timeout = <0x8>;

        xlnx,num-mem = <0x1>;

        xlnx,num-reg = <0x1>;

        xlnx,s-axi-min-size = <0x1ff>;

        xlnx,slv-awidth = <0x20>;

        xlnx,slv-dwidth = <0x20>;

        xlnx,use-wstrb = <0x0>;

    } ;

};

We have set aside 112MB out of the zedboard's 512MB for dma receiving buffers starting at 0x19000000.

Enable the AD9467 in test mode with the following commands in a script:

echo  "524288"          > /sys/bus/iio/devices/iio:device2/buffer/length
echo "checkerboard" > /sys/bus/iio/devices/iio:device2/in_voltage0_test_mode
echo "1"                     > /sys/bus/iio/devices/iio:device2/scan_elements/in_voltage0_en
echo "1"                     > /sys/bus/iio/devices/iio:device2/buffer/enable

In this mode I expect to see alternating samples if 0x5555 and 0xAAAA.

When reading from the iio device directly I get the correct samples so I believe the ADC is working correctly e.g.

zbconsole:~/projects/axidma #  hd < /dev/iio:device2
00000000  aa aa 55 55 aa aa 55 55  aa aa 55 55 aa aa 55 55  |..UU..UU..UU..UU|
00300000  55 55 aa aa 55 55 aa aa  55 55 aa aa 55 55 aa aa  |UU..UU..UU..UU..|
00400000  aa aa 55 55 aa aa 55 55  aa aa 55 55 aa aa 55 55  |..UU..UU..UU..UU|
...

However when I try to copy samples to a buffer via the AXI DMA I only see one test pattern or the other (0xAAAA or 0x5555) but never the alternating pattern we expect.

To get started I used some sample code found in the no-OS driver (no-OS/cf_ad9467.c at master · analogdevicesinc/no-OS · GitHub)

My version of this code looks like this in Linux userland:

...

//open /dev/mem

//map the AD9467 Control Regs

...

mapped_dmac_base_addr = mmap(NULL, 0x10000, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, 0x44A30000);

...

// set channel to RUN
    dmac_write(AXI_DMAC_REG_CTRL, 0x0);
    dmac_write(AXI_DMAC_REG_CTRL, AXI_DMAC_CTRL_ENABLE);
    dmac_write(AXI_DMAC_REG_IRQ_MASK, 0x0);

    dmac_read(AXI_DMAC_REG_TRANSFER_ID, &transfer_id);
    dmac_read(AXI_DMAC_REG_IRQ_PENDING, &reg_val);
    dmac_write(AXI_DMAC_REG_IRQ_PENDING, reg_val);

    // physical destination address
    dmac_write(AXI_DMAC_REG_DEST_ADDRESS, 0x19000000);
    dmac_write(AXI_DMAC_REG_DEST_STRIDE, 0x0);

    dmac_write(AXI_DMAC_REG_SRC_ADDRESS, 0x0);
    dmac_write(AXI_DMAC_REG_SRC_STRIDE, 0x0);
    dmac_write(AXI_DMAC_REG_Y_LENGTH, 0x0);

    dmac_write(AXI_DMAC_REG_X_LENGTH, 524288);

    dmac_write(AXI_DMAC_REG_START_TRANSFER, 0x1);

    // Wait until the new transfer is queued.
    fprintf(stderr, "waiting for transfer to be queued...\n");
    do {
        dmac_read(AXI_DMAC_REG_START_TRANSFER, &reg_val);
    } while(reg_val == 1);

    // Start xfer & wait until completed.
    dmac_write(AXI_DMAC_REG_START_TRANSFER, 0x1);

    fprintf(stderr, "waiting for transfer to complete...\n");
    do {
        dmac_read(AXI_DMAC_REG_IRQ_PENDING, &reg_val);
    } while(reg_val != (AXI_DMAC_IRQ_SOT | AXI_DMAC_IRQ_EOT));
    dmac_write(AXI_DMAC_REG_IRQ_PENDING, reg_val);

    // Wait until the transfer with the ID transfer_id is completed.
    fprintf(stderr, "waiting for transfer_id 0x%04X to complete...\n", transfer_id);
    do {
        dmac_read(AXI_DMAC_REG_TRANSFER_DONE, &reg_val);
    } while((reg_val & (1 << transfer_id)) != (1 << transfer_id));


...


static void dmac_read(uint32_t reg_addr, uint32_t *reg_data)
{
    *reg_data = *(uint32_t *) (mapped_dmac_base_addr + reg_addr);
}

static void dmac_write(uint32_t reg_addr, uint32_t reg_data)
{
    *(uint32_t *) (mapped_dmac_base_addr + reg_addr) = reg_data;
}

So the above code yields this output:

root@zedboard:~/projects/axidma# ./frdmatest

mapping DMAC Control registers...
DMAC_BASE_ADDR (0x0x44a30000) mapped at 0x0xb6ee8000
mapping DMA buffer area...
START_ADDR_DMA_BUFFER (0x0x19000000) mapped at 0x0xb6d85000 for a length of 524288
Control regs before xfer...
AXI_DMAC_REG_IRQ_MASK             (0x0080): 0x00000000
AXI_DMAC_REG_IRQ_PENDING          (0x0084): 0x00000000
AXI_DMAC_REG_IRQ_SOURCE           (0x0088): 0x00000000
AXI_DMAC_REG_CTRL                 (0x0400): 0x00000001
AXI_DMAC_REG_TRANSFER_ID          (0x0404): 0x00000002
AXI_DMAC_REG_START_TRANSFER       (0x0408): 0x00000000
AXI_DMAC_REG_FLAGS                (0x040C): 0x00000000
AXI_DMAC_REG_DEST_ADDRESS         (0x0410): 0x19000000
AXI_DMAC_REG_SRC_ADDRESS          (0x0414): 0x00000000
AXI_DMAC_REG_X_LENGTH             (0x0418): 0x0007FFFF
AXI_DMAC_REG_Y_LENGTH             (0x041C): 0x00000000
AXI_DMAC_REG_DEST_STRIDE          (0x0420): 0x00000000
AXI_DMAC_REG_SRC_STRIDE           (0x0424): 0x00000000
AXI_DMAC_REG_TRANSFER_DONE        (0x0428): 0x00000003
AXI_DMAC_REG_ACTIVE_TRANSFER_ID   (0x042C): 0x00000002
AXI_DMAC_REG_STATUS               (0x0430): 0x00000000
AXI_DMAC_REG_CURRENT_SRC_ADDR     (0x0434): 0x19080000
AXI_DMAC_REG_CURRENT_DEST_ADDR    (0x0438): 0x00000000
DMA enabled
transfer id = 0x0000
dest addr = 0x19000000
waiting for transfer to be queued...
waiting for transfer to complete...
waiting for transfer_id 0x0000 to complete...

DMA buffer dump (1st 128)

[00000000] 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555
[00000020] 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555
[00000040] 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555
[00000060] 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555 5555
transfered data... bufsize: 524288
Control regs after xfer...
AXI_DMAC_REG_IRQ_MASK             (0x0080): 0x00000000
AXI_DMAC_REG_IRQ_PENDING          (0x0084): 0x00000000
AXI_DMAC_REG_IRQ_SOURCE           (0x0088): 0x00000000
AXI_DMAC_REG_CTRL                 (0x0400): 0x00000001
AXI_DMAC_REG_TRANSFER_ID          (0x0404): 0x00000002
AXI_DMAC_REG_START_TRANSFER       (0x0408): 0x00000000
AXI_DMAC_REG_FLAGS                (0x040C): 0x00000000
AXI_DMAC_REG_DEST_ADDRESS         (0x0410): 0x19000000
AXI_DMAC_REG_SRC_ADDRESS          (0x0414): 0x00000000
AXI_DMAC_REG_X_LENGTH             (0x0418): 0x0007FFFF
AXI_DMAC_REG_Y_LENGTH             (0x041C): 0x00000000
AXI_DMAC_REG_DEST_STRIDE          (0x0420): 0x00000000
AXI_DMAC_REG_SRC_STRIDE           (0x0424): 0x00000000
AXI_DMAC_REG_TRANSFER_DONE        (0x0428): 0x00000001
AXI_DMAC_REG_ACTIVE_TRANSFER_ID   (0x042C): 0x00000001
AXI_DMAC_REG_STATUS               (0x0430): 0x00000000
AXI_DMAC_REG_CURRENT_SRC_ADDR     (0x0434): 0x19013680
AXI_DMAC_REG_CURRENT_DEST_ADDR    (0x0438): 0x00000000

Any insight will be greatly appreciated.  I'm still rather a novice with this stuff so I'm guessing I missed something simple.

Regards,

Rock

  • 0
    •  Analog Employees 
    on Nov 12, 2015 8:22 AM over 5 years ago

    Hi,

    Could have a lot of different causes, but most likely this is not related to the DMAC, but to how the memory is mapped or accessed.

    In general we do not recommend and do not support this approach of directly accessing the DMA and physical memory from userspace because of all the pitfalls involved. If you use the existing infrastructure of IIO things will work out of the box and no custom low-level code is necessary.

    I'm curious is there any particular reason why you want to use this approach instead? Any shortcomings or issues with the IIO approach?

    - Lars

  • Hey Lars,

    Yea, I was just trying a fast way to play with the adc/dmac and userspace seemed

    like a quick start...

    I have seen the iio lib but have only a cursory understanding at this point and I'm

    not certain that the iio stuff will work for us here.  Maybe you can tell me if the iio

    lib will work for this scenario:

    The product will reserve say 100 1MB buffers for samples at boot time.  We

    need to be able to program a DMAC to continuously fill a single 1MB buffer cyclically,

    wrapping around at the end of the single 1MB buffer, this is free running

    until an external event is triggered; at the trigger point the product will continue the

    current DMAC xfer for another 50% of the buffer size, this puts the trigger point in

    the middle of the sample buffer.

    At that point we need to somehow seamlessly bump the DMAC to move the samples to the

    next 1MB buffer cyclically (we will have a low tolerance for missed samples) and

    continue filling the new 1MB buffer while also generating an interrupt so the PS

    can empty the previous buffer and send it on the net to another controller allowing

    that 1MB buffer to be returned to the pool for reuse.

    Some custom FPGA code will have support in it for the buffer management portion,

    sample ready interrupts to the PS, monitoring the buffer fill size after the trigger, etc.

    But for now we are just trying to determine if we can actually program a DMAC to work

    as outlined above.

    So, will the iio library support such a design?  If so and you can point me in the

    right direction for some examples?  Does the iio ring buffer support lend itself to

    utility here?

    Thanks.

    - rock

  • 0
    •  Analog Employees 
    on Nov 13, 2015 9:10 AM over 5 years ago

    Hi,

    This will more be an issue of the DMAC than the framework. The DMAC is currently fully software driven, there is no support for changing the execution flow on a hardware event.

    But the DMAC design is quite flexible, it is split into a front-end that decides what should be transferred and backends that do the transfers. You'd have to replace the front-end so it will generate the transfers following the pattern you need.

    - Lars