diff mbox series

[03/21] hw: aspeed_scu: Add AST2600 support

Message ID 20190919055002.6729-4-clg@kaod.org
State Superseded
Headers show
Series None | expand

Commit Message

Cédric Le Goater Sept. 19, 2019, 5:49 a.m. UTC
From: Joel Stanley <joel@jms.id.au>


The SCU controller on the AST2600 SoC has extra registers. Increase
the number of regs of the model and introduce a new field in the class
to customize the MemoryRegion operations depending on the SoC model.

Signed-off-by: Joel Stanley <joel@jms.id.au>

[clg: - improved commit log
      - changed vmstate version
      - reworked model integration into new objet class ]
Signed-off-by: Cédric Le Goater <clg@kaod.org>

---
 include/hw/misc/aspeed_scu.h |   7 +-
 hw/misc/aspeed_scu.c         | 190 +++++++++++++++++++++++++++++++++--
 2 files changed, 189 insertions(+), 8 deletions(-)

-- 
2.21.0

Comments

Andrew Jeffery Sept. 20, 2019, 4:10 a.m. UTC | #1
On Thu, 19 Sep 2019, at 15:19, Cédric Le Goater wrote:
> From: Joel Stanley <joel@jms.id.au>

> 

> The SCU controller on the AST2600 SoC has extra registers. Increase

> the number of regs of the model and introduce a new field in the class

> to customize the MemoryRegion operations depending on the SoC model.

> 

> Signed-off-by: Joel Stanley <joel@jms.id.au>

> [clg: - improved commit log

>       - changed vmstate version

>       - reworked model integration into new objet class ]

> Signed-off-by: Cédric Le Goater <clg@kaod.org>

> ---

>  include/hw/misc/aspeed_scu.h |   7 +-

>  hw/misc/aspeed_scu.c         | 190 +++++++++++++++++++++++++++++++++--

>  2 files changed, 189 insertions(+), 8 deletions(-)


...

> +static void aspeed_ast2600_scu_write(void *opaque, hwaddr offset, 

> uint64_t data,

> +                                     unsigned size)

> +{

> +    AspeedSCUState *s = ASPEED_SCU(opaque);

> +    int reg = TO_REG(offset);

> +

> +    if (reg >= ASPEED_AST2600_SCU_NR_REGS) {

> +        qemu_log_mask(LOG_GUEST_ERROR,

> +                      "%s: Out-of-bounds write at offset 0x%" 

> HWADDR_PRIx "\n",

> +                      __func__, offset);

> +        return;

> +    }

> +

> +    if (reg > PROT_KEY && !s->regs[PROT_KEY]) {

> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: SCU is locked!\n", 

> __func__);

> +    }

> +

> +    trace_aspeed_scu_write(offset, size, data);

> +

