top of page
  • Zach Pfeffer

Integrate a QSPI using PetaLinux Tools Part 1

This post walks through part 1 of a complete integration of a QSPI connected to a Zynq UltraScale+ MPSoC into a Linux kernel using PetaLinux Tools 2017.4.

It traces the connection from a QSPI chip to the QSPI controller on the Zynq UltraScale+ MPSoC (ZU+). Then its demonstrates checking the Linux kernel software layers to ensure the right configuration has been set up.

I posted the solution to

QSPI to ZU+ Connection


MT25QL01GBBB8E12 -0AA connected to a XCZU9EG-FFVC900

Connections (QSPI connected to ZU+)

D3 connected to G18

D2 connected to H17

C4 connected to E18

D4 connected to F18

C2 connected to J17

B2 connected to C18

Figure Out Pins


Per this figure from the MT25QL01GBBB datasheet, the package code is 12 (MT25QL01GBBB8E12 -0AA): a 12 = 24-ball T-PBGA, 05/6 x 8mm (5 x 5 array)




D3 - DQ0,

D2 - DQ1,

C4 - W#/DQ2,

D4 - DQ3/HOLD#,

C2 - S#,

B2 - C

From the QSPI data sheet:

DQ[3:0] (I/O)

Serial I/O: The bidirectional DQ signals transfer address, data, and command information.

When using legacy (x1) SPI commands in extended I/O protocol (XIO-SPI), DQ0 is an input and DQ1 is an output. DQ[3:2] are not used.

When using dual commands in XIO-SPI or when using DIO-SPI, DQ[1:0] are I/O. DQ[3:2] are not used.

When using quad commands in XIO-SPI or when using QIO-SPI, DQ[3:0] are I/O.

C (Input)

Clock: Provides the timing of the serial interface. Command inputs are latched on the rising edge of the clock. In STR commands or protocol, address and data inputs are latched on the rising edge of the clock, while data is output on the falling edge of the clock. In DTR commands or protocol, address and data inputs are latched on both edges of the clock, and data is output on both edges of the clock.


Chip select: When S# is driven HIGH, the device will enter standby mode, unless an internal PROGRAM, ERASE, or WRITE STATUS REGISTER cycle is in progress. All other input pins are ignored and the output pins are tri-stated. On parts with the pin configuration offering a dedicated RESET# pin, however, the RESET# input pin remains active even when S# is HIGH.

Driving S# LOW enables the device, placing it in the active mode.

After power-up, a falling edge on S# is required prior to the start of any command.

ZU+ Pins

Per this figure from the XCZU9EG-FFVC900 datasheet, we're using the FFVC900 package.

G18, H17, E18, F18, J17, C18 are all in PS Bank 500

Pin Out


Pin Pin Name Memory Byte Group Bank I/O Type Super Logic Region







PS_MIO4 - DQ0,

PS_MIO1 - DQ1,

PS_MIO2 - W#/DQ2,


PS_MIO5 - S#,


From Table 28-3 MIO Interfaces (qspi column) of the Zynq UltraScale+ Device TRM:

PS_MIO4 - DQ0 - si_mio[0]

PS_MIO1 - DQ1 - io[1]

PS_MIO2 - W#/DQ2 - io[2]

PS_MIO3 - DQ3/HOLD#, - io[3]

PS_MIO5 - S# - n_ss_out

PS_MIO0 - C - sclk_out

From page 646 of the Zynq UltraScale+ Device TRM:

Also from page 646 of the Zynq UltraScale+ Device TRM:

The following interfaces are used by the generic Quad-SPI controller.

• The APB slave read/write interface is used to read/write the registers and also to write the TX generic FIFO data.

• The AXI master write interface is used to issue DMA write requests on the AXI interface. The data read from flash memory is written into the RXFIFO. The data from the RXFIFO is transferred into external memory (for example, DDR) using this interface. The AXI address bus is 44 bits wide and the data is 32 bits wide.

Base Address of QSPI

From Table 10-6 (p232): I/O Peripherals Register Map (LPD) Base Address of the QSPI controller is: 0xFF0F_0000

Am I Using LQSPI vs. GQSPI?

Using GQSPI as Linux boots up. Register is unchanged.


From page 640 from the Zynq UltraScale+ Device TRM:

At Boot

