From nobody Mon Sep 28 01:15:05 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=1786213912; cv=none; d=zohomail.com; s=zohoarc; b=jkwDN6+BVgOMLDNHmTFhl7x/iH2cIG4VoBP8DJE9WejP5I+oMcWc2vuktA4pciQvoUR9tmDKvBFrJ2WUjVFObWXqWoWfArJ6UhynEJINFONchqVDDrePQ2LuoP8U0SdCgJyNHnWrWqjwx8tj45EbpcqJLPQs5VAEMx5KUsZbw44= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1786213912; h=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=1Dn67L+9M8vhFQZglcqUlJsSPHRNxI/57oCPU2QMPrs=; b=frIL4hMgiah+IM/uyTF3mN3h6R87g6/iyxeuZq2ydwFpX+9ncVvBya0L8xANrmsgH/ccq+AHVGw3kHF+y7CxA6PiRuWNmxf8N0MRi8OMp/Z5tcn4j7aVT005CRkOGgGGCruvxsMsvaORjTwRN6dUCHsDKKCx0IHLxBhIx4LArBI= 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 1786213911913726.7605270110024; Sat, 8 Aug 2026 11:31:51 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wslpa-0008Ui-EA; Sat, 08 Aug 2026 14:31:39 -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 1wslpS-0008U5-HL for qemu-devel@nongnu.org; Sat, 08 Aug 2026 14:31:32 -0400 Received: from mail-pj1-x102e.google.com ([2607:f8b0:4864:20::102e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wslpO-0007de-RZ for qemu-devel@nongnu.org; Sat, 08 Aug 2026 14:31:30 -0400 Received: by mail-pj1-x102e.google.com with SMTP id 98e67ed59e1d1-38deea72eebso566254a91.1 for ; Sat, 08 Aug 2026 11:31:26 -0700 (PDT) Received: from LAPTOP-CS7CB8K1.localdomain ([2409:8a00:6035:79d0:50fd:3a6e:508:beb2]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3925fc6c13asm6772393a91.2.2026.08.08.11.31.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 08 Aug 2026 11:31:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786213885; x=1786818685; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=1Dn67L+9M8vhFQZglcqUlJsSPHRNxI/57oCPU2QMPrs=; b=XpG/tIpp+XpZKRqBkyDGkFiF7C2j+OoFXxCJPtBGOIZG+BqHxYmLbmMFG5tPDkuw1k 0roLP0ePNeHw+k1DPABxMZFv/U2q9t13l2QVdwPofw2bkA4vtfVC0gW61XgYLBgaLA0L yt0IXLngMN+SqHU/lh4dT+HaS2ZWjtL2bUQ2fLiB6K44ANENwkWM4ATi5GdtvjTeRV4J Ftx0n+3h1DZVaIET/6mgTo/TNzevB/rLR6yuoRh7PNCjfq2IBGEAcEOVPo+aGTGQNvVd uv2zO5jIIP4UsiuGcLG3Biv0UqcvrrXfzy0kQbMfIExaEk5LegMqHNKYzPsoiIJrWs52 9/1g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786213885; x=1786818685; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=1Dn67L+9M8vhFQZglcqUlJsSPHRNxI/57oCPU2QMPrs=; b=gMkR53lqgdnpNSQkVVXdtjzv5BU9zrco8D+TzE75ZwMryRX8XdLRX1AyWnw7i6xxMz m3UxSYgXkuLd+fo95G6GjInba/xvImWpysGJAEj8cK1DjRz+ev+m/H9l8V27dwE57DWX 9WgJPRBIcKGTERafT391KxJaY+VekmVABTyWuukeBcBrx0Fy+Roik9w3FeJ3IMivx9uN 1GZ2eLvXGVgxRsYCiAR28EZ3W6FosTb0dy6Qo02eMV3W9OrK8DU0NOcUkEwaHN7l0WkW qi6yffAJu4yONeQWwU34f2xQnvoEj23N6wpZxC8vF7e6LfXpA0JbTQAEJyiFOmUfpUZO Oucg== X-Gm-Message-State: AOJu0YysjREXxj/AunUK54PVCnp+Jsr9dCsuvqZ0st9O6zAaEDFsno1+ m4neqq44cHMOGcLGNTCvxh09vpgfeJnpI+l8Q8ZA9gGT2umaa9UQnGlh68iX8zYwixNK8w== X-Gm-Gg: AR+sD10PXOVA/EeFLzDMgUys8YsqHF6v4IEqJn6W7Zz0iORfgWXj3YS00Q9ouipYvCw M1ejS5vZa2MAqTmQVPzoQzLNeaiVILe/JbED6rBdB2HvCIz8aacYN2L0Syo+z1cV79HnNr/X591 qPJvmZcImzF45Djdd/DyHIBTN2rLbnP19w9rGb7gGXU2zfIw3JUy/J+SkdMy9jSFSNoG/CaZuop Sbp/iUhq4HzDtSNvv51hZdz94PYeodAhcWdUMUjcFGUG0WrlO0o2m6tUVliwr1MxAwFDjpcc/bV yD7v+6wDK4qMi4vZiwjV7LTm0H5iXbAM/HWMSv0Qce+TSGCGlZRBxmQPyqZEmmzA6KFe3d7ujLD nq9jK0mCQO92/CucNSrfK7czoPPngJVYJwkYnqpPqOEtlPshUysaWj37qynSKns/lE8Z2aB3/bz K8Lf43gVIJ6N1m77fxqsZAJ0FRyiXaEzyAAGAVf2IELyf17Bo6CE6S6aTVch1gPB2aRWSnuSOQq T8hC9Ovvj20mHCJ1A== X-Received: by 2002:a17:90b:4a0f:b0:38e:9045:bac0 with SMTP id 98e67ed59e1d1-392823340e5mr9166576a91.5.1786213885108; Sat, 08 Aug 2026 11:31:25 -0700 (PDT) From: Kangjie Huang To: qemu-devel@nongnu.org Cc: qemu-riscv@nongnu.org, 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 Subject: [PATCH v3 1/5] hw/ssi: Add Synopsys DWC SSI standard PIO controller Date: Sun, 9 Aug 2026 02:29:52 +0800 Message-ID: <20260808182956.290561-2-flamboyant.h.01@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260808182956.290561-1-flamboyant.h.01@gmail.com> References: <20260808182956.290561-1-flamboyant.h.01@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable 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::102e; envelope-from=flamboyant.h.01@gmail.com; helo=mail-pj1-x102e.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: 1786213913455158500 Content-Type: text/plain; charset="utf-8" Add a reusable SysBus model for the Synopsys DWC SSI controller. The model uses the DWC CTRLR0 layout, where TMOD is at bits [11:10]; DW APB SSI variants with TMOD at bits [9:8] are 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 handling for unsupported enhanced, DMA, and XIP registers. Add num-cs, fifo-depth, and imr-reset properties. Save only Standard PIO state in VMState and reject migration between devices with different resource profiles. The Synopsys databook requires myDesignWare registration and has no stable public URL. The K230 TRM is the public 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 provide software and capability references: 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 The Intel Arria 10 HPS TRM describes the DW APB SSI family variant and is used only for comparison with the DWC layout: https://www.intel.com/content/www/us/en/docs/programmable/683711/21-2/hard-= processor-system-technical-reference.html Suggested-by: Bin Meng Suggested-by: Chao Liu Suggested-by: Anirudh Srinivasan The controller is not instantiated by this patch. Signed-off-by: Kangjie Huang --- hw/ssi/Kconfig | 4 + hw/ssi/dwc_ssi.c | 976 +++++++++++++++++++++++++++++++++++++++ hw/ssi/meson.build | 1 + include/hw/ssi/dwc_ssi.h | 63 +++ 4 files changed, 1044 insertions(+) create mode 100644 hw/ssi/dwc_ssi.c create mode 100644 include/hw/ssi/dwc_ssi.h diff --git a/hw/ssi/Kconfig b/hw/ssi/Kconfig index 1bd56463c1..f48fdcf880 100644 --- a/hw/ssi/Kconfig +++ b/hw/ssi/Kconfig @@ -6,6 +6,10 @@ config SIFIVE_SPI bool select SSI =20 +config DWC_SSI + bool + select SSI + config SSI bool =20 diff --git a/hw/ssi/dwc_ssi.c b/hw/ssi/dwc_ssi.c new file mode 100644 index 0000000000..c0bcf8b869 --- /dev/null +++ b/hw/ssi/dwc_ssi.c @@ -0,0 +1,976 @@ +/* + * 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 and chip selects. + */ + +#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 (TRM v0.3.1 section 12.3). */ +#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_PIO_TX_BATCH 64 + +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) +#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) + +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; + } + + if (s->cfg.imr_reset & ~DWC_SSI_IMR_WRITABLE_MASK) { + error_setg(errp, "%s: imr-reset contains unsupported bits", + dev->canonical_path); + return false; + } + + return true; +} + +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_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 chip select %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 chip selects 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; +} + +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)) { + return; + } + + fifo32_push(&s->tx_fifo, tx); + + if (s->phase !=3D DWC_SSI_PHASE_STANDARD_TX_ONLY) { + dwc_ssi_run_transfer(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; + + 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; + } + + /* + * TRM: the controller starts shifting only after the TX FIFO + * occupancy exceeds TXFTLR.TXFTHR. Transfers already in flight + * are not interrupted when the FIFO temporarily drops below the + * level. + */ + 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)) { + 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: { + unsigned int frames =3D 0; + + 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) && + frames < DWC_SSI_PIO_TX_BATCH) { + uint32_t tx =3D fifo32_pop(&s->tx_fifo); + + dwc_ssi_send_frame(s, tx); + frames++; + } + if (fifo32_is_empty(&s->tx_fifo)) { + 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; + } + + dwc_ssi_run_transfer(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); + if (s->phase =3D=3D DWC_SSI_PHASE_STANDARD_TX_ONLY) { + dwc_ssi_run_transfer(s); + } + break; + case A_RXFLR: + value =3D fifo32_num_used(&s->rx_fifo); + break; + case A_SR: + value =3D dwc_ssi_status(s); + if (s->phase =3D=3D DWC_SSI_PHASE_STANDARD_TX_ONLY) { + dwc_ssi_run_transfer(s); + } + 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: + /* Keep SPI_FRF at Standard until enhanced SPI is fully modelled. = */ + value &=3D ~R_CTRLR0_SPI_FRF_MASK; + 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); + 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); + + /* + * Changing the selected slave terminates a pending transaction: + * the FIFOs are flushed only by clearing SSIENR (TRM), but a + * stale RO/EEPROM phase must not resume on the new chip select. + */ + 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); + 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); + 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); + 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); + 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->regs[R_CTRLR0] =3D DWC_SSI_CTRLR0_RESET; + s->regs[R_SR] =3D DWC_SSI_SR_RESET; + s->regs[R_IMR] =3D s->cfg.imr_reset & DWC_SSI_IMR_WRITABLE_MASK; + s->regs[R_IDR] =3D DWC_SSI_IDR_RESET; + s->regs[R_SSIC_VERSION_ID] =3D DWC_SSI_VERSION; +} + +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 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 (!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 > DWC_SSI_PHASE_STANDARD_TR) { + 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]); + } + + 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_UINT32_EQUAL(cfg.imr_reset, DwcSsiState), + VMSTATE_UINT32_ARRAY(regs, DwcSsiState, DWC_SSI_NUM_REGS), + VMSTATE_FIFO32(tx_fifo, DwcSsiState), + VMSTATE_FIFO32(rx_fifo, 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); + + 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_UINT32("imr-reset", DwcSsiState, + cfg.imr_reset, 0x0000003f), +}; + +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; +} + +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..6e4f8ea7bb --- /dev/null +++ b/include/hw/ssi/dwc_ssi.h @@ -0,0 +1,63 @@ +/* + * 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 and chip selects. + */ + +#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)) + +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, +} DwcSsiPhase; + +typedef struct DwcSsiConfig { + uint32_t num_cs; + uint32_t fifo_depth; + uint32_t imr_reset; +} DwcSsiConfig; + +struct DwcSsiState { + SysBusDevice parent_obj; + + MemoryRegion mmio; + SSIBus *spi; + + qemu_irq *cs_lines; + + Fifo32 tx_fifo; + Fifo32 rx_fifo; + uint32_t regs[DWC_SSI_NUM_REGS]; + + DwcSsiConfig cfg; + + 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 Mon Sep 28 01:15:05 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=1786213975; cv=none; d=zohomail.com; s=zohoarc; b=gbzj34tc8pzcrR4yyFViy9PDP0Vabr61bN4eCS4IBTFb5eKko1+Kt4xlSFecaBJGtr7RuNokuJRv1bwWH9OipoXlfEgrMXk0o8F4fa10TMyWi8fiiSf73czYOmQ+Wupn617DXEm39NMloNjZu04Q/CVyZmz3TS1/puFN5xy3HQg= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1786213975; h=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=JIW8yEjgRGY3LIhgF42aY5/+Q15tNsIpPIoYrGqSGZA=; b=AstuETpqMAu1ErYPOXZ0rR4/ZkhjsqIZB5CU+u2sKRzTz4Ly0KDPleHJZI/1hkdmBkruXNUNXE+gQ4JXRb+0cM3InFcU0MpA/qiJgAqUdKsoe9oVcUdy+JUR+CUfN/RV5V02LuJlfsg6wOJp16eRakSMJgJ8lAF7cvIV2B3Iq4g= 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 178621397501268.1404947128483; Sat, 8 Aug 2026 11:32:55 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wslph-00006R-SV; Sat, 08 Aug 2026 14:31:45 -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 1wslpc-0008VJ-1g for qemu-devel@nongnu.org; Sat, 08 Aug 2026 14:31:40 -0400 Received: from mail-pj1-x1029.google.com ([2607:f8b0:4864:20::1029]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wslpY-0007eI-N3 for qemu-devel@nongnu.org; Sat, 08 Aug 2026 14:31:38 -0400 Received: by mail-pj1-x1029.google.com with SMTP id 98e67ed59e1d1-38e42560ebcso363014a91.1 for ; Sat, 08 Aug 2026 11:31:35 -0700 (PDT) Received: from LAPTOP-CS7CB8K1.localdomain ([2409:8a00:6035:79d0:50fd:3a6e:508:beb2]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3925fc6c13asm6772393a91.2.2026.08.08.11.31.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 08 Aug 2026 11:31:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786213894; x=1786818694; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=JIW8yEjgRGY3LIhgF42aY5/+Q15tNsIpPIoYrGqSGZA=; b=WkK4VvgbQweQ/ZYPvX7A3FDYO9VF+0ObHcCoCGkZQ3RbFAtPR5YTy6im9rkfqmRdaD tzjTMHG9KjcbcOLvNqUV88ki73YxVhVqUjhsOQzcSR14Kwusr7xW9yAmFEo2LPWS+htP HZ7h2ElAvoklHH58kElTpx89RqzCDjIvAbku78t+HGhVL5s9vxPFv2MfH/4DiMSfIqhr UgetyvKi8zwl0Ss2XKKCAn5agkOAI7Lg3th5n9WzU1yLybuObFJXswgsXBDSP7zN43Kg lPZbaj4NX0xnVdTW6ylFtn+N2b50UV3VjRRZjKwAT5k8nqxs6Sbs1eJq/yY2hRwv2WDa uIog== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786213894; x=1786818694; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=JIW8yEjgRGY3LIhgF42aY5/+Q15tNsIpPIoYrGqSGZA=; b=JWTaogTRBIGlCJCWJFKkbnlbZsFQ4MGV7kuysAxBejW59KD79MvRtD0AG4KNPPFQjn Kgs9KGExgMZ+7+U5ZOAm9wA3gwv2u7tUmkl3DRtMpMP+O72RfStCLuaemLKlB93chh7R PBOIMQFVUXmnQvOwIzom+ybIz99OwZNm5DlJUsfMz7Nn/CHt4M9EVdYIT2yPggeabUxb r5wTi2NSdPdlz0/Qvj3n15+kV900ApRZ+YDdEnISs4MjYubY64vKe/3T+cJ1Jdx6VMJe J24gylj7wiUTXgaA6TeKrD9slc3Tpe+PnekLHtUf9uR09ZicX9s+QYABDSq8NIQoPvGd b11g== X-Gm-Message-State: AOJu0YyIWvsTxScLqAc0Amdq73uBXP4Ko/mOAFUMH0Eh82CbYJia+GQ3 70TheVUtOvVKDEYMnrjpMovFhLU8PbZxVrNqTLpXfxRaGteTUvZuUNEMYlItK7tbsByOhg== X-Gm-Gg: AR+sD10RkJzNErq8mJuz0Jnyamn1+JatSMiEYvW6ojQCqncUX3XvPTBaWpYP863Ejqw IIEmRqD68D60KLvNEP4gAdbD8QEKcKqYT1uM7ax4qsdvvgi7xq6nUmIGWkyvfcnvDqzfCo911IB axafmEdJvj4I+EJC4AVSygmg1aCxGb2oB7o9nJbcpM/LVVIOMpQ08TmvmWv7T8X873gsc6rbRtv L26DsF4XHe7Q4fRJx+GhO4kEnKI7D+OauH/2BnyXA6dx95yQE7cY+ed4SrR4PKbr1bLAM6GSz4P nxLfS7zk0XeYVmFFq/Q5ZpVazNSQtJh+0kIplKIsvQKUPP0ldUqAhIqt2+8/3+YOg+0Q26SgDtI A6VuaiL4YS4TjLxmb7GaOIiOpCHls5sE+1t4YCtlqL+qpBu1LY2TpEfzaGEmMCt5CX3ExhuGpdg D6DOIJvZtdSCudTYKgQE9vfUgyeV6ULp4fqJOWAp32k4Z+dg//umUcWMlwOQtV6HbFAd94ZfC7q kkMHmRn2Ot7qkL1ViIuOYqwOO4NBg== X-Received: by 2002:a17:90b:5350:b0:38f:1a2e:6e56 with SMTP id 98e67ed59e1d1-392822dcc89mr8471971a91.9.1786213894322; Sat, 08 Aug 2026 11:31:34 -0700 (PDT) From: Kangjie Huang To: qemu-devel@nongnu.org Cc: qemu-riscv@nongnu.org, 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 Subject: [PATCH v3 2/5] hw/ssi: Add DWC SSI standard interrupt support Date: Sun, 9 Aug 2026 02:29:53 +0800 Message-ID: <20260808182956.290561-3-flamboyant.h.01@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260808182956.290561-1-flamboyant.h.01@gmail.com> References: <20260808182956.290561-1-flamboyant.h.01@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable 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::1029; envelope-from=flamboyant.h.01@gmail.com; helo=mail-pj1-x1029.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: 1786213975381158500 Content-Type: text/plain; charset="utf-8" Implement raw and masked SSI interrupt status, threshold interrupts, and the documented read-clear registers. TXE and RXF are derived from FIFO levels. TXO, RXO, and RXU are latched causes. Expose the model's nine physical interrupt outputs. TXU, DONE, and AXIE depend on transfer engines outside the Standard PIO scope and remain deasserted. DONECR and AXIECR are RAZ/WI. Signed-off-by: Kangjie Huang Suggested-by: Anirudh Srinivasan Suggested-by: Bin Meng Suggested-by: Chao Liu --- hw/ssi/dwc_ssi.c | 129 ++++++++++++++++++++++++++++++++++++++- include/hw/ssi/dwc_ssi.h | 18 +++++- 2 files changed, 145 insertions(+), 2 deletions(-) diff --git a/hw/ssi/dwc_ssi.c b/hw/ssi/dwc_ssi.c index c0bcf8b869..43c5c5db88 100644 --- a/hw/ssi/dwc_ssi.c +++ b/hw/ssi/dwc_ssi.c @@ -6,7 +6,7 @@ * SPDX-License-Identifier: GPL-2.0-or-later * * Emulates the DesignWare SSI controller in Standard SPI mode, - * covering the PIO/FIFO data path and chip selects. + * covering the PIO/FIFO data path, interrupt outputs and chip selects. */ =20 #include "qemu/osdep.h" @@ -26,6 +26,9 @@ #define DWC_SSI_IDR_RESET 0xa1b2c3d5 #define DWC_SSI_VERSION 0x3130332a #define DWC_SSI_PIO_TX_BATCH 64 +#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) =20 enum { DWC_SSI_TMOD_TR, @@ -217,6 +220,22 @@ REG32(XIP_WRITE_CTRL, 0x148) #define DWC_SSI_RX_SAMPLE_DELAY_WRITABLE_MASK \ (R_RX_SAMPLE_DELAY_RSD_MASK | R_RX_SAMPLE_DELAY_SE_MASK) =20 +/* + * TXU, DONE and AXIE depend on transfer engines outside Standard PIO. + * Their physical outputs are exposed but remain deasserted in this model. + */ +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); @@ -253,6 +272,45 @@ static void dwc_ssi_write_masked(DwcSsiState *s, unsig= ned int reg, s->regs[reg] =3D (s->regs[reg] & ~mask) | (value & mask); } =20 +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; @@ -322,6 +380,7 @@ static void dwc_ssi_abort_transfer(DwcSsiState *s) s->phase =3D DWC_SSI_PHASE_IDLE; s->remaining_frames =3D 0; s->dummy_frame =3D 0; + dwc_ssi_update_irq(s); } =20 static uint32_t dwc_ssi_status(DwcSsiState *s) @@ -349,6 +408,8 @@ static void dwc_ssi_push_tx(DwcSsiState *s, uint32_t tx) } =20 if (fifo32_is_full(&s->tx_fifo)) { + s->irq_latched |=3D R_RISR_TXOIR_MASK; + dwc_ssi_update_irq(s); return; } =20 @@ -357,6 +418,7 @@ static void dwc_ssi_push_tx(DwcSsiState *s, uint32_t tx) if (s->phase !=3D DWC_SSI_PHASE_STANDARD_TX_ONLY) { dwc_ssi_run_transfer(s); } + dwc_ssi_update_irq(s); } =20 static uint32_t dwc_ssi_send_frame(DwcSsiState *s, @@ -414,6 +476,7 @@ static void dwc_ssi_run_transfer(DwcSsiState *s) uint32_t rx =3D dwc_ssi_send_frame(s, tx); =20 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); @@ -602,9 +665,11 @@ static uint64_t dwc_ssi_read(void *opaque, hwaddr addr= , unsigned int size) value =3D fifo32_pop(&s->rx_fifo) & dwc_ssi_frame_masked(s); } else { value =3D 0; + s->irq_latched |=3D R_RISR_RXUIR_MASK; } =20 dwc_ssi_run_transfer(s); + dwc_ssi_update_irq(s); return value; } =20 @@ -634,6 +699,7 @@ static uint64_t dwc_ssi_read(void *opaque, hwaddr addr,= unsigned int size) value =3D fifo32_num_used(&s->tx_fifo); if (s->phase =3D=3D DWC_SSI_PHASE_STANDARD_TX_ONLY) { dwc_ssi_run_transfer(s); + dwc_ssi_update_irq(s); } break; case A_RXFLR: @@ -643,8 +709,35 @@ static uint64_t dwc_ssi_read(void *opaque, hwaddr addr= , unsigned int size) value =3D dwc_ssi_status(s); if (s->phase =3D=3D DWC_SSI_PHASE_STANDARD_TX_ONLY) { dwc_ssi_run_transfer(s); + dwc_ssi_update_irq(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, @@ -708,6 +801,7 @@ static void dwc_ssi_write(void *opaque, hwaddr addr, =20 dwc_ssi_update_cs(s); dwc_ssi_run_transfer(s); + dwc_ssi_update_irq(s); break; } case A_MWCR: @@ -731,6 +825,7 @@ static void dwc_ssi_write(void *opaque, hwaddr addr, =20 dwc_ssi_update_cs(s); dwc_ssi_run_transfer(s); + dwc_ssi_update_irq(s); break; } case A_BAUDR: @@ -750,6 +845,7 @@ static void dwc_ssi_write(void *opaque, hwaddr addr, 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( @@ -761,6 +857,7 @@ static void dwc_ssi_write(void *opaque, hwaddr addr, } 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: @@ -769,6 +866,16 @@ static void dwc_ssi_write(void *opaque, hwaddr addr, 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: @@ -809,12 +916,15 @@ static void dwc_ssi_enter_reset(Object *obj, ResetTyp= e type) s->phase =3D DWC_SSI_PHASE_IDLE; s->remaining_frames =3D 0; s->dummy_frame =3D 0; + s->irq_latched =3D 0; =20 s->regs[R_CTRLR0] =3D DWC_SSI_CTRLR0_RESET; s->regs[R_SR] =3D DWC_SSI_SR_RESET; s->regs[R_IMR] =3D s->cfg.imr_reset & DWC_SSI_IMR_WRITABLE_MASK; s->regs[R_IDR] =3D DWC_SSI_IDR_RESET; s->regs[R_SSIC_VERSION_ID] =3D DWC_SSI_VERSION; + + dwc_ssi_update_irq(s); } =20 static void dwc_ssi_hold_reset(Object *obj, ResetType type) @@ -829,6 +939,13 @@ static void dwc_ssi_hold_reset(Object *obj, ResetType = type) } } =20 +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; @@ -850,6 +967,9 @@ static int dwc_ssi_post_load(void *opaque, int version_= id) 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; @@ -878,6 +998,7 @@ static int dwc_ssi_post_load(void *opaque, int version_= id) qemu_irq_lower(s->cs_lines[s->active_cs]); } =20 + dwc_ssi_update_irq(s); return 0; } =20 @@ -893,6 +1014,7 @@ static const VMStateDescription vmstate_dwc_ssi =3D { 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), @@ -913,6 +1035,10 @@ static void dwc_ssi_init(Object *obj) TYPE_DWC_SSI, DWC_SSI_MMIO_SIZE); sysbus_init_mmio(sbd, &s->mmio); =20 + for (int i =3D 0; i < DWC_SSI_IRQ_COUNT; i++) { + sysbus_init_irq(sbd, &s->irqs[i]); + } + s->active_cs =3D -1; } =20 @@ -957,6 +1083,7 @@ static void dwc_ssi_class_init(ObjectClass *klass, con= st void *data) 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; } =20 static const TypeInfo dwc_ssi_info =3D { diff --git a/include/hw/ssi/dwc_ssi.h b/include/hw/ssi/dwc_ssi.h index 6e4f8ea7bb..19499789af 100644 --- a/include/hw/ssi/dwc_ssi.h +++ b/include/hw/ssi/dwc_ssi.h @@ -6,7 +6,7 @@ * SPDX-License-Identifier: GPL-2.0-or-later * * Emulates the DesignWare SSI controller in Standard SPI mode, - * covering the PIO/FIFO data path and chip selects. + * covering the PIO/FIFO data path, interrupt outputs and chip selects. */ =20 #ifndef HW_SSI_DWC_SSI_H @@ -25,6 +25,20 @@ OBJECT_DECLARE_SIMPLE_TYPE(DwcSsiState, DWC_SSI) #define DWC_SSI_NUM_REGS \ (DWC_SSI_REGS_SIZE / sizeof(uint32_t)) =20 +/* 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, @@ -47,6 +61,7 @@ struct DwcSsiState { SSIBus *spi; =20 qemu_irq *cs_lines; + qemu_irq irqs[DWC_SSI_IRQ_COUNT]; =20 Fifo32 tx_fifo; Fifo32 rx_fifo; @@ -54,6 +69,7 @@ struct DwcSsiState { =20 DwcSsiConfig cfg; =20 + uint32_t irq_latched; uint32_t phase; uint32_t remaining_frames; uint32_t dummy_frame; --=20 2.43.0 From nobody Mon Sep 28 01:15:05 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=1786213984; cv=none; d=zohomail.com; s=zohoarc; b=REYvn/gqyGGS8hXiVHTadOC7YtIKjip3Y9xifT2D06LmSVaGXuvsm9mGUOkfway2SPisrSPsDd8NcCUa8Jzl4TLTJzJvAyURw3sZf5uo5yAnvkSvPhxU5O8rw5n+FK6Xwg3HDLlwUQwz3j4WdqSxs4A+LAsCPDjxCVLqAnnM3Gs= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1786213984; h=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=HoOkEzYDKO/Xal5qtsUzU1HJQnhe4L9PoxYoel78jqs=; b=aZlX/i6aMRqbJH6Rgjo9eCBKM0OjewB58cKCq2gzB5VbYB88lD5w+AnNlnsn0utjpLyh88KA/E8Bbhm6QzUY7qizc/wrLaOIBZSx/feHnji5OsOn0CBr83mCZMtiCe4FU8IqJA+ZCRGmeWiDmUgl30I/NKdpJKTrIFLqKp32/2k= 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 1786213984628229.2506886710372; Sat, 8 Aug 2026 11:33:04 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wslpy-0000He-EP; Sat, 08 Aug 2026 14:32:02 -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 1wslpn-0000E6-4r for qemu-devel@nongnu.org; Sat, 08 Aug 2026 14:31:51 -0400 Received: from mail-pj1-x1034.google.com ([2607:f8b0:4864:20::1034]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wslpi-0007ej-4Z for qemu-devel@nongnu.org; Sat, 08 Aug 2026 14:31:49 -0400 Received: by mail-pj1-x1034.google.com with SMTP id 98e67ed59e1d1-38f620399a0so509299a91.2 for ; Sat, 08 Aug 2026 11:31:45 -0700 (PDT) Received: from LAPTOP-CS7CB8K1.localdomain ([2409:8a00:6035:79d0:50fd:3a6e:508:beb2]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3925fc6c13asm6772393a91.2.2026.08.08.11.31.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 08 Aug 2026 11:31:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786213904; x=1786818704; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=HoOkEzYDKO/Xal5qtsUzU1HJQnhe4L9PoxYoel78jqs=; b=Omewva1F3Sd+b0Egw+9GnuK3JQH48Lf0881bfFvRSl1xCq43zEGMW5RU9fJLT+nbWX VN7jG0su7UtoIDdch1L4PvzMjeHNt8FJ2I93vbDdV63lNYYDT7WnxXui8UUuVO4fEyRM yM2a5HAThqKgACg7fH15ZVVz43OQ/jqQWrPasl5Sp/U/Kc8GgoFY9j0szuXKCnRi9kN3 W1rOIc7q3woxv62cdtlZxExVhBWwfFn/1QOe6xXF88GhIcQ7TAp7s3QZlUaaXQW0Zg+c tYQO5A36vLgfxc4g5zRFP0qrUeRMzHuPajQdkJnu+4k210xF3E4MOT7k6nUuRKITEa6S iVYQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786213904; x=1786818704; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=HoOkEzYDKO/Xal5qtsUzU1HJQnhe4L9PoxYoel78jqs=; b=fb5GzZCiPQcnsZgJCL2P8z+4aONG43G7ujZqyE2zqmGYBYidvhuDFxnaU+6U5k5b+6 800IpXM2v65jd1n4vFjD/+lZH7upZCpRwHW9ChYMjwCkAjrqK8k8yhNa3thZCCSUJi0N a1kpBKw5AcFqcFXEY8jY7unYi6mOBgYhSKofrg10F/+CSwHoGFOGWEG6t4NLer4/nRYI SwEmYo0NLnwG9B5O1Lo7NWLNLwF1QAWX5D3CZGbVxPnaH+JR/whhsadQNQrerA/h3YYZ 8w/7GmrPVBS7ZXfgech+zHaBy2P9iaosF3hGfK6ZNfy5VoYJIZJl39+1wFss/7cosbam X9sw== X-Gm-Message-State: AOJu0YxMXqXqlx0QYVkaUS0WwDh95Kj1rZkM2/szgdP7wa3PB3mVVhj8 WB/9GLNdBbZOLadvR2Qe4md0/xUjjW36DLe2rQcMCT+SfVrFOwlPAtKc2kgMqgpLE40SVg== X-Gm-Gg: AR+sD10FqC3MFXsak+qim7lDkc3mvFZwdARz9WctU8d4CVA2Qy0CNjP5SIRtZlUIlvL FZqqNY0pGRO5jRE3IAdf7DenWLOW7lpm6CCSqCLOdvx42qWYHew3XvL0jTZsLSqkMeAyghwC3+V G6hE50/j8IBBPstp3h2PXlzct5X0/k4+t1hkNdqz7ooN63QdZPHEFpfamQh0IctJk7J5Qmoc9Lg zP1uEJVXcmIyxyPsmVO3Vi2OxWOcH9w5XOxWZ3MLdl3zgbq0FJKZ0jhEHzPem1GmkOMzfG38WzB J14yX8x7ArmdBJAv/bqkJgNvBGLy1bpvze33ezgQHKMEND6AU8c72+aMkd3Zgvh7k3JGO7+7xTL sYMDL0YY+ZNB9pCBZGj6tsVim3fLBDyQL1Z47G9WkYmUiNmGIoXTXUCYPuLhwJCymoQz3Q3smjP MZORgs5iiY9eOm7l83yng5KPKjUYAxHpXTcUAX3ebpRTc+byYKSe63qA0FVhAq4IP9lz9FEbSoY 4Q9j5NYdc9swyQjHisIVoj3xih1 X-Received: by 2002:a17:90a:d445:b0:37e:b6a:6cdf with SMTP id 98e67ed59e1d1-3903c66ba04mr33575106a91.20.1786213904225; Sat, 08 Aug 2026 11:31:44 -0700 (PDT) From: Kangjie Huang To: qemu-devel@nongnu.org Cc: qemu-riscv@nongnu.org, 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 Subject: [PATCH v3 3/5] hw/riscv/k230: Instantiate DWC SSI controllers Date: Sun, 9 Aug 2026 02:29:54 +0800 Message-ID: <20260808182956.290561-4-flamboyant.h.01@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260808182956.290561-1-flamboyant.h.01@gmail.com> References: <20260808182956.290561-1-flamboyant.h.01@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable 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::1034; envelope-from=flamboyant.h.01@gmail.com; helo=mail-pj1-x1034.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: 1786213985624158500 Content-Type: text/plain; charset="utf-8" Instantiate the three DWC SSI profiles used by K230 and map their controller MMIO regions at the documented addresses. Configure the QSPI0, QSPI1, and SPI-OPI instances with their chip-select, FIFO-depth, and interrupt-mask reset profiles. Add K230 SSI qtests for Standard PIO transfers, register masks, FIFO behaviour, interrupt state, unsupported-register semantics, and reset profiles. K230 is used as the hardware carrier for the generic controller tests; PLIC routing is added by the next patch. Signed-off-by: Kangjie Huang Suggested-by: Anirudh Srinivasan Suggested-by: Bin Meng Suggested-by: Chao Liu --- docs/system/riscv/k230.rst | 1 + hw/riscv/Kconfig | 1 + hw/riscv/k230.c | 76 ++++- include/hw/riscv/k230.h | 2 + tests/qtest/k230-dwc-ssi-test.c | 574 ++++++++++++++++++++++++++++++++ tests/qtest/meson.build | 4 +- 6 files changed, 642 insertions(+), 16 deletions(-) create mode 100644 tests/qtest/k230-dwc-ssi-test.c diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst index cea8202e55..7a49cac95b 100644 --- a/docs/system/riscv/k230.rst +++ b/docs/system/riscv/k230.rst @@ -20,6 +20,7 @@ The ``k230`` machine supports the following devices: * Platform-Level Interrupt Controller (PLIC) * 2 K230 Watchdog Timer * 5 UART +* 3 K230 SSI controllers for SPI only =20 Boot options ------------ diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index de37c08cae..0bc0d18ca2 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -162,3 +162,4 @@ config K230 select SERIAL_MM select UNIMP select K230_WDT + select DWC_SSI diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index 656f28190c..ea7eaa35a5 100644 --- a/hw/riscv/k230.c +++ b/hw/riscv/k230.c @@ -102,6 +102,40 @@ static const MemMapEntry memmap[] =3D { [K230_DEV_CLINT] =3D { 0xF04000000, 0x00400000 }, }; =20 +typedef struct K230DwcSsiProfile { + uint32_t num_cs; + uint32_t fifo_depth; + uint32_t imr_reset; +} K230DwcSsiProfile; + +static const K230DwcSsiProfile k230_dwc_ssi_profiles[] =3D { + [0] =3D { /* QSPI0, SDK spi1 */ + .num_cs =3D 5, + .fifo_depth =3D 256, + .imr_reset =3D 0x0000001f, + }, + [1] =3D { /* QSPI1, SDK spi2 */ + .num_cs =3D 5, + .fifo_depth =3D 256, + .imr_reset =3D 0x0000001f, + }, + [2] =3D { /* SPI-OPI/FMC, SDK spi0 */ + .num_cs =3D 1, + .fifo_depth =3D 256, + .imr_reset =3D 0x0000003f, + }, +}; + +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_uint32(dev, "imr-reset", profile->imr_reset); +} + static void k230_soc_init(Object *obj) { K230SoCState *s =3D RISCV_K230_SOC(obj); @@ -110,6 +144,12 @@ static void k230_soc_init(Object *obj) object_initialize_child(obj, "c908-cpu", cpu0, TYPE_RISCV_HART_ARRAY); object_initialize_child(obj, "k230-wdt0", &s->wdt[0], TYPE_K230_WDT); object_initialize_child(obj, "k230-wdt1", &s->wdt[1], TYPE_K230_WDT); + 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); @@ -198,6 +238,13 @@ 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; + } + } + 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)); @@ -206,6 +253,20 @@ static void k230_soc_realize(DeviceState *dev, Error *= *errp) sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[1]), 0, qdev_get_gpio_in(DEVICE(s->c908_plic), K230_WDT1_IR= Q)); =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); + + create_unimplemented_device("spi-flash-xip", + memmap[K230_DEV_FLASH].base, + memmap[K230_DEV_FLASH].size); + + create_unimplemented_device("hi_sys", memmap[K230_DEV_HI_SYS_CFG].base, + memmap[K230_DEV_HI_SYS_CFG].size); + /* unimplemented devices */ create_unimplemented_device("kpu.l2-cache", memmap[K230_DEV_KPU_L2_CACHE].base, @@ -349,23 +410,8 @@ 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); - create_unimplemented_device("ddrc_cfg", memmap[K230_DEV_DDRC_CFG].base, memmap[K230_DEV_DDRC_CFG].size); - - create_unimplemented_device("flash", memmap[K230_DEV_FLASH].base, - memmap[K230_DEV_FLASH].size); } =20 static void k230_soc_class_init(ObjectClass *oc, const void *data) diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h index 592e1c26bf..cd30fd6aaa 100644 --- a/include/hw/riscv/k230.h +++ b/include/hw/riscv/k230.h @@ -17,6 +17,7 @@ =20 #include "hw/core/boards.h" #include "hw/riscv/riscv_hart.h" +#include "hw/ssi/dwc_ssi.h" #include "hw/watchdog/k230_wdt.h" =20 #define C908_CPU_HARTID (0) @@ -33,6 +34,7 @@ typedef struct K230SoCState { RISCVHartArrayState c908_cpu; /* Small core */ =20 K230WdtState wdt[2]; + DwcSsiState dwc_ssi[3]; MemoryRegion sram; MemoryRegion bootrom; =20 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..126b73ea01 --- /dev/null +++ b/tests/qtest/k230-dwc-ssi-test.c @@ -0,0 +1,574 @@ +/* + * QTest for the Kendryte K230 DesignWare 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_SSI_CTRLR0 0x000 +#define K230_SSI_CTRLR1 0x004 +#define K230_SSI_SSIENR 0x008 +#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_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_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; +} K230SsiInstance; + +static const K230SsiInstance k230_ssi_instances[3] =3D { + { + .base =3D K230_SPI0_BASE, + .num_cs =3D 1, + .imr_reset =3D 0x0000003fU, + }, { + .base =3D K230_SPI1_BASE, + .num_cs =3D 5, + .imr_reset =3D 0x0000001fU, + }, { + .base =3D K230_SPI2_BASE, + .num_cs =3D 5, + .imr_reset =3D 0x0000001fU, + }, +}; + +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 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); + + 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); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_AXIECR, UINT32_MAX); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_DONECR, UINT32_MAX); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_AXIECR), + =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_DONECR), + =3D=3D, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + (K230_SSI_INT_DONE | K230_SSI_INT_AXIE), =3D=3D, 0); + 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, K230_SSI_FIFO_DEPTH - 64); + for (int i =3D 0; i < 64; i++) { + k230_ssi_write_frame(qts, K230_SPI1_BASE, i); + } + for (int i =3D 0; i < 4; i++) { + if (k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_TXFLR) =3D=3D 0) { + break; + } + (void)k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_SR); + } + 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); +} + +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/tx-only-mode", test_tx_only_mode); + 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 56ff860e21..43ad1596e6 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -297,7 +297,8 @@ qtests_riscv64 =3D ['riscv-csr-test'] + \ (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and config_all_devices.has_key('CONFIG_RISCV_IOMMU') ? ['iommu-riscv-test'] : []) + \ - (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : []) + (config_all_devices.has_key('CONFIG_K230') ? + ['k230-wdt-test', 'k230-dwc-ssi-test'] : []) =20 qtests_hexagon =3D ['boot-serial-test'] =20 @@ -400,6 +401,7 @@ qtests =3D { [gio], 'erst-test': files('erst-test.c'), 'ivshmem-test': [rt, '../../contrib/ivshmem-server/ivshmem-server.c'], + 'k230-dwc-ssi-test': files('k230-dwc-ssi-test.c'), 'migration-test': test_migration_files + migration_tls_files + migration= _colo_files, 'pxe-test': files('boot-sector.c'), 'pnv-xive2-test': files('pnv-xive2-common.c', 'pnv-xive2-flush-sync.c', --=20 2.43.0 From nobody Mon Sep 28 01:15:05 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=1786213972; cv=none; d=zohomail.com; s=zohoarc; b=BJHk79v3tm6rHQANuocYL83Ie2qaGXfDEfnmvftzq+ttb6PYbB7jt8t1Y9sVf0+vijJaJH/YfZcNU4Y/l/nbwAZTgsCxGpHC9LVh4Eem1AMg5xlzEvD1DXf0n1j80rblPFeRqzLj1kkA1nmgrB0oz2IgLFQjkdZzcrebEB+0G3Q= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1786213972; h=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=M6SoCg6vQ+JGBOPRciaNgEEqNUscszLCnP78OgJYwLY=; b=bihIE0wghq1Vq3eoSPBuOg9eh69gH+K0Hle5br+Du0oo7pZ0nQ5uuKgDDMAzVtkdTiNdyUvKGIcsj+FPEwfLL8MHlDGux6Ge1hbuGmKNp40kEP8LbyoNr0+5uA7vuhAGcI76WQPoMdBacXnnE/W7x70LQdYe+4iW4jvmUlVoGt4= 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 1786213972437967.9047030590125; Sat, 8 Aug 2026 11:32:52 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wslq6-0000Vw-2k; Sat, 08 Aug 2026 14:32:10 -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 1wslpv-0000ID-1G for qemu-devel@nongnu.org; Sat, 08 Aug 2026 14:32:02 -0400 Received: from mail-pj1-x1033.google.com ([2607:f8b0:4864:20::1033]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wslps-0007gu-Vy for qemu-devel@nongnu.org; Sat, 08 Aug 2026 14:31:58 -0400 Received: by mail-pj1-x1033.google.com with SMTP id 98e67ed59e1d1-38e3efab7e0so447651a91.0 for ; Sat, 08 Aug 2026 11:31:54 -0700 (PDT) Received: from LAPTOP-CS7CB8K1.localdomain ([2409:8a00:6035:79d0:50fd:3a6e:508:beb2]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3925fc6c13asm6772393a91.2.2026.08.08.11.31.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 08 Aug 2026 11:31:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786213913; x=1786818713; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=M6SoCg6vQ+JGBOPRciaNgEEqNUscszLCnP78OgJYwLY=; b=gnmPBBD4n6oU2xotRhQpwBUrt5JLq1OCzDCCTrxXiPSqQTrtv4uiAU4I45FKVavXGi 0yMy8KVOLXL9TqxQErdzjmtnX18jXmMLjHm4StyUL7jSgFz3DQ8NUQf9Ltkzq3+i/IB2 CBmN8bi5lQP/ja0qTXO9tMo/D3Cjm3RbMOypZet7GT2LcqC2Vb13y9slXyhmh6FkduRm Ja0dreCsp6l+qfD7CgrycIoMWgaLrOjVgncQDa3jsJ56AwKlh7HfYukPQH72Smih9UZ8 5e6MlcAsfqEPWg/npt9wDxozVxSuCTZDmOuqqgYLskE0Z2sraZPDvD0qz7Mb7N4bm07t xZxw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786213913; x=1786818713; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=M6SoCg6vQ+JGBOPRciaNgEEqNUscszLCnP78OgJYwLY=; b=VE58yCxodLlxNpfTk1UanHFmmyX4vA6CawHL5+FqAGpPWbX2bIcV/qDjSZzFo20QYI 6F7KE2Hp8q3FcI/Paxz6MNbU+ZnBv7lZCGm4IAn7Q5CKXkudHst6UGBVnMomzD5Y92Ty KAnY7JQ6jdrN8/+VJ+qAueo9QnDWMLjNDxAqohn+bDmBGLEyU6V3PYA2v0wGT0jqjLbM ELGLIZU7mVimAPxYFlW5g2A3yopvaSLCS7nVfNXGX/+6k7vL9j6yJZN3Sw7qbh+nNeUn raZZzD7UwDTfDXDox4rwssI/B6nySysI/ry/Zjwxq5uaSuh4BOPgnI40Ej/E+M0m9wus ZD+w== X-Gm-Message-State: AOJu0YxcULyVh7AOhfv6Rnlnd7tK41pV2OOA6she02G1wCj2To9oCu66 dt8KY8Uw4vlWyLpo+9O51u/TELgPnFZJxC7xcqEezBThTO4FlEcVlOtKWpOhLFKdw+WhVA== X-Gm-Gg: AR+sD10o5nNCdB/J00K5Aq/hY8gauwc9s22Ml7k3XdLccPxfoe119uHTQ3Um5eDj/RP HmztmxYAYcsXZ1IcCBHPczEGgG5PfOKpwBLVNe215+OlXJrs4B+ox1NeZgxeat9UhSDAFfsCT+o xobZpL8xqZHy12U/8qVRu2Zwlxucpn+FTu5DxVKY9IJHBRA7zSz9xznA41bw4Yn37WdYiPoVhHE q846GlBSkz7jOdBZKSZXbwER0NQkyJtABSM2gLyJ6QZGSLf4aeq0RpmMqAZg9WkroE6MsWsUhtk GsjLTcjvC3fAHw81S35/1V0FRZpHC1Q1hNGuzJFtXON3BoXZgmv/UhDFxYJPK6Ve5c6jNd/cOwj UHeqDm004SptyOdyxKLbgB0OESbVOncLdhrAy7X+UojGIA5FtfK1TmF3qxLTq+HZpHdiSR6T2LX +ogHgJZ5gy9UQmA7Knol2MVzljo+mvrjttEsJ+bJ0ollHDg5UUdvof/GNrS92GHU43SAjA1hQrY SS5gIuThM6CjTnuCw== X-Received: by 2002:a17:90b:3eca:b0:37f:9e21:91d6 with SMTP id 98e67ed59e1d1-392846ac637mr3312739a91.13.1786213913377; Sat, 08 Aug 2026 11:31:53 -0700 (PDT) From: Kangjie Huang To: qemu-devel@nongnu.org Cc: qemu-riscv@nongnu.org, 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 Subject: [PATCH v3 4/5] hw/riscv/k230: Route SSI interrupts to the PLIC Date: Sun, 9 Aug 2026 02:29:55 +0800 Message-ID: <20260808182956.290561-5-flamboyant.h.01@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260808182956.290561-1-flamboyant.h.01@gmail.com> References: <20260808182956.290561-1-flamboyant.h.01@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable 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::1033; envelope-from=flamboyant.h.01@gmail.com; helo=mail-pj1-x1033.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: 1786213973639158500 Content-Type: text/plain; charset="utf-8" Connect the nine interrupt outputs of each K230 DWC SSI controller to the documented PLIC source range. The TXU, DONE, and AXIE lines are wired to preserve the physical K230 interrupt topology, but remain inactive until their transfer engines are modelled. Extend qtests to cover TXE routing for all instances, RXU routing, and instance isolation. Signed-off-by: Kangjie Huang Suggested-by: Anirudh Srinivasan Suggested-by: Bin Meng Suggested-by: Chao Liu --- hw/riscv/k230.c | 36 ++++++++++++++++++++ include/hw/riscv/k230.h | 3 ++ tests/qtest/k230-dwc-ssi-test.c | 59 ++++++++++++++++++++++++++++----- 3 files changed, 90 insertions(+), 8 deletions(-) diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index ea7eaa35a5..4911f2d1be 100644 --- a/hw/riscv/k230.c +++ b/hw/riscv/k230.c @@ -102,6 +102,21 @@ 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; @@ -191,6 +206,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); @@ -245,6 +279,8 @@ static void k230_soc_realize(DeviceState *dev, Error **= errp) } } =20 + 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)); diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h index cd30fd6aaa..4eee78ddec 100644 --- a/include/hw/riscv/k230.h +++ b/include/hw/riscv/k230.h @@ -131,6 +131,9 @@ enum { K230_UART4_IRQ =3D 20, K230_WDT0_IRQ =3D 107, K230_WDT1_IRQ =3D 108, + 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 index 126b73ea01..a522fc4973 100644 --- a/tests/qtest/k230-dwc-ssi-test.c +++ b/tests/qtest/k230-dwc-ssi-test.c @@ -14,6 +14,8 @@ #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 @@ -63,6 +65,10 @@ #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 =20 #define K230_SSI_RXFTLR 0x01c #define K230_SSI_RXOICR 0x03c @@ -77,6 +83,7 @@ typedef struct K230SsiInstance { uint64_t base; uint32_t num_cs; uint32_t imr_reset; + uint32_t first_irq; } K230SsiInstance; =20 static const K230SsiInstance k230_ssi_instances[3] =3D { @@ -84,14 +91,17 @@ 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 0x0000001fU, + .first_irq =3D 155, }, { .base =3D K230_SPI2_BASE, .num_cs =3D 5, .imr_reset =3D 0x0000001fU, + .first_irq =3D 164, }, }; =20 @@ -112,6 +122,14 @@ static void k230_ssi_writel(QTestState *qts, uint64_t = base, qtest_writel(qts, base + offset, value); } =20 +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); @@ -287,14 +305,38 @@ static void test_interrupt_controller(void) =3D=3D, 1); g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & K230_SSI_INT_RXU, =3D=3D, 0); - k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_AXIECR, UINT32_MAX); - k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_DONECR, UINT32_MAX); - g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_AXIECR), - =3D=3D, 0); - g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_DONECR), - =3D=3D, 0); - g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & - (K230_SSI_INT_DONE | K230_SSI_INT_AXIE), =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); } =20 @@ -560,6 +602,7 @@ int main(int argc, char **argv) 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/eeprom-read-contract", test_eeprom_read_contract); --=20 2.43.0 From nobody Mon Sep 28 01:15:05 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=1786213939; cv=none; d=zohomail.com; s=zohoarc; b=mU72ZyOPvhGFfeHyjEoZFJiva4pm6rTJYhsUE1xfs5w1OoxT7QNVTrYixeaU5/xea8N+8ByR74zG250VnO+DMjVzE3Yf5Udnt34u2iV+H6L8+aBe7ehG37rw353fYzcqu+DYZZeFTpwit3NvvlQ9x8MFjdjcPa+UfA6NkF1MNS4= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1786213939; h=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=3WwhUV+c4Hz7g4wkB288txhSDzf0C0wY0jvSEyNIqvQ=; b=j7roFRFxy0yy9n3/kIa3CibzYmX6Wo9NItl9VC8wJfj1HqjsHO8yemHEL7Cuda4FfdusM5aCZqCu69hXcAHqQkyxyD2OBtxKyA7AvzWyNwYeb5ucaHafA/rr7czM01MSf9/Cn8FbG/PoLpa19NWxBDHTpEl37ktpaJtIJdZsp3E= 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 1786213939947277.3036140642148; Sat, 8 Aug 2026 11:32:19 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wslq8-0000fv-LV; Sat, 08 Aug 2026 14:32:12 -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 1wslq4-0000VJ-39 for qemu-devel@nongnu.org; Sat, 08 Aug 2026 14:32:09 -0400 Received: from mail-pj1-x102e.google.com ([2607:f8b0:4864:20::102e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wslq0-0007hS-Di for qemu-devel@nongnu.org; Sat, 08 Aug 2026 14:32:07 -0400 Received: by mail-pj1-x102e.google.com with SMTP id 98e67ed59e1d1-38759bcd877so558203a91.2 for ; Sat, 08 Aug 2026 11:32:03 -0700 (PDT) Received: from LAPTOP-CS7CB8K1.localdomain ([2409:8a00:6035:79d0:50fd:3a6e:508:beb2]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3925fc6c13asm6772393a91.2.2026.08.08.11.31.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 08 Aug 2026 11:32:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786213923; x=1786818723; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=3WwhUV+c4Hz7g4wkB288txhSDzf0C0wY0jvSEyNIqvQ=; b=dda9sSkEXsjylNrqeW+CelGupQ29eq7n1ESKzjJUZj8Tr1p0GsdLWTNMdtsVPUQFSi ru7E+JUzxjmYtPAF51qTTNwezscR7/umPUk13UI9424DJgWiklPBgwjqXDZCS+wtVJf9 xjFaZOkVW5ZJcUrfgCooNnvjy/OLW9YrTqMOnJknjujG+Pf7pWAwTFQFOjYSkGEX2f5z E08mz0Hk1d1XLPHJ8Po/AKZ8sPkJikEf7EADem48rlGCSp+wc0NmpIB/7oqyKZUrKdaH DkUysIaXLFXUnsySUpYfa1us/h9zhjf9n1rr8sAbjGxFK5s3vBb2fV8s6oJEKeqdTy7i PUaA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786213923; x=1786818723; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=3WwhUV+c4Hz7g4wkB288txhSDzf0C0wY0jvSEyNIqvQ=; b=HBe9YFt9DeFupifP2lmKoR8XiGvqMM42j0ACTc87ZvgxgmHLQlBdETPnd3mOs8wjW8 C9sJR7QZjUTvxofewq2FytE1vZKhMvLJlvPraQeh2lkb+FKZLpXDHbyAVacy+bcktAV5 eDDxsaPmne/0GdzMZgOMP5NpHH/ncFM8dORkGE/++y19JGUUBK6tJK5uVgOeaHpklnUy WOJGL4WGX9rqjn8oW6XOhZG0m0r9GC7nJpxu20yuCyLg8aI9qEoVu7CvFD+6myX2LZGP 4oO9vPBCExkYDKW61hktiDzHUWY+y765lenz/mXGc8SFwSX1gNYioRWhwVH6YTJ5e8LX oLqw== X-Gm-Message-State: AOJu0YwHtIImvX3INyQRUEHB8JlBAZZmrMSrsE32k6aUAtT+ADaReBMa gQIjJVKWO5jUFofLS3iiJgeNoH1BWcbCsJl/T5el0s4V6HrrolTjtNifHMynmWmj2ht2MA== X-Gm-Gg: AR+sD11OUr2uIA0G3wnOf3TKG9OEQYGbLqOFDHIN0OkD1VWFYHvrWkbwlHJHBhiQYXj znks6q5+C18rK2GwMpSwPug5puhDicFs7pZFeaKVG3jtah1Ois3Yf1kD3RxVYVfyBashLg9zJSC 8uypA8qGB+utnH4fzsfHoZwgw2S7uTqGzkZN3+PM0NgyblPnqEflR/JSnk9uhYlUCUP0VEPMNzf FodiAnWwkzF4RzDmgtB4jxbw3ZqEV/a5Eo89Ox16Mb0EuVR+3BhgL/YQHOgURjNSRldXpp4g0Iz JrmrhMaz/ae+xOCSqy8G476XNlrpv1rTAMFHqkeEYKYoMUHX9za8r3YCnsYzegsHLpgBaiRsLHl WM57VKuHxsCrF+3wWmcCUwVVOk6s2NxAnsmhTMQX/bwgN6mHskIBR9Fcquu9JpT7/dA7qogW9DO oix2MkFEhtnDwfB1cr5wOufPLG+CZ2Md9zcE+LF1Bem7yvZpB5vd75GsM27nbDatLCBmswjAzHi suliRWnGmM/WYDjVA== X-Received: by 2002:a17:90b:2245:b0:38f:bbc:6a0f with SMTP id 98e67ed59e1d1-3903c538565mr26465139a91.1.1786213922806; Sat, 08 Aug 2026 11:32:02 -0700 (PDT) From: Kangjie Huang To: qemu-devel@nongnu.org Cc: qemu-riscv@nongnu.org, 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 Subject: [PATCH v3 5/5] hw/riscv/k230: Attach a standard SPI NOR flash Date: Sun, 9 Aug 2026 02:29:56 +0800 Message-ID: <20260808182956.290561-6-flamboyant.h.01@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260808182956.290561-1-flamboyant.h.01@gmail.com> References: <20260808182956.290561-1-flamboyant.h.01@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable 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::102e; envelope-from=flamboyant.h.01@gmail.com; helo=mail-pj1-x102e.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: 1786213941463158500 Content-Type: text/plain; charset="utf-8" Add the optional spi-flash machine property and attach the selected M25P80-compatible flash device to logical spi0 chip select 0. Use the supplied MTD backend when present and retain the erased-flash default otherwise. Document the machine option and add qtests for Standard 1-1-1 JEDEC identification and fixed-address reads. Standard 1-1-1 transfers were also exercised manually through the K230 SDK U-Boot and Linux SPI paths against the attached flash. The enhanced SPI and IDMA boot paths are outside the scope of this series. Signed-off-by: Kangjie Huang Suggested-by: Anirudh Srinivasan Suggested-by: Bin Meng Suggested-by: Chao Liu --- 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 | 85 +++++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+) diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst index 7a49cac95b..620ef263de 100644 --- a/docs/system/riscv/k230.rst +++ b/docs/system/riscv/k230.rst @@ -21,6 +21,7 @@ The ``k230`` machine supports the following devices: * 2 K230 Watchdog Timer * 5 UART * 3 K230 SSI controllers for SPI only +* Optional SPI NOR flash on spi0 CS0 =20 Boot options ------------ diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index 0bc0d18ca2..94bf786ddc 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -163,3 +163,4 @@ config K230 select UNIMP select K230_WDT select DWC_SSI + select SSI_M25P80 diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index 4911f2d1be..c2c101758d 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 @@ -541,6 +544,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, @@ -572,6 +618,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); @@ -584,6 +636,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); @@ -593,6 +652,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 { @@ -600,6 +665,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 4eee78ddec..1fe5713e53 100644 --- a/include/hw/riscv/k230.h +++ b/include/hw/riscv/k230.h @@ -52,6 +52,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 a522fc4973..807f143987 100644 --- a/tests/qtest/k230-dwc-ssi-test.c +++ b/tests/qtest/k230-dwc-ssi-test.c @@ -78,6 +78,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; @@ -577,6 +578,88 @@ 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); + + 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); +} + static void test_unsupported_registers(void) { QTestState *qts =3D k230_ssi_start(); @@ -613,5 +696,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