> +    switch (reg) {

> +    case AST2600_PROT_KEY:

> +        s->regs[reg] = (data == ASPEED_SCU_PROT_KEY) ? 1 : 0;

> +        return;

> +    case AST2600_HW_STRAP1:

> +    case AST2600_HW_STRAP2:

> +        if (s->regs[reg + 2]) {

> +            return;

> +        }

> +        /* fall through */

> +    case AST2600_SYS_RST_CTRL:

> +    case AST2600_SYS_RST_CTRL2:

> +        /* W1S (Write 1 to set) registers */

> +        s->regs[reg] |= data;

> +        return;

> +    case AST2600_SYS_RST_CTRL_CLR:

> +    case AST2600_SYS_RST_CTRL2_CLR:

> +    case AST2600_HW_STRAP1_CLR:

> +    case AST2600_HW_STRAP2_CLR:

> +        /* W1C (Write 1 to clear) registers */

> +        s->regs[reg] &= ~data;


This clear should respect the protection register for each strap case.

Andrew
Cédric Le Goater Sept. 20, 2019, 3:15 p.m. UTC | #2
On 20/09/2019 06:10, Andrew Jeffery wrote:
> 

> 

> On Thu, 19 Sep 2019, at 15:19, Cédric Le Goater wrote:

>> From: Joel Stanley <joel@jms.id.au>

>>

>> The SCU controller on the AST2600 SoC has extra registers. Increase

>> the number of regs of the model and introduce a new field in the class

>> to customize the MemoryRegion operations depending on the SoC model.

>>

>> Signed-off-by: Joel Stanley <joel@jms.id.au>

>> [clg: - improved commit log

>>       - changed vmstate version

>>       - reworked model integration into new objet class ]

>> Signed-off-by: Cédric Le Goater <clg@kaod.org>

>> ---

>>  include/hw/misc/aspeed_scu.h |   7 +-

>>  hw/misc/aspeed_scu.c         | 190 +++++++++++++++++++++++++++++++++--

>>  2 files changed, 189 insertions(+), 8 deletions(-)

> 

> ...

> 

>> +static void aspeed_ast2600_scu_write(void *opaque, hwaddr offset, 

>> uint64_t data,

>> +                                     unsigned size)

>> +{

>> +    AspeedSCUState *s = ASPEED_SCU(opaque);

>> +    int reg = TO_REG(offset);

>> +

>> +    if (reg >= ASPEED_AST2600_SCU_NR_REGS) {

>> +        qemu_log_mask(LOG_GUEST_ERROR,

>> +                      "%s: Out-of-bounds write at offset 0x%" 

>> HWADDR_PRIx "\n",

>> +                      __func__, offset);

>> +        return;

>> +    }

>> +

>> +    if (reg > PROT_KEY && !s->regs[PROT_KEY]) {

>> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: SCU is locked!\n", 

>> __func__);

>> +    }

>> +

>> +    trace_aspeed_scu_write(offset, size, data);

>> +

>> +    switch (reg) {

>> +    case AST2600_PROT_KEY:

>> +        s->regs[reg] = (data == ASPEED_SCU_PROT_KEY) ? 1 : 0;

>> +        return;

>> +    case AST2600_HW_STRAP1:

>> +    case AST2600_HW_STRAP2:

>> +        if (s->regs[reg + 2]) {

>> +            return;

>> +        }

>> +        /* fall through */

>> +    case AST2600_SYS_RST_CTRL:

>> +    case AST2600_SYS_RST_CTRL2:

>> +        /* W1S (Write 1 to set) registers */

>> +        s->regs[reg] |= data;

>> +        return;

>> +    case AST2600_SYS_RST_CTRL_CLR:

>> +    case AST2600_SYS_RST_CTRL2_CLR:

>> +    case AST2600_HW_STRAP1_CLR:

>> +    case AST2600_HW_STRAP2_CLR:

>> +        /* W1C (Write 1 to clear) registers */

>> +        s->regs[reg] &= ~data;

> 

> This clear should respect the protection register for each strap case.


Joel,

You are the expert ! :) 

Thanks,

C.
Joel Stanley Sept. 21, 2019, 4:37 a.m. UTC | #3
On Fri, 20 Sep 2019 at 15:15, Cédric Le Goater <clg@kaod.org> wrote:
>

> On 20/09/2019 06:10, Andrew Jeffery wrote:

> >

> >

> > On Thu, 19 Sep 2019, at 15:19, Cédric Le Goater wrote:

> >> From: Joel Stanley <joel@jms.id.au>

> >>

> >> The SCU controller on the AST2600 SoC has extra registers. Increase

> >> the number of regs of the model and introduce a new field in the class

> >> to customize the MemoryRegion operations depending on the SoC model.

> >>

> >> +    switch (reg) {

> >> +    case AST2600_PROT_KEY:

> >> +        s->regs[reg] = (data == ASPEED_SCU_PROT_KEY) ? 1 : 0;

> >> +        return;

> >> +    case AST2600_HW_STRAP1:

> >> +    case AST2600_HW_STRAP2:

> >> +        if (s->regs[reg + 2]) {

> >> +            return;

> >> +        }

> >> +        /* fall through */

> >> +    case AST2600_SYS_RST_CTRL:

> >> +    case AST2600_SYS_RST_CTRL2:

> >> +        /* W1S (Write 1 to set) registers */

> >> +        s->regs[reg] |= data;

> >> +        return;

> >> +    case AST2600_SYS_RST_CTRL_CLR:

> >> +    case AST2600_SYS_RST_CTRL2_CLR:

> >> +    case AST2600_HW_STRAP1_CLR:

> >> +    case AST2600_HW_STRAP2_CLR:

> >> +        /* W1C (Write 1 to clear) registers */

> >> +        s->regs[reg] &= ~data;

> >

> > This clear should respect the protection register for each strap case.

>

> Joel,

>

> You are the expert ! :)


Someone could implement this if they wanted to. In the future it might
be useful to create a detailed model for the OTP and secure boot
behavior, and that can affect the strapping.

However it is not critical for running guests under qemu. I think we
should defer it until there is some guest code that needs the detailed
behavior.

Cheers,

Joel
Cédric Le Goater Sept. 23, 2019, 5:44 a.m. UTC | #4
On 21/09/2019 06:37, Joel Stanley wrote:
> On Fri, 20 Sep 2019 at 15:15, Cédric Le Goater <clg@kaod.org> wrote:

>>

>> On 20/09/2019 06:10, Andrew Jeffery wrote:

>>>

>>>

>>> On Thu, 19 Sep 2019, at 15:19, Cédric Le Goater wrote:

>>>> From: Joel Stanley <joel@jms.id.au>

>>>>

>>>> The SCU controller on the AST2600 SoC has extra registers. Increase

>>>> the number of regs of the model and introduce a new field in the class

>>>> to customize the MemoryRegion operations depending on the SoC model.

>>>>

>>>> +    switch (reg) {

>>>> +    case AST2600_PROT_KEY:

>>>> +        s->regs[reg] = (data == ASPEED_SCU_PROT_KEY) ? 1 : 0;

>>>> +        return;

>>>> +    case AST2600_HW_STRAP1:

>>>> +    case AST2600_HW_STRAP2:

>>>> +        if (s->regs[reg + 2]) {

>>>> +            return;

>>>> +        }

>>>> +        /* fall through */

>>>> +    case AST2600_SYS_RST_CTRL:

>>>> +    case AST2600_SYS_RST_CTRL2:

>>>> +        /* W1S (Write 1 to set) registers */

>>>> +        s->regs[reg] |= data;

>>>> +        return;

>>>> +    case AST2600_SYS_RST_CTRL_CLR:

>>>> +    case AST2600_SYS_RST_CTRL2_CLR:

>>>> +    case AST2600_HW_STRAP1_CLR:

>>>> +    case AST2600_HW_STRAP2_CLR:

>>>> +        /* W1C (Write 1 to clear) registers */

>>>> +        s->regs[reg] &= ~data;

>>>

>>> This clear should respect the protection register for each strap case.

>>

>> Joel,

>>

>> You are the expert ! :)

> 

> Someone could implement this if they wanted to. In the future it might

> be useful to create a detailed model for the OTP and secure boot

> behavior, and that can affect the strapping.

> 

> However it is not critical for running guests under qemu. I think we

> should defer it until there is some guest code that needs the detailed

> behavior.


ok. It think we could trap the invalid writes with a simple mask
array at the beginning of the write op .

Thanks,

C.
diff mbox series

Patch

diff --git a/include/hw/misc/aspeed_scu.h b/include/hw/misc/aspeed_scu.h
index 239e94fe2c47..1d7f7ffc1598 100644
--- a/include/hw/misc/aspeed_scu.h
+++ b/include/hw/misc/aspeed_scu.h
@@ -17,8 +17,10 @@ 
 #define ASPEED_SCU(obj) OBJECT_CHECK(AspeedSCUState, (obj), TYPE_ASPEED_SCU)
 #define TYPE_ASPEED_2400_SCU TYPE_ASPEED_SCU "-ast2400"
 #define TYPE_ASPEED_2500_SCU TYPE_ASPEED_SCU "-ast2500"
+#define TYPE_ASPEED_2600_SCU TYPE_ASPEED_SCU "-ast2600"
 
 #define ASPEED_SCU_NR_REGS (0x1A8 >> 2)
+#define ASPEED_AST2600_SCU_NR_REGS (0xE20 >> 2)
 
 typedef struct AspeedSCUState {
     /*< private >*/
@@ -27,7 +29,7 @@  typedef struct AspeedSCUState {
     /*< public >*/
     MemoryRegion iomem;
 
-    uint32_t regs[ASPEED_SCU_NR_REGS];
+    uint32_t regs[ASPEED_AST2600_SCU_NR_REGS];
     uint32_t silicon_rev;
     uint32_t hw_strap1;
     uint32_t hw_strap2;
@@ -38,6 +40,7 @@  typedef struct AspeedSCUState {
 #define AST2400_A1_SILICON_REV   0x02010303U
 #define AST2500_A0_SILICON_REV   0x04000303U
 #define AST2500_A1_SILICON_REV   0x04010303U
+#define AST2600_A0_SILICON_REV   0x05000303U
 
 #define ASPEED_IS_AST2500(si_rev)     ((((si_rev) >> 24) & 0xff) == 0x04)
 
@@ -54,6 +57,8 @@  typedef struct  AspeedSCUClass {
     const uint32_t *resets;
     uint32_t (*calc_hpll)(AspeedSCUState *s, uint32_t hpll_reg);
     uint32_t apb_divider;
+    uint32_t nr_regs;
+    const MemoryRegionOps *ops;
 }  AspeedSCUClass;
 
 #define ASPEED_SCU_PROT_KEY      0x1688A8A8
diff --git a/hw/misc/aspeed_scu.c b/hw/misc/aspeed_scu.c
index 620b25c20476..27df6d6e3001 100644
--- a/hw/misc/aspeed_scu.c
+++ b/hw/misc/aspeed_scu.c
@@ -88,6 +88,34 @@ 
 #define BMC_REV              TO_REG(0x19C)
 #define BMC_DEV_ID           TO_REG(0x1A4)
 
+#define AST2600_PROT_KEY          TO_REG(0x00)
+#define AST2600_SILICON_REV       TO_REG(0x04)
+#define AST2600_SILICON_REV2      TO_REG(0x14)
+#define AST2600_SYS_RST_CTRL      TO_REG(0x40)
+#define AST2600_SYS_RST_CTRL_CLR  TO_REG(0x44)
+#define AST2600_SYS_RST_CTRL2     TO_REG(0x50)
+#define AST2600_SYS_RST_CTRL2_CLR TO_REG(0x54)
+#define AST2600_CLK_STOP_CTRL     TO_REG(0x80)
+#define AST2600_CLK_STOP_CTRL_CLR TO_REG(0x84)
+#define AST2600_CLK_STOP_CTRL2     TO_REG(0x90)
+#define AST2600_CLK_STOP_CTR2L_CLR TO_REG(0x94)
+#define AST2600_HPLL_EXT          TO_REG(0x204)
+#define AST2600_MPLL_EXT          TO_REG(0x224)
+#define AST2600_EPLL_EXT          TO_REG(0x244)
+#define AST2600_CLK_SEL           TO_REG(0x300)
+#define AST2600_CLK_SEL2          TO_REG(0x304)
+#define AST2600_CLK_SEL3          TO_REG(0x310)
+#define AST2600_HW_STRAP1         TO_REG(0x500)
+#define AST2600_HW_STRAP1_CLR     TO_REG(0x504)
+#define AST2600_HW_STRAP1_PROT    TO_REG(0x508)
+#define AST2600_HW_STRAP2         TO_REG(0x510)
+#define AST2600_HW_STRAP2_CLR     TO_REG(0x514)
+#define AST2600_HW_STRAP2_PROT    TO_REG(0x518)
+#define AST2600_RNG_CTRL          TO_REG(0x524)
+#define AST2600_RNG_DATA          TO_REG(0x540)
+
+#define AST2600_CLK TO_REG(0x40)
+
 #define SCU_IO_REGION_SIZE 0x1000
 
 static const uint32_t ast2400_a0_resets[ASPEED_SCU_NR_REGS] = {
@@ -178,7 +206,7 @@  static uint64_t aspeed_scu_read(void *opaque, hwaddr offset, unsigned size)
     AspeedSCUState *s = ASPEED_SCU(opaque);
     int reg = TO_REG(offset);
 
-    if (reg >= ARRAY_SIZE(s->regs)) {
+    if (reg >= ASPEED_SCU_NR_REGS) {
         qemu_log_mask(LOG_GUEST_ERROR,
                       "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
                       __func__, offset);
@@ -208,7 +236,7 @@  static void aspeed_scu_write(void *opaque, hwaddr offset, uint64_t data,
     AspeedSCUState *s = ASPEED_SCU(opaque);
     int reg = TO_REG(offset);
 
-    if (reg >= ARRAY_SIZE(s->regs)) {
+    if (reg >= ASPEED_SCU_NR_REGS) {
         qemu_log_mask(LOG_GUEST_ERROR,
                       "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
                       __func__, offset);
@@ -346,7 +374,7 @@  static void aspeed_scu_reset(DeviceState *dev)
     AspeedSCUState *s = ASPEED_SCU(dev);
     AspeedSCUClass *asc = ASPEED_SCU_GET_CLASS(dev);
 
-    memcpy(s->regs, asc->resets, sizeof(s->regs));
+    memcpy(s->regs, asc->resets, asc->nr_regs * 4);
     s->regs[SILICON_REV] = s->silicon_rev;
     s->regs[HW_STRAP1] = s->hw_strap1;
     s->regs[HW_STRAP2] = s->hw_strap2;
@@ -358,6 +386,7 @@  static uint32_t aspeed_silicon_revs[] = {
     AST2400_A1_SILICON_REV,
     AST2500_A0_SILICON_REV,
     AST2500_A1_SILICON_REV,
+    AST2600_A0_SILICON_REV,
 };
 
 bool is_supported_silicon_rev(uint32_t silicon_rev)
@@ -377,6 +406,7 @@  static void aspeed_scu_realize(DeviceState *dev, Error **errp)
 {
     SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
     AspeedSCUState *s = ASPEED_SCU(dev);
+    AspeedSCUClass *asc = ASPEED_SCU_GET_CLASS(dev);
 
     if (!is_supported_silicon_rev(s->silicon_rev)) {
         error_setg(errp, "Unknown silicon revision: 0x%" PRIx32,
@@ -384,7 +414,7 @@  static void aspeed_scu_realize(DeviceState *dev, Error **errp)
         return;
     }
 
-    memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_scu_ops, s,
+    memory_region_init_io(&s->iomem, OBJECT(s), asc->ops, s,
                           TYPE_ASPEED_SCU, SCU_IO_REGION_SIZE);
 
     sysbus_init_mmio(sbd, &s->iomem);
@@ -392,10 +422,10 @@  static void aspeed_scu_realize(DeviceState *dev, Error **errp)
 
 static const VMStateDescription vmstate_aspeed_scu = {
     .name = "aspeed.scu",
-    .version_id = 1,
-    .minimum_version_id = 1,
+    .version_id = 2,
+    .minimum_version_id = 2,
     .fields = (VMStateField[]) {
-        VMSTATE_UINT32_ARRAY(regs, AspeedSCUState, ASPEED_SCU_NR_REGS),
+        VMSTATE_UINT32_ARRAY(regs, AspeedSCUState, ASPEED_AST2600_SCU_NR_REGS),
         VMSTATE_END_OF_LIST()
     }
 };
@@ -436,6 +466,8 @@  static void aspeed_2400_scu_class_init(ObjectClass *klass, void *data)
     asc->resets = ast2400_a0_resets;
     asc->calc_hpll = aspeed_2400_scu_calc_hpll;
     asc->apb_divider = 2;
+    asc->nr_regs = ASPEED_SCU_NR_REGS;
+    asc->ops = &aspeed_scu_ops;
 }
 
 static const TypeInfo aspeed_2400_scu_info = {
@@ -454,6 +486,8 @@  static void aspeed_2500_scu_class_init(ObjectClass *klass, void *data)
     asc->resets = ast2500_a1_resets;
     asc->calc_hpll = aspeed_2500_scu_calc_hpll;
     asc->apb_divider = 4;
+    asc->nr_regs = ASPEED_SCU_NR_REGS;
+    asc->ops = &aspeed_scu_ops;
 }
 
 static const TypeInfo aspeed_2500_scu_info = {
@@ -463,11 +497,153 @@  static const TypeInfo aspeed_2500_scu_info = {
     .class_init = aspeed_2500_scu_class_init,
 };
 
+static uint64_t aspeed_ast2600_scu_read(void *opaque, hwaddr offset,
+                                        unsigned size)
+{
+    AspeedSCUState *s = ASPEED_SCU(opaque);
+    int reg = TO_REG(offset);
+
+    if (reg >= ASPEED_AST2600_SCU_NR_REGS) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
+                      __func__, offset);
+        return 0;
+    }
+
+    switch (reg) {
+    case AST2600_HPLL_EXT:
+    case AST2600_EPLL_EXT:
+    case AST2600_MPLL_EXT:
+        /* PLLs are always "locked" */
+        return s->regs[reg] | BIT(31);
+    case AST2600_RNG_DATA:
+        /*
+         * On hardware, RNG_DATA works regardless of the state of the
+         * enable bit in RNG_CTRL
+         *
+         * TODO: Check this is true for ast2600
+         */
+        s->regs[AST2600_RNG_DATA] = aspeed_scu_get_random();
+        break;
+    }
+
+    return s->regs[reg];
+}
+
+static void aspeed_ast2600_scu_write(void *opaque, hwaddr offset, uint64_t data,
+                                     unsigned size)
+{
+    AspeedSCUState *s = ASPEED_SCU(opaque);
+    int reg = TO_REG(offset);
+
+    if (reg >= ASPEED_AST2600_SCU_NR_REGS) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
+                      __func__, offset);
+        return;
+    }
+
+    if (reg > PROT_KEY && !s->regs[PROT_KEY]) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: SCU is locked!\n", __func__);
+    }
+
+    trace_aspeed_scu_write(offset, size, data);
+
+    switch (reg) {
+    case AST2600_PROT_KEY:
+        s->regs[reg] = (data == ASPEED_SCU_PROT_KEY) ? 1 : 0;
+        return;
+    case AST2600_HW_STRAP1:
+    case AST2600_HW_STRAP2:
+        if (s->regs[reg + 2]) {
+            return;
+        }
+        /* fall through */
+    case AST2600_SYS_RST_CTRL:
+    case AST2600_SYS_RST_CTRL2:
+        /* W1S (Write 1 to set) registers */
+        s->regs[reg] |= data;
+        return;
+    case AST2600_SYS_RST_CTRL_CLR:
+    case AST2600_SYS_RST_CTRL2_CLR:
+    case AST2600_HW_STRAP1_CLR:
+    case AST2600_HW_STRAP2_CLR:
+        /* W1C (Write 1 to clear) registers */
+        s->regs[reg] &= ~data;
+        return;
+
+    case AST2600_RNG_DATA:
+    case AST2600_SILICON_REV:
+    case AST2600_SILICON_REV2:
+        /* Add read only registers here */
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Write to read-only offset 0x%" HWADDR_PRIx "\n",
+                      __func__, offset);
+        return;
+    }
+
+    s->regs[reg] = data;
+}
+
+static const MemoryRegionOps aspeed_ast2600_scu_ops = {
+    .read = aspeed_ast2600_scu_read,
+    .write = aspeed_ast2600_scu_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid.min_access_size = 4,
+    .valid.max_access_size = 4,
+    .valid.unaligned = false,
+};
+
+static const uint32_t ast2600_a0_resets[ASPEED_AST2600_SCU_NR_REGS] = {
+    [AST2600_SILICON_REV]       = AST2600_SILICON_REV,
+    [AST2600_SILICON_REV2]      = AST2600_SILICON_REV,
+    [AST2600_SYS_RST_CTRL]      = 0xF7CFFEDC | 0x100,
+    [AST2600_SYS_RST_CTRL2]     = 0xFFFFFFFC,
+    [AST2600_CLK_STOP_CTRL]     = 0xEFF43E8B,
+    [AST2600_CLK_STOP_CTRL2]    = 0xFFF0FFF0,
+};
+
+static void aspeed_ast2600_scu_reset(DeviceState *dev)
+{
+    AspeedSCUState *s = ASPEED_SCU(dev);
+    AspeedSCUClass *asc = ASPEED_SCU_GET_CLASS(dev);
+
+    memcpy(s->regs, asc->resets, asc->nr_regs * 4);
+
+    s->regs[AST2600_SILICON_REV] = s->silicon_rev;
+    s->regs[AST2600_SILICON_REV2] = s->silicon_rev;
+    s->regs[AST2600_HW_STRAP1] = s->hw_strap1;
+    s->regs[AST2600_HW_STRAP2] = s->hw_strap2;
+    s->regs[PROT_KEY] = s->hw_prot_key;
+}
+
+static void aspeed_2600_scu_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    AspeedSCUClass *asc = ASPEED_SCU_CLASS(klass);
+
+    dc->desc = "ASPEED 2600 System Control Unit";
+    dc->reset = aspeed_ast2600_scu_reset;
+    asc->resets = ast2600_a0_resets;
+    asc->calc_hpll = aspeed_2500_scu_calc_hpll; /* No change since AST2500 */
+    asc->apb_divider = 4;
+    asc->nr_regs = ASPEED_AST2600_SCU_NR_REGS;
+    asc->ops = &aspeed_ast2600_scu_ops;
+}
+
+static const TypeInfo aspeed_2600_scu_info = {
+    .name = TYPE_ASPEED_2600_SCU,
+    .parent = TYPE_ASPEED_SCU,
+    .instance_size = sizeof(AspeedSCUState),
+    .class_init = aspeed_2600_scu_class_init,
+};
+
 static void aspeed_scu_register_types(void)
 {
     type_register_static(&aspeed_scu_info);
     type_register_static(&aspeed_2400_scu_info);
     type_register_static(&aspeed_2500_scu_info);
+    type_register_static(&aspeed_2600_scu_info);
 }
 
 type_init(aspeed_scu_register_types);