The QSPI (Quad Serial Peripheral Interface) controller is a high-speed, synchronous serial bus master implemented in Qualcomm SoCs. It extends the traditional 4-wire SPI bus by supporting up to four bidirectional data lines (IO0–IO3), enabling up to 4× the throughput of standard SPI at the same clock frequency.
Figure : QSPI bus signal interface
Bus Signal Descriptions
| Signal | Direction | Description |
|---|
CLK | Output | Serial clock generated by the QSPI master. |
CS_N | Output | Chip select (active low). Asserted to select the first peripheral. Held asserted across fragmented transfers. |
IO0 | Bidir | Data line 0. |
IO1 | Bidir | Data line 1. |
IO2 | Bidir | Data line 2. |
IO3 | Bidir | Data line 3. |
Figure : QSPI I/O mode waveforms: 1-bit, 2-bit and 4-bit
QSPI Controller Internal Architecture
Figure : QSPI controller internal block diagram
QSPI Configuration
Linux
This section provides information about the QSPI software driver kernel configuration and device tree node changes.
The following driver kernel configurations are required to support the QSPI interface.
QSPI Interface Components
This section provides information about the kernel device tree nodes, and related documentation.
Table : QSPI interface: Linux
| File type | Description |
|---|
| Device tree source | |
| Pinctrl settings | |
QSPI DT node:
qspi: spi@88df000 {
compatible = "qcom,qcs615-qspi",
"qcom,qspi-v1";
reg = <0x0 0x088df000 0x0 0x1000>;
interrupts = <GIC_SPI 82 IRQ_TYPE_LEVEL_HIGH 0>;
clocks = <&gcc GCC_QSPI_CNOC_PERIPH_AHB_CLK>,
<&gcc GCC_QSPI_CORE_CLK>;
clock-names = "iface",
"core";
interconnects = <&gem_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY
&config_noc SLAVE_QSPI QCOM_ICC_TAG_ACTIVE_ONLY>,
<&aggre1_noc MASTER_QSPI QCOM_ICC_TAG_ALWAYS
&mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>;
interconnect-names = "qspi-config",
"qspi-memory";
power-domains = <&rpmhpd RPMHPD_CX>;
operating-points-v2 = <&qspi_opp_table>;
iommus = <&apps_smmu 0x160 0x0>;
pinctrl-0 = <&qspi_clk>, <&qspi_cs0>, <&qspi_data0123>;
pinctrl-names = "default";
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
QSPI Pinctrl:
qspi_cs0: qspi-cs0-state {
pins = "gpio44";
function = "qspi";
bias-disable;
drive-strength = <6>;
};
qspi_data0123: qspi-data0123-state {
pins = "gpio45", "gpio46", "gpio47", "gpio49";
function = "qspi";
bias-pull-down;
drive-strength = <6>;
};
qspi_clk: qspi-clk-state {
pins = "gpio48";
function = "qspi";
bias-pull-down;
drive-strength = <6>;
};
Linux QSPI Validations with SPI-NOR
These validations confirm that the QSPI controller works correctly with a JEDEC-compliant SPI-NOR flash device on Linux. The tests cover three core areas: verifying the hardware is detected and enumerated correctly at boot, confirming data can be read reliably from the flash, and ensuring write operations complete successfully after an erase. Use these checks after bringing up a new board or after modifying the QSPI device tree configuration.
The following device tree snippet enables the QSPI controller and binds it to a SPI-NOR flash device :
&qspi {
status = "okay";
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <25000000>;
spi-tx-bus-width = <4>;
spi-rx-bus-width = <4>;
};
};
1. Initialization & Detection
These tests confirm that the QSPI controller and NOR flash are correctly recognized by Linux at boot time, and that all flash partitions are available as MTD devices before any read or write is attempted.
| Test Description | Sample Command | Expected Result |
|---|
| Check that the QSPI controller starts up successfully on boot with no errors in the kernel log | dmesg | grep -i spi | QSPI controller initializes without errors |
| Confirm the NOR flash chip is recognized by reading its JEDEC ID from the sysfs interface | cat /sys/class/mtd/mtd0/device/spi-nor/jedec_id | NOR flash detected with correct JEDEC ID |
| Confirm the flash total size and erase block layout reported by the kernel match the device specification | cat /proc/mtd | Flash size and erase size match specs |
| Confirm all flash partitions are accessible as MTD character devices under /dev | ls -l /dev/mtd* | All MTD devices and partitions visible |
2. Read Operations
These tests validate that data can be read from the flash accurately, including edge cases such as reads from the end of the flash address space and repeated reads under stress conditions.
| Test Description | Sample Command | Expected Result |
|---|
| Read a 256-byte block from the start of the flash and confirm the data is retrieved without errors | dd if=/dev/mtd0 of=/tmp/read.bin bs=256 count=1 | Data read successfully |
| Read from the last 256 bytes of the flash to confirm no out-of-bounds access or buffer overflow occurs | dd if=/dev/mtd0 of=/tmp/boundary.bin bs=1 count=256 skip=$((SIZE-256)) | No boundary overflow |
| Perform 100 consecutive read cycles of 4K blocks to confirm stable, error-free operation under repeated access | for i in {1..100}; do echo "iter $i"; dd if=/dev/mtd0 of=/dev/null bs=4K count=10; done | Stable repeated reads |
3. Write Operations
These tests confirm that data can be written correctly to the flash after an erase, including multi-page writes and overwrite scenarios, which are the most common operations during firmware update workflows.
| Test Description | Sample Command | Expected Result |
|---|
| Erase a flash sector and write a test pattern to confirm basic single-sector write functionality | flash_erase /dev/mtd0 0 1 && echo TEST > /tmp/t.bin && dd if=/tmp/t.bin of=/dev/mtd0 | Write successful |
| Write 16 KB of random data spanning multiple pages to confirm the driver handles page-boundary crossing correctly | dd if=/dev/urandom of=/tmp/m.bin bs=4K count=4 && flashcp /tmp/m.bin /dev/mtd0 | Multi-page write succeeds |
| Erase a previously written sector and rewrite it to confirm the flash correctly accepts new data after erasure | flash_erase /dev/mtd0 0 1 && flashcp /tmp/t.bin /dev/mtd0 | New data written correctly |