The CSU BootROM uses the GQSPI controller for system boot. The Width Detection parameter in the boot header selects between 4- and 8-bit I/O. If the XIP FSBL is selected (FSBL length = 0 in the boot header), the BootROM switches to the LQSPI controller before handing-off the system to the FSBL code.

From page 643 from the Zynq UltraScale+ Device TRM:

Controller Selection

One controller is selected at a time using the LQSPI_CFG [LQ_MODE] bit. The generic controller is selected by setting the bit = 0 and the legacy linear controller is selected by setting the bit = 1. The active controller must be quiescent before switching from one controller to the other.

Check the Register Spec

Register Name LQSPI_CFG Relative Address 0x000000A0 Absolute Address 0xFF0F00A0 (QSPI) Width 32 Type rw Reset Value 0x000002EB Description Configuration Specifically for the Linear Quad-SPI Controller

Decode 0x000002EB

0x000002EB (Reset value)


LQ_MODE: Controller Select 0: Generic Quad-SPI

TWO_MEM: I/O Configuration: 0: One memory device

SEP_BUS: I/O Configuration: 0: Single memory interface


ADDR_32BIT: Flash Memory Address based on AXI address: 0: Lower 24 bits of AXI address on linear port are used as address to the flash.

MODE_EN: MODE_ON and MODE_BITS are disabled

INST_CODE: Fast read quad I/O

Can my Kernel See the MT25QL01GBBB8E12 -0AA?

In a PetaLinux Tools managed build the Linux kernel code is at build/tmp/work-shared/plnx_aarch64/kernel-source/.

The SPI-NOR driver is at drivers/mtd/spi-nor/spi-nor.c

The specific spi-nor.c used in PetaLinux Tools 2017.4 is at:

SPI NOR framework

Check Kconfig @

menuconfig MTD_SPI_NOR tristate "SPI-NOR device support" depends on MTD help This is the framework for the SPI NOR which can be used by the SPI device drivers and the SPI-NOR device driver.

Check the PetaLinux kernel config:

Note: this command will wipe out the kernel source in build/tmp/work-shared/plnx_aarch64/kernel-source

Note 2: PetaLinux 2017.4 uses See this link to for a method to figure this out.

Type '/' to search for MTD_SPI_NOR

Its on:

Look at dmesg

[ 1.636157] mtdoops: mtd device (mtddev=name/number) must be supplied

Check device tree overlay

Hmmm.. this is looking for zynq-qspi-1.0, I'm passing in: zynqmp-qspi-1.0

Check the original (non-overlay) device tree

system-top.dts pulls in all the dtsi components:


So zynqmp-qspi-1.0 seems okay.

Configure flash through petalinux-config

Select Subsystem AUTO Hardware Settings

Select Flash Settings --->


Use some printks

Launch devshell

petalinux-build -c kernel -x devshell

