Skip to content

Lemans spinor bringup - #132

Open
vandhiadevan wants to merge 14 commits into
qualcomm-linux:qcom-nextfrom
vandhiadevan:lemans-spinor-bringup
Open

Lemans spinor bringup#132
vandhiadevan wants to merge 14 commits into
qualcomm-linux:qcom-nextfrom
vandhiadevan:lemans-spinor-bringup

Conversation

@vandhiadevan

@vandhiadevan vandhiadevan commented Sep 2, 2026

Copy link
Copy Markdown

Title: arm64: lemans: Enable SPI-NOR flash support and fastboot GPT flashing

Summary

  • Add Qualcomm GENI QUPv3 SPI controller driver, fix RX_RFR_WATERMARK
    depth vs width bug, and add SA8775p QUPv3-wrapper-3 SPI clock support.
  • Bind SPI-NOR into the MTD block subsystem (sf_probe/mtdblock changes,
    blk_desc lba fix, plat data struct) and allow blk uclass lookup by a
    device's own uclass_id.
  • Add fastboot 'oem set-block-target' and 'oem spi-nor-init' commands,
    and geni firmware reinit support, to flash SPI-NOR over fastboot.
  • Wire up SPI21/QUPv3-wrapper-3 in the lemans-evk devicetree (pinctrl,
    clocks, jedec,spi-nor flash node, spi0 alias) and enable it in the
    snagboot defconfig.
  • Fix NULL pointer deref in qcom_geni_load_firmware() and force GPT
    partition-type re-detection after a fastboot GPT write to SPI-NOR
    (previously showed "Partition Type: MTD" until a manual sf probe).

Test plan

  • Built and signed via snagboot_uboot_build_sign.sh for
    qcom_lemans_snagboot_defconfig (SWIV + qtestsign tz flow).
  • Verified on Lemans EVK: fastboot flash to SPI-NOR, part list mtd 0
    reports Partition Type: EFI with partitions visible immediately
    after GPT write (no sf probe workaround needed).
  • No regressions on other QUP/SPI/UFS/USB paths touched by the
    clock-driver changes.

Add a DM_SPI driver for the SPI-protocol personality of the Qualcomm
GENI Serial Engine, found inside a QUPv3 wrapper on Qualcomm SoCs
such as SDM845, SM8250 and SA8775P. The Serial Engine is shared
across UART/I2C/SPI protocols and needs firmware for the desired
protocol loaded into it before use, via the existing
qcom_geni_load_firmware() helper.

The driver supports both the CPU-driven FIFO transfer path and the
Serial Engine's own DMA engine (SE-DMA). Whether FIFO mode is usable
is read back from hardware (GENI_IF_DISABLE_RO); SE-DMA is always
available. The driver picks FIFO for small transfers when FIFO is
available, and SE-DMA otherwise, matching the mode-selection logic
of the equivalent Linux driver.

Add the register offsets and the fifo-depth helper needed by the new
driver to the shared include/soc/qcom/geni-se.h, and wire up the new
driver's Kconfig entry and Makefile rule.

Signed-off-by: Vandhiadevan Karunamoorthy <vandhiadevan.karunamoorthy@oss.qualcomm.com>
load_se_firmware() derives the RX_RFR_WATERMARK value from
QUPV3_SE_HW_PARAM_1 using RX_FIFO_WIDTH_BIT/RX_FIFO_WIDTH_MASK, which
decode the RX FIFO element width (bits per FIFO word), not its depth
(number of entries). The watermark register expects a depth-based
threshold, so this produces an incorrect watermark value and, on QUP
HW versions >= 3.10 where the depth field widened to 8 bits for
256-byte-deep FIFOs, an inconsistent one depending on core revision.

This mirrors geni_i2c_get_tx_fifo_depth() in drivers/i2c/geni_i2c.c,
which already reads SE_HW_PARAM_0 with the HW-version-gated
TX_FIFO_DEPTH_MSK/TX_FIFO_DEPTH_MSK_256_BYTES masks for the same
reason on the TX side.

