On Thu, 2026-07-23 at 23:18 +0800, Bin Meng wrote:
> HSS v2024.06 performs DDR and SGMII initialization through the DMC
> register window before it can complete platform startup. The existing
> model, which was validated against older version HSS in the early
> days,
> exposes only a few fixed training results, so newer version HSS
> stalls
> while waiting for PVT, PLL, DLL, ADDCMD, and verification status.
>
> Update the DMC model by adding the minimum state and status behavior
> needed by that firmware flow. Model the SGMII control registers,
> main DDR PLL lock, deterministic ADDCMD transitions, and successful
> gate and DQ/DQS verification. Keep training progress and writable
> controls per device and clear them on reset.
>
> These values are compatibility responses for HSS v2024.06 rather than
> a cycle-accurate description of the DDR PHY. Document that limitation
> in the source.
>
> While we are here, update the license text to use the SPDX format.
>
> Note at the time being there is an HSS bug [1] that consistenly
> blocks
> the DDR traning from succeed when using an HSS image built from
> certain
> version compiler like GCC 8.3.0. A patch [2] was proposed and
> verified
> against HSS v2024.06.
>
> [1] https://github.com/polarfire-soc/platform/issues/36
> [2] https://github.com/processmission/qemu-machine-images/blob/main/
> machine/riscv64/microchip-icicle-kit/
> 0001-mpfs_hal-preserve-DDR-pattern-test-parameters.patch
>
> Signed-off-by: Bin Meng <[email protected]>
> ---
>
> include/hw/misc/mchp_pfsoc_dmc.h | 6 +
> hw/misc/mchp_pfsoc_dmc.c | 272 ++++++++++++++++++++++++++++-
> --
> 2 files changed, 254 insertions(+), 24 deletions(-)
>
> diff --git a/include/hw/misc/mchp_pfsoc_dmc.h
> b/include/hw/misc/mchp_pfsoc_dmc.h
> index 2ed582fb8c..6a1986c2c2 100644
> --- a/include/hw/misc/mchp_pfsoc_dmc.h
> +++ b/include/hw/misc/mchp_pfsoc_dmc.h
> @@ -28,10 +28,16 @@
> /* DDR SGMII PHY module */
>
> #define MCHP_PFSOC_DDR_SGMII_PHY_REG_SIZE 0x1000
> +#define MCHP_PFSOC_DDR_SGMII_PHY_REG_NUM \
> + (MCHP_PFSOC_DDR_SGMII_PHY_REG_SIZE / sizeof(uint32_t))
>
> typedef struct MchpPfSoCDdrSgmiiPhyState {
> SysBusDevice parent;
> MemoryRegion sgmii_phy;
> + uint32_t regs[MCHP_PFSOC_DDR_SGMII_PHY_REG_NUM];
> + uint8_t training_status_bit;
> + uint8_t addcmd_tap;
> + bool addcmd_move_active;
> } MchpPfSoCDdrSgmiiPhyState;
>
> #define TYPE_MCHP_PFSOC_DDR_SGMII_PHY "mchp.pfsoc.ddr_sgmii_phy"
> diff --git a/hw/misc/mchp_pfsoc_dmc.c b/hw/misc/mchp_pfsoc_dmc.c
> index 1fe4535464..443de5ddd1 100644
> --- a/hw/misc/mchp_pfsoc_dmc.c
> +++ b/hw/misc/mchp_pfsoc_dmc.c
> @@ -2,22 +2,20 @@
> * Microchip PolarFire SoC DDR Memory Controller module emulation
> *
> * Copyright (c) 2020 Wind River Systems, Inc.
> + * Copyright (c) 2026 Process Mission
> *
> * Author:
> * Bin Meng <[email protected]>
> *
> - * This program is free software; you can redistribute it and/or
> - * modify it under the terms of the GNU General Public License as
> - * published by the Free Software Foundation; either version 2 or
> - * (at your option) version 3 of the License.
> + * Updated by Bin Meng <[email protected]> to support DDR
> + * training emulation for with newer version Hart Software Services,
> + * aka HSS.
> *
> - * This program is distributed in the hope that it will be useful,
> - * but WITHOUT ANY WARRANTY; without even the implied warranty of
> - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> - * GNU General Public License for more details.
> + * The DMC register model is based on HSS v2024.06. Its read/write
> + * behavior is the minimum needed for DDR training to complete and
> + * may not reflect actual hardware register behavior.
> *
> - * You should have received a copy of the GNU General Public License
> along
> - * with this program; if not, see <http://www.gnu.org/licenses/>.
> + * SPDX-License-Identifier: GPL-2.0-or-later
> */
>
> #include "qemu/osdep.h"
> @@ -29,17 +27,56 @@
>
> /* DDR SGMII PHY module */
>
> -#define SGMII_PHY_IOC_REG1 0x208
> -#define SGMII_PHY_TRAINING_STATUS 0x814
> -#define SGMII_PHY_DQ_DQS_ERR_DONE 0x834
> -#define SGMII_PHY_DQDQS_STATUS1 0x84c
> -#define SGMII_PHY_PVT_STAT 0xc20
> +#define PHY_ADDCMD_CK_TRANSITION_TAP 12
> +#define PHY_ADDCMD_A5_TRANSITION_TAP 20
> +#define PHY_ADDCMD_CK_READBACK 5
> +#define PHY_ADDCMD_A5_READBACK (3 << 8)
> +#define PHY_GT_TXDLY_VALUE 0x01010101
> +#define PHY_DQDQS_STATUS2_VALUE 5
> +
> +#define SGMII_PHY_PLL_CTRL_MAIN 0x084
> +#define SGMII_PHY_PLL_CTRL_MAIN_CONTROL_LO_MASK 0x0000007f
> +#define SGMII_PHY_PLL_CTRL_MAIN_LP_REQUIRES_LOCK BIT(24)
> +#define SGMII_PHY_PLL_CTRL_MAIN_LOCK BIT(25)
> +#define SGMII_PHY_IOC_REG1 0x208
> +#define SGMII_PHY_TRAINING_STATUS 0x814
> +#define SGMII_PHY_GT_ERR_COMB 0x81c
> +#define SGMII_PHY_GT_CLK_SEL 0x820
> +#define SGMII_PHY_GT_TXDLY 0x824
> +#define SGMII_PHY_DQ_DQS_ERR_DONE 0x834
> +#define SGMII_PHY_DQDQS_STATUS1 0x84c
> +#define SGMII_PHY_DQDQS_STATUS2 0x850
> +#define SGMII_PHY_EXPERT_DLYCNT_MOVE_REG1 0x880
> +#define SGMII_PHY_EXPERT_DLYCNT_MOVE_CONTROL_MASK 0x001fffff
> +#define SGMII_PHY_EXPERT_DLYCNT_MOVE_ADDCMD_MASK 0x00180000
> +#define SGMII_PHY_EXPERT_DLYCNT_LOAD_REG1 0x890
> +#define SGMII_PHY_EXPERT_DLYCNT_LOAD_CONTROL_MASK 0x001fffff
> +#define SGMII_PHY_EXPERT_DLYCNT_LOAD_ADDCMD_MASK 0x00180000
> +#define SGMII_PHY_EXPERT_ADDCMD_LN_READBACK 0x8ac
> +#define SGMII_PHY_SOFT_RESET_SGMII 0xc00
> +#define SGMII_PHY_SGMII_MODE 0xc04
> +#define SGMII_PHY_PLL_CNTL 0xc08
> +#define SGMII_PHY_PLL_CNTL_LOCK BIT(7)
> +#define SGMII_PHY_CH0_CNTL 0xc0c
> +#define SGMII_PHY_CH1_CNTL 0xc10
> +#define SGMII_PHY_RECAL_CNTL 0xc14
> +#define SGMII_PHY_RECAL_CNTL_STATUS_MASK 0xffff0000
> +#define SGMII_PHY_RECAL_CNTL_LOCK BIT(23)
> +#define SGMII_PHY_CLK_CNTL 0xc18
> +#define SGMII_PHY_DYN_CNTL 0xc1c
> +#define SGMII_PHY_PVT_STAT 0xc20
> +#define SGMII_PHY_PVT_STAT_IO_ENABLE BIT(6)
> +#define SGMII_PHY_PVT_STAT_CALIBRATED BIT(14)
> +#define SGMII_PHY_PVT_STAT_GUEST_CTRL_MASK 0xc0000000
> +#define SGMII_PHY_SPARE_CNTL 0xc24
> +#define SGMII_PHY_SPARE_STAT 0xc28
>
> static uint64_t mchp_pfsoc_ddr_sgmii_phy_read(void *opaque, hwaddr
> offset,
> unsigned size)
> {
> uint32_t val = 0;
> - static int training_status_bit;
> + MchpPfSoCDdrSgmiiPhyState *s = opaque;
> + uint32_t index = offset / sizeof(uint32_t);
>
> switch (offset) {
> case SGMII_PHY_IOC_REG1:
> @@ -53,8 +90,8 @@ static uint64_t mchp_pfsoc_ddr_sgmii_phy_read(void
> *opaque, hwaddr offset,
> *
> * See ddr_setup() in mss_ddr.c in the HSS source codes.
> */
> - val = 1 << training_status_bit;
> - training_status_bit = (training_status_bit + 1) % 5;
> + val = BIT(s->training_status_bit);
> + s->training_status_bit = (s->training_status_bit + 1) % 5;
> break;
> case SGMII_PHY_DQ_DQS_ERR_DONE:
> /*
> @@ -71,8 +108,106 @@ static uint64_t
> mchp_pfsoc_ddr_sgmii_phy_read(void *opaque, hwaddr offset,
> val = 0xff;
> break;
> case SGMII_PHY_PVT_STAT:
> - /* See sgmii_channel_setup() in HSS */
> - val = BIT(14) | BIT(6);
> + /*
> + * HSS polls IO enable and calibration status, then writes
> the
> + * calibration lock. The HSS register definitions mark bits
> 31:30
> + * as writable controls.
> + *
> + * See sgmii_channel_setup() in mss_sgmii.c in HSS.
> + */
> + val = s->regs[index] | SGMII_PHY_PVT_STAT_IO_ENABLE |
> + SGMII_PHY_PVT_STAT_CALIBRATED;
> + break;
> + case SGMII_PHY_PLL_CTRL_MAIN:
> + /*
> + * HSS programs the main DDR PLL controls and polls LOCK
> after
> + * changing the PLL dividers during LPDDR4 manual training.
> The
> + * HSS register definitions mark bits 6:0 and 24 as
> writable.
> + *
> + * See ddr_pll_config() in mss_pll.c and
> + * lpddr4_manual_training() in mss_ddr.c in HSS.
> + */
> + val = s->regs[index] | SGMII_PHY_PLL_CTRL_MAIN_LOCK;
> + break;
> + case SGMII_PHY_PLL_CNTL:
> + /*
> + * HSS programs the SGMII PLL controls and polls
> aro_pll0_lock.
> + *
> + * See setup_sgmii_rpc_per_config() and
> sgmii_channel_setup()
> + * in mss_sgmii.c in HSS.
> + */
> + val = s->regs[index] | SGMII_PHY_PLL_CNTL_LOCK;
> + break;
> + case SGMII_PHY_RECAL_CNTL:
> + /*
> + * HSS programs the low control half, polls sro_dll_lock,
> and
> + * reads sro_dll_90_code from the read-only status half.
> + *
> + * See setup_sgmii_rpc_per_config() and
> sgmii_channel_setup()
> + * in mss_sgmii.c in HSS.
> + */
> + val = s->regs[index] | SGMII_PHY_RECAL_CNTL_LOCK;
> + break;
> + case SGMII_PHY_EXPERT_ADDCMD_LN_READBACK:
> + /*
> + * HSS pulses the ADDCMD move controls while sampling CK
> from
> + * bits 3:0 and A5 from bits 9:8. The transition tap
> positions
> + * below are deterministic QEMU model choices.
> + *
> + * See lpddr4_manual_training() in mss_ddr.c in HSS.
> + */
> + if (s->addcmd_tap >= PHY_ADDCMD_CK_TRANSITION_TAP) {
> + val |= PHY_ADDCMD_CK_READBACK;
> + }
> + if (s->addcmd_tap >= PHY_ADDCMD_A5_TRANSITION_TAP) {
> + val |= PHY_ADDCMD_A5_READBACK;
> + }
> + break;
> + case SGMII_PHY_GT_ERR_COMB:
> + case SGMII_PHY_GT_CLK_SEL:
> + /*
> + * HSS fails DDR verification on any gate error, a zero
> delay
> + * selected by gt_clk_sel, or more than one zero delay byte.
> + * Model no gate errors; the all-nonzero delay below makes
> the
> + * deterministic clock selection immaterial.
> + *
> + * See DDR_TRAINING_IP_SM_VERIFY in ddr_setup() in mss_ddr.c
> + * in HSS.
> + */
> + val = 0;
> + break;
> + case SGMII_PHY_GT_TXDLY:
> + val = PHY_GT_TXDLY_VALUE;
> + break;
> + case SGMII_PHY_DQDQS_STATUS2:
> + /*
> + * HSS requires the calculated DQ/DQS window to be at least
> + * DQ_DQS_NUM_TAPS, which is 5. Return the minimum pass
> value.
> + *
> + * See DDR_TRAINING_IP_SM_VERIFY in ddr_setup() and the
> + * DQ_DQS_NUM_TAPS definition in mss_ddr.h in HSS.
> + */
> + val = PHY_DQDQS_STATUS2_VALUE;
> + break;
> + case SGMII_PHY_EXPERT_DLYCNT_MOVE_REG1:
> + case SGMII_PHY_EXPERT_DLYCNT_LOAD_REG1:
> + case SGMII_PHY_SOFT_RESET_SGMII:
> + case SGMII_PHY_SGMII_MODE:
> + case SGMII_PHY_CH0_CNTL:
> + case SGMII_PHY_CH1_CNTL:
> + case SGMII_PHY_CLK_CNTL:
> + case SGMII_PHY_DYN_CNTL:
> + case SGMII_PHY_SPARE_CNTL:
> + case SGMII_PHY_SPARE_STAT:
> + /*
> + * HSS programs the ADDCMD and SGMII controls and reads
> + * SPARE_STAT to classify the silicon and select eye-width
> + * thresholds.
> + *
> + * See lpddr4_manual_training() in mss_ddr.c and the SGMII
> setup
> + * functions in mss_sgmii.c in HSS.
> + */
> + val = s->regs[index];
> break;
> default:
> qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read "
> @@ -87,10 +222,88 @@ static uint64_t
> mchp_pfsoc_ddr_sgmii_phy_read(void *opaque, hwaddr offset,
> static void mchp_pfsoc_ddr_sgmii_phy_write(void *opaque, hwaddr
> offset,
> uint64_t value, unsigned
> size)
> {
> - qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write "
> - "(size %d, value 0x%" PRIx64
> - ", offset 0x%" HWADDR_PRIx ")\n",
> - __func__, size, value, offset);
> + MchpPfSoCDdrSgmiiPhyState *s = opaque;
> + uint32_t index = offset / sizeof(uint32_t);
> +
> + switch (offset) {
> + case SGMII_PHY_IOC_REG1:
> + case SGMII_PHY_TRAINING_STATUS:
> + case SGMII_PHY_GT_ERR_COMB:
> + case SGMII_PHY_GT_CLK_SEL:
> + case SGMII_PHY_GT_TXDLY:
> + case SGMII_PHY_DQ_DQS_ERR_DONE:
> + case SGMII_PHY_DQDQS_STATUS1:
> + case SGMII_PHY_DQDQS_STATUS2:
> + case SGMII_PHY_EXPERT_ADDCMD_LN_READBACK:
> + case SGMII_PHY_SPARE_STAT:
> + /*
> + * The HSS register definitions declare these status and
> + * readback registers read-only.
> + *
> + * See mss_ddr_sgmii_phy_defs.h in HSS.
> + */
> + return;
> + case SGMII_PHY_PLL_CTRL_MAIN:
> + value &= SGMII_PHY_PLL_CTRL_MAIN_CONTROL_LO_MASK |
> + SGMII_PHY_PLL_CTRL_MAIN_LP_REQUIRES_LOCK;
> + break;
> + case SGMII_PHY_PLL_CNTL:
> + value &= ~SGMII_PHY_PLL_CNTL_LOCK;
> + break;
> + case SGMII_PHY_RECAL_CNTL:
> + value &= ~SGMII_PHY_RECAL_CNTL_STATUS_MASK;
> + break;
> + case SGMII_PHY_PVT_STAT:
> + value &= SGMII_PHY_PVT_STAT_GUEST_CTRL_MASK;
> + break;
> + case SGMII_PHY_EXPERT_DLYCNT_MOVE_REG1: {
> + bool active;
> +
> + /*
> + * HSS pulses 0 -> 0x180000 -> 0 while scanning the ADDCMD
> eye.
> + * Count each rising pulse as one abstract delay tap.
> + *
> + * See lpddr4_manual_training() in mss_ddr.c in HSS.
> + */
> + value &= SGMII_PHY_EXPERT_DLYCNT_MOVE_CONTROL_MASK;
> + active = value & SGMII_PHY_EXPERT_DLYCNT_MOVE_ADDCMD_MASK;
> + if (active && !s->addcmd_move_active &&
> + s->addcmd_tap != UINT8_MAX) {
> + s->addcmd_tap++;
> + }
> + s->addcmd_move_active = active;
> + break;
> + }
> + case SGMII_PHY_EXPERT_DLYCNT_LOAD_REG1:
> + /*
> + * HSS pulses 0x180000 before each ADDCMD scan. Restart the
> + * abstract QEMU tap position when that load pulse is
> asserted.
> + *
> + * See lpddr4_manual_training() in mss_ddr.c in HSS.
> + */
> + value &= SGMII_PHY_EXPERT_DLYCNT_LOAD_CONTROL_MASK;
> + if (value & SGMII_PHY_EXPERT_DLYCNT_LOAD_ADDCMD_MASK) {
> + s->addcmd_tap = 0;
> + s->addcmd_move_active = false;
> + }
> + break;
> + case SGMII_PHY_SOFT_RESET_SGMII:
> + case SGMII_PHY_SGMII_MODE:
> + case SGMII_PHY_CH0_CNTL:
> + case SGMII_PHY_CH1_CNTL:
> + case SGMII_PHY_CLK_CNTL:
> + case SGMII_PHY_DYN_CNTL:
> + case SGMII_PHY_SPARE_CNTL:
> + break;
> + default:
> + qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write "
> + "(size %d, value 0x%" PRIx64
> + ", offset 0x%" HWADDR_PRIx ")\n",
> + __func__, size, value, offset);
> + return;
> + }
> +
> + s->regs[index] = value;
> }
>
> static const MemoryRegionOps mchp_pfsoc_ddr_sgmii_phy_ops = {
> @@ -99,6 +312,16 @@ static const MemoryRegionOps
> mchp_pfsoc_ddr_sgmii_phy_ops = {
> .endianness = DEVICE_LITTLE_ENDIAN,
> };
>
> +static void mchp_pfsoc_ddr_sgmii_phy_reset(DeviceState *dev)
> +{
> + MchpPfSoCDdrSgmiiPhyState *s = MCHP_PFSOC_DDR_SGMII_PHY(dev);
> +
> + memset(s->regs, 0, sizeof(s->regs));
> + s->training_status_bit = 0;
> + s->addcmd_tap = 0;
> + s->addcmd_move_active = false;
> +}
> +
> static void mchp_pfsoc_ddr_sgmii_phy_realize(DeviceState *dev, Error
> **errp)
> {
> MchpPfSoCDdrSgmiiPhyState *s = MCHP_PFSOC_DDR_SGMII_PHY(dev);
> @@ -117,6 +340,7 @@ static void
> mchp_pfsoc_ddr_sgmii_phy_class_init(ObjectClass *klass,
>
> dc->desc = "Microchip PolarFire SoC DDR SGMII PHY module";
> dc->realize = mchp_pfsoc_ddr_sgmii_phy_realize;
> + device_class_set_legacy_reset(dc,
> mchp_pfsoc_ddr_sgmii_phy_reset);
Can we use the non-legacy reset instead
Alistair
> }
>
> static const TypeInfo mchp_pfsoc_ddr_sgmii_phy_info = {