From nobody Sat Aug 1 21:32:41 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=1785612652; cv=none; d=zohomail.com; s=zohoarc; b=JrmgMhdbQ7bRCNBB6W+E1poQlwazQjy0eUVGGmHqlH0ye/FYj+f2WZlXQESLaPrtwn4Q8Y+S2CoR2nax/ZOJK0JR2fmiDrKcr2994ogJJrzPdzF37nkujGJk6Hr/O/fDXU2hjUkURcQ1sw+WJ4cjqEW+7avQRjO1q5kdJ/O4b/s= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1785612652; 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=D8D23kkpS8WmoYzlRLgMJLgWMVLmfPsq5rgeYo2sShY=; b=exSk6XIy6BWEYdgPmbHMoBx7VOuzqFh+GmTSxRsZY1kGRu4XHdosstmIWN4i3d0j4EPQOEDbZJ6t92WFNXlgpdemINRg2tfcDtWHsatB6TiE8P9gUolaIzoqDU6pmkCWlbT6baMqRXNnIo+G/6TH5hqgaqMWRaAwgV37MUzuYKA= 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 1785612652558306.9610795589982; Sat, 1 Aug 2026 12:30:52 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wqFPZ-0002rZ-5v; Sat, 01 Aug 2026 15:30:21 -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 1wqFPX-0002r9-J6 for qemu-devel@nongnu.org; Sat, 01 Aug 2026 15:30:19 -0400 Received: from mail-pj1-x1036.google.com ([2607:f8b0:4864:20::1036]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wqFPT-0002Hc-VD for qemu-devel@nongnu.org; Sat, 01 Aug 2026 15:30:19 -0400 Received: by mail-pj1-x1036.google.com with SMTP id 98e67ed59e1d1-381216921aaso1926177a91.1 for ; Sat, 01 Aug 2026 12:30:15 -0700 (PDT) Received: from localhost.localdomain ([111.198.227.173]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-38fb2bac654sm2148766a91.10.2026.08.01.12.30.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 01 Aug 2026 12:30:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785612614; x=1786217414; 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=D8D23kkpS8WmoYzlRLgMJLgWMVLmfPsq5rgeYo2sShY=; b=NnP4JWg2r0XOnnOJEZf6ywMIckAb+DNgJVwZGbINLJPup+mxz22lQkx8aQk7da9I0t uQSjQA1mYAIbjywDbGnZB1Drsxlfg+kC5Cc3nt55By30NHIfKxBq+ZhBPji6aZtqSnLR sloV+1viOw5MwL7s1a9R5CKT7LWmxSNz83tkL2X7Oo62Z2dCvNJp8ABCYsOupc1P0H51 ObrDjG0vqXdBxRlQQ10jm10eIJ8h0PJLNSEgv9cCSb8BMNSv7WvCI3Vx/EMFEn3C8Zsi yPzkA0gE2rrU0WR56KINLrv007ziFqsU/UtWcdjjm3+Xi3KtfdQ5tJNA3pIVQpWPueNB Eztw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785612614; x=1786217414; 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=D8D23kkpS8WmoYzlRLgMJLgWMVLmfPsq5rgeYo2sShY=; b=kiUgCn4H52zzShdp5e1JYb9W9wFMAacB5RtVLvP3vkiS2vfprK750MpgJHnQcRykrh GfLdUSvU5pNuL37ONtjJcQCBKeKFJuTQFNHvFRjZfVgkXYa2tmsp1vS4bIKcuJI44bhb zbNEVMMHCbLOEPr8DgT8wsObRJY8ASpfghB55abhZ6xZoTyq9tud5ou+DS+uDjjZrWoP xBPIzPICA/Nbi/vxS3vk/PEZv7xS/pZIO7hSasgMA/iFh+inQJ8X5Q1GjRSrk2Bx+7bF bLTtWml27A8cQYre50DRbtWbf0HoXQwG+o7g4lM+IxcGqLcA7kDUGwayuX1ex4NVaWiR SK3w== X-Gm-Message-State: AOJu0YzKD+NSYkDCMJu6tfKq09mjH420GnLYT0tjtov9G1qRLIuuX8Td lsP5TbKl8wR8XXqoGiGe2THoa+74O5lE4pHukBHoNgJdGuttP114q8P9xUTGecIRV2vb1Q== X-Gm-Gg: AR+sD12536y65GsGtrj0M1D36KsKsJFPnSEQiY+kcEybvEu5ns7MjQcr0PUi10GNjLs DLDsRFyKIhIOwAC7OVVBUGYo6Na7A6fxpHwrtORm8adpGzZHAlCGL/MPkwM9+beLyaG8Kt4M6t9 mMqGMcxCAyPDroViKSYRgpwDFT/lmf7y/Yn6gOMZE2J8y1Q79NBqWzJ8RGKv05BbqvmPM3v+hq8 jU/C4xlU8QG8MFSa1E38Bg+65Kw8i2e3ETp9ed8DUYJHhKmvk8tyTKkprKKfEhM6uKsPH5hAeFK Po57DARPt46CIDGOQM9+7h4b4/RTbbUs8Myk5CahVu9+zLh7KzyUeBoGn3W2HR9FNAk1TdGQab/ 34xvlsGuw/veur27UZ0W2ToagwAKJpuBdrVzY2EYcCHkrsglsqzu2uhqMciL+c72iRBek/U26fW vggWVIRz7zu428vkUOD4ZlFLUcdNUWzDRZmzPIIIc8LeN8oTTRCZ5QKKNzfS9AmlZ5WsXV6nPHQ p+9dbrmPw== X-Received: by 2002:a17:90b:5823:b0:38e:740f:9764 with SMTP id 98e67ed59e1d1-38fbc49eadbmr3967395a91.18.1785612614284; Sat, 01 Aug 2026 12:30:14 -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 , Kangjie Huang Subject: [PATCH v2 1/5] hw/ssi: Add Synopsys DesignWare SSI standard PIO controller Date: Sun, 2 Aug 2026 03:28:44 +0800 Message-ID: <20260801192848.30606-2-flamboyant.h.01@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260801192848.30606-1-flamboyant.h.01@gmail.com> References: <20260801192848.30606-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::1036; envelope-from=flamboyant.h.01@gmail.com; helo=mail-pj1-x1036.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: 1785612655978158500 Content-Type: text/plain; charset="utf-8" Add a reusable SysBus model for the Synopsys DesignWare SSI controller. 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 controller is not instantiated by this patch. Signed-off-by: Kangjie Huang --- hw/ssi/Kconfig | 4 + hw/ssi/dw_ssi.c | 956 ++++++++++++++++++++++++++++++++++++++++ hw/ssi/meson.build | 1 + include/hw/ssi/dw_ssi.h | 62 +++ 4 files changed, 1023 insertions(+) create mode 100644 hw/ssi/dw_ssi.c create mode 100644 include/hw/ssi/dw_ssi.h diff --git a/hw/ssi/Kconfig b/hw/ssi/Kconfig index 1bd56463c1..c48cd2ff6d 100644 --- a/hw/ssi/Kconfig +++ b/hw/ssi/Kconfig @@ -6,6 +6,10 @@ config SIFIVE_SPI bool select SSI =20 +config DW_SSI + bool + select SSI + config SSI bool =20 diff --git a/hw/ssi/dw_ssi.c b/hw/ssi/dw_ssi.c new file mode 100644 index 0000000000..f9f0e23730 --- /dev/null +++ b/hw/ssi/dw_ssi.c @@ -0,0 +1,956 @@ +/* + * 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/dw_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 DW_SSI_CTRLR0_RESET 0x00004007 +#define DW_SSI_SR_RESET 0x00000006 +#define DW_SSI_IDR_RESET 0xa1b2c3d5 +#define DW_SSI_VERSION 0x3130332a +#define DW_SSI_PIO_TX_BATCH 64 + +enum { + DW_SSI_TMOD_TR, + DW_SSI_TMOD_TO, + DW_SSI_TMOD_RO, + DW_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 DW_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 DW_SSI_CTRLR1_WRITABLE_MASK R_CTRLR1_NDF_MASK +#define DW_SSI_MWCR_WRITABLE_MASK \ + (R_MWCR_MWMOD_MASK | R_MWCR_MDD_MASK) +#define DW_SSI_BAUDR_WRITABLE_MASK R_BAUDR_SCKDV_MASK +#define DW_SSI_TXFTLR_WRITABLE_MASK \ + (R_TXFTLR_TFT_MASK | R_TXFTLR_TXFTHR_MASK) +#define DW_SSI_RXFTLR_WRITABLE_MASK R_RXFTLR_RFT_MASK +#define DW_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 DW_SSI_RX_SAMPLE_DELAY_WRITABLE_MASK \ + (R_RX_SAMPLE_DELAY_RSD_MASK | R_RX_SAMPLE_DELAY_SE_MASK) + +static bool dw_ssi_validate_config(DwSsiState *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 & ~DW_SSI_IMR_WRITABLE_MASK) { + error_setg(errp, "%s: imr-reset contains unsupported bits", + dev->canonical_path); + return false; + } + + return true; +} + +static bool dw_ssi_fifo_threshold_valid(const DwSsiState *s, uint32_t valu= e) +{ + return value < s->cfg.fifo_depth; +} + +static void dw_ssi_write_masked(DwSsiState *s, unsigned int reg, + uint32_t value, uint32_t mask) +{ + s->regs[reg] =3D (s->regs[reg] & ~mask) | (value & mask); +} + +static uint32_t dw_ssi_frame_masked(DwSsiState *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 dw_ssi_is_enabled(const DwSsiState *s) +{ + return FIELD_EX32(s->regs[R_SSIENR], SSIENR, SSIC_EN); +} + +static void dw_ssi_deselect(DwSsiState *s) +{ + if (s->active_cs < 0) { + return; + } + + qemu_irq_raise(s->cs_lines[s->active_cs]); + s->active_cs =3D -1; +} + +static void dw_ssi_select(DwSsiState *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); + dw_ssi_deselect(s); + return; + } + + if (s->active_cs =3D=3D cs) { + return; + } + + dw_ssi_deselect(s); + qemu_irq_lower(s->cs_lines[cs]); + s->active_cs =3D cs; +} + +static void dw_ssi_update_cs(DwSsiState *s) +{ + uint32_t ser =3D s->regs[R_SER]; + + if (!dw_ssi_is_enabled(s) || !ser) { + dw_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); + dw_ssi_deselect(s); + return; + } + + dw_ssi_select(s, ctz32(ser)); +} + +static void dw_ssi_abort_transfer(DwSsiState *s) +{ + dw_ssi_deselect(s); + fifo32_reset(&s->tx_fifo); + fifo32_reset(&s->rx_fifo); + s->phase =3D DW_SSI_PHASE_IDLE; + s->remaining_frames =3D 0; +} + +static uint32_t dw_ssi_status(DwSsiState *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 DW_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 dw_ssi_run_transfer(DwSsiState *s); + +static void dw_ssi_push_tx(DwSsiState *s, uint32_t tx) +{ + if (!dw_ssi_is_enabled(s)) { + return; + } + + if (fifo32_is_full(&s->tx_fifo)) { + return; + } + + fifo32_push(&s->tx_fifo, tx); + + if (s->phase !=3D DW_SSI_PHASE_STANDARD_TX_ONLY) { + dw_ssi_run_transfer(s); + } +} + +static uint32_t dw_ssi_send_frame(DwSsiState *s, + uint32_t tx) +{ + uint32_t mask =3D dw_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 dw_ssi_run_transfer(DwSsiState *s) +{ + uint32_t tmod; + + if (!dw_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 DW_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 DW_SSI_TMOD_TR: + if (s->phase =3D=3D DW_SSI_PHASE_IDLE) { + s->phase =3D DW_SSI_PHASE_STANDARD_TR; + } + + while (!fifo32_is_empty(&s->tx_fifo)) { + uint32_t tx =3D fifo32_pop(&s->tx_fifo); + uint32_t rx =3D dw_ssi_send_frame(s, tx); + + if (!fifo32_is_full(&s->rx_fifo)) { + fifo32_push(&s->rx_fifo, rx); + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: RX FIFO full, dropping frame\n", + DEVICE(s)->canonical_path); + break; + } + } + + if (fifo32_is_empty(&s->tx_fifo)) { + s->phase =3D DW_SSI_PHASE_IDLE; + } + break; + case DW_SSI_TMOD_TO: { + unsigned int frames =3D 0; + + if (fifo32_is_empty(&s->tx_fifo)) { + s->phase =3D DW_SSI_PHASE_IDLE; + break; + } + + s->phase =3D DW_SSI_PHASE_STANDARD_TX_ONLY; + while (!fifo32_is_empty(&s->tx_fifo) && + frames < DW_SSI_PIO_TX_BATCH) { + uint32_t tx =3D fifo32_pop(&s->tx_fifo); + + dw_ssi_send_frame(s, tx); + frames++; + } + if (fifo32_is_empty(&s->tx_fifo)) { + s->phase =3D DW_SSI_PHASE_IDLE; + } + break; + } + case DW_SSI_TMOD_RO: + switch (s->phase) { + case DW_SSI_PHASE_IDLE: + if (fifo32_is_empty(&s->tx_fifo)) { + break; + } + fifo32_pop(&s->tx_fifo); + s->phase =3D DW_SSI_PHASE_RX_ONLY; + s->remaining_frames =3D FIELD_EX32(s->regs[R_CTRLR1], CTRLR1, + NDF) + 1; + /* Fall through. */ + case DW_SSI_PHASE_RX_ONLY: + while (!fifo32_is_full(&s->rx_fifo) && + s->remaining_frames > 0) { + uint32_t rx =3D dw_ssi_send_frame(s, 0x00); + + fifo32_push(&s->rx_fifo, rx); + s->remaining_frames--; + } + if (s->remaining_frames =3D=3D 0) { + s->phase =3D DW_SSI_PHASE_IDLE; + } + break; + } + break; + case DW_SSI_TMOD_EEPROM_READ: + switch (s->phase) { + case DW_SSI_PHASE_IDLE: + if (fifo32_is_empty(&s->tx_fifo)) { + break; + } + s->phase =3D DW_SSI_PHASE_EEPROM_COMMAND; + /* Fall through. */ + case DW_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); + + dw_ssi_send_frame(s, tx); + } + s->phase =3D DW_SSI_PHASE_EEPROM_DATA; + s->remaining_frames =3D FIELD_EX32(s->regs[R_CTRLR1], CTRLR1, + NDF) + 1; + /* Fall through. */ + case DW_SSI_PHASE_EEPROM_DATA: + while (!fifo32_is_full(&s->rx_fifo) && + s->remaining_frames > 0) { + uint32_t rx =3D dw_ssi_send_frame(s, 0x00); + + fifo32_push(&s->rx_fifo, rx); + s->remaining_frames--; + } + if (s->remaining_frames =3D=3D 0) { + s->phase =3D DW_SSI_PHASE_IDLE; + } + break; + } + break; + default: + g_assert_not_reached(); + } +} + +static bool dw_ssi_is_dr(hwaddr addr) +{ + return addr >=3D A_DR0 && addr <=3D A_DR_END && + (addr & 0x3) =3D=3D 0; +} + +static bool dw_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 dw_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 dw_ssi_unsupported_read(DwSsiState *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 dw_ssi_unsupported_write(DwSsiState *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 dw_ssi_read(void *opaque, hwaddr addr, unsigned int size) +{ + DwSsiState *s =3D DW_SSI(opaque); + uint32_t value =3D 0; + + if (dw_ssi_is_dr(addr)) { + if (!fifo32_is_empty(&s->rx_fifo)) { + value =3D fifo32_pop(&s->rx_fifo) & dw_ssi_frame_masked(s); + } else { + value =3D 0; + } + + dw_ssi_run_transfer(s); + return value; + } + + if (dw_ssi_is_unsupported_offset(addr)) { + value =3D dw_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 DW_SSI_PHASE_STANDARD_TX_ONLY) { + dw_ssi_run_transfer(s); + } + break; + case A_RXFLR: + value =3D fifo32_num_used(&s->rx_fifo); + break; + case A_SR: + value =3D dw_ssi_status(s); + if (s->phase =3D=3D DW_SSI_PHASE_STANDARD_TX_ONLY) { + dw_ssi_run_transfer(s); + } + break; + default: + if (addr >=3D DW_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 dw_ssi_write(void *opaque, hwaddr addr, + uint64_t value, unsigned int size) +{ + DwSsiState *s =3D DW_SSI(opaque); + + if (dw_ssi_is_dr(addr)) { + dw_ssi_push_tx(s, value); + return; + } + + if (dw_ssi_is_unsupported_offset(addr)) { + dw_ssi_unsupported_write(s, addr, value); + return; + } + + if (dw_ssi_write_requires_disabled(addr) && + dw_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: + value &=3D ~R_CTRLR0_SPI_FRF_MASK; + dw_ssi_write_masked(s, R_CTRLR0, value, + DW_SSI_CTRLR0_STANDARD_WRITABLE_MASK); + break; + case A_CTRLR1: + dw_ssi_write_masked(s, R_CTRLR1, value, + DW_SSI_CTRLR1_WRITABLE_MASK); + break; + case A_SSIENR: { + bool old_enabled =3D dw_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) { + dw_ssi_abort_transfer(s); + return; + } + + dw_ssi_update_cs(s); + dw_ssi_run_transfer(s); + break; + } + case A_MWCR: + dw_ssi_write_masked(s, R_MWCR, value, + DW_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 DW_SSI_PHASE_IDLE) { + s->phase =3D DW_SSI_PHASE_IDLE; + s->remaining_frames =3D 0; + } + s->regs[R_SER] =3D new_ser; + + dw_ssi_update_cs(s); + dw_ssi_run_transfer(s); + break; + } + case A_BAUDR: + dw_ssi_write_masked(s, R_BAUDR, value, + DW_SSI_BAUDR_WRITABLE_MASK); + break; + case A_TXFTLR: + if (!dw_ssi_fifo_threshold_valid( + s, FIELD_EX32(value, TXFTLR, TFT)) || + !dw_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; + } + dw_ssi_write_masked(s, R_TXFTLR, value, + DW_SSI_TXFTLR_WRITABLE_MASK); + dw_ssi_run_transfer(s); + break; + case A_RXFTLR: + if (!dw_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; + } + dw_ssi_write_masked(s, R_RXFTLR, value, + DW_SSI_RXFTLR_WRITABLE_MASK); + break; + case A_TXFLR: + case A_RXFLR: + case A_SR: + break; + case A_IMR: + dw_ssi_write_masked(s, R_IMR, value, + DW_SSI_IMR_WRITABLE_MASK); + break; + case A_IDR: + case A_SSIC_VERSION_ID: + break; + default: + if (addr >=3D DW_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 dw_ssi_ops =3D { + .read =3D dw_ssi_read, + .write =3D dw_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 dw_ssi_enter_reset(Object *obj, ResetType type) +{ + DwSsiState *s =3D DW_SSI(obj); + + memset(s->regs, 0, sizeof(s->regs)); + fifo32_reset(&s->tx_fifo); + fifo32_reset(&s->rx_fifo); + s->phase =3D DW_SSI_PHASE_IDLE; + s->remaining_frames =3D 0; + + s->regs[R_CTRLR0] =3D DW_SSI_CTRLR0_RESET; + s->regs[R_SR] =3D DW_SSI_SR_RESET; + s->regs[R_IMR] =3D s->cfg.imr_reset & DW_SSI_IMR_WRITABLE_MASK; + s->regs[R_IDR] =3D DW_SSI_IDR_RESET; + s->regs[R_SSIC_VERSION_ID] =3D DW_SSI_VERSION; +} + +static void dw_ssi_hold_reset(Object *obj, ResetType type) +{ + DwSsiState *s =3D DW_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 int dw_ssi_post_load(void *opaque, int version_id) +{ + DwSsiState *s =3D opaque; + uint32_t ndf =3D FIELD_EX32(s->regs[R_CTRLR1], CTRLR1, NDF) + 1; + + if (s->active_cs < -1 || s->active_cs >=3D (int)s->cfg.num_cs) { + return -EINVAL; + } + if (s->phase > DW_SSI_PHASE_STANDARD_TR) { + return -EINVAL; + } + if (s->phase =3D=3D DW_SSI_PHASE_RX_ONLY || + s->phase =3D=3D DW_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 && + (!dw_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_dw_ssi =3D { + .name =3D TYPE_DW_SSI, + .version_id =3D 1, + .minimum_version_id =3D 1, + .post_load =3D dw_ssi_post_load, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT32_EQUAL(cfg.num_cs, DwSsiState), + VMSTATE_UINT32_EQUAL(cfg.fifo_depth, DwSsiState), + VMSTATE_UINT32_EQUAL(cfg.imr_reset, DwSsiState), + VMSTATE_UINT32_ARRAY(regs, DwSsiState, DW_SSI_NUM_REGS), + VMSTATE_FIFO32(tx_fifo, DwSsiState), + VMSTATE_FIFO32(rx_fifo, DwSsiState), + VMSTATE_UINT32(phase, DwSsiState), + VMSTATE_UINT32(remaining_frames, DwSsiState), + VMSTATE_INT32(active_cs, DwSsiState), + VMSTATE_END_OF_LIST() + }, +}; + +static void dw_ssi_init(Object *obj) +{ + DwSsiState *s =3D DW_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, &dw_ssi_ops, s, + TYPE_DW_SSI, DW_SSI_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->mmio); + + s->active_cs =3D -1; +} + +static void dw_ssi_realize(DeviceState *dev, Error **errp) +{ + DwSsiState *s =3D DW_SSI(dev); + + if (!dw_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 dw_ssi_finalize(Object *obj) +{ + DwSsiState *s =3D DW_SSI(obj); + + fifo32_destroy(&s->tx_fifo); + fifo32_destroy(&s->rx_fifo); + g_free(s->cs_lines); +} + +static const Property dw_ssi_properties[] =3D { + DEFINE_PROP_UINT32("num-cs", DwSsiState, cfg.num_cs, 1), + DEFINE_PROP_UINT32("fifo-depth", DwSsiState, cfg.fifo_depth, 256), + DEFINE_PROP_UINT32("imr-reset", DwSsiState, + cfg.imr_reset, 0x0000003f), +}; + +static void dw_ssi_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + ResettableClass *rc =3D RESETTABLE_CLASS(klass); + + dc->realize =3D dw_ssi_realize; + dc->vmsd =3D &vmstate_dw_ssi; + device_class_set_props(dc, dw_ssi_properties); + rc->phases.enter =3D dw_ssi_enter_reset; + rc->phases.hold =3D dw_ssi_hold_reset; +} + +static const TypeInfo dw_ssi_info =3D { + .name =3D TYPE_DW_SSI, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(DwSsiState), + .instance_init =3D dw_ssi_init, + .instance_finalize =3D dw_ssi_finalize, + .class_init =3D dw_ssi_class_init, +}; + +static void dw_ssi_register_types(void) +{ + type_register_static(&dw_ssi_info); +} + +type_init(dw_ssi_register_types) diff --git a/hw/ssi/meson.build b/hw/ssi/meson.build index 6afb1ea200..447780846c 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_DW_SSI', if_true: files('dw_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/dw_ssi.h b/include/hw/ssi/dw_ssi.h new file mode 100644 index 0000000000..a74e906ef8 --- /dev/null +++ b/include/hw/ssi/dw_ssi.h @@ -0,0 +1,62 @@ +/* + * 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_DW_SSI_H +#define HW_SSI_DW_SSI_H + +#include "hw/core/sysbus.h" +#include "hw/ssi/ssi.h" +#include "qemu/fifo32.h" +#include "qom/object.h" + +#define TYPE_DW_SSI "designware-ssi" +OBJECT_DECLARE_SIMPLE_TYPE(DwSsiState, DW_SSI) + +#define DW_SSI_MMIO_SIZE 0x1000 +#define DW_SSI_REGS_SIZE 0x14c +#define DW_SSI_NUM_REGS \ + (DW_SSI_REGS_SIZE / sizeof(uint32_t)) + +typedef enum DwSsiPhase { + DW_SSI_PHASE_IDLE, + DW_SSI_PHASE_STANDARD_TX_ONLY, + DW_SSI_PHASE_RX_ONLY, + DW_SSI_PHASE_EEPROM_COMMAND, + DW_SSI_PHASE_EEPROM_DATA, + DW_SSI_PHASE_STANDARD_TR, +} DwSsiPhase; + +typedef struct DwSsiConfig { + uint32_t num_cs; + uint32_t fifo_depth; + uint32_t imr_reset; +} DwSsiConfig; + +struct DwSsiState { + SysBusDevice parent_obj; + + MemoryRegion mmio; + SSIBus *spi; + + qemu_irq *cs_lines; + + Fifo32 tx_fifo; + Fifo32 rx_fifo; + uint32_t regs[DW_SSI_NUM_REGS]; + + DwSsiConfig cfg; + + uint32_t phase; + uint32_t remaining_frames; + + int active_cs; +}; +#endif /* HW_SSI_DW_SSI_H */ --=20 2.43.0 From nobody Sat Aug 1 21:32:41 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=1785612696; cv=none; d=zohomail.com; s=zohoarc; b=m3YaN6akli5Oe3gglNE+ibVE859a1XSGjSWDjPSElV5dtPt8As6nxQQH0T0eOPvV4Cq4SfvL5chVrI5G5v280cwQRU/dRaEoAuTBbyCiUqBxBMdfHt2wCcikuGAURfElmH2F08NhhvSPSYzjxt83FClD07Tak6MSnJJLftnhw18= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1785612696; 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=rictVP0n1BHRIjGHJIrYt/EJeZRqsVqeFgYRbXHONws=; b=U4xqVdTZwn8vbjpPSXMRekwPGnAtzCA/27g+5pqPj1o4FnV9Qxce/9KApzv3zedPmYaUgeVkJ13Ebk1/uCM5om8kBqljRtOVoSFYY8d4tvawBCmzJi1s7/MnBG96ZMzAsm6w8b2ctefDRHVhVyEkKjiHq8UmxTptjgjWNiBX/Sw= 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 1785612696134750.8621170349251; Sat, 1 Aug 2026 12:31:36 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wqFPi-0002sq-42; Sat, 01 Aug 2026 15:30:30 -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 1wqFPg-0002sQ-FO for qemu-devel@nongnu.org; Sat, 01 Aug 2026 15:30:28 -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 1wqFPe-0002I8-9l for qemu-devel@nongnu.org; Sat, 01 Aug 2026 15:30:28 -0400 Received: by mail-pj1-x1029.google.com with SMTP id 98e67ed59e1d1-382ef647e20so2299093a91.1 for ; Sat, 01 Aug 2026 12:30:25 -0700 (PDT) Received: from localhost.localdomain ([111.198.227.173]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-38fb2bac654sm2148766a91.10.2026.08.01.12.30.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 01 Aug 2026 12:30:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785612625; x=1786217425; 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=rictVP0n1BHRIjGHJIrYt/EJeZRqsVqeFgYRbXHONws=; b=bFMF0mS4ghSkvXat1MTYTWL1JikSgMPbtm7SIGRGO7rQm6iw6ad9ZyI8RH7ilzkg2m AtyDcTVIJUlnS73iV7v4Ma+dt1HI0QsANaixaQYSbUPh3Vx98LVfsFnP4YMYIn2fsxh+ LtYW+ACgFoCC6AI2hrK3CVsIVPtRySHD/Z9/A7fLv8kDUdbfNSBSnWCZBf8Lx9fpwELj z/tDxVvdvurxG0IJrLbPaqwELQ4Q2ITba/ZyxVBDa5FG1IN0s94r4p6qabzKNYOfBOeZ n/OIPdZ6MV/BR0deqpEfC0tP25WaDq/uijnZYaVdseUb8eTt/wStjN4uRKoDo8fXBdLi QC9w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785612625; x=1786217425; 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=rictVP0n1BHRIjGHJIrYt/EJeZRqsVqeFgYRbXHONws=; b=L3/ftjkUqvusFp3qYMdSM0B4AxJj8VZ+bP3Hx5IBrL0RVh12HKQYU8flzFyISPC+sd 1PvStxMRa0Na8ejnuiwlAcQtQm2mZvihQDePW8YGvHwhFoLo7rka/2jquqqd43r/ibYB lhpw1mbs9EL1TxwwRoHXii67YXzW+4ZqJqV8gI2hfuqC42YGZYuT2rDQkwMcCR4do54z FuQJt5c/sl4WsYw8JbS9DbuNScn1C326VHfwWhr3OaXx/zUJmOVCvBEhkTZFfSheTitC ZNg3Gv+ROaI5FDLazXUkDfPY1en1y8czGEw8wU5SlzUnULwMncVevmFepUKtTobE9A0v TeSQ== X-Gm-Message-State: AOJu0Yx3ucInPV/rMXeCcScGvlzt8JOwLHZBgXqeVsCXz6JY5I50nX23 Wj+jQDZ5LiAUEOkpcVZWllFXNr6FPpwvyr4ui0+i3UuWkl6dOCxxfHCsoQ7ab35OeG/8sA== X-Gm-Gg: AR+sD10wy7Ww97gNDwz/Mv7DC+EojabKypGY+ei4fjgUMqqQx9ZXj6DEJ3qjSOTv3Bu 9dGhRxGNA1hZR3h80vc0nfHDVivS7jCBiJdWTu9fI7qDOqr5l8ZiW6wuVSLucyRQeI3Hlnmrtlc lWacAV54WVecHGH6lQ2K5ORoE0vjaKlT1UzO5/zxzVz74ikNwtVnLNj4yUiLZRaor3JvwlydMFv z5QS5cQbH+UUsH/f1zoFHo09wKYM97PJ7jnIIXyECVNG7jVqJc7drxfstKYNKM5yISFFwZOKYGq YPFwaYeeBxKYKqTm/fiCnz96kUyjoHDTVF8/afXz8Kuzukhj14MARUq4TL8uRHD0c62xYKi0lpy 7w0iQpMpsSSdn7z6fyg2DAl0XikL/VCzIqYfI1aXOGfUeEYo2bhbHRy71H7wnh4DKVrYlpjv+Op 7qGw3ANLrPdF7VGHwMgq3RzqeT+WZ1B34SZs2QW4Pb83AbJaLJeo1V1gb/9I0n3RmNrVKil3oV+ Hiyuf2uG2ZUrsoNdr7W X-Received: by 2002:a17:90b:3851:b0:38e:dc4:3f64 with SMTP id 98e67ed59e1d1-38fbc52da42mr3876668a91.38.1785612624855; Sat, 01 Aug 2026 12:30:24 -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 , Kangjie Huang Subject: [PATCH v2 2/5] hw/ssi: Add DesignWare SSI standard interrupt support Date: Sun, 2 Aug 2026 03:28:45 +0800 Message-ID: <20260801192848.30606-3-flamboyant.h.01@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260801192848.30606-1-flamboyant.h.01@gmail.com> References: <20260801192848.30606-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: 1785612697611158500 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 nine physical interrupt outputs used by the K230 SSI integration. TXU, DONE, and AXIE depend on transfer engines outside the Standard PIO scope and remain deasserted in this model. Signed-off-by: Kangjie Huang --- hw/ssi/dw_ssi.c | 129 +++++++++++++++++++++++++++++++++++++++- include/hw/ssi/dw_ssi.h | 18 +++++- 2 files changed, 145 insertions(+), 2 deletions(-) diff --git a/hw/ssi/dw_ssi.c b/hw/ssi/dw_ssi.c index f9f0e23730..57bca1687b 100644 --- a/hw/ssi/dw_ssi.c +++ b/hw/ssi/dw_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 DW_SSI_IDR_RESET 0xa1b2c3d5 #define DW_SSI_VERSION 0x3130332a #define DW_SSI_PIO_TX_BATCH 64 +#define DW_SSI_IRQ_VALID_MASK 0x000009bf +#define DW_SSI_IMPLEMENTED_LATCHED_IRQ_MASK \ + (R_RISR_TXOIR_MASK | R_RISR_RXOIR_MASK | R_RISR_RXUIR_MASK) =20 enum { DW_SSI_TMOD_TR, @@ -217,6 +220,22 @@ REG32(XIP_WRITE_CTRL, 0x148) #define DW_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 dw_ssi_irq_status_mask[DW_SSI_IRQ_COUNT] =3D { + [DW_SSI_IRQ_TXE] =3D R_RISR_TXEIR_MASK, + [DW_SSI_IRQ_TXO] =3D R_RISR_TXOIR_MASK, + [DW_SSI_IRQ_RXF] =3D R_RISR_RXFIR_MASK, + [DW_SSI_IRQ_RXO] =3D R_RISR_RXOIR_MASK, + [DW_SSI_IRQ_TXU] =3D R_RISR_TXUIR_MASK, + [DW_SSI_IRQ_RXU] =3D R_RISR_RXUIR_MASK, + [DW_SSI_IRQ_MST] =3D R_RISR_MSTIR_MASK, + [DW_SSI_IRQ_DONE] =3D R_RISR_DONER_MASK, + [DW_SSI_IRQ_AXIE] =3D R_RISR_AXIER_MASK, +}; + static bool dw_ssi_validate_config(DwSsiState *s, Error **errp) { DeviceState *dev =3D DEVICE(s); @@ -253,6 +272,45 @@ static void dw_ssi_write_masked(DwSsiState *s, unsigne= d int reg, s->regs[reg] =3D (s->regs[reg] & ~mask) | (value & mask); } =20 +static uint32_t dw_ssi_irq_raw_status(DwSsiState *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 & DW_SSI_IRQ_VALID_MASK; +} + +static void dw_ssi_update_irq(DwSsiState *s) +{ + uint32_t status =3D dw_ssi_irq_raw_status(s) & + s->regs[R_IMR] & DW_SSI_IRQ_VALID_MASK; + + for (int i =3D 0; i < DW_SSI_IRQ_COUNT; i++) { + qemu_set_irq(s->irqs[i], !!(status & dw_ssi_irq_status_mask[i])); + } +} + +static uint32_t dw_ssi_irq_read_clear(DwSsiState *s, + uint32_t clear_mask) +{ + uint32_t active =3D s->irq_latched & clear_mask; + + s->irq_latched &=3D ~clear_mask; + dw_ssi_update_irq(s); + return !!active; +} + static uint32_t dw_ssi_frame_masked(DwSsiState *s) { unsigned int bits =3D FIELD_EX32(s->regs[R_CTRLR0], CTRLR0, DFS) + 1; @@ -321,6 +379,7 @@ static void dw_ssi_abort_transfer(DwSsiState *s) fifo32_reset(&s->rx_fifo); s->phase =3D DW_SSI_PHASE_IDLE; s->remaining_frames =3D 0; + dw_ssi_update_irq(s); } =20 static uint32_t dw_ssi_status(DwSsiState *s) @@ -348,6 +407,8 @@ static void dw_ssi_push_tx(DwSsiState *s, uint32_t tx) } =20 if (fifo32_is_full(&s->tx_fifo)) { + s->irq_latched |=3D R_RISR_TXOIR_MASK; + dw_ssi_update_irq(s); return; } =20 @@ -356,6 +417,7 @@ static void dw_ssi_push_tx(DwSsiState *s, uint32_t tx) if (s->phase !=3D DW_SSI_PHASE_STANDARD_TX_ONLY) { dw_ssi_run_transfer(s); } + dw_ssi_update_irq(s); } =20 static uint32_t dw_ssi_send_frame(DwSsiState *s, @@ -415,6 +477,7 @@ static void dw_ssi_run_transfer(DwSsiState *s) if (!fifo32_is_full(&s->rx_fifo)) { fifo32_push(&s->rx_fifo, rx); } else { + 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 dw_ssi_read(void *opaque, hwaddr addr,= unsigned int size) value =3D fifo32_pop(&s->rx_fifo) & dw_ssi_frame_masked(s); } else { value =3D 0; + s->irq_latched |=3D R_RISR_RXUIR_MASK; } =20 dw_ssi_run_transfer(s); + dw_ssi_update_irq(s); return value; } =20 @@ -634,6 +699,7 @@ static uint64_t dw_ssi_read(void *opaque, hwaddr addr, = unsigned int size) value =3D fifo32_num_used(&s->tx_fifo); if (s->phase =3D=3D DW_SSI_PHASE_STANDARD_TX_ONLY) { dw_ssi_run_transfer(s); + dw_ssi_update_irq(s); } break; case A_RXFLR: @@ -643,8 +709,35 @@ static uint64_t dw_ssi_read(void *opaque, hwaddr addr,= unsigned int size) value =3D dw_ssi_status(s); if (s->phase =3D=3D DW_SSI_PHASE_STANDARD_TX_ONLY) { dw_ssi_run_transfer(s); + dw_ssi_update_irq(s); } break; + case A_ISR: + value =3D dw_ssi_irq_raw_status(s) & s->regs[R_IMR] & + DW_SSI_IRQ_VALID_MASK; + break; + case A_RISR: + value =3D dw_ssi_irq_raw_status(s); + break; + case A_TXEICR: + value =3D dw_ssi_irq_read_clear( + s, R_RISR_TXOIR_MASK | R_RISR_TXUIR_MASK); + break; + case A_RXOICR: + value =3D dw_ssi_irq_read_clear(s, R_RISR_RXOIR_MASK); + break; + case A_RXUICR: + value =3D dw_ssi_irq_read_clear(s, R_RISR_RXUIR_MASK); + break; + case A_MSTICR: + value =3D dw_ssi_irq_read_clear(s, R_RISR_MSTIR_MASK); + break; + case A_ICR: + value =3D dw_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 DW_SSI_REGS_SIZE || (addr & 0x3) !=3D 0) { qemu_log_mask(LOG_GUEST_ERROR, @@ -707,6 +800,7 @@ static void dw_ssi_write(void *opaque, hwaddr addr, =20 dw_ssi_update_cs(s); dw_ssi_run_transfer(s); + dw_ssi_update_irq(s); break; } case A_MWCR: @@ -730,6 +824,7 @@ static void dw_ssi_write(void *opaque, hwaddr addr, =20 dw_ssi_update_cs(s); dw_ssi_run_transfer(s); + dw_ssi_update_irq(s); break; } case A_BAUDR: @@ -749,6 +844,7 @@ static void dw_ssi_write(void *opaque, hwaddr addr, dw_ssi_write_masked(s, R_TXFTLR, value, DW_SSI_TXFTLR_WRITABLE_MASK); dw_ssi_run_transfer(s); + dw_ssi_update_irq(s); break; case A_RXFTLR: if (!dw_ssi_fifo_threshold_valid( @@ -760,6 +856,7 @@ static void dw_ssi_write(void *opaque, hwaddr addr, } dw_ssi_write_masked(s, R_RXFTLR, value, DW_SSI_RXFTLR_WRITABLE_MASK); + dw_ssi_update_irq(s); break; case A_TXFLR: case A_RXFLR: @@ -768,6 +865,16 @@ static void dw_ssi_write(void *opaque, hwaddr addr, case A_IMR: dw_ssi_write_masked(s, R_IMR, value, DW_SSI_IMR_WRITABLE_MASK); + dw_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: @@ -807,12 +914,15 @@ static void dw_ssi_enter_reset(Object *obj, ResetType= type) fifo32_reset(&s->rx_fifo); s->phase =3D DW_SSI_PHASE_IDLE; s->remaining_frames =3D 0; + s->irq_latched =3D 0; =20 s->regs[R_CTRLR0] =3D DW_SSI_CTRLR0_RESET; s->regs[R_SR] =3D DW_SSI_SR_RESET; s->regs[R_IMR] =3D s->cfg.imr_reset & DW_SSI_IMR_WRITABLE_MASK; s->regs[R_IDR] =3D DW_SSI_IDR_RESET; s->regs[R_SSIC_VERSION_ID] =3D DW_SSI_VERSION; + + dw_ssi_update_irq(s); } =20 static void dw_ssi_hold_reset(Object *obj, ResetType type) @@ -827,6 +937,13 @@ static void dw_ssi_hold_reset(Object *obj, ResetType t= ype) } } =20 +static void dw_ssi_exit_reset(Object *obj, ResetType type) +{ + DwSsiState *s =3D DW_SSI(obj); + + dw_ssi_update_irq(s); +} + static int dw_ssi_post_load(void *opaque, int version_id) { DwSsiState *s =3D opaque; @@ -835,6 +952,9 @@ static int dw_ssi_post_load(void *opaque, int version_i= d) if (s->active_cs < -1 || s->active_cs >=3D (int)s->cfg.num_cs) { return -EINVAL; } + if (s->irq_latched & ~DW_SSI_IMPLEMENTED_LATCHED_IRQ_MASK) { + return -EINVAL; + } if (s->phase > DW_SSI_PHASE_STANDARD_TR) { return -EINVAL; } @@ -859,6 +979,7 @@ static int dw_ssi_post_load(void *opaque, int version_i= d) qemu_irq_lower(s->cs_lines[s->active_cs]); } =20 + dw_ssi_update_irq(s); return 0; } =20 @@ -874,6 +995,7 @@ static const VMStateDescription vmstate_dw_ssi =3D { VMSTATE_UINT32_ARRAY(regs, DwSsiState, DW_SSI_NUM_REGS), VMSTATE_FIFO32(tx_fifo, DwSsiState), VMSTATE_FIFO32(rx_fifo, DwSsiState), + VMSTATE_UINT32(irq_latched, DwSsiState), VMSTATE_UINT32(phase, DwSsiState), VMSTATE_UINT32(remaining_frames, DwSsiState), VMSTATE_INT32(active_cs, DwSsiState), @@ -893,6 +1015,10 @@ static void dw_ssi_init(Object *obj) TYPE_DW_SSI, DW_SSI_MMIO_SIZE); sysbus_init_mmio(sbd, &s->mmio); =20 + for (int i =3D 0; i < DW_SSI_IRQ_COUNT; i++) { + sysbus_init_irq(sbd, &s->irqs[i]); + } + s->active_cs =3D -1; } =20 @@ -937,6 +1063,7 @@ static void dw_ssi_class_init(ObjectClass *klass, cons= t void *data) device_class_set_props(dc, dw_ssi_properties); rc->phases.enter =3D dw_ssi_enter_reset; rc->phases.hold =3D dw_ssi_hold_reset; + rc->phases.exit =3D dw_ssi_exit_reset; } =20 static const TypeInfo dw_ssi_info =3D { diff --git a/include/hw/ssi/dw_ssi.h b/include/hw/ssi/dw_ssi.h index a74e906ef8..4d4e1fa4c8 100644 --- a/include/hw/ssi/dw_ssi.h +++ b/include/hw/ssi/dw_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_DW_SSI_H @@ -25,6 +25,20 @@ OBJECT_DECLARE_SIMPLE_TYPE(DwSsiState, DW_SSI) #define DW_SSI_NUM_REGS \ (DW_SSI_REGS_SIZE / sizeof(uint32_t)) =20 +/* SSI GPIO output ordering differs from RISR/ISR bit ordering. */ +typedef enum DwSsiIrq { + DW_SSI_IRQ_TXE, + DW_SSI_IRQ_TXO, + DW_SSI_IRQ_RXF, + DW_SSI_IRQ_RXO, + DW_SSI_IRQ_TXU, + DW_SSI_IRQ_RXU, + DW_SSI_IRQ_MST, + DW_SSI_IRQ_DONE, + DW_SSI_IRQ_AXIE, + DW_SSI_IRQ_COUNT, +} DwSsiIrq; + typedef enum DwSsiPhase { DW_SSI_PHASE_IDLE, DW_SSI_PHASE_STANDARD_TX_ONLY, @@ -47,6 +61,7 @@ struct DwSsiState { SSIBus *spi; =20 qemu_irq *cs_lines; + qemu_irq irqs[DW_SSI_IRQ_COUNT]; =20 Fifo32 tx_fifo; Fifo32 rx_fifo; @@ -54,6 +69,7 @@ struct DwSsiState { =20 DwSsiConfig cfg; =20 + uint32_t irq_latched; uint32_t phase; uint32_t remaining_frames; =20 --=20 2.43.0 From nobody Sat Aug 1 21:32:41 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=1785612663; cv=none; d=zohomail.com; s=zohoarc; b=jKrqYI78qOc7ZS+u/zHxpg+/YgG5fWXgkg4YYie+LmRfcpuEFRu8QQt1xRLagvqqhfg/TIQ/kuC9DOjkCRMsOabNSPaGTOyT2fbpPGBnjGOpNAJ/80E1evmOpOzOGjhD+5g6/XSBZYHqI5mgZfCfP21mohkE6esqoWg/B355Bio= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1785612663; 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=6uYe5cvZN/4TkFYhyZc1NlhPmRAYtJ/eQoSiPr3av+M=; b=bg9920Kyf7FRkIXX9QwQdox/m41bNnkitfGZ8B54QUVkwYmB6RKZ7DYbs9XI9NfQ/rXIbDyPi1NWAUQdL1YVGrHelYufbvfWn2bBWv0dwVTXqDajP0IpeuBKYd186DfouQ+fWwUQlzvE6IzFNDQk2QaFpkCDMsFJaNOueSDNNv8= 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 1785612663017577.656940000206; Sat, 1 Aug 2026 12:31:03 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wqFPo-0002uJ-SX; Sat, 01 Aug 2026 15:30:36 -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 1wqFPm-0002tt-V6 for qemu-devel@nongnu.org; Sat, 01 Aug 2026 15:30:35 -0400 Received: from mail-pj1-x1035.google.com ([2607:f8b0:4864:20::1035]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wqFPj-0002Ib-LH for qemu-devel@nongnu.org; Sat, 01 Aug 2026 15:30:34 -0400 Received: by mail-pj1-x1035.google.com with SMTP id 98e67ed59e1d1-38e3617ba36so2186237a91.3 for ; Sat, 01 Aug 2026 12:30:31 -0700 (PDT) Received: from localhost.localdomain ([111.198.227.173]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-38fb2bac654sm2148766a91.10.2026.08.01.12.30.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 01 Aug 2026 12:30:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785612630; x=1786217430; 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=6uYe5cvZN/4TkFYhyZc1NlhPmRAYtJ/eQoSiPr3av+M=; b=s8GKbo8JxF3ISjrwHjcz0Hy2JgoBw3yhZUr5+4BkACC32YNYDRqPufDTIJx6/E7Ixf IlAEWmpADyMIPlFTu8IWlVF/amSh/0blANmEupldjLqmtvXg6iYpXx9BsE3GQ3zZyugA SVRPuLc5lEozhDSom+HmsHPUZ0bGknmU5dJI+UnCvtptH3YiLZ6TTA0A3dEi4GHRi0oA ryNXeI72+R1ypLNqreG0UPhQxUqT3ZHBjJnVzYcgvDzF0KWo+JvpP1uwN68peuOBj/4P u0Ac/M0eaDSKFbDduPshwKDFMsBvO9anipuavSogJEXXx/X95FyDx8AuUfM0unEJ3Tsj FWhw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785612630; x=1786217430; 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=6uYe5cvZN/4TkFYhyZc1NlhPmRAYtJ/eQoSiPr3av+M=; b=DrXLps/mHkppo7yuIl3BYgevda6FgmmcXLznP6yNJsAf6moG41jztWUEYfkODTi8fa SjAqtDy5z1q38RqjKUVjNpyK8pArWWjn3EWGVcCP8tMZ0ozmRWpu58HG/V1XJPDwrfsv bTcBpehl0p2iXXlKhmmBLJ/m4Lie3oD6jhzKGsu6jH4bs6lr/l8UDrmCK1ur8z06DocX u6XRuZShYr6+/wze0Aws4g7oet9nIzilKDOqSofvZ+PTNz59YWxdFxp59+yzQHPGMrB3 KR89lX2YID2UieGgbOwUy6xavgAwn8EPKF7GFjXjk9Za0Bxv0vWvv+3V0+GvBRn9JtPW u0vA== X-Gm-Message-State: AOJu0YyyTHxXVnoKc2WMLJBy6BFZH1IY7VFHqu+fkCRwZBsW632Uvxar U7/JpeirndawTcda+NmvheTuazTKWSTo90Nb6ZnwtE9g/AXhdb6wyVKqPTVU+HI8Y9PNBw== X-Gm-Gg: AR+sD12jhSSU8wpQ1eOy43hEbZIEN4rDsFC//9xHedF/XbrIUAZ+QJeRVqRxIuXYmBG D3YCnWFgNX3FB/N4ng4Gfo4eXVMx2XIrdEwZk7VCSjMMwPJziAHcJwUn55zlNmfJ5x3qaHOm40A wye5nRT0rOCGCV9ExjrHduMZwEzLAfZM1GK7RK74pE95ZopqsG0GqSqn7AMIM1kSJrCC/ZfcDHJ e4Lw3hKJphOtqhFpeNtP5lWf7nGuXHxfPACZG/eofV2/zYzpzEzlOJOArSlrLjnoItRRub9tpmO 38YRJIHY2m4UkhBcFK3UZ1wzDWKjEEr0QdAnU90W4752NnZK7en4DPjQETQvaE7KW6cq6nphX2Q D8PcfBMMX1MoRpTw2vPmAQyg5tczt0gUIlgv/X0HBS88KNxRjjOJssTNKVuC8qBu9g4Gbn+Bgn6 f5Gkkk1pcS2hiS3w6fzj4Rz7g0XEiUtDnpwvURqUvWx6tN6LoS9fh7O+91d2TJMu8uCgYZlEScT DVjlzwT5Q== X-Received: by 2002:a17:90b:4b45:b0:36d:9e0b:3801 with SMTP id 98e67ed59e1d1-38fbc40905dmr4275738a91.8.1785612629986; Sat, 01 Aug 2026 12:30:29 -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 , Kangjie Huang Subject: [PATCH v2 3/5] hw/riscv/k230: Instantiate DesignWare SSI controllers Date: Sun, 2 Aug 2026 03:28:46 +0800 Message-ID: <20260801192848.30606-4-flamboyant.h.01@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260801192848.30606-1-flamboyant.h.01@gmail.com> References: <20260801192848.30606-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::1035; envelope-from=flamboyant.h.01@gmail.com; helo=mail-pj1-x1035.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: 1785612665634158500 Content-Type: text/plain; charset="utf-8" Instantiate the three DesignWare 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 --- docs/system/riscv/k230.rst | 1 + hw/riscv/Kconfig | 1 + hw/riscv/k230.c | 79 ++++-- include/hw/riscv/k230.h | 2 + tests/qtest/k230-dw-ssi-test.c | 469 +++++++++++++++++++++++++++++++++ tests/qtest/meson.build | 4 +- 6 files changed, 540 insertions(+), 16 deletions(-) create mode 100644 tests/qtest/k230-dw-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..60a260691c 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -162,3 +162,4 @@ config K230 select SERIAL_MM select UNIMP select K230_WDT + select DW_SSI diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index 656f28190c..4c321002cd 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" @@ -102,6 +104,40 @@ static const MemMapEntry memmap[] =3D { [K230_DEV_CLINT] =3D { 0xF04000000, 0x00400000 }, }; =20 +typedef struct K230DwSsiProfile { + uint32_t num_cs; + uint32_t fifo_depth; + uint32_t imr_reset; +} K230DwSsiProfile; + +static const K230DwSsiProfile k230_dw_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_dw_ssi(DwSsiState *ssi, + const K230DwSsiProfile *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 +146,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->dw_ssi[0], + TYPE_DW_SSI); + object_initialize_child(obj, "k230-qspi1", &s->dw_ssi[1], + TYPE_DW_SSI); + object_initialize_child(obj, "k230-spi-opi", &s->dw_ssi[2], + TYPE_DW_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 +240,13 @@ static void k230_soc_realize(DeviceState *dev, Error *= *errp) } } =20 + for (int i =3D 0; i < ARRAY_SIZE(s->dw_ssi); i++) { + k230_configure_dw_ssi(&s->dw_ssi[i], &k230_dw_ssi_profiles[i]); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->dw_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 +255,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->dw_ssi[0]), 0, + memmap[K230_DEV_QSPI0].base); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->dw_ssi[1]), 0, + memmap[K230_DEV_QSPI1].base); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->dw_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 +412,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) @@ -511,6 +559,7 @@ static void k230_machine_class_init(ObjectClass *oc, co= nst 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; + } =20 static const TypeInfo k230_machine_typeinfo =3D { diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h index 592e1c26bf..99996a93b1 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/dw_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]; + DwSsiState dw_ssi[3]; MemoryRegion sram; MemoryRegion bootrom; =20 diff --git a/tests/qtest/k230-dw-ssi-test.c b/tests/qtest/k230-dw-ssi-test.c new file mode 100644 index 0000000000..6361c2b5ee --- /dev/null +++ b/tests/qtest/k230-dw-ssi-test.c @@ -0,0 +1,469 @@ +/* + * 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_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_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_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); + /* 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, 0); + 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, = 0); + } + + k230_ssi_disable(qts, K230_SPI1_BASE); + status =3D k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_SR); + g_assert_cmpuint(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + =3D=3D, 0); + g_assert_cmphex(status & (K230_SSI_SR_BUSY | K230_SSI_SR_TFNF | + K230_SSI_SR_TFE | K230_SSI_SR_RFNE), + =3D=3D, K230_SSI_SR_TFNF | K230_SSI_SR_TFE); + qtest_quit(qts); +} + +static void test_interrupt_controller(void) +{ + QTestState *qts =3D k230_ssi_start(); + + k230_ssi_configure(qts, K230_SPI1_BASE, K230_SSI_TMOD_TR, 8, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_TXFTLR, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_IMR, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SSIENR, 1); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_TXE, =3D=3D, K230_SSI_INT_TXE); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_ISR) & + K230_SSI_INT_TXE, =3D=3D, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_IMR, + K230_SSI_INT_TXE); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_ISR) & + K230_SSI_INT_TXE, =3D=3D, K230_SSI_INT_TXE); + + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_IMR, + K230_SSI_INT_RXU); + (void)k230_ssi_read_frame(qts, K230_SPI1_BASE); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXU, =3D=3D, K230_SSI_INT_RXU); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_ISR) & + K230_SSI_INT_RXU, =3D=3D, K230_SSI_INT_RXU); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXUICR), + =3D=3D, 1); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXU, =3D=3D, 0); + qtest_quit(qts); +} + + +static void test_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); + qtest_quit(qts); +} + +static void test_eeprom_read_contract(void) +{ + QTestState *qts =3D k230_ssi_start(); + + configure_loopback(qts, K230_SSI_TMOD_EEPROM_READ, 3); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0x03); + 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, = 0); + } + + configure_loopback(qts, K230_SSI_TMOD_EEPROM_READ, 0); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0x03); + 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); + 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 + 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_RXO, =3D=3D, K230_SSI_INT_RXO); + 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); + 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-dw-ssi/register-contract", test_register_contrac= t); + qtest_add_func("/k230-dw-ssi/pio-data-path", test_pio_data_path); + qtest_add_func("/k230-dw-ssi/interrupt-controller", + test_interrupt_controller); + qtest_add_func("/k230-dw-ssi/tx-only-mode", test_tx_only_mode); + qtest_add_func("/k230-dw-ssi/eeprom-read-contract", + test_eeprom_read_contract); + qtest_add_func("/k230-dw-ssi/rx-fifo-overflow", test_rx_fifo_overflow); + qtest_add_func("/k230-dw-ssi/txfthr-start-gate", test_txfthr_start_gat= e); + qtest_add_func("/k230-dw-ssi/baudr-zero", test_baudr_zero); + qtest_add_func("/k230-dw-ssi/ser-terminates-ro", test_ser_terminates_r= o); + qtest_add_func("/k230-dw-ssi/icr-total-clear", test_icr_total_clear); + qtest_add_func("/k230-dw-ssi/unsupported-registers", + test_unsupported_registers); + return g_test_run(); +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 56ff860e21..f5bcc7bece 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-dw-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-dw-ssi-test': files('k230-dw-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 Sat Aug 1 21:32:41 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=1785612695; cv=none; d=zohomail.com; s=zohoarc; b=BVXVQcej99EmNsY8fzpHDdUyoHQfpoRzKaj5UhqCGscg1TftWME9C3vuQStBoOeIi4UhJXXRUJUl+8XJkyRWYo+1A2G9zHfbvFYj/z5Y3CEwgo1/2/uTGT9Yb/e2Czv7hkMS4OjOpAgbTe0U40Daa3Gnav0enfnslMNy0vcAIv4= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1785612695; 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=Tjg9FT4duWt9gr9ZOut+ObsOGQFgQaiIiqjZb9R3imc=; b=QqcaUX4Ey1PyClw60Ofjez9QfLJwo+okoIKniBvHvNii28Cbbgkpz9A2De1CLroIBOEOVz/n/zCZGhP3x7OFb3bc4GZsh/yk+ZpjYzqTqeinSvxJqGcZmwHA/ZBl+acDweqjNMOpruWvI7dfwhPrO6vzXi5tnfiPESgEP6T+9Co= 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 1785612695678490.7626146780311; Sat, 1 Aug 2026 12:31:35 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wqFPr-0002vc-Jf; Sat, 01 Aug 2026 15:30: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 1wqFPp-0002uS-I4 for qemu-devel@nongnu.org; Sat, 01 Aug 2026 15:30:37 -0400 Received: from mail-pj1-x1032.google.com ([2607:f8b0:4864:20::1032]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wqFPn-0002It-NS for qemu-devel@nongnu.org; Sat, 01 Aug 2026 15:30:37 -0400 Received: by mail-pj1-x1032.google.com with SMTP id 98e67ed59e1d1-38dcbade417so2000358a91.1 for ; Sat, 01 Aug 2026 12:30:35 -0700 (PDT) Received: from localhost.localdomain ([111.198.227.173]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-38fb2bac654sm2148766a91.10.2026.08.01.12.30.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 01 Aug 2026 12:30:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785612634; x=1786217434; 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=Tjg9FT4duWt9gr9ZOut+ObsOGQFgQaiIiqjZb9R3imc=; b=Gh46x4owLfvDeS/mvfuTCOTUS19eLCol3B/+iq4ymimAJ2Q8nOsNlVqdozInUpOHnY H5Xt1X9/4vyOVsBAz2bcBuFDYC5o92fF63etoKLezzjMeufu7SGNPfSZISqH40EAo9DG p8p1Z7rjj0Wi73zTjM+uwwYenUrpY17fBuQY6sRFwiYCSFjQ6ZftElSULoW9PdegEpxx VZHez3gUfY3UdnBml6fCWgGNCIo+f3zUTDbkFSlKNCV0Uj/OeL8E4veN8DWFrTgUiw3F bTWPfaWYU3xv+UujCq5Sh/sNBfgAfqGuaiopP5rLC6+dwaySK6iFJIg1ZqQbVxi0CXBt HUtA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785612634; x=1786217434; 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=Tjg9FT4duWt9gr9ZOut+ObsOGQFgQaiIiqjZb9R3imc=; b=mc0A9sEP2ePJc0Y+y6lHwd982SjFkphgfyGB2VejeXHIF/w52dJ7RDpEVdnVNjlcah u0kOLUDrQSj7MS9QuC81WlOh9WCHAXmCpuDH9l13fL7Wxk33f4Q080q+ONh1eEELcP6G dyDXduTcQUONzd6xipnDpiNlliLuoVkMeyFjbrUTh1+gHZPSbb/tyaibmJrpq6AwjUAm xaETKHbNF7JSMmD+WhPTEbnCS43SAc2BCrsnCAgMKvFoDeB/JD4cM0L0TjLG6Mo4Vzlr Tp+hKIkbD039BLnKEVGFtBPi3MsMNyi8oQmxp2IafPoo+WkH+40E1vy+/ggkkNk7kU69 zDFg== X-Gm-Message-State: AOJu0YzBHctQH6+1nXEwgMT4vaHK97Ld7YFsfIzhUemdYumLCv1JtH17 rfbedcaZ3WNyur9YQpzo4Z7WXMgvv8qo9uajisgh2dlGrhm9GCF4kM9A9NrIgXR5sI8Tag== X-Gm-Gg: AR+sD10W9q9akNnFA/UykOFMPMgeeXOxUuQqk+r2XDZxzbYWO6oTCaZGmqCFERsKLtZ ClvQlOuD4df0LLV81fFI0A64eGhd91c1DuqEWyPiLBnE9pVhuTIR8zbNnuILzjSAVBYwXQYilLZ e5MOHTx+0FpK5PiEjl8VsnVW0OQhhojU1nT/l77GKbZdr8S1D7maebtfCxAaBH/DtSeqbZdm78U 4r3WreDVIOnLjjaWdiqYWxgfCjDwB1sbQ9iS6Eq0cJvH8Ec+tHLvm+q19mh2cAQP2S2d8suxFXf CJaAx3wBB/5OgmjpsCxK6ZvgN7sceqwC3JvaKd340s0MvgCwPdDvhIeiVRUtwBaMrTcAxTusdZX 8CNx7YfxzHlqLJ26MPaOOWkxD7GM/u1Q8xZY8jYO2+od82feLN6QmFEvfvaonGM0qmOdg5+YVdj 3dPYeiIqTJ4CiB6qwsKuOuP4ldSlDoV8Gq4ZLk/cp+EZhkgQulwje8plolVVIma3ykOy0DKoUVD sGj0ZqAww== X-Received: by 2002:a17:90b:5867:b0:381:96a2:14d7 with SMTP id 98e67ed59e1d1-38fbc4ae4e7mr4158839a91.22.1785612634273; Sat, 01 Aug 2026 12:30: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 , Kangjie Huang Subject: [PATCH v2 4/5] hw/riscv/k230: Route SSI interrupts to the PLIC Date: Sun, 2 Aug 2026 03:28:47 +0800 Message-ID: <20260801192848.30606-5-flamboyant.h.01@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260801192848.30606-1-flamboyant.h.01@gmail.com> References: <20260801192848.30606-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::1032; envelope-from=flamboyant.h.01@gmail.com; helo=mail-pj1-x1032.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: 1785612697562158500 Content-Type: text/plain; charset="utf-8" Connect the nine interrupt outputs of each K230 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 modeled. Extend qtests to cover TXE routing for all instances and RXU routing and instance isolation. Signed-off-by: Kangjie Huang --- hw/riscv/k230.c | 36 +++++++++++++++++++++++++++ include/hw/riscv/k230.h | 3 +++ tests/qtest/k230-dw-ssi-test.c | 45 ++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index 4c321002cd..d856864839 100644 --- a/hw/riscv/k230.c +++ b/hw/riscv/k230.c @@ -104,6 +104,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 K230DwSsiProfile { uint32_t num_cs; uint32_t fifo_depth; @@ -193,6 +208,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->dw_ssi)); + g_assert(route->irq_base + DW_SSI_IRQ_COUNT <=3D + K230_PLIC_NUM_SOURCES); + + ssi =3D SYS_BUS_DEVICE(&s->dw_ssi[route->ssi_index]); + for (unsigned int i =3D 0; i < DW_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); @@ -247,6 +281,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 99996a93b1..275b6b62c3 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-dw-ssi-test.c b/tests/qtest/k230-dw-ssi-test.c index 6361c2b5ee..7252302c0e 100644 --- a/tests/qtest/k230-dw-ssi-test.c +++ b/tests/qtest/k230-dw-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 @@ -57,6 +59,8 @@ #define K230_SSI_INT_TXO BIT(1) #define K230_SSI_INT_RXO BIT(3) #define K230_SSI_INT_RXU BIT(2) +#define K230_SSI_IRQ_TXE 0 +#define K230_SSI_IRQ_RXU 5 =20 #define K230_SSI_RXFTLR 0x01c #define K230_SSI_RXOICR 0x03c @@ -71,6 +75,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 { @@ -78,14 +83,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 @@ -106,6 +114,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); @@ -281,6 +297,34 @@ static void test_interrupt_controller(void) } =20 =20 +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); + } + + k230_ssi_writel(qts, target->base, K230_SSI_IMR, K230_SSI_INT_RXU); + (void)k230_ssi_read_frame(qts, target->base); + g_assert_true(k230_ssi_plic_pending(qts, + target->first_irq + + K230_SSI_IRQ_RXU)); + for (int i =3D 0; i < ARRAY_SIZE(k230_ssi_instances); i++) { + if (&k230_ssi_instances[i] !=3D target) { + g_assert_false(k230_ssi_plic_pending( + qts, k230_ssi_instances[i].first_irq + K230_SSI_IRQ_RXU)); + } + } + qtest_quit(qts); +} + static void test_tx_only_mode(void) { QTestState *qts =3D k230_ssi_start(); @@ -455,6 +499,7 @@ int main(int argc, char **argv) qtest_add_func("/k230-dw-ssi/pio-data-path", test_pio_data_path); qtest_add_func("/k230-dw-ssi/interrupt-controller", test_interrupt_controller); + qtest_add_func("/k230-dw-ssi/plic-routing", test_plic_routing); qtest_add_func("/k230-dw-ssi/tx-only-mode", test_tx_only_mode); qtest_add_func("/k230-dw-ssi/eeprom-read-contract", test_eeprom_read_contract); --=20 2.43.0 From nobody Sat Aug 1 21:32:41 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=1785612652; cv=none; d=zohomail.com; s=zohoarc; b=OXulkwEufnaJeio0lKJafGV9fY1sKKyKP0f8bDrTp1XAE1MeYrkc3YTpOPva1fk6CtDmcg8YzqO8QpD90Uoa2nHHg/EHl5wgRnEraIZpZach8HDH93A/M0L3BkqOS2XdA5A2+Ruf/68bJlKo67+AANlFBcmTreM29ZTgk+VQLWQ= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1785612652; 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=7hRPu+C0KkUU0VjTSzktZR1AdUmxXdimI6NZhYDwu5o=; b=QQ2qB4eZzAtmkhygNx2MSp3RbeLQJDhVj3rDGQUtJ6/J+C39zY98IlVNHpo37AnapcyEnuBkavwzOHF9NRxeU5zzPiIZ/eNzj1spnP4UUgS31UB+eqG6h3y59865l8vwmwP9FZjyA3qXvuPPIEka4pdefNE55th4jPOdaYCbpME= 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 1785612652945214.85563591838434; Sat, 1 Aug 2026 12:30:52 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wqFPz-0002xl-VO; Sat, 01 Aug 2026 15:30:48 -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 1wqFPv-0002wG-04 for qemu-devel@nongnu.org; Sat, 01 Aug 2026 15:30:43 -0400 Received: from mail-pj1-x102d.google.com ([2607:f8b0:4864:20::102d]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wqFPs-0002JY-UL for qemu-devel@nongnu.org; Sat, 01 Aug 2026 15:30:42 -0400 Received: by mail-pj1-x102d.google.com with SMTP id 98e67ed59e1d1-38f0f132f56so2216201a91.0 for ; Sat, 01 Aug 2026 12:30:40 -0700 (PDT) Received: from localhost.localdomain ([111.198.227.173]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-38fb2bac654sm2148766a91.10.2026.08.01.12.30.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 01 Aug 2026 12:30:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785612639; x=1786217439; 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=7hRPu+C0KkUU0VjTSzktZR1AdUmxXdimI6NZhYDwu5o=; b=AtKg/KDJbJlmdzo3PJlI+BiBUVWZxVHsK2lRDLbHT8Kq/nKOBrUFtYSKqdcTmDJAe0 CZMftYXg6rKZ4OC20FxeG4f79RY9tKpXjJt3bCzClU0bUhFIQf8hUt5OdgShBjvmre8u 3QSNNew5YcXeBSHl7nQS2/D3RaCEQeki5tQ441bJAcTNKogfyZ40n0Y23kIgAo9JeHW1 C7Yw5RkzeheGlHWbNrId0NXIZBXFazZKJxSc2oqeayIra8BKYXYPtyUQjmwiF8BaFmBA xx5aJOu9fah2Dj4jUnO52Q/2ndcc/ZxJWmAXmtt7xhZzg33BtcJPPWucmQR7o+gv9GkB KS6A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785612639; x=1786217439; 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=7hRPu+C0KkUU0VjTSzktZR1AdUmxXdimI6NZhYDwu5o=; b=gOinajmZSVoXyPJHteHRmvHGcKdwy9mUumHhAi7bhej8AcePiJWPKXprTRSt2k1rLE R1pJ6tv2lHVZbg+FIIdg2kOT/bj6imXkGdWgVEKzp6iHOucGcBwPUl2YljGcZ6oflIlk QPPCF+vY2fOu7FZBf6WMnQE49c2Sn0UOiWF4Xj21Ejwf6rI9dQ6gedLNoRIcrMHh6uq9 MVK1DvF4yTzyTEa9bxmyszT8qL/RJKKSTgmShHCJhItkCkfHLkoADV+rPk80Z4l5AR14 L3FQk9N4S06MTtbhkcQ4PxVc37Lh7nKucxb9/Dr9TQwoxiwgob5nUtxRfEF3PcGAj1mC NS6w== X-Gm-Message-State: AOJu0Yz3wkOZgbukbrz8o5fNycaUptoyUB5/vD0zFR7xBhNVz2zPHIvh Y2CDbhMqtIlVsaK5CG40kwX0zTwc0eOGZ9CO9oaR3Wl38R5IytBy+dLw6w0LlyihIXARjg== X-Gm-Gg: AR+sD137KacSL5rAWq94CHWZtuCIHuMQ10t31y4IRFxxfxXY34fE9UoJHs7FLCPUgVS oObk/6HDZXt0XgYx32Q2ZmuDPH7kykD5A6B4NI3wQ96lO2PRVRQhnNBLMdXmZOvcyKXBOE/5u3z L+2dGfYG8vC+g4f59q4jU4HX0WTGOjYGOCkou8bgoVBsJMfJUz0gzZYe1bVFCCoITGPJQySpud2 oFP/FOuPgY4FQ+UPD0YNhP/ECTzwr4hJmtgfj6d5GIspR/Ox4XApe7loCvCLlize5DZWEGM6zXy LtyFCjBIpG6q6g+lawM/Qpwy7m7l6slzDWqdZ2ZPnxFfnRA0UhXN+omHZYhHDOQF+NffNIQnbGY DPN2SFmehI9NO/4Pm+7UznO092PB/kJeQ79DNDQPpytK9WQP9MUxpRUrV+kWbNzmouIBDJdkDAL 8F06nlacWxOn0oSaXBUms9ZcYOUtrYUOAVRgb2ElJALkwPeTJidNTIGYj2Ab1F5/vl9RroMefzG uXLMJIUVw== X-Received: by 2002:a17:90b:4d85:b0:387:d5bd:6231 with SMTP id 98e67ed59e1d1-38fbcaf9ea4mr2920106a91.20.1785612639466; Sat, 01 Aug 2026 12:30:39 -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 , Kangjie Huang Subject: [PATCH v2 5/5] hw/riscv/k230: Attach a standard SPI NOR flash Date: Sun, 2 Aug 2026 03:28:48 +0800 Message-ID: <20260801192848.30606-6-flamboyant.h.01@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260801192848.30606-1-flamboyant.h.01@gmail.com> References: <20260801192848.30606-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::102d; envelope-from=flamboyant.h.01@gmail.com; helo=mail-pj1-x102d.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: 1785612655961158500 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 --- docs/system/riscv/k230.rst | 1 + hw/riscv/Kconfig | 1 + hw/riscv/k230.c | 63 ++++++++++++++++++++++++++++++++++ include/hw/riscv/k230.h | 2 ++ tests/qtest/k230-dw-ssi-test.c | 55 +++++++++++++++++++++++++++++ 5 files changed, 122 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 60a260691c..9d59465ad6 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -163,3 +163,4 @@ config K230 select UNIMP select K230_WDT select DW_SSI + select SSI_M25P80 diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index d856864839..5dd49a6752 100644 --- a/hw/riscv/k230.c +++ b/hw/riscv/k230.c @@ -33,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 @@ -543,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(DwSsiState *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, @@ -574,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.dw_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); @@ -586,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); @@ -596,6 +653,11 @@ static void k230_machine_class_init(ObjectClass *oc, c= onst void *data) mc->default_ram_id =3D "riscv.K230.ram"; /* DDR */ mc->default_ram_size =3D memmap[K230_DEV_DDRC].size; =20 + 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 { @@ -603,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 275b6b62c3..80fb4c0e69 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-dw-ssi-test.c b/tests/qtest/k230-dw-ssi-test.c index 7252302c0e..f381be14b5 100644 --- a/tests/qtest/k230-dw-ssi-test.c +++ b/tests/qtest/k230-dw-ssi-test.c @@ -474,6 +474,59 @@ 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"); + uint32_t byte; + + 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); +} + static void test_unsupported_registers(void) { QTestState *qts =3D k230_ssi_start(); @@ -510,5 +563,7 @@ int main(int argc, char **argv) qtest_add_func("/k230-dw-ssi/icr-total-clear", test_icr_total_clear); qtest_add_func("/k230-dw-ssi/unsupported-registers", test_unsupported_registers); + qtest_add_func("/k230-dw-ssi/flash-jedec-id", test_flash_jedec_id); + qtest_add_func("/k230-dw-ssi/flash-fixed-read", test_flash_fixed_read); return g_test_run(); } --=20 2.43.0