From nobody Sat Sep 26 20:51:42 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1788174799; cv=none; d=zohomail.com; s=zohoarc; b=PciUdvbxQ7tcxaOxbCawH+cMaeENL6pD5ScJMz6v801ZNZQ7FrW+XXZiCazEh9VC8CMjcBhbm/JdVi7n/685HlvCiSsfJo3dzvFhuRF+5mYmfrVlQvJpyFFasJmuQ5SlDcIZTouPrB77NmvJe9z51ZEQdRR2NGHCHaSJ23hgz+o= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788174799; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=uobf1dYLMh44mUTMcUiM2zqCKNgGxG1OzMfqTsc9p+4=; b=SD6ql//2HLIYu+ZangiFZ4/jY5+2VEnvMkZv7mxxLIT/CKk/byQnt1JzBisaxs8b1YYWWa6EHtTD5EGkyp/R8p73Qt3DOLfldoWcFmK8D4kj0BgKzELUekVZLpjRqiPQ1itDP3pD8iDU9SnSDiF3o+s7d5AF0tVp7o5aMc4FCUs= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788174799808547.0055553225147; Mon, 31 Aug 2026 04:13:19 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0zwY-0001pF-Ua; Mon, 31 Aug 2026 07:12:50 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0zwX-0001lx-03 for qemu-devel@nongnu.org; Mon, 31 Aug 2026 07:12:49 -0400 Received: from mail-pf1-x436.google.com ([2607:f8b0:4864:20::436]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1x0zwS-0003Dm-I1 for qemu-devel@nongnu.org; Mon, 31 Aug 2026 07:12:48 -0400 Received: by mail-pf1-x436.google.com with SMTP id d2e1a72fcca58-84f3ab8750cso2467072b3a.0 for ; Mon, 31 Aug 2026 04:12:44 -0700 (PDT) Received: from LAPTOP-CS7CB8K1.localdomain ([2409:8900:7ad7:8121:d841:db7e:846b:ea5e]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-856a397d927sm3367329b3a.54.2026.08.31.04.12.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 04:12:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788174763; x=1788779563; darn=nongnu.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=uobf1dYLMh44mUTMcUiM2zqCKNgGxG1OzMfqTsc9p+4=; b=fwYPt/8/4k0enFV8g2Ch9HL82Xm2tC6Vo4r/nTTwNwAJlsQAuWo19hCRqaMaqiDPcd I2aAg1b9m5QapHOD8JXavYwixjBTIPoLEfnKDH/SI1WaNPmih1MzJxG5m0JUsK04Nxxf RTVBRHo5zRoyCCzLsBD7BSY7FA9zMtQb7VJPmZLCWKF3PQVPO9iK7qTOY3Oa/LQyqvif XJZ1bHCRMG0wWAeSYrzVAcKTn5kK1DGpwSmhs1P/kJLdQqWFYJihvDBd+8YZr4EPYmAd +naUOuEov5VNPEkc4NYPI5cOEIocJO4LBoVviMnms3xuN61D4/dc2/EUFDd87qcjgumJ FF+Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788174763; x=1788779563; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=uobf1dYLMh44mUTMcUiM2zqCKNgGxG1OzMfqTsc9p+4=; b=gHVvISw+hFYY4yViETazs9bOioPrvQxZ5Rl5XWStZem7iJEs0z0mxtNSIvDde1iKs7 W05onDJi1+WXozfBv4U3opuWv+ertztZzgyoqnPHF2OgXR8ZC7l+w58fmRUzozBPpQEj /Hkx/ESNAegyWLMWiCjOnZ3yeI/3v77RCptt2/SP5BAg8bf5c11sb9rejedYJxDdiDQ2 WRd90gU5TwTNAtcoIfiCg3qyehJJB/VTBJsqaOMpGj4ZyRjAFEI8DYcQ9INPKQDwaB7U OgIWAspbLt/IvsyYj+a7LkgIWg/xHQfbbXJuDJ+wyfgRzRV7zNhehi6Nlm53xMuwvUb9 30iA== X-Gm-Message-State: AFuF++kzaHEVRkO0Hu/gu2KIrlVCLf2/Lj6bA3Z9+jpqeCf7lsRDB1y6 Y1TDXSQckqtZOpclvlc9D33OyI5CsAfSdJMktu4m4D/nxa9rBwmr/kL8 X-Gm-Gg: AR+sD11rfa0I337s6PefTKKp6sDMI2Mj2HqqTmdDHbwxGQ6LyPENBZIop2+x6dGerSL UyWqTz/elc5jxI1BFhKgB7Lg7FtJOyw9/xuHM//o0QoZYN8LArexQw8ch9G80xO6jpOQWbzVg8l AFVp0ylpbM45ku0XTcmNIL+El5jg8CuNlzDqlfIAWW3tJybZljBtOhYSdMCihGVUD8c+9cz4/4R I2mz4psXp7urVXA89cSdhCVAp2qvhGHTllCFQX6TR7jzXTcXPNCjJnVIWhyoBR3/MJS0Im5TDqR wudk3woPXFJRFtdqXbUFnhY4Hdz8IWZvl4Z+X/LONo5McLuKR3B5GfmyN3kb8i5gl4Gm8L1nqW0 f3G59+qZv8MbZT9zQfUd8dAsEEAyxnJ88LV2ZxQG3HMaemTkK7uLxQa+BuzI2WktcEtoWz4gwtN haRqSPFJ7AFDspga8XfE/EiQ4Pbi0Dgf4cYWYU3NFKJEMzajZEmYNdIwYuNQSpGCEzT/uPRyUvy H+FPlMzNzqTZuTshZucu1NcOobtcxO4zuKa X-Received: by 2002:a05:6a00:ac2:b0:857:7384:b5f8 with SMTP id d2e1a72fcca58-85b5bcf6a98mr359040b3a.20.1788174762738; Mon, 31 Aug 2026 04:12:42 -0700 (PDT) From: Kangjie Huang Date: Mon, 31 Aug 2026 19:11:12 +0800 Subject: [PATCH v4 1/3] hw/ssi: Add Synopsys DWC SSI standard PIO controller MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260831-t_v4_spi_patch-v4-1-370d416f26b5@gmail.com> References: <20260831-t_v4_spi_patch-v4-0-370d416f26b5@gmail.com> In-Reply-To: <20260831-t_v4_spi_patch-v4-0-370d416f26b5@gmail.com> To: qemu-devel@nongnu.org, qemu-riscv@nongnu.org Cc: Portia Stephens , Bin Meng , Chao Liu , Alistair Francis , Alistair Francis , Palmer Dabbelt , Weiwei Li , Daniel Henrique Barboza , Liu Zhiwei , Pierrick Bouvier , Paolo Bonzini , Fabiano Rosas , Laurent Vivier , Anirudh Srinivasan , Kangjie Huang X-Mailer: b4 0.13.0 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::436; envelope-from=flamboyant.h.01@gmail.com; helo=mail-pf1-x436.google.com X-Spam_score_int: -17 X-Spam_score: -1.8 X-Spam_bar: - X-Spam_report: (-1.8 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1788174803239154100 Add a reusable Synopsys DWC SSI SysBus model. It follows the DWC CTRLR0 layout where TMOD sits at [11:10]; the DW APB SSI variant with TMOD at [9:8] is not supported. Implement the Standard SPI register subset, configurable chip-select and FIFO resources, the four Standard PIO transfer modes, reset handling, chip-select GPIOs, and RAZ/WI for the enhanced, DMA and XIP registers that are not supported. Implement the SSI raw and masked interrupt status, threshold interrupts and the read-to-clear registers. TXE and RXF are derived from FIFO thresholds, while TXO, RXO and RXU are latched. Nine physical interrupt outputs are exposed. TXU, DONE and AXIE depend on a transfer engine beyond the Standard PIO scope, so they remain deasserted in the current model. The K230 TRM serves as the register reference for this model: https://github.com/revyos/external-docs/blob/79b3a79072412ead81427e6755b4d9= e6d9ded8d8/K230/en-us/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf The Linux driver and binding serve as a reference for the software-visible behavior and capabilities: https://github.com/torvalds/linux/blob/f9a2394a23482bfd330911e9c8295b71724f= eacd/drivers/spi/spi-dw-core.c https://github.com/torvalds/linux/blob/f9a2394a23482bfd330911e9c8295b71724f= eacd/drivers/spi/spi-dw.h https://github.com/torvalds/linux/blob/f9a2394a23482bfd330911e9c8295b71724f= eacd/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml Signed-off-by: Kangjie Huang Reviewed-by: Portia Stephens --- MAINTAINERS | 1 + hw/ssi/Kconfig | 5 + hw/ssi/dwc_ssi.c | 1108 ++++++++++++++++++++++++++++++++++++++++++= ++++ hw/ssi/meson.build | 1 + include/hw/ssi/dwc_ssi.h | 80 ++++ 5 files changed, 1195 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index dc2f465628..a5e76934b6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2408,6 +2408,7 @@ F: docs/devel/ssi.rst F: hw/ssi/* F: hw/block/m25p80* F: include/hw/ssi/ssi.h +F: include/hw/ssi/dwc_ssi.h X: hw/ssi/xilinx_* F: tests/qtest/aspeed_smc-test.c =20 diff --git a/hw/ssi/Kconfig b/hw/ssi/Kconfig index 1bd56463c1..db7f1488fc 100644 --- a/hw/ssi/Kconfig +++ b/hw/ssi/Kconfig @@ -6,6 +6,11 @@ config SIFIVE_SPI bool select SSI =20 +config DWC_SSI + bool + select SSI + select REGISTER + config SSI bool =20 diff --git a/hw/ssi/dwc_ssi.c b/hw/ssi/dwc_ssi.c new file mode 100644 index 0000000000..b5cc6d614c --- /dev/null +++ b/hw/ssi/dwc_ssi.c @@ -0,0 +1,1108 @@ +/* + * Synopsys DesignWare SSI + * + * Copyright (c) 2026 Kangjie Huang + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Emulates the DesignWare SSI controller in Standard SPI mode, + * covering the PIO/FIFO data path, interrupt outputs and cs and + * serial-slave is not modelled. + */ + +#include "qemu/osdep.h" +#include "hw/core/registerfields.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/irq.h" +#include "hw/ssi/dwc_ssi.h" +#include "migration/vmstate.h" +#include "qapi/error.h" +#include "qemu/bitops.h" +#include "qemu/log.h" +#include "qemu/module.h" + +/* DWC SSI 1.03 derivative */ +#define DWC_SSI_CTRLR0_RESET 0x00004007 +#define DWC_SSI_SR_RESET 0x00000006 +#define DWC_SSI_IDR_RESET 0xa1b2c3d5 +#define DWC_SSI_VERSION 0x3130332a +#define DWC_SSI_IMR_RESET_BASE 0x0000001f +#define DWC_SSI_IRQ_VALID_MASK 0x000009bf +#define DWC_SSI_IMPLEMENTED_LATCHED_IRQ_MASK \ + (R_RISR_TXOIR_MASK | R_RISR_RXOIR_MASK | R_RISR_RXUIR_MASK) + +enum { + DWC_SSI_TMOD_TR, + DWC_SSI_TMOD_TO, + DWC_SSI_TMOD_RO, + DWC_SSI_TMOD_EEPROM_READ, +}; + +REG32(CTRLR0, 0x000) + FIELD(CTRLR0, DFS, 0, 5) + FIELD(CTRLR0, FRF, 6, 2) + FIELD(CTRLR0, SCPH, 8, 1) + FIELD(CTRLR0, SCPOL, 9, 1) + FIELD(CTRLR0, TMOD, 10, 2) + FIELD(CTRLR0, SLV_OE, 12, 1) + FIELD(CTRLR0, SRL, 13, 1) + FIELD(CTRLR0, SSTE, 14, 1) + FIELD(CTRLR0, CFS, 16, 4) + FIELD(CTRLR0, SPI_FRF, 22, 2) + FIELD(CTRLR0, SPI_HYPERBUS_EN, 24, 1) + FIELD(CTRLR0, SPI_DWS_EN, 25, 1) +REG32(CTRLR1, 0x004) + FIELD(CTRLR1, NDF, 0, 16) +REG32(SSIENR, 0x008) + FIELD(SSIENR, SSIC_EN, 0, 1) +REG32(MWCR, 0x00c) + FIELD(MWCR, MWMOD, 0, 1) + FIELD(MWCR, MDD, 1, 1) + FIELD(MWCR, MHS, 2, 1) +REG32(SER, 0x010) +REG32(BAUDR, 0x014) + FIELD(BAUDR, SCKDV, 1, 15) +REG32(TXFTLR, 0x018) + FIELD(TXFTLR, TFT, 0, 8) + FIELD(TXFTLR, TXFTHR, 16, 11) +REG32(RXFTLR, 0x01c) + FIELD(RXFTLR, RFT, 0, 8) +REG32(TXFLR, 0x020) + FIELD(TXFLR, TXTFL, 0, 9) +REG32(RXFLR, 0x024) + FIELD(RXFLR, RXTFL, 0, 9) +REG32(SR, 0x028) + FIELD(SR, CMPLTD_DF, 15, 17) + FIELD(SR, DCOL, 6, 1) + FIELD(SR, TXE, 5, 1) + FIELD(SR, BUSY, 0, 1) + FIELD(SR, TFNF, 1, 1) + FIELD(SR, TFE, 2, 1) + FIELD(SR, RFNE, 3, 1) + FIELD(SR, RFF, 4, 1) +REG32(IMR, 0x02c) + FIELD(IMR, DONEM, 11, 1) + FIELD(IMR, SPITEM, 10, 1) + FIELD(IMR, AXIEM, 8, 1) + FIELD(IMR, TXUIM, 7, 1) + FIELD(IMR, XRXOIM, 6, 1) + FIELD(IMR, MSTIM, 5, 1) + FIELD(IMR, RXOIM, 3, 1) + FIELD(IMR, RXUIM, 2, 1) + FIELD(IMR, TXOIM, 1, 1) + FIELD(IMR, TXEIM, 0, 1) + FIELD(IMR, RXFIM, 4, 1) +REG32(ISR, 0x030) + FIELD(ISR, DONES, 11, 1) + FIELD(ISR, SPITES, 10, 1) + FIELD(ISR, AXIES, 8, 1) + FIELD(ISR, TXUIS, 7, 1) + FIELD(ISR, XRXOIS, 6, 1) + FIELD(ISR, MSTIS, 5, 1) + FIELD(ISR, RXOIS, 3, 1) + FIELD(ISR, RXUIS, 2, 1) + FIELD(ISR, TXOIS, 1, 1) + FIELD(ISR, TXEIS, 0, 1) + FIELD(ISR, RXFIS, 4, 1) +REG32(RISR, 0x034) + FIELD(RISR, DONER, 11, 1) + FIELD(RISR, SPITER, 10, 1) + FIELD(RISR, AXIER, 8, 1) + FIELD(RISR, TXUIR, 7, 1) + FIELD(RISR, XRXOIR, 6, 1) + FIELD(RISR, MSTIR, 5, 1) + FIELD(RISR, RXOIR, 3, 1) + FIELD(RISR, RXUIR, 2, 1) + FIELD(RISR, TXOIR, 1, 1) + FIELD(RISR, TXEIR, 0, 1) + FIELD(RISR, RXFIR, 4, 1) +REG32(TXEICR, 0x038) + FIELD(TXEICR, TXEICR, 0, 1) +REG32(RXOICR, 0x03c) + FIELD(RXOICR, RXOICR, 0, 1) +REG32(RXUICR, 0x040) + FIELD(RXUICR, RXUICR, 0, 1) +REG32(MSTICR, 0x044) + FIELD(MSTICR, MSTICR, 0, 1) +REG32(ICR, 0x048) + FIELD(ICR, ICR, 0, 1) +REG32(DMACR, 0x04c) + FIELD(DMACR, RDMAE, 0, 1) + FIELD(DMACR, TDMAE, 1, 1) + FIELD(DMACR, IDMAE, 2, 1) + FIELD(DMACR, ATW, 3, 2) + FIELD(DMACR, AINC, 6, 1) + FIELD(DMACR, ACACHE, 8, 4) + FIELD(DMACR, APROT, 12, 3) + FIELD(DMACR, AID, 15, 4) +REG32(AXIAWLEN, 0x050) + FIELD(AXIAWLEN, AWLEN, 8, 8) +REG32(AXIARLEN, 0x054) + FIELD(AXIARLEN, ARLEN, 8, 8) +REG32(IDR, 0x058) + FIELD(IDR, IDCODE, 0, 32) +REG32(SSIC_VERSION_ID, 0x05c) + FIELD(SSIC_VERSION_ID, VERSION_ID, 0, 32) +REG32(DR0, 0x060) +REG32(DR_END, 0x0ec) +REG32(RX_SAMPLE_DELAY, 0x0f0) + FIELD(RX_SAMPLE_DELAY, RSD, 0, 8) + FIELD(RX_SAMPLE_DELAY, SE, 16, 1) +REG32(SPI_CTRLR0, 0x0f4) + FIELD(SPI_CTRLR0, CLK_STRETCH_EN, 30, 1) + FIELD(SPI_CTRLR0, XIP_PREFETCH_EN, 29, 1) + FIELD(SPI_CTRLR0, XIP_MBL, 26, 2) + FIELD(SPI_CTRLR0, SPI_RXDS_SIG_EN, 25, 1) + FIELD(SPI_CTRLR0, SPI_DM_EN, 24, 1) + FIELD(SPI_CTRLR0, SSIC_XIP_CONT_XFER_EN, 21, 1) + FIELD(SPI_CTRLR0, XIP_INST_EN, 20, 1) + FIELD(SPI_CTRLR0, XIP_DFS_HC, 19, 1) + FIELD(SPI_CTRLR0, SPI_RXDS_EN, 18, 1) + FIELD(SPI_CTRLR0, INST_DDR_EN, 17, 1) + FIELD(SPI_CTRLR0, SPI_DDR_EN, 16, 1) + FIELD(SPI_CTRLR0, WAIT_CYCLES, 11, 5) + FIELD(SPI_CTRLR0, INST_L, 8, 2) + FIELD(SPI_CTRLR0, XIP_MD_BIT_EN, 7, 1) + FIELD(SPI_CTRLR0, ADDR_L, 2, 4) + FIELD(SPI_CTRLR0, TRANS_TYPE, 0, 2) +REG32(DDR_DRIVE_EDGE, 0x0f8) + FIELD(DDR_DRIVE_EDGE, TDE, 0, 8) +REG32(XIP_MODE_BITS, 0x0fc) + FIELD(XIP_MODE_BITS, XIP_MD_BITS, 0, 16) +REG32(XIP_INCR_INST, 0x100) + FIELD(XIP_INCR_INST, INCR_INST, 0, 16) +REG32(XIP_WRAP_INST, 0x104) + FIELD(XIP_WRAP_INST, WRAP_INST, 0, 16) +REG32(XIP_CTRL, 0x108) +REG32(XIP_SER, 0x10c) +REG32(XRXOICR, 0x110) +REG32(XIP_CNT_TIME_OUT, 0x114) +REG32(SPI_CTRLR1, 0x118) +REG32(SPITECR, 0x11c) +REG32(SPIDR, 0x120) + FIELD(SPIDR, SPI_INST, 0, 16) +REG32(SPIAR, 0x124) + FIELD(SPIAR, SDAR, 0, 32) +REG32(AXIAR0, 0x128) + FIELD(AXIAR0, AXIAR_0_31, 0, 32) +REG32(AXIAR1, 0x12c) + FIELD(AXIAR1, AXIAR_32_63, 0, 32) +REG32(AXIECR, 0x130) + FIELD(AXIECR, AXIECR, 0, 1) +REG32(DONECR, 0x134) + FIELD(DONECR, DONECR, 0, 1) +REG32(RSVD_138, 0x138) +REG32(RSVD_13C, 0x13c) +REG32(XIP_WRITE_INCR_INST, 0x140) +REG32(XIP_WRITE_WRAP_INST, 0x144) +REG32(XIP_WRITE_CTRL, 0x148) + +#define DWC_SSI_CTRLR0_STANDARD_WRITABLE_MASK \ + (R_CTRLR0_DFS_MASK | \ + R_CTRLR0_SCPH_MASK | \ + R_CTRLR0_SCPOL_MASK | \ + R_CTRLR0_TMOD_MASK | \ + R_CTRLR0_SLV_OE_MASK | \ + R_CTRLR0_SRL_MASK | \ + R_CTRLR0_SSTE_MASK | \ + R_CTRLR0_CFS_MASK) + +#define DWC_SSI_CTRLR1_WRITABLE_MASK R_CTRLR1_NDF_MASK +#define DWC_SSI_MWCR_WRITABLE_MASK \ + (R_MWCR_MWMOD_MASK | R_MWCR_MDD_MASK | R_MWCR_MHS_MASK) +#define DWC_SSI_BAUDR_WRITABLE_MASK R_BAUDR_SCKDV_MASK +#define DWC_SSI_TXFTLR_WRITABLE_MASK \ + (R_TXFTLR_TFT_MASK | R_TXFTLR_TXFTHR_MASK) +#define DWC_SSI_RXFTLR_WRITABLE_MASK R_RXFTLR_RFT_MASK +#define DWC_SSI_IMR_WRITABLE_MASK \ + (R_IMR_TXEIM_MASK | R_IMR_TXOIM_MASK | R_IMR_RXUIM_MASK | \ + R_IMR_RXOIM_MASK | R_IMR_RXFIM_MASK | R_IMR_MSTIM_MASK | \ + R_IMR_TXUIM_MASK) +#define DWC_SSI_RX_SAMPLE_DELAY_WRITABLE_MASK \ + (R_RX_SAMPLE_DELAY_RSD_MASK | R_RX_SAMPLE_DELAY_SE_MASK) + +/* + * TXU/DONE/AXIE: not modeled in Standard PIO. + * Just wired out and tied low. + */ +static const uint32_t dwc_ssi_irq_status_mask[DWC_SSI_IRQ_COUNT] =3D { + [DWC_SSI_IRQ_TXE] =3D R_RISR_TXEIR_MASK, + [DWC_SSI_IRQ_TXO] =3D R_RISR_TXOIR_MASK, + [DWC_SSI_IRQ_RXF] =3D R_RISR_RXFIR_MASK, + [DWC_SSI_IRQ_RXO] =3D R_RISR_RXOIR_MASK, + [DWC_SSI_IRQ_TXU] =3D R_RISR_TXUIR_MASK, + [DWC_SSI_IRQ_RXU] =3D R_RISR_RXUIR_MASK, + [DWC_SSI_IRQ_MST] =3D R_RISR_MSTIR_MASK, + [DWC_SSI_IRQ_DONE] =3D R_RISR_DONER_MASK, + [DWC_SSI_IRQ_AXIE] =3D R_RISR_AXIER_MASK, +}; + +static bool dwc_ssi_validate_config(DwcSsiState *s, Error **errp) +{ + DeviceState *dev =3D DEVICE(s); + + if (s->cfg.num_cs =3D=3D 0 || s->cfg.num_cs > 8) { + error_setg(errp, "%s: num-cs must be in range 1..8", + dev->canonical_path); + return false; + } + + if (s->cfg.fifo_depth < 2 || s->cfg.fifo_depth > 256) { + error_setg(errp, "%s: fifo-depth must be in range 2..256", + dev->canonical_path); + return false; + } + + return true; +} + +static uint32_t dwc_ssi_imr_reset(const DwcSsiState *s) +{ + /* Select the profile-specific IMR reset value */ + return DWC_SSI_IMR_RESET_BASE | + (s->cfg.master_mode ? R_IMR_MSTIM_MASK : 0); +} + +static bool dwc_ssi_fifo_threshold_valid(const DwcSsiState *s, uint32_t va= lue) +{ + return value < s->cfg.fifo_depth; +} + +static void dwc_ssi_write_masked(DwcSsiState *s, unsigned int reg, + uint32_t value, uint32_t mask) +{ + s->regs[reg] =3D (s->regs[reg] & ~mask) | (value & mask); +} + +static uint32_t dwc_ssi_irq_raw_status(DwcSsiState *s) +{ + uint32_t status =3D s->irq_latched; + uint32_t tx_used =3D fifo32_num_used(&s->tx_fifo); + uint32_t rx_used =3D fifo32_num_used(&s->rx_fifo); + uint32_t tx_threshold =3D + FIELD_EX32(s->regs[R_TXFTLR], TXFTLR, TFT); + uint32_t rx_threshold =3D + FIELD_EX32(s->regs[R_RXFTLR], RXFTLR, RFT); + + if (tx_used <=3D tx_threshold) { + status |=3D R_RISR_TXEIR_MASK; + } + if (rx_used > rx_threshold) { + status |=3D R_RISR_RXFIR_MASK; + } + return status & DWC_SSI_IRQ_VALID_MASK; +} + +static void dwc_ssi_update_irq(DwcSsiState *s) +{ + uint32_t status =3D dwc_ssi_irq_raw_status(s) & + s->regs[R_IMR] & DWC_SSI_IRQ_VALID_MASK; + + for (int i =3D 0; i < DWC_SSI_IRQ_COUNT; i++) { + qemu_set_irq(s->irqs[i], !!(status & dwc_ssi_irq_status_mask[i])); + } +} + +static uint32_t dwc_ssi_irq_read_clear(DwcSsiState *s, + uint32_t clear_mask) +{ + uint32_t active =3D s->irq_latched & clear_mask; + + s->irq_latched &=3D ~clear_mask; + dwc_ssi_update_irq(s); + return !!active; +} + +static uint32_t dwc_ssi_frame_masked(DwcSsiState *s) +{ + unsigned int bits =3D FIELD_EX32(s->regs[R_CTRLR0], CTRLR0, DFS) + 1; + + return bits =3D=3D 32 ? UINT32_MAX : MAKE_64BIT_MASK(0, bits); +} + +static bool dwc_ssi_is_enabled(const DwcSsiState *s) +{ + return FIELD_EX32(s->regs[R_SSIENR], SSIENR, SSIC_EN); +} + +static void dwc_ssi_deselect(DwcSsiState *s) +{ + if (s->active_cs < 0) { + return; + } + + qemu_irq_raise(s->cs_lines[s->active_cs]); + s->active_cs =3D -1; +} + +static void dwc_ssi_select(DwcSsiState *s, unsigned cs) +{ + if (cs >=3D s->cfg.num_cs) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid cs %u\n", + DEVICE(s)->canonical_path, cs); + dwc_ssi_deselect(s); + return; + } + + if (s->active_cs =3D=3D cs) { + return; + } + + dwc_ssi_deselect(s); + qemu_irq_lower(s->cs_lines[cs]); + s->active_cs =3D cs; +} + +static void dwc_ssi_update_cs(DwcSsiState *s) +{ + uint32_t ser =3D s->regs[R_SER]; + + if (!dwc_ssi_is_enabled(s) || !ser) { + dwc_ssi_deselect(s); + return; + } + + if (ser & (ser - 1)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: multiple cs enabled: 0x%x\n", + DEVICE(s)->canonical_path, ser); + dwc_ssi_deselect(s); + return; + } + + dwc_ssi_select(s, ctz32(ser)); +} + +static void dwc_ssi_abort_transfer(DwcSsiState *s) +{ + dwc_ssi_deselect(s); + fifo32_reset(&s->tx_fifo); + fifo32_reset(&s->rx_fifo); + s->phase =3D DWC_SSI_PHASE_IDLE; + s->remaining_frames =3D 0; + s->dummy_frame =3D 0; + dwc_ssi_update_irq(s); +} + +static uint32_t dwc_ssi_status(DwcSsiState *s) +{ + uint32_t tx_used =3D fifo32_num_used(&s->tx_fifo); + uint32_t rx_used =3D fifo32_num_used(&s->rx_fifo); + uint32_t sr =3D 0; + + sr =3D FIELD_DP32(sr, SR, BUSY, s->phase !=3D DWC_SSI_PHASE_IDLE); + sr =3D FIELD_DP32(sr, SR, TFNF, tx_used < s->cfg.fifo_depth); + sr =3D FIELD_DP32(sr, SR, TFE, tx_used =3D=3D 0); + sr =3D FIELD_DP32(sr, SR, RFNE, rx_used !=3D 0); + sr =3D FIELD_DP32(sr, SR, RFF, + rx_used =3D=3D s->cfg.fifo_depth); + + return sr; +} + +static void dwc_ssi_run_transfer(DwcSsiState *s); + +static void dwc_ssi_push_tx(DwcSsiState *s, uint32_t tx) +{ + if (!dwc_ssi_is_enabled(s)) { + return; + } + + if (fifo32_is_full(&s->tx_fifo)) { + s->irq_latched |=3D R_RISR_TXOIR_MASK; + dwc_ssi_update_irq(s); + return; + } + + fifo32_push(&s->tx_fifo, tx); + dwc_ssi_run_transfer(s); + dwc_ssi_update_irq(s); +} + +static uint32_t dwc_ssi_send_frame(DwcSsiState *s, + uint32_t tx) +{ + uint32_t mask =3D dwc_ssi_frame_masked(s); + uint32_t rx; + + tx &=3D mask; + + /* + * For frame widths greater than 8 bits, 8-bit device models such as + * m25p80 consume only the lower 8 bits; the upper bits are ignored. + */ + if (mask > 0xff) { + qemu_log_mask(LOG_UNIMP, + "%s: frames wider than 8 bits are passed to the " + "bus as-is; 8-bit peripherals drop the high bits\n", + DEVICE(s)->canonical_path); + } + + if (FIELD_EX32(s->regs[R_CTRLR0], CTRLR0, SRL)) { + rx =3D tx; + } else { + rx =3D ssi_transfer(s->spi, tx); + } + + return rx & mask; +} + +static void dwc_ssi_run_transfer(DwcSsiState *s) +{ + uint32_t tmod; + + if (!dwc_ssi_is_enabled(s) || s->active_cs < 0) { + return; + } + + /* SCKDV=3D0 disables sclk_out: no transfer can start. */ + if (FIELD_EX32(s->regs[R_BAUDR], BAUDR, SCKDV) =3D=3D 0) { + return; + } + + /* + * Shift doesn't start until TX FIFO > TXFTLR.TXFTHR. + * In-flight transfers won't be aborted if FIFO drops below. + */ + if (s->phase =3D=3D DWC_SSI_PHASE_IDLE && + fifo32_num_used(&s->tx_fifo) <=3D + FIELD_EX32(s->regs[R_TXFTLR], TXFTLR, TXFTHR)) { + return; + } + + tmod =3D FIELD_EX32(s->regs[R_CTRLR0], CTRLR0, TMOD); + + switch (tmod) { + case DWC_SSI_TMOD_TR: + if (s->phase =3D=3D DWC_SSI_PHASE_IDLE) { + s->phase =3D DWC_SSI_PHASE_STANDARD_TR; + } + + while (!fifo32_is_empty(&s->tx_fifo)) { + uint32_t tx =3D fifo32_pop(&s->tx_fifo); + uint32_t rx =3D dwc_ssi_send_frame(s, tx); + + if (fifo32_is_full(&s->rx_fifo)) { + s->irq_latched |=3D R_RISR_RXOIR_MASK; + qemu_log_mask(LOG_GUEST_ERROR, + "%s: RX FIFO full, dropping frame\n", + DEVICE(s)->canonical_path); + } else { + fifo32_push(&s->rx_fifo, rx); + } + } + + if (fifo32_is_empty(&s->tx_fifo)) { + s->phase =3D DWC_SSI_PHASE_IDLE; + } + break; + case DWC_SSI_TMOD_TO: + if (fifo32_is_empty(&s->tx_fifo)) { + s->phase =3D DWC_SSI_PHASE_IDLE; + break; + } + + s->phase =3D DWC_SSI_PHASE_STANDARD_TX_ONLY; + while (!fifo32_is_empty(&s->tx_fifo)) { + uint32_t tx =3D fifo32_pop(&s->tx_fifo); + + dwc_ssi_send_frame(s, tx); + } + s->phase =3D DWC_SSI_PHASE_IDLE; + break; + case DWC_SSI_TMOD_RO: + switch (s->phase) { + case DWC_SSI_PHASE_IDLE: + if (fifo32_is_empty(&s->tx_fifo)) { + break; + } + s->dummy_frame =3D fifo32_pop(&s->tx_fifo); + s->phase =3D DWC_SSI_PHASE_RX_ONLY; + s->remaining_frames =3D FIELD_EX32(s->regs[R_CTRLR1], CTRLR1, + NDF) + 1; + /* Fall through */ + case DWC_SSI_PHASE_RX_ONLY: + while (s->remaining_frames > 0 && + !fifo32_is_full(&s->rx_fifo)) { + uint32_t rx =3D dwc_ssi_send_frame(s, s->dummy_frame); + + fifo32_push(&s->rx_fifo, rx); + s->remaining_frames--; + } + if (s->remaining_frames =3D=3D 0) { + s->phase =3D DWC_SSI_PHASE_IDLE; + } + break; + } + break; + case DWC_SSI_TMOD_EEPROM_READ: + switch (s->phase) { + case DWC_SSI_PHASE_IDLE: + if (fifo32_is_empty(&s->tx_fifo)) { + break; + } + s->phase =3D DWC_SSI_PHASE_EEPROM_COMMAND; + /* Fall through */ + case DWC_SSI_PHASE_EEPROM_COMMAND: + if (fifo32_is_empty(&s->tx_fifo)) { + break; + } + while (!fifo32_is_empty(&s->tx_fifo)) { + uint32_t tx =3D fifo32_pop(&s->tx_fifo); + + dwc_ssi_send_frame(s, tx); + } + s->phase =3D DWC_SSI_PHASE_EEPROM_DATA; + s->remaining_frames =3D FIELD_EX32(s->regs[R_CTRLR1], CTRLR1, + NDF) + 1; + /* Fall through */ + case DWC_SSI_PHASE_EEPROM_DATA: + while (s->remaining_frames > 0 && + !fifo32_is_full(&s->rx_fifo)) { + uint32_t rx =3D dwc_ssi_send_frame(s, 0x00); + + fifo32_push(&s->rx_fifo, rx); + s->remaining_frames--; + } + if (s->remaining_frames =3D=3D 0) { + s->phase =3D DWC_SSI_PHASE_IDLE; + } + break; + } + break; + default: + g_assert_not_reached(); + } +} + +static bool dwc_ssi_is_dr(hwaddr addr) +{ + return addr >=3D A_DR0 && addr <=3D A_DR_END && + (addr & 0x3) =3D=3D 0; +} + +static bool dwc_ssi_is_unsupported_offset(hwaddr addr) +{ + switch (addr) { + /* Enhanced SPI extensions */ + case A_SPI_CTRLR0: + case A_DDR_DRIVE_EDGE: + + /* XIP extensions */ + case A_XIP_MODE_BITS: + case A_XIP_INCR_INST: + case A_XIP_WRAP_INST: + case A_XIP_CTRL: + case A_XIP_SER: + case A_XRXOICR: + case A_XIP_CNT_TIME_OUT: + case A_SPI_CTRLR1: + case A_SPITECR: + case A_RSVD_138: + case A_RSVD_13C: + case A_XIP_WRITE_INCR_INST: + case A_XIP_WRITE_WRAP_INST: + case A_XIP_WRITE_CTRL: + + /* Internal-AXI DMA/IDMA extensions */ + case A_DMACR: + case A_AXIAWLEN: + case A_AXIARLEN: + case A_SPIDR: + case A_SPIAR: + case A_AXIAR0: + case A_AXIAR1: + case A_AXIECR: + case A_DONECR: + + /* SSI unsupported extensions */ + case A_RX_SAMPLE_DELAY: + return true; + default: + return false; + } +} + +static bool dwc_ssi_write_requires_disabled(hwaddr addr) +{ + switch (addr) { + case A_CTRLR0: + case A_CTRLR1: + case A_MWCR: + case A_BAUDR: + return true; + default: + return false; + } +} + +static uint32_t dwc_ssi_unsupported_read(DwcSsiState *s, + hwaddr addr) +{ + qemu_log_mask(LOG_UNIMP, + "%s: unsupported read at 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, addr); + return 0; +} + +static void dwc_ssi_unsupported_write(DwcSsiState *s, + hwaddr addr, + uint32_t value) +{ + qemu_log_mask(LOG_UNIMP, + "%s: unsupported write at 0x%" HWADDR_PRIx + " value 0x%08x\n", + DEVICE(s)->canonical_path, addr, value); +} + +static uint64_t dwc_ssi_read(void *opaque, hwaddr addr, unsigned int size) +{ + DwcSsiState *s =3D DWC_SSI(opaque); + uint32_t value =3D 0; + + if (dwc_ssi_is_dr(addr)) { + if (!fifo32_is_empty(&s->rx_fifo)) { + value =3D fifo32_pop(&s->rx_fifo) & dwc_ssi_frame_masked(s); + } else { + value =3D 0; + s->irq_latched |=3D R_RISR_RXUIR_MASK; + } + + dwc_ssi_run_transfer(s); + dwc_ssi_update_irq(s); + return value; + } + + if (dwc_ssi_is_unsupported_offset(addr)) { + value =3D dwc_ssi_unsupported_read(s, addr); + return value; + } + + switch (addr) { + case A_CTRLR0: + case A_CTRLR1: + case A_SSIENR: + case A_MWCR: + case A_BAUDR: + case A_TXFTLR: + case A_RXFTLR: + case A_IMR: + case A_IDR: + case A_SSIC_VERSION_ID: + value =3D s->regs[addr / sizeof(uint32_t)]; + break; + + case A_SER: + value =3D s->regs[R_SER] & MAKE_64BIT_MASK(0, s->cfg.num_cs); + break; + case A_TXFLR: + value =3D fifo32_num_used(&s->tx_fifo); + break; + case A_RXFLR: + value =3D fifo32_num_used(&s->rx_fifo); + break; + case A_SR: + value =3D dwc_ssi_status(s); + break; + case A_ISR: + value =3D dwc_ssi_irq_raw_status(s) & s->regs[R_IMR] & + DWC_SSI_IRQ_VALID_MASK; + break; + case A_RISR: + value =3D dwc_ssi_irq_raw_status(s); + break; + case A_TXEICR: + value =3D dwc_ssi_irq_read_clear( + s, R_RISR_TXOIR_MASK | R_RISR_TXUIR_MASK); + break; + case A_RXOICR: + value =3D dwc_ssi_irq_read_clear(s, R_RISR_RXOIR_MASK); + break; + case A_RXUICR: + value =3D dwc_ssi_irq_read_clear(s, R_RISR_RXUIR_MASK); + break; + case A_MSTICR: + value =3D dwc_ssi_irq_read_clear(s, R_RISR_MSTIR_MASK); + break; + case A_ICR: + value =3D dwc_ssi_irq_read_clear( + s, R_RISR_TXOIR_MASK | R_RISR_RXUIR_MASK | + R_RISR_RXOIR_MASK | R_RISR_MSTIR_MASK); + break; + + default: + if (addr >=3D DWC_SSI_REGS_SIZE || (addr & 0x3) !=3D 0) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: bad read offset 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, addr); + } + break; + } + + return value; +} + +static void dwc_ssi_write(void *opaque, hwaddr addr, + uint64_t value, unsigned int size) +{ + DwcSsiState *s =3D DWC_SSI(opaque); + + if (dwc_ssi_is_dr(addr)) { + dwc_ssi_push_tx(s, value); + return; + } + + if (dwc_ssi_is_unsupported_offset(addr)) { + dwc_ssi_unsupported_write(s, addr, value); + return; + } + + if (dwc_ssi_write_requires_disabled(addr) && + dwc_ssi_is_enabled(s)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to offset 0x%" HWADDR_PRIx + " while SSI is enabled\n", + DEVICE(s)->canonical_path, addr); + return; + } + + switch (addr) { + case A_CTRLR0: { + uint32_t dfs =3D FIELD_EX32(value, CTRLR0, DFS); + + if (dfs < 3) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: unsupported DFS value %u (must encode " + "4..32 bits)\n", + DEVICE(s)->canonical_path, dfs); + return; + } + + if (FIELD_EX32(value, CTRLR0, SPI_FRF) !=3D 0) { + qemu_log_mask(LOG_UNIMP, + "%s: enhanced SPI frame formats are not modelled= \n", + DEVICE(s)->canonical_path); + } + dwc_ssi_write_masked(s, R_CTRLR0, value, + DWC_SSI_CTRLR0_STANDARD_WRITABLE_MASK); + break; + } + case A_CTRLR1: + dwc_ssi_write_masked(s, R_CTRLR1, value, + DWC_SSI_CTRLR1_WRITABLE_MASK); + break; + case A_SSIENR: { + bool old_enabled =3D dwc_ssi_is_enabled(s); + bool new_enabled =3D value & R_SSIENR_SSIC_EN_MASK; + + if (old_enabled =3D=3D new_enabled) { + return; + } + + s->regs[R_SSIENR] =3D FIELD_DP32(0, SSIENR, SSIC_EN, new_enabled); + if (!new_enabled) { + dwc_ssi_abort_transfer(s); + return; + } + + dwc_ssi_update_cs(s); + dwc_ssi_run_transfer(s); + dwc_ssi_update_irq(s); + break; + } + case A_MWCR: + dwc_ssi_write_masked(s, R_MWCR, value, + DWC_SSI_MWCR_WRITABLE_MASK); + break; + case A_SER: { + uint32_t old_ser =3D s->regs[R_SER]; + uint32_t new_ser =3D value & MAKE_64BIT_MASK(0, s->cfg.num_cs); + + /* + * Switching CS aborts pending xfer. + * Stale RO/EEPROM phase must not resume on the new slave. + */ + if (old_ser !=3D new_ser && s->phase !=3D DWC_SSI_PHASE_IDLE) { + s->phase =3D DWC_SSI_PHASE_IDLE; + s->remaining_frames =3D 0; + } + s->regs[R_SER] =3D new_ser; + + dwc_ssi_update_cs(s); + dwc_ssi_run_transfer(s); + dwc_ssi_update_irq(s); + break; + } + case A_BAUDR: + dwc_ssi_write_masked(s, R_BAUDR, value, + DWC_SSI_BAUDR_WRITABLE_MASK); + break; + case A_TXFTLR: + if (!dwc_ssi_fifo_threshold_valid( + s, FIELD_EX32(value, TXFTLR, TFT)) || + !dwc_ssi_fifo_threshold_valid( + s, FIELD_EX32(value, TXFTLR, TXFTHR))) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid txftlr value %x\n", + DEVICE(s)->canonical_path, (unsigned int)value); + break; + } + dwc_ssi_write_masked(s, R_TXFTLR, value, + DWC_SSI_TXFTLR_WRITABLE_MASK); + dwc_ssi_run_transfer(s); + dwc_ssi_update_irq(s); + break; + case A_RXFTLR: + if (!dwc_ssi_fifo_threshold_valid( + s, FIELD_EX32(value, RXFTLR, RFT))) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid rxftlr value %x\n", + DEVICE(s)->canonical_path, (unsigned int)value); + break; + } + dwc_ssi_write_masked(s, R_RXFTLR, value, + DWC_SSI_RXFTLR_WRITABLE_MASK); + dwc_ssi_update_irq(s); + break; + case A_TXFLR: + case A_RXFLR: + case A_SR: + break; + case A_IMR: + dwc_ssi_write_masked(s, R_IMR, value, + DWC_SSI_IMR_WRITABLE_MASK); + dwc_ssi_update_irq(s); + break; + case A_ISR: + case A_RISR: + break; + case A_TXEICR: + case A_RXOICR: + case A_RXUICR: + case A_MSTICR: + case A_ICR: + break; + case A_IDR: + case A_SSIC_VERSION_ID: + break; + default: + if (addr >=3D DWC_SSI_REGS_SIZE || (addr & 0x3) !=3D 0) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: bad write offset 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, addr); + } + break; + } +} + +static const MemoryRegionOps dwc_ssi_ops =3D { + .read =3D dwc_ssi_read, + .write =3D dwc_ssi_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + .unaligned =3D false, + }, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + .unaligned =3D false, + }, +}; + +static void dwc_ssi_enter_reset(Object *obj, ResetType type) +{ + DwcSsiState *s =3D DWC_SSI(obj); + + memset(s->regs, 0, sizeof(s->regs)); + fifo32_reset(&s->tx_fifo); + fifo32_reset(&s->rx_fifo); + s->phase =3D DWC_SSI_PHASE_IDLE; + s->remaining_frames =3D 0; + s->dummy_frame =3D 0; + s->irq_latched =3D 0; + + s->regs[R_CTRLR0] =3D DWC_SSI_CTRLR0_RESET; + s->regs[R_SR] =3D DWC_SSI_SR_RESET; + s->regs[R_IMR] =3D dwc_ssi_imr_reset(s); + s->regs[R_IDR] =3D DWC_SSI_IDR_RESET; + s->regs[R_SSIC_VERSION_ID] =3D DWC_SSI_VERSION; + + dwc_ssi_update_irq(s); +} + +static void dwc_ssi_hold_reset(Object *obj, ResetType type) +{ + DwcSsiState *s =3D DWC_SSI(obj); + s->active_cs =3D -1; + + if (s->cs_lines) { + for (int i =3D 0; i < s->cfg.num_cs; i++) { + qemu_irq_raise(s->cs_lines[i]); + } + } +} + +static void dwc_ssi_exit_reset(Object *obj, ResetType type) +{ + DwcSsiState *s =3D DWC_SSI(obj); + + dwc_ssi_update_irq(s); +} + +static bool dwc_ssi_fifo_valid(const Fifo32 *fifo, uint32_t depth) +{ + const Fifo8 *fifo8 =3D &fifo->fifo; + uint32_t capacity =3D depth * sizeof(uint32_t); + + return fifo8->capacity =3D=3D capacity && + (fifo8->capacity % sizeof(uint32_t)) =3D=3D 0 && + fifo8->head < fifo8->capacity && + fifo8->num <=3D fifo8->capacity && + (fifo8->head % sizeof(uint32_t)) =3D=3D 0 && + (fifo8->num % sizeof(uint32_t)) =3D=3D 0; +} + +static int dwc_ssi_post_load(void *opaque, int version_id) +{ + DwcSsiState *s =3D opaque; + uint32_t ndf =3D FIELD_EX32(s->regs[R_CTRLR1], CTRLR1, NDF) + 1; + + if (s->active_cs < -1 || s->active_cs >=3D (int)s->cfg.num_cs) { + return -EINVAL; + } + if (s->irq_latched & ~DWC_SSI_IMPLEMENTED_LATCHED_IRQ_MASK) { + return -EINVAL; + } + if (!dwc_ssi_fifo_valid(&s->tx_fifo, s->cfg.fifo_depth) || + !dwc_ssi_fifo_valid(&s->rx_fifo, s->cfg.fifo_depth)) { + return -EINVAL; + } + if (s->phase >=3D DWC_SSI_PHASE_COUNT) { + return -EINVAL; + } + if (s->phase =3D=3D DWC_SSI_PHASE_RX_ONLY || + s->phase =3D=3D DWC_SSI_PHASE_EEPROM_DATA) { + if (s->remaining_frames =3D=3D 0 || s->remaining_frames > ndf) { + return -EINVAL; + } + } else if (s->remaining_frames !=3D 0) { + return -EINVAL; + } + if (s->active_cs >=3D 0 && + (!dwc_ssi_is_enabled(s) || + s->regs[R_SER] !=3D BIT(s->active_cs))) { + return -EINVAL; + } + + for (int i =3D 0; i < s->cfg.num_cs; i++) { + qemu_irq_raise(s->cs_lines[i]); + } + if (s->active_cs >=3D 0) { + qemu_irq_lower(s->cs_lines[s->active_cs]); + } + + dwc_ssi_update_irq(s); + return 0; +} + +static const VMStateDescription vmstate_dwc_ssi =3D { + .name =3D TYPE_DWC_SSI, + .version_id =3D 1, + .minimum_version_id =3D 1, + .post_load =3D dwc_ssi_post_load, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT32_EQUAL(cfg.num_cs, DwcSsiState), + VMSTATE_UINT32_EQUAL(cfg.fifo_depth, DwcSsiState), + VMSTATE_BOOL(cfg.master_mode, DwcSsiState), + VMSTATE_UINT32_ARRAY(regs, DwcSsiState, DWC_SSI_NUM_REGS), + VMSTATE_FIFO32(tx_fifo, DwcSsiState), + VMSTATE_FIFO32(rx_fifo, DwcSsiState), + VMSTATE_UINT32(irq_latched, DwcSsiState), + VMSTATE_UINT32(phase, DwcSsiState), + VMSTATE_UINT32(remaining_frames, DwcSsiState), + VMSTATE_UINT32(dummy_frame, DwcSsiState), + VMSTATE_INT32(active_cs, DwcSsiState), + VMSTATE_END_OF_LIST() + }, +}; + +static void dwc_ssi_init(Object *obj) +{ + DwcSsiState *s =3D DWC_SSI(obj); + DeviceState *dev =3D DEVICE(obj); + SysBusDevice *sbd =3D SYS_BUS_DEVICE(obj); + + s->spi =3D ssi_create_bus(dev, "spi"); + + memory_region_init_io(&s->mmio, obj, &dwc_ssi_ops, s, + TYPE_DWC_SSI, DWC_SSI_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->mmio); + + for (int i =3D 0; i < DWC_SSI_IRQ_COUNT; i++) { + sysbus_init_irq(sbd, &s->irqs[i]); + } + + s->active_cs =3D -1; +} + +static void dwc_ssi_realize(DeviceState *dev, Error **errp) +{ + DwcSsiState *s =3D DWC_SSI(dev); + + if (!dwc_ssi_validate_config(s, errp)) { + return; + } + + s->cs_lines =3D g_new0(qemu_irq, s->cfg.num_cs); + qdev_init_gpio_out_named(dev, s->cs_lines, "cs", s->cfg.num_cs); + + fifo32_create(&s->tx_fifo, s->cfg.fifo_depth); + fifo32_create(&s->rx_fifo, s->cfg.fifo_depth); +} + +static void dwc_ssi_finalize(Object *obj) +{ + DwcSsiState *s =3D DWC_SSI(obj); + + fifo32_destroy(&s->tx_fifo); + fifo32_destroy(&s->rx_fifo); + g_free(s->cs_lines); +} + +static const Property dwc_ssi_properties[] =3D { + DEFINE_PROP_UINT32("num-cs", DwcSsiState, cfg.num_cs, 1), + DEFINE_PROP_UINT32("fifo-depth", DwcSsiState, cfg.fifo_depth, 256), + DEFINE_PROP_BOOL("master-mode", DwcSsiState, cfg.master_mode, true), +}; + +static void dwc_ssi_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + ResettableClass *rc =3D RESETTABLE_CLASS(klass); + + dc->realize =3D dwc_ssi_realize; + dc->vmsd =3D &vmstate_dwc_ssi; + device_class_set_props(dc, dwc_ssi_properties); + rc->phases.enter =3D dwc_ssi_enter_reset; + rc->phases.hold =3D dwc_ssi_hold_reset; + rc->phases.exit =3D dwc_ssi_exit_reset; +} + +static const TypeInfo dwc_ssi_info =3D { + .name =3D TYPE_DWC_SSI, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(DwcSsiState), + .instance_init =3D dwc_ssi_init, + .instance_finalize =3D dwc_ssi_finalize, + .class_init =3D dwc_ssi_class_init, +}; + +static void dwc_ssi_register_types(void) +{ + type_register_static(&dwc_ssi_info); +} + +type_init(dwc_ssi_register_types) diff --git a/hw/ssi/meson.build b/hw/ssi/meson.build index 6afb1ea200..e3776325ce 100644 --- a/hw/ssi/meson.build +++ b/hw/ssi/meson.build @@ -1,6 +1,7 @@ system_ss.add(when: 'CONFIG_ALLWINNER_A10_SPI', if_true: files('allwinner-= a10-spi.c')) system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_smc.c')) system_ss.add(when: 'CONFIG_MSF2', if_true: files('mss-spi.c')) +system_ss.add(when: 'CONFIG_DWC_SSI', if_true: files('dwc_ssi.c')) system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_fiu.c', 'npc= m_pspi.c')) system_ss.add(when: 'CONFIG_PL022', if_true: files('pl022.c')) system_ss.add(when: 'CONFIG_SIFIVE_SPI', if_true: files('sifive_spi.c')) diff --git a/include/hw/ssi/dwc_ssi.h b/include/hw/ssi/dwc_ssi.h new file mode 100644 index 0000000000..cc7ab40928 --- /dev/null +++ b/include/hw/ssi/dwc_ssi.h @@ -0,0 +1,80 @@ +/* + * Synopsys DesignWare SSI + * + * Copyright (c) 2026 Kangjie Huang + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Emulates the DesignWare SSI controller in Standard SPI mode, + * covering the PIO/FIFO data path, interrupt outputs and cs. + */ + +#ifndef HW_SSI_DWC_SSI_H +#define HW_SSI_DWC_SSI_H + +#include "hw/core/sysbus.h" +#include "hw/ssi/ssi.h" +#include "qemu/fifo32.h" +#include "qom/object.h" + +#define TYPE_DWC_SSI "dwc-ssi" +OBJECT_DECLARE_SIMPLE_TYPE(DwcSsiState, DWC_SSI) + +#define DWC_SSI_MMIO_SIZE 0x1000 +#define DWC_SSI_REGS_SIZE 0x14c +#define DWC_SSI_NUM_REGS \ + (DWC_SSI_REGS_SIZE / sizeof(uint32_t)) + +/* SSI GPIO output ordering differs from RISR/ISR bit ordering */ +typedef enum DwcSsiIrq { + DWC_SSI_IRQ_TXE, + DWC_SSI_IRQ_TXO, + DWC_SSI_IRQ_RXF, + DWC_SSI_IRQ_RXO, + DWC_SSI_IRQ_TXU, + DWC_SSI_IRQ_RXU, + DWC_SSI_IRQ_MST, + DWC_SSI_IRQ_DONE, + DWC_SSI_IRQ_AXIE, + DWC_SSI_IRQ_COUNT, +} DwcSsiIrq; + +typedef enum DwcSsiPhase { + DWC_SSI_PHASE_IDLE, + DWC_SSI_PHASE_STANDARD_TX_ONLY, + DWC_SSI_PHASE_RX_ONLY, + DWC_SSI_PHASE_EEPROM_COMMAND, + DWC_SSI_PHASE_EEPROM_DATA, + DWC_SSI_PHASE_STANDARD_TR, + DWC_SSI_PHASE_COUNT, +} DwcSsiPhase; + +typedef struct DwcSsiConfig { + uint32_t num_cs; + uint32_t fifo_depth; + bool master_mode; +} DwcSsiConfig; + +struct DwcSsiState { + SysBusDevice parent_obj; + + MemoryRegion mmio; + SSIBus *spi; + + qemu_irq *cs_lines; + qemu_irq irqs[DWC_SSI_IRQ_COUNT]; + + Fifo32 tx_fifo; + Fifo32 rx_fifo; + uint32_t regs[DWC_SSI_NUM_REGS]; + + DwcSsiConfig cfg; + + uint32_t irq_latched; + uint32_t phase; + uint32_t remaining_frames; + uint32_t dummy_frame; + + int active_cs; +}; +#endif /* HW_SSI_DWC_SSI_H */ --=20 2.43.0 From nobody Sat Sep 26 20:51:42 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1788174799; cv=none; d=zohomail.com; s=zohoarc; b=G/w71U/LjZol9AH33/B++Cyy2xHOM6Dl32iydOmAMifbuf+bmSkUMF3e7TPvy8t8tHSB4ZNi7tjeOj1yZt1BoXVo/IMB0UU9Dd28ORseVh0uI1gcHa2Wq+iHFcjsU51iO+1qHN5E4/1hlEswabjGkYkymMcncbernynLCN9S4cY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788174799; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=jAJJ4hsDhig6g2rKa3iQToV8WIMDpaQd8tesI1K/Ozc=; b=P50L4jjMiMNTH/5RwOHDj0b1GvKtjoSNeC7At5roWQ3+6MHW8tFdIOppo0IDCv+X/6jiHnMPYBtAt/JFbyNBHEazUTq3uejR/3Koylrs8CCbL5lFLxdicggV7g7hwL153QO+HLCbK6KU+OazrMd9esebffsCR0Z61Tdd0UsaXNc= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788174799282773.0208000087459; Mon, 31 Aug 2026 04:13:19 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0zwd-0001pz-Cg; Mon, 31 Aug 2026 07:12:55 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0zwc-0001pg-4G for qemu-devel@nongnu.org; Mon, 31 Aug 2026 07:12:54 -0400 Received: from mail-pf1-x42c.google.com ([2607:f8b0:4864:20::42c]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1x0zwY-0003Eb-Gn for qemu-devel@nongnu.org; Mon, 31 Aug 2026 07:12:53 -0400 Received: by mail-pf1-x42c.google.com with SMTP id d2e1a72fcca58-854f8068301so1949691b3a.0 for ; Mon, 31 Aug 2026 04:12:50 -0700 (PDT) Received: from LAPTOP-CS7CB8K1.localdomain ([2409:8900:7ad7:8121:d841:db7e:846b:ea5e]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-856a397d927sm3367329b3a.54.2026.08.31.04.12.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 04:12:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788174769; x=1788779569; darn=nongnu.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=jAJJ4hsDhig6g2rKa3iQToV8WIMDpaQd8tesI1K/Ozc=; b=C+uI5qJl3eA1ebFvTuIaiihnvQXnlN6Tzic2TkpbO3gw2MEe5uFkPb9l1/u67NqB/X jEI2wtg13oQBr7Ncl44slYKI9t0oLTdSNXVEZTHOVxoUVbbSyDM+pbj0QO3r3GvlFVK3 xp3s7zjlNZ5zUS8slWKXtQyJ+zAyYFxz0x/Rd7SR7KvPaHdzTXvUK36hbqS01jWNNwjf 5whauERTO/1HxRX6JiKIPZvdzKq7ENUN0+fwU/cKBYAoTOxucHB7FF9GTuNuCllq+4Iv EWTX7dtuCggjn/e4P+uhmBiPDNdqfQfL90eS0iXWnmoPTOtsoHUvlVNTqGXNHogmJoMy okoQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788174769; x=1788779569; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=jAJJ4hsDhig6g2rKa3iQToV8WIMDpaQd8tesI1K/Ozc=; b=jVjdAabx2xTzziMaZzh2r0VMaJVapCgiDrQ0GBXeHIIgrGawqAsG2U/VzNhMX34rwB unQihe7WAmpwU/dTy5jz+aq6t89smw8vHcKeynxETcVyuLtKPgwMP48A8omSUlgxMwWn V5bvDvHoRz9p/R1eDQZq42Lo1uZGg/pkdZ6+6hhlzoWI/88sdUm9XN93zy0Hkffkqzjn I1N/8DJvsAzUOwjEXYULPwOsiHSLKhir1yWlwxTtHGfDaKHjpv3Zk0aE+i9OxtfvwmYN mIEmcsLc12Rz0JR7mU7SyCre5mpLzbTblS/IIfKuG4anDAPzprXEiNhboq65wt4/DMkA +PjA== X-Gm-Message-State: AFuF++njmDYOskqyIrz2i5H8CWvIGsS69iClmzt7Ip/XhcccTMUsbxCD 5gtw84ZyGGCpdQYm32LLCoDdzTwSXbXYYNC7MqFYloeYbFEoWmI+e59f X-Gm-Gg: AR+sD12RbELi/Go+GPZKTpw9ppIB69BX0e5IekmaoMdfpldqxNujcbYCAx8QgieUhLo vbfoiJ8c4HsbT0/XUh0HZrqJosX0eCv3t4gs90AQNhLR2bEo6sHdsHFJ9H0lzl5EMZ1xSIAbJQH +gXZe9bOBTJULSCtmo9TMMkYLugF7p4sWc+KJMYB91JTHNJrcfdACkYiNgRy5/V3lO+Gqw+WbdI 4wgdjt+eG+hXJYTRXDeO5kozvPOdwZ7bk2rZ6prSP3VWH42/KJ5N2Eoed8W6/IYAVkh2wN0aPD4 M/dcBZiDzPbQ0r9pihHgyiH/UP7LfsnH2dWPHkxFmirE4J2g5zgUmVkPjTojewN8bPelrIdxIER 6fFQRtzbxE4wgyb6J3P8GZy99LEEf0UxXwKWcwolYuoJi6WyI8auvFUCTBfWp6XDnRR/g615lpr 2k6lApc0aNIacJnDIdnpDMS9JCf03QimGrejrMfMln6bZYJphVC/9Cta/2UOzMVdTOjim8sJfSa 03sEuwTHrGMYfH022WY0DIQRw== X-Received: by 2002:a05:6a00:2988:b0:857:727c:a1f7 with SMTP id d2e1a72fcca58-857727ca3f7mr27704618b3a.25.1788174768806; Mon, 31 Aug 2026 04:12:48 -0700 (PDT) From: Kangjie Huang Date: Mon, 31 Aug 2026 19:11:13 +0800 Subject: [PATCH v4 2/3] hw/riscv/k230: Instantiate DWC SSI controllers and route IRQs to PLIC MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260831-t_v4_spi_patch-v4-2-370d416f26b5@gmail.com> References: <20260831-t_v4_spi_patch-v4-0-370d416f26b5@gmail.com> In-Reply-To: <20260831-t_v4_spi_patch-v4-0-370d416f26b5@gmail.com> To: qemu-devel@nongnu.org, qemu-riscv@nongnu.org Cc: Portia Stephens , Bin Meng , Chao Liu , Alistair Francis , Alistair Francis , Palmer Dabbelt , Weiwei Li , Daniel Henrique Barboza , Liu Zhiwei , Pierrick Bouvier , Paolo Bonzini , Fabiano Rosas , Laurent Vivier , Anirudh Srinivasan , Kangjie Huang X-Mailer: b4 0.13.0 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::42c; envelope-from=flamboyant.h.01@gmail.com; helo=mail-pf1-x42c.google.com X-Spam_score_int: -17 X-Spam_score: -1.8 X-Spam_bar: - X-Spam_report: (-1.8 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1788174803254154100 Instantiate the three DWC SSI profiles used on the K230 and map each controller's MMIO region at the documented address. Configure the chip-select count, FIFO depth and master-mode setting used to derive the IMR reset value for the QSPI0, QSPI1 and SPI-OPI instances. Wire the nine interrupt outputs of each K230 DWC SSI controller to the PLIC source range given in the documentation. The TXU, DONE and AXIE lines are wired to preserve the physical K230 interrupt topology but remain inactive until their transfer engines are modelled. Add a K230 SSI qtest covering the three instance profiles, interrupt routing and instance isolation. Signed-off-by: Kangjie Huang --- MAINTAINERS | 1 + docs/system/riscv/k230.rst | 1 + hw/riscv/Kconfig | 1 + hw/riscv/k230.c | 99 ++++++- include/hw/riscv/k230.h | 5 + tests/qtest/k230-dwc-ssi-test.c | 637 ++++++++++++++++++++++++++++++++++++= ++++ tests/qtest/meson.build | 3 +- 7 files changed, 737 insertions(+), 10 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index a5e76934b6..832348bd51 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1877,6 +1877,7 @@ F: tests/qtest/k230-ddr-test.c F: tests/qtest/k230-wdt-test.c F: tests/qtest/k230-gsdma-test.c F: tests/qtest/k230-decomp-gzip-test.c +F: tests/qtest/k230-dwc-ssi-test.c =20 RX Machines ----------- diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst index 237611eddf..a7f7a494cd 100644 --- a/docs/system/riscv/k230.rst +++ b/docs/system/riscv/k230.rst @@ -23,6 +23,7 @@ The ``k230`` machine supports the following devices: * K230 DDRC CFG and DDR PHY * System Direct Memory Access (SDMA) * GZIP Decompress Engine (Decomp_gzip) +* 3 DWC SSI controllers (Standard SPI mode) =20 Boot options ------------ diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index d06ac26648..f348da6d20 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -164,3 +164,4 @@ config K230 select K230_WDT select K230_GSDMA select K230_DECOMP_GZIP + select DWC_SSI diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index 558f30b97e..eb40b53ba8 100644 --- a/hw/riscv/k230.c +++ b/hw/riscv/k230.c @@ -103,6 +103,55 @@ static const MemMapEntry memmap[] =3D { [K230_DEV_CLINT] =3D { 0xF04000000, 0x00400000 }, }; =20 +typedef struct K230SsiRoute { + unsigned int ssi_index; + unsigned int irq_base; +} K230SsiRoute; + +/* + * SDK numbering follows the address map: spi0 is the SPI-OPI instance, + * while spi1 and spi2 are QSPI0 and QSPI1. + */ +static const K230SsiRoute k230_ssi_routes[] =3D { + { .ssi_index =3D 2, .irq_base =3D K230_SPI0_IRQ_BASE }, + { .ssi_index =3D 0, .irq_base =3D K230_SPI1_IRQ_BASE }, + { .ssi_index =3D 1, .irq_base =3D K230_SPI2_IRQ_BASE }, +}; + +typedef struct K230DwcSsiProfile { + uint32_t num_cs; + uint32_t fifo_depth; + bool master_mode; +} K230DwcSsiProfile; + +static const K230DwcSsiProfile k230_dwc_ssi_profiles[] =3D { + [0] =3D { /* QSPI0, SDK spi1 */ + .num_cs =3D 5, + .fifo_depth =3D 256, + .master_mode =3D true, + }, + [1] =3D { /* QSPI1, SDK spi2 */ + .num_cs =3D 5, + .fifo_depth =3D 256, + .master_mode =3D true, + }, + [2] =3D { /* SPI-OPI/FMC, SDK spi0 */ + .num_cs =3D 1, + .fifo_depth =3D 256, + .master_mode =3D true, + }, +}; + +static void k230_configure_dwc_ssi(DwcSsiState *ssi, + const K230DwcSsiProfile *profile) +{ + DeviceState *dev =3D DEVICE(ssi); + + qdev_prop_set_uint32(dev, "num-cs", profile->num_cs); + qdev_prop_set_uint32(dev, "fifo-depth", profile->fifo_depth); + qdev_prop_set_bit(dev, "master-mode", profile->master_mode); +} + static void k230_soc_init(Object *obj) { K230SoCState *s =3D RISCV_K230_SOC(obj); @@ -119,6 +168,12 @@ static void k230_soc_init(Object *obj) object_initialize_child(obj, "k230-gsdma", &s->gsdma, TYPE_K230_GSDMA); object_initialize_child(obj, "k230-decomp-gzip", &s->decomp_gzip, TYPE_K230_DECOMP_GZIP); + object_initialize_child(obj, "k230-qspi0", &s->dwc_ssi[0], + TYPE_DWC_SSI); + object_initialize_child(obj, "k230-qspi1", &s->dwc_ssi[1], + TYPE_DWC_SSI); + object_initialize_child(obj, "k230-spi-opi", &s->dwc_ssi[2], + TYPE_DWC_SSI); =20 qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0); qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C9= 08); @@ -160,6 +215,25 @@ static void k230_create_uart(MemoryRegion *sys_mem, De= viceState *plic, 399193, serial_hd(index), DEVICE_LITTLE_ENDIAN); } =20 +static void k230_connect_ssi_irqs(K230SoCState *s) +{ + for (size_t route_idx =3D 0; + route_idx < ARRAY_SIZE(k230_ssi_routes); route_idx++) { + const K230SsiRoute *route =3D &k230_ssi_routes[route_idx]; + SysBusDevice *ssi; + + g_assert(route->ssi_index < ARRAY_SIZE(s->dwc_ssi)); + g_assert(route->irq_base + DWC_SSI_IRQ_COUNT <=3D + K230_PLIC_NUM_SOURCES); + + ssi =3D SYS_BUS_DEVICE(&s->dwc_ssi[route->ssi_index]); + for (unsigned int i =3D 0; i < DWC_SSI_IRQ_COUNT; i++) { + sysbus_connect_irq(ssi, i, + qdev_get_gpio_in(DEVICE(s->c908_plic), route->irq_base + i= )); + } + } +} + static void k230_soc_realize(DeviceState *dev, Error **errp) { K230SoCState *s =3D RISCV_K230_SOC(dev); @@ -217,6 +291,15 @@ static void k230_soc_realize(DeviceState *dev, Error *= *errp) } } =20 + for (int i =3D 0; i < ARRAY_SIZE(s->dwc_ssi); i++) { + k230_configure_dwc_ssi(&s->dwc_ssi[i], &k230_dwc_ssi_profiles[i]); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->dwc_ssi[i]), errp)) { + return; + } + } + + k230_connect_ssi_irqs(s); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt[0]), 0, memmap[K230_DEV_WDT0].b= ase); sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[0]), 0, qdev_get_gpio_in(DEVICE(s->c908_plic), K230_WDT0_IR= Q)); @@ -253,6 +336,13 @@ static void k230_soc_realize(DeviceState *dev, Error *= *errp) /* Noc stub, the k230 decomp_gzip driver in uboot will access this reg= ion */ create_unimplemented_device("noc-stub", 0x91302000, 0x1000); =20 + sysbus_mmio_map(SYS_BUS_DEVICE(&s->dwc_ssi[0]), 0, + memmap[K230_DEV_QSPI0].base); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->dwc_ssi[1]), 0, + memmap[K230_DEV_QSPI1].base); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->dwc_ssi[2]), 0, + memmap[K230_DEV_SPI].base); + /* unimplemented devices */ create_unimplemented_device("kpu.l2-cache", memmap[K230_DEV_KPU_L2_CACHE].base, @@ -389,15 +479,6 @@ static void k230_soc_realize(DeviceState *dev, Error *= *errp) create_unimplemented_device("sd1", memmap[K230_DEV_SD1].base, memmap[K230_DEV_SD1].size); =20 - create_unimplemented_device("qspi0", memmap[K230_DEV_QSPI0].base, - memmap[K230_DEV_QSPI0].size); - - create_unimplemented_device("qspi1", memmap[K230_DEV_QSPI1].base, - memmap[K230_DEV_QSPI1].size); - - create_unimplemented_device("spi", memmap[K230_DEV_SPI].base, - memmap[K230_DEV_SPI].size); - create_unimplemented_device("hi_sys_cfg", memmap[K230_DEV_HI_SYS_CFG].= base, memmap[K230_DEV_HI_SYS_CFG].size); =20 diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h index 8679d09891..fdabf454be 100644 --- a/include/hw/riscv/k230.h +++ b/include/hw/riscv/k230.h @@ -18,6 +18,7 @@ #include "hw/core/boards.h" #include "hw/misc/k230_ddr.h" #include "hw/riscv/riscv_hart.h" +#include "hw/ssi/dwc_ssi.h" #include "hw/watchdog/k230_wdt.h" #include "hw/dma/k230_gsdma.h" #include "hw/misc/k230_decomp_gzip.h" @@ -40,6 +41,7 @@ typedef struct K230SoCState { K230WdtState wdt[2]; K230GSDMAState gsdma; K230DecompGzipState decomp_gzip; + DwcSsiState dwc_ssi[3]; MemoryRegion sram; MemoryRegion bootrom; =20 @@ -138,6 +140,9 @@ enum { K230_WDT0_IRQ =3D 107, K230_WDT1_IRQ =3D 108, K230_GSDMA_IRQ =3D 140, + K230_SPI0_IRQ_BASE =3D 146, + K230_SPI1_IRQ_BASE =3D 155, + K230_SPI2_IRQ_BASE =3D 164, }; =20 #define K230_UART_COUNT 5 diff --git a/tests/qtest/k230-dwc-ssi-test.c b/tests/qtest/k230-dwc-ssi-tes= t.c new file mode 100644 index 0000000000..42e3190cb4 --- /dev/null +++ b/tests/qtest/k230-dwc-ssi-test.c @@ -0,0 +1,637 @@ +/* + * QTest for the Kendryte K230 DWC SSI + * + * Copyright (c) 2026 Kangjie Huang + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/bitops.h" +#include "qemu/units.h" + +#define K230_SPI0_BASE 0x91584000ULL +#define K230_SPI1_BASE 0x91582000ULL +#define K230_SPI2_BASE 0x91583000ULL +#define K230_PLIC_BASE 0xf00000000ULL +#define K230_PLIC_PENDING_BASE 0x1000 +#define K230_SSI_CTRLR0 0x000 +#define K230_SSI_CTRLR1 0x004 +#define K230_SSI_SSIENR 0x008 +#define K230_SSI_MWCR 0x00c +#define K230_SSI_SER 0x010 +#define K230_SSI_BAUDR 0x014 +#define K230_SSI_TXFTLR 0x018 +#define K230_SSI_TXFLR 0x020 +#define K230_SSI_RXFLR 0x024 +#define K230_SSI_SR 0x028 +#define K230_SSI_IMR 0x02c +#define K230_SSI_ISR 0x030 +#define K230_SSI_RISR 0x034 +#define K230_SSI_RXUICR 0x040 +#define K230_SSI_IDR 0x058 +#define K230_SSI_VERSION_ID 0x05c +#define K230_SSI_DR0 0x060 +#define K230_SSI_SPI_CTRLR0 0x0f4 +#define K230_SSI_AXIECR 0x130 +#define K230_SSI_DONECR 0x134 + +#define K230_SSI_CTRLR0_RESET 0x00004007U +#define K230_SSI_IDR_RESET 0xa1b2c3d5U +#define K230_SSI_VERSION_RESET 0x3130332aU + +#define K230_SSI_CTRLR0_WRITABLE_MASK 0x000f7f1fU +#define K230_SSI_BAUDR_WRITABLE_MASK 0x0000fffeU + +#define K230_SSI_CTRLR0_DFS_MASK 0x1fU +#define K230_SSI_CTRLR0_TMOD_SHIFT 10 +#define K230_SSI_CTRLR0_SRL BIT(13) +#define K230_SSI_CTRLR0_SPI_FRF_MASK (3U << 22) +#define K230_SSI_MWCR_WRITABLE_MASK 0x00000007U +#define K230_SSI_TXFTLR_TXFTHR_SHIFT 16 +#define K230_SSI_TMOD_TR 0 +#define K230_SSI_TMOD_TO 1 +#define K230_SSI_TMOD_RO 2 +#define K230_SSI_TMOD_EEPROM_READ 3 + +#define K230_SSI_SR_BUSY BIT(0) +#define K230_SSI_SR_TFNF BIT(1) +#define K230_SSI_SR_TFE BIT(2) +#define K230_SSI_SR_RFNE BIT(3) + +#define K230_SSI_INT_TXE BIT(0) +#define K230_SSI_INT_TXO BIT(1) +#define K230_SSI_INT_RXO BIT(3) +#define K230_SSI_INT_RXU BIT(2) +#define K230_SSI_INT_RXF BIT(4) +#define K230_SSI_INT_DONE BIT(11) +#define K230_SSI_INT_AXIE BIT(8) +#define K230_SSI_IRQ_TXE 0 +#define K230_SSI_IRQ_RXU 5 +#define K230_SSI_IRQ_DONE 7 +#define K230_SSI_IRQ_AXIE 8 + +#define K230_SSI_RXFTLR 0x01c +#define K230_SSI_RXOICR 0x03c +#define K230_SSI_TXEICR 0x038 +#define K230_SSI_ICR 0x048 +#define K230_SSI_DMACR 0x04c +#define K230_SSI_XIP_MODE_BITS 0x0fc + +#define K230_SSI_FIFO_DEPTH 256 + +typedef struct K230SsiInstance { + uint64_t base; + uint32_t num_cs; + uint32_t imr_reset; + uint32_t first_irq; +} K230SsiInstance; + +static const K230SsiInstance k230_ssi_instances[3] =3D { + { + .base =3D K230_SPI0_BASE, + .num_cs =3D 1, + .imr_reset =3D 0x0000003fU, + .first_irq =3D 146, + }, { + .base =3D K230_SPI1_BASE, + .num_cs =3D 5, + .imr_reset =3D 0x0000003fU, + .first_irq =3D 155, + }, { + .base =3D K230_SPI2_BASE, + .num_cs =3D 5, + .imr_reset =3D 0x0000003fU, + .first_irq =3D 164, + }, +}; + +static QTestState *k230_ssi_start(void) +{ + return qtest_init("-machine k230"); +} + +static uint32_t k230_ssi_readl(QTestState *qts, uint64_t base, + uint32_t offset) +{ + return qtest_readl(qts, base + offset); +} + +static void k230_ssi_writel(QTestState *qts, uint64_t base, + uint32_t offset, uint32_t value) +{ + qtest_writel(qts, base + offset, value); +} + +static bool k230_ssi_plic_pending(QTestState *qts, uint32_t irq) +{ + uint64_t addr =3D K230_PLIC_BASE + K230_PLIC_PENDING_BASE + + (irq / 32) * sizeof(uint32_t); + + return qtest_readl(qts, addr) & BIT(irq % 32); +} + +static void k230_ssi_disable(QTestState *qts, uint64_t base) +{ + k230_ssi_writel(qts, base, K230_SSI_SSIENR, 0); +} + +static void k230_ssi_configure(QTestState *qts, uint64_t base, + uint32_t tmod, uint32_t dfs_bits, + uint32_t ndf) +{ + uint32_t ctrlr0; + + g_assert_cmpuint(dfs_bits, >=3D, 4); + g_assert_cmpuint(dfs_bits, <=3D, 32); + g_assert_cmpuint(tmod, <=3D, K230_SSI_TMOD_EEPROM_READ); + + k230_ssi_disable(qts, base); + ctrlr0 =3D (dfs_bits - 1) & K230_SSI_CTRLR0_DFS_MASK; + ctrlr0 |=3D tmod << K230_SSI_CTRLR0_TMOD_SHIFT; + k230_ssi_writel(qts, base, K230_SSI_CTRLR0, ctrlr0); + k230_ssi_writel(qts, base, K230_SSI_CTRLR1, ndf); + k230_ssi_writel(qts, base, K230_SSI_BAUDR, 2); +} + +static void k230_ssi_enable_cs(QTestState *qts, uint64_t base, uint32_t se= r) +{ + k230_ssi_writel(qts, base, K230_SSI_SER, ser); + k230_ssi_writel(qts, base, K230_SSI_SSIENR, 1); +} + +static void k230_ssi_write_frame(QTestState *qts, uint64_t base, + uint32_t value) +{ + k230_ssi_writel(qts, base, K230_SSI_DR0, value); +} + +static uint32_t k230_ssi_read_frame(QTestState *qts, uint64_t base) +{ + return k230_ssi_readl(qts, base, K230_SSI_DR0); +} + +static void k230_ssi_wait_mask(QTestState *qts, uint64_t base, + uint32_t offset, uint32_t mask, + uint32_t expected) +{ + for (int i =3D 0; i < 1000; i++) { + uint32_t value =3D k230_ssi_readl(qts, base, offset); + + if ((value & mask) =3D=3D expected) { + return; + } + qtest_clock_step(qts, 1000); + } + + g_assert_cmphex(k230_ssi_readl(qts, base, offset) & mask, + =3D=3D, expected); +} + +static void configure_loopback(QTestState *qts, uint32_t tmod, + uint32_t ndf) +{ + uint32_t ctrlr0; + + k230_ssi_configure(qts, K230_SPI1_BASE, tmod, 8, ndf); + ctrlr0 =3D k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_CTRLR0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_CTRLR0, + ctrlr0 | K230_SSI_CTRLR0_SRL); +} + +static void test_register_contract(void) +{ + QTestState *qts =3D k230_ssi_start(); + + for (int i =3D 0; i < ARRAY_SIZE(k230_ssi_instances); i++) { + const K230SsiInstance *inst =3D &k230_ssi_instances[i]; + + g_assert_cmphex(k230_ssi_readl(qts, inst->base, K230_SSI_CTRLR0), + =3D=3D, K230_SSI_CTRLR0_RESET); + g_assert_cmphex(k230_ssi_readl(qts, inst->base, K230_SSI_SSIENR), + =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, inst->base, + K230_SSI_SPI_CTRLR0), + =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, inst->base, K230_SSI_IMR), + =3D=3D, inst->imr_reset); + k230_ssi_writel(qts, inst->base, K230_SSI_SER, UINT32_MAX); + g_assert_cmphex(k230_ssi_readl(qts, inst->base, K230_SSI_SER), + =3D=3D, MAKE_64BIT_MASK(0, inst->num_cs)); + } + + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI0_BASE, K230_SSI_IDR), + =3D=3D, K230_SSI_IDR_RESET); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI0_BASE, + K230_SSI_VERSION_ID), + =3D=3D, K230_SSI_VERSION_RESET); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_CTRLR0, UINT32_MAX); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_CTRLR0), + =3D=3D, K230_SSI_CTRLR0_WRITABLE_MASK); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_CTRLR0, + K230_SSI_CTRLR0_RESET | K230_SSI_CTRLR0_SPI_FRF_MASK); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_CTRLR0) & + K230_SSI_CTRLR0_SPI_FRF_MASK, =3D=3D, 0); + /* SSTE is R/W per TRM; the frame-boundary toggle is not modelled. */ + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_CTRLR0, + K230_SSI_CTRLR0_RESET & ~BIT(14)); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_CTRLR0) & + BIT(14), =3D=3D, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_BAUDR, UINT32_MAX); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_BAUDR), + =3D=3D, K230_SSI_BAUDR_WRITABLE_MASK); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_MWCR, UINT32_MAX); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_MWCR), + =3D=3D, K230_SSI_MWCR_WRITABLE_MASK); + + qtest_system_reset(qts); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI0_BASE, K230_SSI_CTRLR0), + =3D=3D, K230_SSI_CTRLR0_RESET); + qtest_quit(qts); +} + +static void test_pio_data_path(void) +{ + QTestState *qts =3D k230_ssi_start(); + uint32_t status; + + configure_loopback(qts, K230_SSI_TMOD_TR, 0); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0xa5); + k230_ssi_wait_mask(qts, K230_SPI1_BASE, K230_SSI_SR, + K230_SSI_SR_RFNE, K230_SSI_SR_RFNE); + g_assert_cmphex(k230_ssi_read_frame(qts, K230_SPI1_BASE), =3D=3D, 0xa5= ); + + configure_loopback(qts, K230_SSI_TMOD_RO, 3); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0xa5); + k230_ssi_wait_mask(qts, K230_SPI1_BASE, K230_SSI_RXFLR, + UINT32_MAX, 4); + for (int i =3D 0; i < 4; i++) { + g_assert_cmphex(k230_ssi_read_frame(qts, K230_SPI1_BASE), =3D=3D, = 0xa5); + } + + k230_ssi_disable(qts, K230_SPI1_BASE); + status =3D k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_SR); + g_assert_cmpuint(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + =3D=3D, 0); + g_assert_cmphex(status & (K230_SSI_SR_BUSY | K230_SSI_SR_TFNF | + K230_SSI_SR_TFE | K230_SSI_SR_RFNE), + =3D=3D, K230_SSI_SR_TFNF | K230_SSI_SR_TFE); + qtest_quit(qts); +} + +static void test_interrupt_controller(void) +{ + QTestState *qts =3D k230_ssi_start(); + + k230_ssi_configure(qts, K230_SPI1_BASE, K230_SSI_TMOD_TR, 8, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_TXFTLR, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_IMR, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SSIENR, 1); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_TXE, =3D=3D, K230_SSI_INT_TXE); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_ISR) & + K230_SSI_INT_TXE, =3D=3D, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_IMR, + K230_SSI_INT_TXE); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_ISR) & + K230_SSI_INT_TXE, =3D=3D, K230_SSI_INT_TXE); + + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_IMR, + K230_SSI_INT_RXU); + (void)k230_ssi_read_frame(qts, K230_SPI1_BASE); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXU, =3D=3D, K230_SSI_INT_RXU); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_ISR) & + K230_SSI_INT_RXU, =3D=3D, K230_SSI_INT_RXU); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXUICR), + =3D=3D, 1); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXU, =3D=3D, 0); + qtest_quit(qts); +} + +static void test_plic_routing(void) +{ + QTestState *qts =3D k230_ssi_start(); + const K230SsiInstance *target =3D &k230_ssi_instances[1]; + + for (int i =3D 0; i < ARRAY_SIZE(k230_ssi_instances); i++) { + const K230SsiInstance *inst =3D &k230_ssi_instances[i]; + + g_assert_true(k230_ssi_plic_pending(qts, + inst->first_irq + + K230_SSI_IRQ_TXE)); + k230_ssi_writel(qts, inst->base, K230_SSI_IMR, 0); + g_assert_false(k230_ssi_plic_pending(qts, inst->first_irq + + K230_SSI_IRQ_DONE)); + g_assert_false(k230_ssi_plic_pending(qts, inst->first_irq + + K230_SSI_IRQ_AXIE)); + } + + k230_ssi_writel(qts, target->base, K230_SSI_IMR, K230_SSI_INT_RXU); + (void)k230_ssi_read_frame(qts, target->base); + g_assert_true(k230_ssi_plic_pending(qts, + target->first_irq + + K230_SSI_IRQ_RXU)); + for (int i =3D 0; i < ARRAY_SIZE(k230_ssi_instances); i++) { + if (&k230_ssi_instances[i] !=3D target) { + g_assert_false(k230_ssi_plic_pending( + qts, k230_ssi_instances[i].first_irq + K230_SSI_IRQ_RXU)); + } + } + qtest_quit(qts); +} + +static void test_tx_only_mode(void) +{ + QTestState *qts =3D k230_ssi_start(); + + configure_loopback(qts, K230_SSI_TMOD_TO, 0); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0x55); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_SR) & + K230_SSI_SR_TFE, =3D=3D, K230_SSI_SR_TFE); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_TXFLR), + =3D=3D, 0); + + configure_loopback(qts, K230_SSI_TMOD_TO, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_TXFTLR, + (K230_SSI_FIFO_DEPTH - 1) << + K230_SSI_TXFTLR_TXFTHR_SHIFT); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SER, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SSIENR, 1); + for (int i =3D 0; i < K230_SSI_FIFO_DEPTH; i++) { + k230_ssi_write_frame(qts, K230_SPI1_BASE, i); + } + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SER, BIT(0)); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_TXFLR), + =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_SR) & + K230_SSI_SR_BUSY, =3D=3D, 0); + qtest_quit(qts); +} + +/* + * An interrupt-driven guest waits for TXE without reading TXFLR or SR. + * The transfer must already be complete by the time the last frame is + * queued, since the FIFO drains synchronously on DR writes. + */ +static void test_tx_only_irq_ready(void) +{ + QTestState *qts =3D k230_ssi_start(); + + configure_loopback(qts, K230_SSI_TMOD_TO, 0); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + for (int i =3D 0; i < 8; i++) { + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0x11 * (i + 1)); + } + + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_TXFLR), + =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_ISR) & + K230_SSI_INT_TXE, =3D=3D, K230_SSI_INT_TXE); + qtest_quit(qts); +} + +static void test_eeprom_read_contract(void) +{ + QTestState *qts =3D k230_ssi_start(); + const int frames =3D K230_SSI_FIFO_DEPTH + 4; + + configure_loopback(qts, K230_SSI_TMOD_EEPROM_READ, 3); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SSIENR, 1); + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0x03); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SER, BIT(0)); + /* EEPROM data must start without a status read driving the engine. */ + k230_ssi_wait_mask(qts, K230_SPI1_BASE, K230_SSI_ISR, + K230_SSI_INT_RXF, K230_SSI_INT_RXF); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + =3D=3D, 4); + for (int i =3D 0; i < 4; i++) { + g_assert_cmphex(k230_ssi_read_frame(qts, K230_SPI1_BASE), =3D=3D, = 0); + } + + configure_loopback(qts, K230_SSI_TMOD_EEPROM_READ, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SSIENR, 1); + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0x03); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SER, BIT(0)); + k230_ssi_wait_mask(qts, K230_SPI1_BASE, K230_SSI_RXFLR, + UINT32_MAX, 1); + g_assert_cmphex(k230_ssi_read_frame(qts, K230_SPI1_BASE), =3D=3D, 0); + + configure_loopback(qts, K230_SSI_TMOD_EEPROM_READ, frames - 1); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SSIENR, 1); + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0x03); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SER, BIT(0)); + k230_ssi_wait_mask(qts, K230_SPI1_BASE, K230_SSI_RXFLR, + UINT32_MAX, K230_SSI_FIFO_DEPTH); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXO, =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_SR) & + K230_SSI_SR_BUSY, =3D=3D, K230_SSI_SR_BUSY); + g_assert_cmphex(k230_ssi_read_frame(qts, K230_SPI1_BASE), =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + =3D=3D, K230_SSI_FIFO_DEPTH); + for (int i =3D 1; i < frames; i++) { + g_assert_cmphex(k230_ssi_read_frame(qts, K230_SPI1_BASE), =3D=3D, = 0); + } + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXO, =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_SR) & + K230_SSI_SR_BUSY, =3D=3D, 0); + qtest_quit(qts); +} + +static void test_rx_fifo_overflow(void) +{ + QTestState *qts =3D k230_ssi_start(); + + configure_loopback(qts, K230_SSI_TMOD_TR, 0); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + for (int i =3D 0; i < K230_SSI_FIFO_DEPTH; i++) { + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0xa5); + } + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_TXFTLR, + (K230_SSI_FIFO_DEPTH - 1) << + K230_SSI_TXFTLR_TXFTHR_SHIFT); + for (int i =3D 0; i < K230_SSI_FIFO_DEPTH; i++) { + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0xa5); + } + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXO, =3D=3D, K230_SSI_INT_RXO); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_TXFLR), + =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_SR) & + K230_SSI_SR_BUSY, =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + =3D=3D, K230_SSI_FIFO_DEPTH); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, + K230_SSI_RXOICR), =3D=3D, 1); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXO, =3D=3D, 0); + + configure_loopback(qts, K230_SSI_TMOD_RO, K230_SSI_FIFO_DEPTH + 3); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_TXFTLR, 0); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0xa5); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXO, =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_SR) & + K230_SSI_SR_BUSY, =3D=3D, K230_SSI_SR_BUSY); + g_assert_cmphex(k230_ssi_read_frame(qts, K230_SPI1_BASE), =3D=3D, 0xa5= ); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + =3D=3D, K230_SSI_FIFO_DEPTH); + for (int i =3D 1; i < K230_SSI_FIFO_DEPTH + 4; i++) { + g_assert_cmphex(k230_ssi_read_frame(qts, K230_SPI1_BASE), =3D=3D, = 0xa5); + } + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXO, =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_SR) & + K230_SSI_SR_BUSY, =3D=3D, 0); + qtest_quit(qts); +} + +static void test_txfthr_start_gate(void) +{ + QTestState *qts =3D k230_ssi_start(); + + /* TXFTHR=3D4: transfer starts only once 5+ frames are queued. */ + k230_ssi_configure(qts, K230_SPI1_BASE, K230_SSI_TMOD_TR, 8, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_TXFTLR, 4 << 16); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0x11); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_TXFLR), + =3D=3D, 1); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + =3D=3D, 0); + + for (int i =3D 0; i < 4; i++) { + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0x22 + i); + } + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_TXFLR), + =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + =3D=3D, 5); + qtest_quit(qts); +} + +static void test_baudr_zero(void) +{ + QTestState *qts =3D k230_ssi_start(); + + /* SCKDV=3D0 disables sclk_out: queued frames must not be shifted out.= */ + k230_ssi_configure(qts, K230_SPI1_BASE, K230_SSI_TMOD_TR, 8, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_BAUDR, 0); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0xa5); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_TXFLR), + =3D=3D, 1); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + =3D=3D, 0); + qtest_quit(qts); +} + +static void test_ser_terminates_ro(void) +{ + QTestState *qts =3D k230_ssi_start(); + + /* + * RO transfer with NDF larger than the RX FIFO: after the FIFO + * fills, SER=3D0 must terminate the pending transaction. Re-selecting + * the slave must not resume receiving the stale remaining frames + * without a new dummy word. + */ + k230_ssi_configure(qts, K230_SPI1_BASE, K230_SSI_TMOD_RO, 8, 0xffff); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0); + k230_ssi_wait_mask(qts, K230_SPI1_BASE, K230_SSI_RXFLR, + UINT32_MAX, K230_SSI_FIFO_DEPTH); + + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SER, 0); + (void)k230_ssi_read_frame(qts, K230_SPI1_BASE); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + =3D=3D, K230_SSI_FIFO_DEPTH - 1); + + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SER, BIT(0)); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + =3D=3D, K230_SSI_FIFO_DEPTH - 1); + + /* A direct BIT(0) -> BIT(1) switch must also terminate the tx. */ + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SER, BIT(1)); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + =3D=3D, K230_SSI_FIFO_DEPTH - 1); + qtest_quit(qts); +} + +static void test_icr_total_clear(void) +{ + QTestState *qts =3D k230_ssi_start(); + + configure_loopback(qts, K230_SSI_TMOD_TR, 0); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + (void)k230_ssi_read_frame(qts, K230_SPI1_BASE); /* latch RXU */ + for (int i =3D 0; i < K230_SSI_FIFO_DEPTH + 1; i++) { + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0xa5); + } + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXU, =3D=3D, K230_SSI_INT_RXU); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, + K230_SSI_ICR), =3D=3D, 1); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXU, =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXO, =3D=3D, 0); + qtest_quit(qts); +} + + + +static void test_unsupported_registers(void) +{ + QTestState *qts =3D k230_ssi_start(); + static const uint32_t unsupported[] =3D { + K230_SSI_DMACR, K230_SSI_XIP_MODE_BITS, K230_SSI_SPI_CTRLR0, + }; + + for (size_t i =3D 0; i < ARRAY_SIZE(unsupported); i++) { + uint32_t off =3D unsupported[i]; + + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, off), =3D=3D, = 0); + k230_ssi_writel(qts, K230_SPI1_BASE, off, UINT32_MAX); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, off), =3D=3D, = 0); + } + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/k230-dwc-ssi/register-contract", test_register_contra= ct); + qtest_add_func("/k230-dwc-ssi/pio-data-path", test_pio_data_path); + qtest_add_func("/k230-dwc-ssi/interrupt-controller", + test_interrupt_controller); + qtest_add_func("/k230-dwc-ssi/plic-routing", test_plic_routing); + qtest_add_func("/k230-dwc-ssi/tx-only-mode", test_tx_only_mode); + qtest_add_func("/k230-dwc-ssi/tx-only-irq-ready", + test_tx_only_irq_ready); + qtest_add_func("/k230-dwc-ssi/eeprom-read-contract", + test_eeprom_read_contract); + qtest_add_func("/k230-dwc-ssi/rx-fifo-overflow", test_rx_fifo_overflow= ); + qtest_add_func("/k230-dwc-ssi/txfthr-start-gate", test_txfthr_start_ga= te); + qtest_add_func("/k230-dwc-ssi/baudr-zero", test_baudr_zero); + qtest_add_func("/k230-dwc-ssi/ser-terminates-ro", test_ser_terminates_= ro); + qtest_add_func("/k230-dwc-ssi/icr-total-clear", test_icr_total_clear); + qtest_add_func("/k230-dwc-ssi/unsupported-registers", + test_unsupported_registers); + return g_test_run(); +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index cbdef5a545..1cfff1471a 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -300,7 +300,8 @@ qtests_riscv64 =3D ['riscv-csr-test'] + \ config_all_devices.has_key('CONFIG_RISCV_IOMMU') ? ['iommu-riscv-test'] : []) + \ (config_all_devices.has_key('CONFIG_K230') ? - ['k230-ddr-test', 'k230-wdt-test', 'k230-gsdma-test', 'k230-decomp-gzip= -test'] : []) + ['k230-ddr-test', 'k230-wdt-test', 'k230-gsdma-test', + 'k230-decomp-gzip-test', 'k230-dwc-ssi-test'] : []) =20 qtests_hexagon =3D ['boot-serial-test', 'l2vic-test', 'qct-qtimer-test'] =20 --=20 2.43.0 From nobody Sat Sep 26 20:51:42 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1788174815; cv=none; d=zohomail.com; s=zohoarc; b=CvYVL03Eh8eJtpYAFVWl/FMhWoL/DWPbduN8cU7by+dzQOmmMQ+Sfj67Jz2Dblb0H1ZxXRA+by/S0bstw8ucvk0PhG4cK6/Qz+eWz0l33Smnf6sp7mvSggMCGHPTswGFXEQdKY2qsDa7O2JKRvNORTaFyRZOhqoOCbOCxLTJwO0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788174815; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=/Z+X/PncSkuE6MgGSZCE8eysDDYP/aNPOAR017HvVGw=; b=c8QybjOvFbL5djeok7Jxxh9DLQ4kWo4g66beG8j7gGI0E974gfPLPbskEOqCl+GV9Uq4HyaW++FnsW1h95c4YRseb8mzp4/EWYNDdgVRxhif9uRmey2R7QgIfRJv5eoxbvIGBqjMcrMrUjYoEhnlf39lJj7NE1hss1HxUpeLvhg= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788174815693534.5117627259552; Mon, 31 Aug 2026 04:13:35 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0zwj-0001ro-5m; Mon, 31 Aug 2026 07:13:01 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0zwh-0001qu-34 for qemu-devel@nongnu.org; Mon, 31 Aug 2026 07:12:59 -0400 Received: from mail-pf1-x42a.google.com ([2607:f8b0:4864:20::42a]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1x0zwe-0003Ez-QH for qemu-devel@nongnu.org; Mon, 31 Aug 2026 07:12:58 -0400 Received: by mail-pf1-x42a.google.com with SMTP id d2e1a72fcca58-8487214ad2bso4386490b3a.1 for ; Mon, 31 Aug 2026 04:12:56 -0700 (PDT) Received: from LAPTOP-CS7CB8K1.localdomain ([2409:8900:7ad7:8121:d841:db7e:846b:ea5e]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-856a397d927sm3367329b3a.54.2026.08.31.04.12.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 04:12:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788174775; x=1788779575; darn=nongnu.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=/Z+X/PncSkuE6MgGSZCE8eysDDYP/aNPOAR017HvVGw=; b=ZVZ7a3n96UjU1lxMCmTIoe2aFXkU+b3luRFUqzvgZq8v7bp/nTFAzfr3IPvGc/VHDU zzDcIxjoFZoFmoNbOHjcYIy97U1fNXFPFoCCuTvwVIABoCjhoguRYL/q+0Jfx9g5AK/1 8yqkUFVQUoW/JbHN3nlb+ECqqX3Bt2vDmSmN34NjiadYrKl4zgVrRt4xQMWGye/Yzyvh OqPQduZbXHg7UucbsWk9Bp8lZmzOCX8rzA+WCZ+s15eDfLInge34VIRl7lSlVqUc3Rye DBQVs/FHzwDT0hum3dwSJRT7UuBJ8zZfY2D2qypRYxTJtxbdjU/tPn+R9qgWKlgFe46S wpUg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788174775; x=1788779575; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=/Z+X/PncSkuE6MgGSZCE8eysDDYP/aNPOAR017HvVGw=; b=cCwaeqgZzkbl8lAwJBfDoKxHPnZXMskrEZUl2UbTwAW7lst63loEsWIEiCLnTr20/p odJJiio2pHZTZB0JMdxFcnPRNdB80kFJCp9dme8NW7tsmN9VLda5JqEkSkfz+AbaWgHP qMs9VXPmwhsk6cKnRjjyz44t3ehc+bbkKlPAh8DEGcG0Zkjqz2qc7YKrU/gzWyxka7Xb ngR8prJVOyXbhDwrKFoPrbyn6/1Nt2vJ7szGg84tRi01s8lFj4iWi3SGf+lnXJiRZg38 mjIH4LAkwl6ZOLCwshrf0vZJGEkGcXOLyQi5ZNHf+oihI21UMt1D7OUnW/t7WAGLzM2a ZlKw== X-Gm-Message-State: AFuF++nrqQjl7ydWqKmqI6gSilggHIiyFf0do33DPTQSqmy6IlZt+w71 1DWa7oe2isA50P3R8lkB7ewoXNd6xZBrUSaV8G1I2YBOLmN8yroxCsN0 X-Gm-Gg: AR+sD10by1klFnirC1rHIokJrNVc0UUwaSFfR+LkgixLXETG95KpQFQikJ00QuEAdaF NzNwFeEKobd5QMGsK6ly/1dvfXBpJPdOh/GEdxUQCHGQ+YFMK62SfF2BQGO3TYmAKC5OquupPd4 D12Iy/mRad6e3fbpQKlJOlz2af/jYrlCWHp5v3zTNVAjLXc50iNrdtI9Y3w1d2wwnJw6Iph52rx qVh4ri+Arrhc4t+/zIiDAnzHCD1J83N3ruaFeTO4r82Pp9U5waIC/DMZRC2xv26zhAGqD7Ze3EC fNiojRvI0vkni1dTZVFAw5NtrMtWA2M7/o9u89vSbC+UaGzO9rURk4piZ/Wmh5uvw20cBbNfS9u Xi54TzbD+EgmNtiTdHKO3BwtWabG5flt7ihCvt8Tfd2z8lVWIuPVZQCRalLJU3NGep4yVDxMtCW FIav5U4wUyoxLOd/Xt8HS98oWoaVJhNp9hsxxSeS4VelEQRGg/gLvhyF5QYcNGNJO7BtbdPd/vn ihVD3XmUYrgs3jyY6QAHXQ8kFC0WGK5hhcA X-Received: by 2002:a05:6a00:ab0a:b0:84e:7ec:aa42 with SMTP id d2e1a72fcca58-85629f09863mr42946001b3a.10.1788174775164; Mon, 31 Aug 2026 04:12:55 -0700 (PDT) From: Kangjie Huang Date: Mon, 31 Aug 2026 19:11:14 +0800 Subject: [PATCH v4 3/3] hw/riscv/k230: Attach a standard SPI NOR flash MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260831-t_v4_spi_patch-v4-3-370d416f26b5@gmail.com> References: <20260831-t_v4_spi_patch-v4-0-370d416f26b5@gmail.com> In-Reply-To: <20260831-t_v4_spi_patch-v4-0-370d416f26b5@gmail.com> To: qemu-devel@nongnu.org, qemu-riscv@nongnu.org Cc: Portia Stephens , Bin Meng , Chao Liu , Alistair Francis , Alistair Francis , Palmer Dabbelt , Weiwei Li , Daniel Henrique Barboza , Liu Zhiwei , Pierrick Bouvier , Paolo Bonzini , Fabiano Rosas , Laurent Vivier , Anirudh Srinivasan , Kangjie Huang X-Mailer: b4 0.13.0 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::42a; envelope-from=flamboyant.h.01@gmail.com; helo=mail-pf1-x42a.google.com X-Spam_score_int: -17 X-Spam_score: -1.8 X-Spam_bar: - X-Spam_report: (-1.8 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1788174819753158500 Add an optional `spi-flash` machine property and attach the selected M25P80-compatible flash device to logical spi0 CS0. Add qtests covering the Standard 1-1-1 JEDEC ID read, a read at a fixed address, and an EEPROM read with a multi-byte command and a non-zero address. Standard 1-1-1 transfers against the attached flash were also manually verified through the U-Boot and Linux SPI paths of the K230 SDK. Enhanced SPI, IDMA and XIP paths are out of scope for this series. Signed-off-by: Kangjie Huang --- docs/system/riscv/k230.rst | 1 + hw/riscv/Kconfig | 1 + hw/riscv/k230.c | 66 ++++++++++++++++++++++++++++++++ include/hw/riscv/k230.h | 2 + tests/qtest/k230-dwc-ssi-test.c | 83 +++++++++++++++++++++++++++++++++++++= ++++ 5 files changed, 153 insertions(+) diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst index a7f7a494cd..7113e79ab1 100644 --- a/docs/system/riscv/k230.rst +++ b/docs/system/riscv/k230.rst @@ -24,6 +24,7 @@ The ``k230`` machine supports the following devices: * System Direct Memory Access (SDMA) * GZIP Decompress Engine (Decomp_gzip) * 3 DWC SSI controllers (Standard SPI mode) +* Optional SPI NOR flash on spi0 CS0 =20 Boot options ------------ diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index f348da6d20..1fbf1e0722 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -165,3 +165,4 @@ config K230 select K230_GSDMA select K230_DECOMP_GZIP select DWC_SSI + select SSI_M25P80 diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index eb40b53ba8..abd1cc508c 100644 --- a/hw/riscv/k230.c +++ b/hw/riscv/k230.c @@ -21,7 +21,9 @@ #include "system/device_tree.h" #include "system/system.h" #include "system/memory.h" +#include "system/blockdev.h" #include "target/riscv/cpu.h" +#include "hw/block/flash.h" #include "hw/core/loader.h" #include "hw/core/sysbus.h" #include "hw/riscv/k230.h" @@ -31,6 +33,7 @@ #include "hw/intc/sifive_plic.h" #include "hw/char/serial-mm.h" #include "hw/misc/unimp.h" +#include "hw/ssi/ssi.h" =20 /* Align K230_SDK k230_canmv_defconfig */ #define K230_DIRECT_OPENSBI_ADDR 0x8000000 @@ -577,6 +580,49 @@ static void k230_firmware_boot(K230MachineState *s, Ma= chineState *machine) memmap[K230_DEV_BOOTROM].size, 0, 0); } =20 +static char *k230_machine_get_spi_flash(Object *obj, Error **errp) +{ + K230MachineState *s =3D RISCV_K230_MACHINE(obj); + + return g_strdup(s->spi_flash_model); +} + +static void k230_machine_set_spi_flash(Object *obj, const char *value, + Error **errp) +{ + K230MachineState *s =3D RISCV_K230_MACHINE(obj); + + g_free(s->spi_flash_model); + s->spi_flash_model =3D g_strdup(value); +} + +static void k230_connect_spi_flash(DwcSsiState *ssi, unsigned int cs, + const char *flash_type, DriveInfo *dinf= o) +{ + ObjectClass *flash_class; + DeviceState *flash; + qemu_irq flash_cs; + + flash_class =3D module_object_class_by_name(flash_type); + if (!flash_class || object_class_is_abstract(flash_class) || + !object_class_dynamic_cast(flash_class, TYPE_M25P80)) { + error_report("'%s' is either abstract or not a subtype of m25p80", + flash_type); + exit(EXIT_FAILURE); + } + + flash =3D qdev_new(flash_type); + + if (dinfo) { + qdev_prop_set_drive(flash, "drive", blk_by_legacy_dinfo(dinfo)); + } + + qdev_realize_and_unref(flash, BUS(ssi->spi), &error_fatal); + + flash_cs =3D qdev_get_gpio_in_named(flash, SSI_GPIO_CS, 0); + qdev_connect_gpio_out_named(DEVICE(ssi), "cs", cs, flash_cs); +} + static void k230_machine_done(Notifier *notifier, void *data) { K230MachineState *s =3D container_of(notifier, K230MachineState, @@ -608,6 +654,12 @@ static void k230_machine_init(MachineState *machine) TYPE_RISCV_K230_SOC); qdev_realize(DEVICE(&s->soc), NULL, &error_fatal); =20 + if (s->spi_flash_model) { + k230_connect_spi_flash(&s->soc.dwc_ssi[2], 0, + s->spi_flash_model, + drive_get(IF_MTD, 0, 0)); + } + /* Data Memory */ memory_region_add_subregion(sys_mem, memmap[K230_DEV_DDRC].base, machine->ram); @@ -620,6 +672,13 @@ static void k230_machine_instance_init(Object *obj) { } =20 +static void k230_machine_instance_finalize(Object *obj) +{ + K230MachineState *s =3D RISCV_K230_MACHINE(obj); + + g_clear_pointer(&s->spi_flash_model, g_free); +} + static void k230_machine_class_init(ObjectClass *oc, const void *data) { MachineClass *mc =3D MACHINE_CLASS(oc); @@ -629,6 +688,12 @@ static void k230_machine_class_init(ObjectClass *oc, c= onst void *data) mc->default_cpus =3D 1; mc->default_ram_id =3D "riscv.K230.ram"; /* DDR */ mc->default_ram_size =3D memmap[K230_DEV_DDRC].size; + + object_class_property_add_str(oc, "spi-flash", + k230_machine_get_spi_flash, + k230_machine_set_spi_flash); + object_class_property_set_description( + oc, "spi-flash", "Attach an M25P80-compatible flash to spi0 CS0"); } =20 static const TypeInfo k230_machine_typeinfo =3D { @@ -636,6 +701,7 @@ static const TypeInfo k230_machine_typeinfo =3D { .parent =3D TYPE_MACHINE, .class_init =3D k230_machine_class_init, .instance_init =3D k230_machine_instance_init, + .instance_finalize =3D k230_machine_instance_finalize, .instance_size =3D sizeof(K230MachineState), .interfaces =3D riscv64_machine_interfaces, }; diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h index fdabf454be..9b581ff3e0 100644 --- a/include/hw/riscv/k230.h +++ b/include/hw/riscv/k230.h @@ -59,6 +59,8 @@ typedef struct K230MachineState { /*< public >*/ K230SoCState soc; Notifier machine_done; + + char *spi_flash_model; } K230MachineState; =20 enum { diff --git a/tests/qtest/k230-dwc-ssi-test.c b/tests/qtest/k230-dwc-ssi-tes= t.c index 42e3190cb4..a0ec580679 100644 --- a/tests/qtest/k230-dwc-ssi-test.c +++ b/tests/qtest/k230-dwc-ssi-test.c @@ -80,6 +80,7 @@ #define K230_SSI_XIP_MODE_BITS 0x0fc =20 #define K230_SSI_FIFO_DEPTH 256 +#define K230_SSI_FLASH_SIZE MiB =20 typedef struct K230SsiInstance { uint64_t base; @@ -593,7 +594,87 @@ static void test_icr_total_clear(void) qtest_quit(qts); } =20 +static void test_flash_jedec_id(void) +{ + QTestState *qts =3D qtest_init("-machine k230,spi-flash=3Dm25p80"); + uint32_t id; + + k230_ssi_configure(qts, K230_SPI0_BASE, K230_SSI_TMOD_TR, 8, 0); + k230_ssi_enable_cs(qts, K230_SPI0_BASE, BIT(0)); + + /* JEDEC RDID (0x9f): m25p80 reports 0x20 0x20 0x14. */ + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x9f); + (void)k230_ssi_read_frame(qts, K230_SPI0_BASE); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + id =3D k230_ssi_read_frame(qts, K230_SPI0_BASE); + g_assert_cmphex(id & 0xff, =3D=3D, 0x20); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + id =3D k230_ssi_read_frame(qts, K230_SPI0_BASE); + g_assert_cmphex(id & 0xff, =3D=3D, 0x20); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + id =3D k230_ssi_read_frame(qts, K230_SPI0_BASE); + g_assert_cmphex(id & 0xff, =3D=3D, 0x14); + + qtest_quit(qts); +} + +static void test_flash_fixed_read(void) +{ + QTestState *qts =3D qtest_init("-machine k230,spi-flash=3Dm25p80"); + g_autofree uint8_t *image =3D g_malloc(K230_SSI_FLASH_SIZE); + g_autofree char *flash_path =3D NULL; + uint32_t byte; + int fd; + + k230_ssi_configure(qts, K230_SPI0_BASE, K230_SSI_TMOD_TR, 8, 0); + k230_ssi_enable_cs(qts, K230_SPI0_BASE, BIT(0)); + + /* Standard 1-1-1 READ (0x03) at address 0x000000. */ + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x03); + (void)k230_ssi_read_frame(qts, K230_SPI0_BASE); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + (void)k230_ssi_read_frame(qts, K230_SPI0_BASE); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + (void)k230_ssi_read_frame(qts, K230_SPI0_BASE); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + (void)k230_ssi_read_frame(qts, K230_SPI0_BASE); + + /* Without a backend the flash is erased and reads back 0xff. */ + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + byte =3D k230_ssi_read_frame(qts, K230_SPI0_BASE); + g_assert_cmphex(byte & 0xff, =3D=3D, 0xff); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + byte =3D k230_ssi_read_frame(qts, K230_SPI0_BASE); + g_assert_cmphex(byte & 0xff, =3D=3D, 0xff); + + qtest_quit(qts); =20 + memset(image, 0xff, K230_SSI_FLASH_SIZE); + image[0x123] =3D 0x5a; + image[0x124] =3D 0xc3; + fd =3D g_file_open_tmp("qtest-k230-ssi-XXXXXX", &flash_path, NULL); + g_assert_cmpint(fd, >=3D, 0); + close(fd); + g_assert_true(g_file_set_contents(flash_path, (char *)image, + K230_SSI_FLASH_SIZE, NULL)); + + qts =3D qtest_initf("-machine k230,spi-flash=3Dm25p80 " + "-drive file=3D%s,format=3Draw,if=3Dmtd", flash_path= ); + k230_ssi_configure(qts, K230_SPI0_BASE, K230_SSI_TMOD_EEPROM_READ, + 8, 1); + k230_ssi_writel(qts, K230_SPI0_BASE, K230_SSI_SSIENR, 1); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x03); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x01); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x23); + k230_ssi_writel(qts, K230_SPI0_BASE, K230_SSI_SER, BIT(0)); + k230_ssi_wait_mask(qts, K230_SPI0_BASE, K230_SSI_RXFLR, + UINT32_MAX, 2); + g_assert_cmphex(k230_ssi_read_frame(qts, K230_SPI0_BASE), =3D=3D, 0x5a= ); + g_assert_cmphex(k230_ssi_read_frame(qts, K230_SPI0_BASE), =3D=3D, 0xc3= ); + qtest_quit(qts); + g_assert_cmpint(g_unlink(flash_path), =3D=3D, 0); +} =20 static void test_unsupported_registers(void) { @@ -633,5 +714,7 @@ int main(int argc, char **argv) qtest_add_func("/k230-dwc-ssi/icr-total-clear", test_icr_total_clear); qtest_add_func("/k230-dwc-ssi/unsupported-registers", test_unsupported_registers); + qtest_add_func("/k230-dwc-ssi/flash-jedec-id", test_flash_jedec_id); + qtest_add_func("/k230-dwc-ssi/flash-fixed-read", test_flash_fixed_read= ); return g_test_run(); } --=20 2.43.0