(this command is not documented anywhere - it also doesn't seem to work: it executed for 2 hours)

cat /proc/devices

Printk Based Debug

Go through the SPI-NOR Linux kernel doc and see if functions are getting called (via printks)

SPI NOR framework ============================================

Part I - Why do we need this framework? ---------------------------------------

SPI bus controllers (drivers/spi/) only deal with streams of bytes; the bus

The Xilinx Zynq UltraScale+ MPSoC Quad-SPI (QSPI) controller driver (master mode only) is @ link.

Which Kconfig is needed?

Look at the Makefile

Found: Is the Kconfig on?

Output the current .config with

Grep it: Yup.

Does probe get called (and succeed)?

Add a printk and a dump_stack():

Code added:

Rebuild the kernel with:


Checked kernel log. No output.

Did the kernel get rebuilt?


Should be May 13th.

Check the reference guide. Nothing.

Check the workflow guide. Nothing.

Try petalinux-build --help. Saw:

Output from command:


its in there:

Hmm,.. need to do a petalinux-build again?

Hmmmm...petalinux-build is building a lot of stuff:

Comment: It is surprising that petalinux-build -c kernel -x compile -f didn't put an Image into images/linux/Image

Comment: It is surprising that a subsequent petalinux-build took another few minutes to run.

Yes, probe gets called and succeeds.

controller operates agnostic of the specific device attached. However, some controllers (such as Freescale's QuadSPI controller) cannot easily handle arbitrary streams of bytes, but rather are designed specifically for SPI NOR.

In particular, Freescale's QuadSPI controller must know the NOR commands to find the right LUT sequence. Unfortunately, the SPI subsystem has no notion of opcodes, addresses, or data payloads; a SPI controller simply knows to send or receive bytes (Tx and Rx). Therefore, we must define a new layering scheme under which the controller driver is aware of the opcodes, addressing, and other details of the SPI NOR protocol.

Part II - How does the framework work? --------------------------------------

This framework just adds a new layer between the MTD and the SPI bus driver. With this new layer, the SPI NOR controller driver does not depend on the m25p80 code anymore.

Before this framework, the layer is like:

MTD ------------------------ m25p80 ------------------------ SPI bus driver ------------------------ SPI NOR chip

After this framework, the layer is like:

MTD ------------------------ SPI NOR framework ------------------------ m25p80 ------------------------ SPI bus driver ------------------------ SPI NOR chip

With the SPI NOR controller driver (Freescale QuadSPI), it looks like:

MTD ------------------------ SPI NOR framework ------------------------ fsl-quadSPI ------------------------ SPI NOR chip

Part III - How can drivers use the framework? ---------------------------------------------

The main API is spi_nor_scan(). Before you call the hook, a driver should initialize the necessary fields for spi_nor{}. Please see

Does spi_nor_scan() get called?

Look for it:


Who calls it:


Are any of these built in?



Check the .config:


So nothing will call spi_nor_scan()

drivers/mtd/spi-nor/spi-nor.c for detail. Please also refer to fsl-quadspi.c when you want to write a new driver for a SPI NOR controller. Another API is spi_nor_restore(), this is used to restore the status of SPI flash chip such as addressing mode. Call it whenever detach the driver from device or reboot the system.

Do I need a special driver / aren't all these JEDEC? drivers? Wait? I don't need a new SPI NOR controller, do I?

Look for n25q



It looks like we should just use the string "jedec,spi-nor" from this:

Kconfig for m25p80:



Check if CONFIG_MTD_M25P80 is in our .config, Yes!

Find it:


Change to:

Some output:

Check if system.dtb got updated:

Got it!

Now I see the following in the dmesg log:


I don't think I need the following, since it simply finds? the actual partitions:

Check in doc at

I don't see anything about partitions. Try without:

Check that the dtb was updated:

Boot the unit.

Examine log:

Without the specific partitions, it seems to have found up just the partitions on the device.

Does it find the partitions or are the partitions picked up in another dts?

Look at docs again. Partitions are documented at link.

Look in our directory:

Look for "partitions" in "dt*" files:

Found a few:


How are all the dtsi's composed?

system-top.dts pulls in all the dtsi components:



./components/plnx_workspace/device-tree/device-tree-generation/zynqmp.dtsi ./build/tmp/work-shared/plnx_aarch64/kernel-source/arch/arm64/boot/dts/xilinx/zynqmp.dtsi

These ^^^^ differ, e.g. iommus are commented out in ./build. Which one is used?




These ^^^^ differ, e.g. iommus are commented out in ./build. Which one is used?








Look at the flattened device tree (PETALINUX is your install dir)

Find dtc

Print dtb

Hmmm... it looks like my dtb changes were not picked up:


Try "petalinux-build" again

Didn't work (should be May 14th):

Hack to try it out:

Hmmm, still present.

How does it get in?

Its not in the top level of any of these:

Found here among other places:

File contents:

This file gets included at the top of ./project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi:

Look at

Remove via petalinux-config


Change the name to "boot_me" to test how this flows through:

Examine it:

Saw "boot_me"

So I "can" and "should" use petalinux-config to specify the partitions.

How do I figure out the name's and sizes of the partitions from the BOOT.bin or bootimage.bif?


Can I look in the BOOT.bin?

I could match the first bytes of each image. No,

I can get all the partitions by passing -log trace to bootgen:

How can I get rid of PetaLinux managed partitions?

Select Manual:

Run: petalinux-build

Check with:


Partitions are gone!

My kernel now fails to boot!

Its hung at:

Add the following:

To: ./project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi

Force build the device-tree:

Run petalinux-build again so that the system.dtb gets updated:



Still hung.

Try turning partitions back on. Maybe bootenv needs to be defined?

After using:

The kernel boots again. Why?

We see:

General flash / mtd debug:

Leaving things here.


bottom of page