The same depth-vs-width distinction, and the HW-version gating for
QUP HW >= 3.10, was introduced upstream in Linux by commit
fe8aa1ba0783 ("soc: qcom: geni-se: Update Tx and Rx fifo depth based
on QUP HW version"). The Linux driver this file's firmware-loading
sequence was ported from, added by commit d4bf06592ad6 ("soc: qcom:
geni-se: Add support to load QUP SE Firmware via Linux subsystem"),
computes the RX_RFR_WATERMARK value via geni_se_get_rx_fifo_depth()
which applies that same depth mask -- confirming this is a porting
bug rather than an intentional difference.

Signed-off-by: Vandhiadevan Karunamoorthy <vandhiadevan.karunamoorthy@oss.qualcomm.com>
mtd_blk_probe() never initializes bdesc->lba, so every MTD block
device reports a block count of zero regardless of the underlying
MTD device's actual size. This makes MTD block devices unusable for
partition table (GPT/MBR) scanning, since partition code relies on
lba to know the device's extent.

Set lba from the MTD device's size and the block descriptor's
blksz, mirroring how other block drivers populate this field during
probe.

Signed-off-by: Vandhiadevan Karunamoorthy <vandhiadevan.karunamoorthy@oss.qualcomm.com>
Add struct spi_flash_blk_plat, which will hold the struct mtd_info
pointer that mtd_bind() requires when binding a SPI-NOR device into
the MTD block subsystem. Forward-declare struct mtd_info since this
header doesn't otherwise need the full MTD definitions.

Signed-off-by: Vandhiadevan Karunamoorthy <vandhiadevan.karunamoorthy@oss.qualcomm.com>
Wire jedec_spi_nor into the existing mtd_bind() mechanism so a
SPI-NOR device gets a block device usable for partition (GPT/MBR)
scanning, same as NAND/eMMC-backed MTD devices.

mtd_bind() defaults to a 512-byte sector and the native MTD
partition scheme (mtdparts=), which suits NAND/eMMC but not NOR:
NOR is commonly GPT-partitioned, and sub-erase-block writes require
a read-modify-erase-write cycle anyway, so partition images for NOR
are built using the erase-block size as the logical sector. Rather
than teaching mtd_bind() about NOR specifics, override blksz (from
optional DT property nor-blk-size, else the flash's erase size),
log2blksz, lba and part_type on the resulting block device after
binding.

No changes to mtd_bind()'s signature or behavior for existing
callers.

Signed-off-by: Vandhiadevan Karunamoorthy <vandhiadevan.karunamoorthy@oss.qualcomm.com>
blk_get_devnum_by_uclass_idname() rejected any block device whose
parent's uclass differs from the uclass being looked up. This breaks
lookup for MTD block devices created via mtd_bind(): their parent is
the underlying storage device (e.g. UCLASS_SPI for SPI-NOR), but the
block device itself declares uclass_id = UCLASS_MTD.

Also accept a match against the block descriptor's own uclass_id, so
such devices can still be found via interface name (e.g. "mtd").

Signed-off-by: Vandhiadevan Karunamoorthy <vandhiadevan.karunamoorthy@oss.qualcomm.com>
Add fastboot_block_set_target() so an OEM command can point the
generic block fastboot backend at a different interface/device at
runtime, without requiring a rebuild. Falls back to the existing
build-time CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME/
CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID defaults when no override is
set.

Expose this to fastboot clients via a new "oem set-block-target
<interface> [device]" command, gated behind
CONFIG_FASTBOOT_CMD_OEM_SET_BLOCK_TARGET (depends on
CONFIG_FASTBOOT_FLASH_BLOCK).

This is needed to target storage devices, such as SPI-NOR exposed
through the MTD block uclass, that aren't known until runtime or
that coexist with another default block interface on the same
board.

Signed-off-by: Vandhiadevan Karunamoorthy <vandhiadevan.karunamoorthy@oss.qualcomm.com>
Add an "oem spi-nor-init" fastboot command that removes the default
SPI flash device and reprobes it, mirroring the remove+re-probe
pattern already used by fastboot_spi_flash_probe() in fb_spi_flash.c.
This lets the driver's own probe() perform any bring-up it needs
(e.g. loading firmware).

Some SoCs load SPI-NOR firmware from storage in a single bulk pass
at boot, before a bare reprobe of the SPI flash device is enough to
pick it up (e.g. if the firmware source wasn't available yet at that
point). To let such platforms redo that one-time, driver-external
step here without hardcoding any vendor-specific call into generic
fastboot code, add a weak fastboot_oem_spi_nor_reinit() hook that is
called between the removal and the reprobe. The default
implementation is a no-op; SoC-specific code may override it.

This is useful on boards where the SPI flash device is not usable
until fastboot has flashed something it depends on, such as
firmware or configuration data. It is intended to be used together
with "oem set-block-target" once the device has been brought up.

Signed-off-by: Vandhiadevan Karunamoorthy <vandhiadevan.karunamoorthy@oss.qualcomm.com>
qcom_geni_fw_initialise() runs once, at EVT_LAST_STAGE_INIT, and reads
GENI SE firmware from a dedicated storage partition into memory for
its children to pick up during their own probe(). If that partition
does not exist yet at that point (e.g. first boot, before fastboot has
flashed it), it finds nothing to load and does not retry later. A
later bare SPI-NOR reprobe cannot recover from that on its own, since
the SPI driver's probe() only re-reads firmware already cached by
qcom_geni_fw_initialise(); it does not re-read storage itself.

Override the fastboot_oem_spi_nor_reinit() hook to re-run
qcom_geni_fw_initialise(), so that "fastboot oem spi-nor-init" can
pick up firmware that has been flashed since boot before reprobing
the SPI-NOR device.

Signed-off-by: Vandhiadevan Karunamoorthy <vandhiadevan.karunamoorthy@oss.qualcomm.com>
Add the clock RCG, gate bit definitions, and frequency table needed
to enable and configure the SE0 (SPI) serial engine clock on QUPv3
wrapper 3, GCC_QUPV3_WRAP3_S0_CLK. This is required to bring up the
GENI SPI controller on QUPv3 wrapper 3, e.g. for SPI-NOR flash
attached to that wrapper.

Also add the wrapper's M-AHB/S-AHB clock gates and the QSPI/AGGRE-NOC
gates that must be enabled alongside the SE0 clock.

Signed-off-by: Vandhiadevan Karunamoorthy <vandhiadevan.karunamoorthy@oss.qualcomm.com>
Wire up the QUPv3 wrapper 3 / SPI21 GENI SPI controller and its
clock/pinctrl dependencies so the SPI-NOR flash on the LeMans EVK can
be probed in U-Boot proper.

Mark GCC, RPMH clock controller, Apps RSC, TLMM and QUPv3 wrapper 3
as bootph-all so they remain bound prior to relocation, add the
SPI21 pinctrl state, set the SE0 clock to 48 MHz via assigned-clocks
on GCC, and drop the power-domain/DMA properties on SPI21 that are
not usable prior to relocation. Add a jedec,spi-nor flash child node
and an spi0 alias for the SPI-NOR flash.

Signed-off-by: Vandhiadevan Karunamoorthy <vandhiadevan.karunamoorthy@oss.qualcomm.com>
Turn on the GENI/QUPv3 wrapper-3 SPI-NOR stack and the fastboot
runtime block-target override that this branch already implements,
using the existing qcom_lemans_snagboot_defconfig rather than adding
a new one:

 - CONFIG_OF_UPSTREAM plus CONFIG_QCOM_COMMAND_DB for RPMH clock
   resource lookups
 - CONFIG_IOMMU / CONFIG_QCOM_HYP_SMMU so the QUP3 GENI SE-DMA stream
   is routed through the hypervisor-backed SMMU instead of silently
   faulting
 - DM_SPI / SPI_GENI_QCOM, DM_SPI_FLASH / SPI_FLASH_MTD and the MTD
   block layer for SPI-NOR on QUPv3 wrapper 3, SE0
 - the broader SPI-NOR vendor set from drivers/mtd/spi/Kconfig
   (Atmel, Dosilicon, EON, GigaDevice, ISSI, Macronix, Puya, Silicon
   Kaiser, Spansion, STMicro, SST, Winbond, XMC, XTX, ZBIT), instead
   of Macronix alone
 - FASTBOOT_CMD_OEM_SET_BLOCK_TARGET / FASTBOOT_CMD_OEM_SPI_NOR_INIT
   so fastboot can switch its block backend from the snagboot
   default ("scsi") to "mtd" at runtime, once SPI-NOR has probed

Validated with a full build for lemans-evk (qcom_lemans_snagboot_defconfig),
confirming qcom_geni_spi.o, sf_probe.o, mtdblock.o and fb_block.o all
compile and link into u-boot.bin.

Signed-off-by: Vandhiadevan Karunamoorthy <vandhiadevan.karunamoorthy@oss.qualcomm.com>
…are()

qcom_geni_load_firmware() unconditionally dereferences the firmware
blob pointer cached in the GENI wrapper's private data
(dev_get_priv(dev->parent)) without checking it for NULL.

qcom_geni_fw_initialise() only populates that private data when it
successfully locates and reads the dedicated QUP firmware partition
at EVT_LAST_STAGE_INIT. If no such partition exists yet - e.g. a
board with no MMC/UFS storage and an unprovisioned SPI-NOR, before
any image has been flashed - it prints "QUP firmware partition not
found" and returns without ever setting the wrapper's private data,
leaving it NULL.

Any later probe of a GENI peripheral (SPI/I2C/UART) whose firmware
is not yet loaded on that hardware instance then calls
qcom_geni_load_firmware(), which dereferences the NULL pointer and
crashes with a synchronous abort/data abort.

Add a NULL check and fail gracefully with -ENOENT, matching how the
SPI caller already handles a nonzero return from this function.

Signed-off-by: Vandhiadevan Karunamoorthy <vandhiadevan.karunamoorthy@oss.qualcomm.com>
write_mbr_and_gpt_partitions() calls part_init() after writing the
GPT to re-detect the partition table, but part_init() takes a fast
path when desc->part_type is already cached: it only re-validates the
existing driver's test() instead of re-scanning all partition
drivers.

On SPI-NOR/MTD block devices, part_type is cached as PART_TYPE_MTD
from the initial probe, since the MTD partition driver's test()
unconditionally matches. When fastboot subsequently writes a GPT to
SPI-NOR (e.g. via 'gpt write'), the post-write part_init() call keeps
reusing the stale MTD type instead of detecting EFI, so
'part list mtd 0' reports "Partition Type: MTD" with no partitions
until an unrelated full reprobe (e.g. 'sf probe') resets part_type
and forces a re-scan.

Reset part_type to PART_TYPE_UNKNOWN before the post-write
part_init() call so it always performs a full re-scan and correctly
detects EFI immediately.

Signed-off-by: Vandhiadevan Karunamoorthy <vandhiadevan.karunamoorthy@oss.qualcomm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant