On Tue, 2026-08-18 at 11:52 +1000, Alistair Francis wrote:
> On Thu, 2026-07-16 at 21:24 +0800, Junze Cao wrote:
> > The K230 SDK U-Boot SPL programs K230 DDRC CFG and K230 DDR PHY
> > registers before DRAM can be used.
> > 
> > Add separate SysBus devices for both register ranges. Model
> > controller
> > reset values, DFI and software-update handshakes. For the PHY,
> > model
> > register ownership, the training mailbox, and DFI completion.
> > 
> > Include migration state for both devices.
> > 
> > Signed-off-by: Junze Cao <[email protected]>
> 
> This generates a range of failures as part of `make check`
> 
> Summary of Failures:
> 
>  34/557 qemu:func-quick+func-i386 / func-i386-migration              
> ERROR            0.09s   exit status 1
>  36/557 qemu:func-quick+func-i386 / func-i386-empty_cpu_model        
> ERROR            0.08s   exit status 1
>  37/557 qemu:func-quick+func-i386 / func-i386-info_usernet           
> ERROR            0.09s   exit status 1
>  38/557 qemu:func-quick+func-i386 / func-i386-linters                
> ERROR            0.09s   exit status 1
>  39/557 qemu:func-quick+func-i386 / func-i386-monitor_hotplug        
> ERROR            0.10s   exit status 1
>  40/557 qemu:func-quick+func-i386 / func-i386-version                
> ERROR            0.09s   exit status 1
>  41/557 qemu:func-quick+func-i386 / func-i386-vnc                    
> ERROR            0.06s   exit status 1
>  42/557 qemu:func-quick+func-x86_64 / func-x86_64-bad_vmstate        
> ERROR            0.08s   exit status 1
>  43/557 qemu:func-quick+func-x86_64 / func-x86_64-cpu_model_versions 
> ERROR            0.09s   exit status 1
>  44/557 qemu:func-quick+func-x86_64 / func-x86_64-cpu_queries        
> ERROR            0.09s   exit status 1
>  45/557 qemu:func-quick+func-x86_64 / func-x86_64-mem_addr_space     
> ERROR            0.09s   exit status 1
>  46/557 qemu:func-quick+func-x86_64 / func-x86_64-migration          
> ERROR            0.09s   exit status 1
>  47/557 qemu:func-quick+func-x86_64 / func-x86_64-
> pc_cpu_hotplug_props
> ERROR            0.09s   exit status 1
>  48/557 qemu:func-quick+func-x86_64 / func-x86_64-virtio_version     
> ERROR            0.05s   exit status 1
>  49/557 qemu:func-quick+func-x86_64 / func-x86_64-vmstate            
> ERROR            0.09s   exit status 1
>  50/557 qemu:func-quick+func-x86_64 / func-x86_64-memlock            
> ERROR            0.08s   exit status 1
>  51/557 qemu:func-quick+func-x86_64 / func-x86_64-empty_cpu_model    
> ERROR            0.08s   exit status 1
>  52/557 qemu:func-quick+func-x86_64 / func-x86_64-info_usernet       
> ERROR            0.09s   exit status 1
>  53/557 qemu:func-quick+func-x86_64 / func-x86_64-linters            
> ERROR            0.08s   exit status 1
>  54/557 qemu:func-quick+func-x86_64 / func-x86_64-monitor_hotplug    
> ERROR            0.10s   exit status 1
>  55/557 qemu:func-quick+func-x86_64 / func-x86_64-version            
> ERROR            0.09s   exit status 1
>  56/557 qemu:func-quick+func-x86_64 / func-x86_64-vnc                
> ERROR            0.06s   exit status 1
>  58/557 qemu:func-quick+func-aarch64 / func-aarch64-migration        
> ERROR            0.09s   exit status 1
>  59/557 qemu:func-quick+func-aarch64 / func-aarch64-vmstate          
> ERROR            0.09s   exit status 1
>  60/557 qemu:func-quick+func-aarch64 / func-aarch64-empty_cpu_model  
> ERROR            0.09s   exit status 1
>  61/557 qemu:func-quick+func-aarch64 / func-aarch64-info_usernet     
> ERROR            0.09s   exit status 1
>  62/557 qemu:func-quick+func-aarch64 / func-aarch64-linters          
> ERROR            0.09s   exit status 1
>  63/557 qemu:func-quick+func-aarch64 / func-aarch64-vnc              
> ERROR            0.06s   exit status 1
>  64/557 qemu:func-quick+func-aarch64 / func-aarch64-monitor_hotplug  
> ERROR            0.11s   exit status 1
>  65/557 qemu:func-quick+func-aarch64 / func-aarch64-version          
> ERROR            0.10s   exit status 1
>  66/557 qemu:func-quick+func-riscv64 / func-riscv64-migration        
> ERROR            0.09s   exit status 1
>  67/557 qemu:func-quick+func-riscv64 / func-riscv64-opensbi          
> ERROR            0.09s   exit status 1
>  68/557 qemu:func-quick+func-riscv64 / func-riscv64-empty_cpu_model  
> ERROR            0.09s   exit status 1
>  69/557 qemu:func-quick+func-riscv64 / func-riscv64-info_usernet     
> ERROR            0.09s   exit status 1
>  70/557 qemu:func-quick+func-riscv64 / func-riscv64-linters          
> ERROR            0.09s   exit status 1
>  71/557 qemu:func-quick+func-riscv64 / func-riscv64-monitor_hotplug  
> ERROR            0.09s   exit status 1
>  72/557 qemu:func-quick+func-riscv64 / func-riscv64-vnc              
> ERROR            0.05s   exit status 1
>  73/557 qemu:func-quick+func-riscv64 / func-riscv64-version          
> ERROR            0.09s   exit status 1
>  74/557 qemu:func-quick+func-riscv32 / func-riscv32-migration        
> ERROR            0.09s   exit status 1
>  75/557 qemu:func-quick+func-riscv32 / func-riscv32-opensbi          
> ERROR            0.08s   exit status 1
>  76/557 qemu:func-quick+func-riscv32 / func-riscv32-empty_cpu_model  
> ERROR            0.09s   exit status 1
>  77/557 qemu:func-quick+func-riscv32 / func-riscv32-info_usernet     
> ERROR            0.09s   exit status 1
>  78/557 qemu:func-quick+func-riscv32 / func-riscv32-linters          
> ERROR            0.08s   exit status 1
>  79/557 qemu:func-quick+func-riscv32 / func-riscv32-monitor_hotplug  
> ERROR            0.10s   exit status 1
>  80/557 qemu:func-quick+func-riscv32 / func-riscv32-version          
> ERROR            0.08s   exit status 1
>  81/557 qemu:func-quick+func-riscv32 / func-riscv32-vnc              
> ERROR            0.06s   exit status 1
> 374/557 qemu:block / io-qcow2-040                                    
> ERROR            0.15s   exit status 1
> 377/557 qemu:block / io-qcow2-041                                    
> ERROR            0.15s   exit status 1
> 422/557 qemu:block / io-qcow2-141                                    
> ERROR            0.15s   exit status 1
> 442/557 qemu:block / io-qcow2-203                                    
> ERROR            0.15s   exit status 1
> 452/557 qemu:block / io-qcow2-256                                    
> ERROR            0.15s   exit status 1
> 457/557 qemu:block / io-qcow2-283                                    
> ERROR            0.15s   exit status 1
> 460/557 qemu:block / io-qcow2-299                                    
> ERROR            0.14s   exit status 1
> 465/557 qemu:block / io-qcow2-copy-before-write                      
> ERROR            0.13s   exit status 1
> 467/557 qemu:block / io-qcow2-iothreads-commit-active                
> ERROR            0.17s   exit status 1
> 469/557 qemu:block / io-qcow2-iothreads-stream                       
> ERROR            0.17s   exit status 1
> 472/557 qemu:block / io-qcow2-nbd-multiconn                          
> ERROR            0.15s   exit status 1
> 
> Ok:                 478 
> Expected Fail:      0   
> Fail:               57  
> Unexpected Pass:    0   
> Skipped:            22  
> Timeout:            0   

Nevermind! Seems like that was an issue upstream. Sorry about that

Alistair

> 
> 
> Alistair
> 
> > ---
> >  MAINTAINERS                |   2 +
> >  hw/misc/k230_ddr.c         | 544
> > +++++++++++++++++++++++++++++++++++++
> >  hw/misc/meson.build        |   1 +
> >  include/hw/misc/k230_ddr.h |  59 ++++
> >  4 files changed, 606 insertions(+)
> >  create mode 100644 hw/misc/k230_ddr.c
> >  create mode 100644 include/hw/misc/k230_ddr.h
> > 
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index ecb8cfdc41..1cb3ef6c02 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -1826,8 +1826,10 @@ M: Chao Liu <[email protected]>
> >  L: [email protected]
> >  S: Maintained
> >  F: docs/system/riscv/k230.rst
> > +F: hw/misc/k230_ddr.c
> >  F: hw/riscv/k230.c
> >  F: hw/watchdog/k230_wdt.c
> > +F: include/hw/misc/k230_ddr.h
> >  F: include/hw/riscv/k230.h
> >  F: include/hw/watchdog/k230_wdt.h
> >  F: tests/functional/riscv64/test_k230.py
> > diff --git a/hw/misc/k230_ddr.c b/hw/misc/k230_ddr.c
> > new file mode 100644
> > index 0000000000..9ff2714e34
> > --- /dev/null
> > +++ b/hw/misc/k230_ddr.c
> > @@ -0,0 +1,544 @@
> > +/*
> > + * Kendryte K230 DDR controller and PHY models
> > + *
> > + * Models the K230 DDRC CFG registers and K230 DDR PHY registers
> > exercised
> > + * by the K230 SDK U-Boot SPL.
> > + *
> > + * Copyright (c) 2026 Junze Cao <[email protected]>
> > + *
> > + * SPDX-License-Identifier: GPL-2.0-or-later
> > + */
> > +
> > +#include "qemu/osdep.h"
> > +#include "qemu/module.h"
> > +#include "migration/vmstate.h"
> > +#include "hw/core/registerfields.h"
> > +#include "hw/core/resettable.h"
> > +#include "hw/misc/k230_ddr.h"
> > +
> > +/* DDRC CFG registers */
> > +
> > +enum K230DDRCOperatingMode {
> > +    K230_DDRC_MODE_INIT = 0,
> > +    K230_DDRC_MODE_NORMAL = 1,
> > +};
> > +
> > +REG32(K230_DDRC_MSTR, 0x000)
> > +
> > +REG32(K230_DDRC_STAT, 0x004)
> > +    FIELD(K230_DDRC_STAT, OPERATING_MODE, 0, 3)
> > +
> > +REG32(K230_DDRC_PWRCTL, 0x030)
> > +
> > +REG32(K230_DDRC_RFSHCTL0, 0x050)
> > +
> > +REG32(K230_DDRC_RFSHTMG, 0x064)
> > +
> > +REG32(K230_DDRC_RFSHTMG1, 0x068)
> > +
> > +REG32(K230_DDRC_INIT0, 0x0d0)
> > +
> > +REG32(K230_DDRC_INIT2, 0x0d8)
> > +
> > +REG32(K230_DDRC_INIT3, 0x0dc)
> > +
> > +REG32(K230_DDRC_INIT5, 0x0e4)
> > +
> > +REG32(K230_DDRC_DRAMTMG0, 0x100)
> > +#define R_K230_DDRC_DRAMTMG(index) \
> > +    (R_K230_DDRC_DRAMTMG0 + (index))
> > +
> > +REG32(K230_DDRC_ZQCTL0, 0x180)
> > +
> > +REG32(K230_DDRC_ZQCTL1, 0x184)
> > +
> > +REG32(K230_DDRC_ZQCTL2, 0x188)
> > +
> > +REG32(K230_DDRC_ZQSTAT, 0x18c)
> > +
> > +REG32(K230_DDRC_DFITMG0, 0x190)
> > +
> > +REG32(K230_DDRC_DFITMG1, 0x194)
> > +
> > +REG32(K230_DDRC_DFIUPD0, 0x1a0)
> > +
> > +REG32(K230_DDRC_DFIUPD1, 0x1a4)
> > +
> > +REG32(K230_DDRC_DFIUPD2, 0x1a8)
> > +
> > +REG32(K230_DDRC_DFIMISC, 0x1b0)
> > +    FIELD(K230_DDRC_DFIMISC, DFI_INIT_COMPLETE_EN, 0, 1)
> > +    FIELD(K230_DDRC_DFIMISC, DFI_INIT_START, 5, 1)
> > +
> > +REG32(K230_DDRC_DFITMG2, 0x1b4)
> > +
> > +REG32(K230_DDRC_DFISTAT, 0x1bc)
> > +    FIELD(K230_DDRC_DFISTAT, DFI_INIT_COMPLETE, 0, 1)
> > +
> > +REG32(K230_DDRC_ODTCFG, 0x240)
> > +
> > +REG32(K230_DDRC_SWCTL, 0x320)
> > +    FIELD(K230_DDRC_SWCTL, SW_DONE, 0, 1)
> > +
> > +REG32(K230_DDRC_SWSTAT, 0x324)
> > +    FIELD(K230_DDRC_SWSTAT, SW_DONE_ACK, 0, 1)
> > +
> > +static const uint32_t k230_ddrc_reset_values[K230_DDRC_REG_COUNT]
> > =
> > {
> > +    [R_K230_DDRC_MSTR]      = 0x01040000,
> > +    [R_K230_DDRC_RFSHCTL0]  = 0x00210000,
> > +    [R_K230_DDRC_RFSHTMG]   = 0x0062008c,
> > +    [R_K230_DDRC_RFSHTMG1]  = 0x0000008c,
> > +    [R_K230_DDRC_INIT0]     = 0x0002004e,
> > +    [R_K230_DDRC_INIT3]     = 0x00000510,
> > +    [R_K230_DDRC_INIT5]     = 0x00100000,
> > +    [R_K230_DDRC_DRAMTMG(0)]  = 0x0f101b0f,
> > +    [R_K230_DDRC_DRAMTMG(1)]  = 0x00080414,
> > +    [R_K230_DDRC_DRAMTMG(2)]  = 0x0305060d,
> > +    [R_K230_DDRC_DRAMTMG(3)]  = 0x00004000,
> > +    [R_K230_DDRC_DRAMTMG(4)]  = 0x05040405,
> > +    [R_K230_DDRC_DRAMTMG(5)]  = 0x05050403,
> > +    [R_K230_DDRC_DRAMTMG(6)]  = 0x02020005,
> > +    [R_K230_DDRC_DRAMTMG(7)]  = 0x00000202,
> > +    [R_K230_DDRC_DRAMTMG(8)]  = 0x03034405,
> > +    [R_K230_DDRC_DRAMTMG(9)]  = 0x0004040d,
> > +    [R_K230_DDRC_DRAMTMG(10)] = 0x001c180a,
> > +    [R_K230_DDRC_DRAMTMG(11)] = 0x440c021c,
> > +    [R_K230_DDRC_DRAMTMG(12)] = 0x00020610,
> > +    [R_K230_DDRC_DRAMTMG(13)] = 0x1c200004,
> > +    [R_K230_DDRC_DRAMTMG(14)] = 0x000000a0,
> > +    [R_K230_DDRC_DRAMTMG(16)] = 0x05100404,
> > +    [R_K230_DDRC_ZQCTL0]    = 0x02000040,
> > +    [R_K230_DDRC_ZQCTL1]    = 0x02000100,
> > +    [R_K230_DDRC_DFITMG0]   = 0x07020002,
> > +    [R_K230_DDRC_DFITMG1]   = 0x00000404,
> > +    [R_K230_DDRC_DFIUPD0]   = 0x00400003,
> > +    [R_K230_DDRC_DFIUPD1]   = 0x00010001,
> > +    [R_K230_DDRC_DFIUPD2]   = 0x80000000,
> > +    [R_K230_DDRC_DFIMISC]   = 0x00000001,
> > +    [R_K230_DDRC_DFITMG2]   = 0x00000202,
> > +    [R_K230_DDRC_ODTCFG]    = 0x04000400,
> > +    [R_K230_DDRC_SWCTL]     = 0x00000001,
> > +    [R_K230_DDRC_SWSTAT]    = 0x00000001,
> > +};
> > +
> > +/* K230 DDR PHY CSRs use byte offset = CSR index * 4. */
> > +#define K230_DDR_PHY_CSR_OFFSET(index) ((index) * 4)
> > +#define K230_DDR_PHY_MASTER_CSR(reg) \
> > +    K230_DDR_PHY_CSR_OFFSET(0x20000 + (reg))
> > +#define K230_DDR_PHY_APBONLY_CSR(reg) \
> > +    K230_DDR_PHY_CSR_OFFSET(0xd0000 + (reg))
> > +
> > +#define K230_DDR_PHY_ATX_IMPEDANCE_RESET UINT32_C(0x03ff)
> > +
> > +REG32(K230_DDR_PHY_MICRO_CONT_MUX_SEL,
> > +      K230_DDR_PHY_APBONLY_CSR(0x000))
> > +    FIELD(K230_DDR_PHY_MICRO_CONT_MUX_SEL, MICRO_CONT_MUX_SEL, 0,
> > 1)
> > +
> > +REG32(K230_DDR_PHY_TRAINING_STATUS,
> > +      K230_DDR_PHY_APBONLY_CSR(0x004))
> > +    FIELD(K230_DDR_PHY_TRAINING_STATUS, MAILBOX_EMPTY, 0, 1)
> > +
> > +REG32(K230_DDR_PHY_TRAINING_ACK,
> > +      K230_DDR_PHY_APBONLY_CSR(0x031))
> > +
> > +REG32(K230_DDR_PHY_TRAINING_MESSAGE,
> > +      K230_DDR_PHY_APBONLY_CSR(0x032))
> > +
> > +REG32(K230_DDR_PHY_TRAINING_TRIGGER,
> > +      K230_DDR_PHY_APBONLY_CSR(0x099))
> > +
> > +#define K230_DDR_PHY_TX_IMPEDANCE_CTRL1_RESET UINT32_C(0x0fff)
> > +
> > +REG32(K230_DDR_PHY_DFI_INIT_COMPLETE,
> > +      K230_DDR_PHY_MASTER_CSR(0x0f9))
> > +    FIELD(K230_DDR_PHY_DFI_INIT_COMPLETE, DFI_INIT_COMPLETE, 0, 1)
> > +
> > +REG32(K230_DDR_PHY_VREF_IN_GLOBAL,
> > +      K230_DDR_PHY_MASTER_CSR(0x0b2))
> > +#define K230_DDR_PHY_VREF_IN_GLOBAL_RESET UINT32_C(0x0200)
> > +
> > +static void k230_ddr_cfg_update_state(K230DDRCfgState *s)
> > +{
> > +    bool dfi_complete = false;
> > +    unsigned int operating_mode = K230_DDRC_MODE_INIT;
> > +
> > +    if (s->phy) {
> > +        dfi_complete = FIELD_EX32(s->phy->dfi_init_complete,
> > +                                  K230_DDR_PHY_DFI_INIT_COMPLETE,
> > +                                  DFI_INIT_COMPLETE);
> > +    }
> > +
> > +    ARRAY_FIELD_DP32(s->regs, K230_DDRC_DFISTAT,
> > +                     DFI_INIT_COMPLETE, dfi_complete);
> > +    ARRAY_FIELD_DP32(s->regs, K230_DDRC_SWSTAT, SW_DONE_ACK,
> > +                     ARRAY_FIELD_EX32(s->regs, K230_DDRC_SWCTL,
> > SW_DONE));
> > +
> > +    if (dfi_complete &&
> > +        ARRAY_FIELD_EX32(s->regs, K230_DDRC_DFIMISC,
> > +                         DFI_INIT_COMPLETE_EN)) {
> > +        s->initialized = true;
> > +    }
> > +
> > +    if (s->initialized) {
> > +        operating_mode = K230_DDRC_MODE_NORMAL;
> > +    }
> > +
> > +    ARRAY_FIELD_DP32(s->regs, K230_DDRC_STAT,
> > +                     OPERATING_MODE, operating_mode);
> > +}
> > +
> > +static uint64_t k230_ddr_cfg_read(void *opaque, hwaddr addr,
> > unsigned int size)
> > +{
> > +    K230DDRCfgState *s = opaque;
> > +    uint64_t value = 0;
> > +
> > +    if (addr < K230_DDRC_REG_SIZE) {
> > +        k230_ddr_cfg_update_state(s);
> > +        value = s->regs[addr / sizeof(uint32_t)];
> > +    }
> > +
> > +    return value;
> > +}
> > +
> > +static void k230_ddr_cfg_write(void *opaque, hwaddr addr, uint64_t
> > value,
> > +                               unsigned int size)
> > +{
> > +    K230DDRCfgState *s = opaque;
> > +    uint32_t val = value;
> > +
> > +    if (addr >= K230_DDRC_REG_SIZE) {
> > +        return;
> > +    }
> > +
> > +    switch (addr) {
> > +    case A_K230_DDRC_STAT:
> > +    case A_K230_DDRC_DFISTAT:
> > +    case A_K230_DDRC_ZQSTAT:
> > +    case A_K230_DDRC_SWSTAT:
> > +        return;
> > +    case A_K230_DDRC_PWRCTL:
> > +        s->regs[R_K230_DDRC_PWRCTL] = val & MAKE_64BIT_MASK(0, 9);
> > +        break;
> > +    case A_K230_DDRC_ZQCTL2:
> > +        /* ZQ reset completes immediately and the W1S bit self-
> > clears. */
> > +        s->regs[R_K230_DDRC_ZQCTL2] = 0;
> > +        s->regs[R_K230_DDRC_ZQSTAT] = 0;
> > +        break;
> > +    case A_K230_DDRC_DFIMISC:
> > +        s->regs[R_K230_DDRC_DFIMISC] =
> > +            val & (MAKE_64BIT_MASK(0, 6) | MAKE_64BIT_MASK(8, 5));
> > +        if (s->phy && s->phy->training_complete &&
> > +            FIELD_EX32(val, K230_DDRC_DFIMISC, DFI_INIT_START)) {
> > +            s->phy->dfi_init_complete = FIELD_DP32(
> > +                s->phy->dfi_init_complete,
> > +                K230_DDR_PHY_DFI_INIT_COMPLETE, DFI_INIT_COMPLETE,
> > 1);
> > +        }
> > +        break;
> > +    case A_K230_DDRC_SWCTL:
> > +        s->regs[R_K230_DDRC_SWCTL] =
> > +            val & R_K230_DDRC_SWCTL_SW_DONE_MASK;
> > +        break;
> > +    default:
> > +        s->regs[addr / sizeof(uint32_t)] = val;
> > +        break;
> > +    }
> > +
> > +    k230_ddr_cfg_update_state(s);
> > +}
> > +
> > +static bool k230_ddr_phy_decode_anib(hwaddr addr, unsigned int
> > *anib)
> > +{
> > +    uint32_t csr = addr / sizeof(uint32_t);
> > +
> > +    if ((csr & 0xfff) != 0x043) {
> > +        return false;
> > +    }
> > +
> > +    *anib = csr >> 12;
> > +    return *anib < K230_DDR_PHY_ANIB_COUNT;
> > +}
> > +
> > +static bool k230_ddr_phy_decode_dbyte(hwaddr addr, uint32_t reg,
> > +                                     unsigned int *dbyte,
> > +                                     unsigned int *nibble)
> > +{
> > +    uint32_t csr = addr / sizeof(uint32_t);
> > +    uint32_t block_offset;
> > +
> > +    if (csr < 0x10000) {
> > +        return false;
> > +    }
> > +
> > +    csr -= 0x10000;
> > +    *dbyte = csr >> 12;
> > +    if (*dbyte >= K230_DDR_PHY_DBYTE_COUNT) {
> > +        return false;
> > +    }
> > +
> > +    block_offset = csr & 0xfff;
> > +    *nibble = block_offset >> 8;
> > +    return *nibble < K230_DDR_PHY_NIBBLES_PER_DBYTE &&
> > +           (block_offset & 0xff) == reg;
> > +}
> > +
> > +static uint64_t k230_ddr_phy_read(void *opaque, hwaddr addr,
> > +                                  unsigned int size)
> > +{
> > +    K230DDRPhyState *s = opaque;
> > +    unsigned int dbyte;
> > +    unsigned int nibble;
> > +    unsigned int anib;
> > +    uint64_t value = 0;
> > +
> > +    switch (addr) {
> > +    case A_K230_DDR_PHY_MICRO_CONT_MUX_SEL:
> > +        value = s->micro_cont_mux_sel;
> > +        break;
> > +    case A_K230_DDR_PHY_TRAINING_STATUS:
> > +        value = s->mailbox_message_pending ? 0 :
> > +                R_K230_DDR_PHY_TRAINING_STATUS_MAILBOX_EMPTY_MASK;
> > +        break;
> > +    case A_K230_DDR_PHY_TRAINING_MESSAGE:
> > +        value = s->training_complete ? 0x07 : 0;
> > +        break;
> > +    case A_K230_DDR_PHY_TRAINING_ACK:
> > +    case A_K230_DDR_PHY_TRAINING_TRIGGER:
> > +        break;
> > +    case A_K230_DDR_PHY_DFI_INIT_COMPLETE:
> > +        value = s->dfi_init_complete;
> > +        break;
> > +    case A_K230_DDR_PHY_VREF_IN_GLOBAL:
> > +        value = s->vref_in_global;
> > +        break;
> > +    default:
> > +        if (k230_ddr_phy_decode_anib(addr, &anib)) {
> > +            value = s->atx_impedance[anib];
> > +        } else if (k230_ddr_phy_decode_dbyte(addr, 0x049,
> > +                                            &dbyte, &nibble)) {
> > +            value = s->tx_impedance_ctrl1[dbyte][nibble];
> > +        } else if (k230_ddr_phy_decode_dbyte(addr, 0x04d,
> > +                                            &dbyte, &nibble)) {
> > +            value = s->tx_odt_drv_stren[dbyte][nibble];
> > +        }
> > +        break;
> > +    }
> > +
> > +    return value;
> > +}
> > +
> > +static void k230_ddr_phy_write(void *opaque, hwaddr addr, uint64_t
> > value,
> > +                               unsigned int size)
> > +{
> > +    K230DDRPhyState *s = opaque;
> > +    uint16_t val = value;
> > +    unsigned int dbyte;
> > +    unsigned int nibble;
> > +    unsigned int anib;
> > +
> > +    switch (addr) {
> > +    case A_K230_DDR_PHY_MICRO_CONT_MUX_SEL:
> > +        s->micro_cont_mux_sel =
> > +            val &
> > R_K230_DDR_PHY_MICRO_CONT_MUX_SEL_MICRO_CONT_MUX_SEL_MASK;
> > +        return;
> > +    case A_K230_DDR_PHY_TRAINING_STATUS:
> > +    case A_K230_DDR_PHY_TRAINING_MESSAGE:
> > +        return;
> > +    case A_K230_DDR_PHY_TRAINING_ACK:
> > +        if (val == 0) {
> > +            s->mailbox_message_pending = false;
> > +        }
> > +        return;
> > +    case A_K230_DDR_PHY_TRAINING_TRIGGER:
> > +        if (val != 0) {
> > +            s->training_trigger_seen = true;
> > +        } else if (s->training_trigger_seen) {
> > +            /* Abstract the loaded firmware run to its final
> > mailbox
> > message. */
> > +            s->training_trigger_seen = false;
> > +            s->training_complete = true;
> > +            s->mailbox_message_pending = true;
> > +        }
> > +        return;
> > +    default:
> > +        break;
> > +    }
> > +
> > +    if (FIELD_EX32(s->micro_cont_mux_sel,
> > +                   K230_DDR_PHY_MICRO_CONT_MUX_SEL,
> > +                   MICRO_CONT_MUX_SEL)) {
> > +        return;
> > +    }
> > +
> > +    switch (addr) {
> > +    case A_K230_DDR_PHY_DFI_INIT_COMPLETE:
> > +        s->dfi_init_complete =
> > +            val &
> > R_K230_DDR_PHY_DFI_INIT_COMPLETE_DFI_INIT_COMPLETE_MASK;
> > +        return;
> > +    case A_K230_DDR_PHY_VREF_IN_GLOBAL:
> > +        s->vref_in_global = val & UINT16_C(0x7fff);
> > +        return;
> > +    default:
> > +        break;
> > +    }
> > +
> > +    if (k230_ddr_phy_decode_anib(addr, &anib)) {
> > +        s->atx_impedance[anib] = val & UINT16_C(0x03ff);
> > +        return;
> > +    }
> > +
> > +    if (k230_ddr_phy_decode_dbyte(addr, 0x049, &dbyte, &nibble)) {
> > +        s->tx_impedance_ctrl1[dbyte][nibble] = val &
> > UINT16_C(0x0fff);
> > +        return;
> > +    }
> > +
> > +    if (k230_ddr_phy_decode_dbyte(addr, 0x04d, &dbyte, &nibble)) {
> > +        s->tx_odt_drv_stren[dbyte][nibble] = val &
> > UINT16_C(0x0fff);
> > +    }
> > +}
> > +
> > +static const MemoryRegionOps k230_ddr_cfg_ops = {
> > +    .read = k230_ddr_cfg_read,
> > +    .write = k230_ddr_cfg_write,
> > +    .endianness = DEVICE_LITTLE_ENDIAN,
> > +    .valid = {
> > +        .min_access_size = 4,
> > +        .max_access_size = 4,
> > +        .unaligned = false,
> > +    },
> > +};
> > +
> > +static const MemoryRegionOps k230_ddr_phy_ops = {
> > +    .read = k230_ddr_phy_read,
> > +    .write = k230_ddr_phy_write,
> > +    .endianness = DEVICE_LITTLE_ENDIAN,
> > +    .valid = {
> > +        .min_access_size = 4,
> > +        .max_access_size = 4,
> > +        .unaligned = false,
> > +    },
> > +};
> > +
> > +static void k230_ddr_cfg_init(Object *obj)
> > +{
> > +    K230DDRCfgState *s = K230_DDR_CFG(obj);
> > +
> > +    memory_region_init_io(&s->mmio, obj, &k230_ddr_cfg_ops, s,
> > +                          "k230-ddr-cfg", K230_DDRC_MMIO_SIZE);
> > +    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
> > +}
> > +
> > +static void k230_ddr_phy_init(Object *obj)
> > +{
> > +    K230DDRPhyState *s = K230_DDR_PHY(obj);
> > +
> > +    memory_region_init_io(&s->mmio, obj, &k230_ddr_phy_ops, s,
> > +                          "k230-ddr-phy", K230_DDR_PHY_MMIO_SIZE);
> > +    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
> > +}
> > +
> > +static void k230_ddr_cfg_reset_enter(Object *obj, ResetType type)
> > +{
> > +    K230DDRCfgState *s = K230_DDR_CFG(obj);
> > +
> > +    s->initialized = false;
> > +    memcpy(s->regs, k230_ddrc_reset_values, sizeof(s->regs));
> > +}
> > +
> > +static void k230_ddr_phy_reset_enter(Object *obj, ResetType type)
> > +{
> > +    K230DDRPhyState *s = K230_DDR_PHY(obj);
> > +    unsigned int dbyte;
> > +    unsigned int nibble;
> > +    unsigned int anib;
> > +
> > +    memset(s->tx_odt_drv_stren, 0, sizeof(s->tx_odt_drv_stren));
> > +
> > +    for (anib = 0; anib < ARRAY_SIZE(s->atx_impedance); anib++) {
> > +        s->atx_impedance[anib] = K230_DDR_PHY_ATX_IMPEDANCE_RESET;
> > +    }
> > +
> > +    for (dbyte = 0; dbyte < ARRAY_SIZE(s->tx_impedance_ctrl1);
> > dbyte++) {
> > +        for (nibble = 0;
> > +             nibble < ARRAY_SIZE(s->tx_impedance_ctrl1[dbyte]);
> > nibble++) {
> > +            s->tx_impedance_ctrl1[dbyte][nibble] =
> > +                K230_DDR_PHY_TX_IMPEDANCE_CTRL1_RESET;
> > +        }
> > +    }
> > +
> > +    s->dfi_init_complete = 0;
> > +    s->vref_in_global = K230_DDR_PHY_VREF_IN_GLOBAL_RESET;
> > +    s->micro_cont_mux_sel = 0;
> > +    s->training_trigger_seen = false;
> > +    s->training_complete = false;
> > +    s->mailbox_message_pending = false;
> > +}
> > +
> > +static const VMStateDescription vmstate_k230_ddr_cfg = {
> > +    .name = "k230-ddr-cfg",
> > +    .version_id = 1,
> > +    .minimum_version_id = 1,
> > +    .fields = (const VMStateField[]) {
> > +        VMSTATE_UINT32_ARRAY(regs, K230DDRCfgState,
> > K230_DDRC_REG_COUNT),
> > +        VMSTATE_BOOL(initialized, K230DDRCfgState),
> > +        VMSTATE_END_OF_LIST()
> > +    },
> > +};
> > +
> > +static const VMStateDescription vmstate_k230_ddr_phy = {
> > +    .name = "k230-ddr-phy",
> > +    .version_id = 1,
> > +    .minimum_version_id = 1,
> > +    .fields = (const VMStateField[]) {
> > +        VMSTATE_UINT16_ARRAY(atx_impedance, K230DDRPhyState,
> > +                             K230_DDR_PHY_ANIB_COUNT),
> > +        VMSTATE_UINT16_2DARRAY(tx_impedance_ctrl1,
> > K230DDRPhyState,
> > +                               K230_DDR_PHY_DBYTE_COUNT,
> > +                               K230_DDR_PHY_NIBBLES_PER_DBYTE),
> > +        VMSTATE_UINT16_2DARRAY(tx_odt_drv_stren, K230DDRPhyState,
> > +                               K230_DDR_PHY_DBYTE_COUNT,
> > +                               K230_DDR_PHY_NIBBLES_PER_DBYTE),
> > +        VMSTATE_UINT16(dfi_init_complete, K230DDRPhyState),
> > +        VMSTATE_UINT16(vref_in_global, K230DDRPhyState),
> > +        VMSTATE_UINT16(micro_cont_mux_sel, K230DDRPhyState),
> > +        VMSTATE_BOOL(training_trigger_seen, K230DDRPhyState),
> > +        VMSTATE_BOOL(training_complete, K230DDRPhyState),
> > +        VMSTATE_BOOL(mailbox_message_pending, K230DDRPhyState),
> > +        VMSTATE_END_OF_LIST()
> > +    },
> > +};
> > +
> > +static void k230_ddr_cfg_class_init(ObjectClass *klass, const void
> > *data)
> > +{
> > +    DeviceClass *dc = DEVICE_CLASS(klass);
> > +    ResettableClass *rc = RESETTABLE_CLASS(klass);
> > +
> > +    rc->phases.enter = k230_ddr_cfg_reset_enter;
> > +    dc->vmsd = &vmstate_k230_ddr_cfg;
> > +}
> > +
> > +static void k230_ddr_phy_class_init(ObjectClass *klass, const void
> > *data)
> > +{
> > +    DeviceClass *dc = DEVICE_CLASS(klass);
> > +    ResettableClass *rc = RESETTABLE_CLASS(klass);
> > +
> > +    rc->phases.enter = k230_ddr_phy_reset_enter;
> > +    dc->vmsd = &vmstate_k230_ddr_phy;
> > +}
> > +
> > +static const TypeInfo k230_ddr_types[] = {
> > +    {
> > +        .name = TYPE_K230_DDR_CFG,
> > +        .parent = TYPE_SYS_BUS_DEVICE,
> > +        .instance_size = sizeof(K230DDRCfgState),
> > +        .instance_init = k230_ddr_cfg_init,
> > +        .class_init = k230_ddr_cfg_class_init,
> > +    },
> > +    {
> > +        .name = TYPE_K230_DDR_PHY,
> > +        .parent = TYPE_SYS_BUS_DEVICE,
> > +        .instance_size = sizeof(K230DDRPhyState),
> > +        .instance_init = k230_ddr_phy_init,
> > +        .class_init = k230_ddr_phy_class_init,
> > +    },
> > +};
> > +
> > +DEFINE_TYPES(k230_ddr_types)
> > diff --git a/hw/misc/meson.build b/hw/misc/meson.build
> > index 23265f6035..6d5e2b2647 100644
> > --- a/hw/misc/meson.build
> > +++ b/hw/misc/meson.build
> > @@ -28,6 +28,7 @@ system_ss.add(when: 'CONFIG_IOSB', if_true:
> > files('iosb.c'))
> >  system_ss.add(when: 'CONFIG_VIRT_CTRL', if_true:
> > files('virt_ctrl.c'))
> >  
> >  # RISC-V devices
> > +system_ss.add(when: 'CONFIG_K230', if_true: files('k230_ddr.c'))
> >  system_ss.add(when: 'CONFIG_MCHP_PFSOC_DMC', if_true:
> > files('mchp_pfsoc_dmc.c'))
> >  system_ss.add(when: 'CONFIG_MCHP_PFSOC_IOSCB', if_true:
> > files('mchp_pfsoc_ioscb.c'))
> >  system_ss.add(when: 'CONFIG_MCHP_PFSOC_SYSREG', if_true:
> > files('mchp_pfsoc_sysreg.c'))
> > diff --git a/include/hw/misc/k230_ddr.h
> > b/include/hw/misc/k230_ddr.h
> > new file mode 100644
> > index 0000000000..d912e8e1b0
> > --- /dev/null
> > +++ b/include/hw/misc/k230_ddr.h
> > @@ -0,0 +1,59 @@
> > +/*
> > + * Kendryte K230 DDR controller and PHY models
> > + *
> > + * Device state and type declarations for the K230 DDRC CFG and
> > K230
> > DDR PHY
> > + * registers.
> > + *
> > + * Copyright (c) 2026 Junze Cao <[email protected]>
> > + *
> > + * SPDX-License-Identifier: GPL-2.0-or-later
> > + */
> > +
> > +#ifndef HW_MISC_K230_DDR_H
> > +#define HW_MISC_K230_DDR_H
> > +
> > +#include "hw/core/sysbus.h"
> > +
> > +#define K230_DDRC_MMIO_SIZE    0x02000000
> > +#define K230_DDR_PHY_MMIO_SIZE 0x00400000
> > +
> > +#define K230_DDRC_REG_SIZE  0x328
> > +#define K230_DDRC_REG_COUNT (K230_DDRC_REG_SIZE /
> > sizeof(uint32_t))
> > +
> > +#define K230_DDR_PHY_ANIB_COUNT             10
> > +#define K230_DDR_PHY_DBYTE_COUNT             4
> > +#define K230_DDR_PHY_NIBBLES_PER_DBYTE       2
> > +
> > +#define TYPE_K230_DDR_CFG "riscv.k230.ddr-cfg"
> > +OBJECT_DECLARE_SIMPLE_TYPE(K230DDRCfgState, K230_DDR_CFG)
> > +
> > +#define TYPE_K230_DDR_PHY "riscv.k230.ddr-phy"
> > +OBJECT_DECLARE_SIMPLE_TYPE(K230DDRPhyState, K230_DDR_PHY)
> > +
> > +struct K230DDRCfgState {
> > +    SysBusDevice parent_obj;
> > +
> > +    MemoryRegion mmio;
> > +    K230DDRPhyState *phy;
> > +    uint32_t regs[K230_DDRC_REG_COUNT];
> > +    bool initialized;
> > +};
> > +
> > +struct K230DDRPhyState {
> > +    SysBusDevice parent_obj;
> > +
> > +    MemoryRegion mmio;
> > +    uint16_t atx_impedance[K230_DDR_PHY_ANIB_COUNT];
> > +    uint16_t tx_impedance_ctrl1[K230_DDR_PHY_DBYTE_COUNT]
> > +                                [K230_DDR_PHY_NIBBLES_PER_DBYTE];
> > +    uint16_t tx_odt_drv_stren[K230_DDR_PHY_DBYTE_COUNT]
> > +                             [K230_DDR_PHY_NIBBLES_PER_DBYTE];
> > +    uint16_t dfi_init_complete;
> > +    uint16_t vref_in_global;
> > +    uint16_t micro_cont_mux_sel;
> > +    bool training_trigger_seen;
> > +    bool training_complete;
> > +    bool mailbox_message_pending;
> > +};
> > +
> > +#endif

Reply via email to