All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jamin Lin <jamin_lin@aspeedtech.com>
To: "Paolo Bonzini" <pbonzini@redhat.com>,
	"Peter Maydell" <peter.maydell@linaro.org>,
	"Cédric Le Goater" <clg@kaod.org>,
	"Steven Lee" <steven_lee@aspeedtech.com>,
	"Troy Lee" <leetroy@gmail.com>,
	"Kane Chen" <kane_chen@aspeedtech.com>,
	"Andrew Jeffery" <andrew@codeconstruct.com.au>,
	"Joel Stanley" <joel@jms.id.au>,
	"open list:ARM TCG CPUs" <qemu-arm@nongnu.org>,
	"open list:All patches CC here" <qemu-devel@nongnu.org>
Cc: "Jamin Lin" <jamin_lin@aspeedtech.com>,
	"Troy Lee" <troy_lee@aspeedtech.com>,
	"Philippe Mathieu-Daudé" <philmd@oss.qualcomm.com>
Subject: [PATCH v4 1/4] hw/usb/aspeed-udc: Add ASPEED UDC device controller
Date: Tue, 1 Sep 2026 02:44:02 +0000	[thread overview]
Message-ID: <20260901024400.3488429-2-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20260901024400.3488429-1-jamin_lin@aspeedtech.com>

The AST2600 has a USB 2.0 Device Controller (UDC) at 0x1e6a2000 with one
control endpoint and four programmable endpoints.

Add the controller as a sysbus (system) device: the MMIO register map
described with the registerfields macros, the interrupt line and the
soft reset. This is only the register/system side.

Note: this "device controller" is the system-bus device (TYPE_ASPEED_UDC).
It is not the gadget USB device (TYPE_ASPEED_UDC_GADGET) that a host
controller enumerates, which is added in the next patch.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
---
 include/hw/usb/aspeed-udc.h |  49 ++++++++
 hw/usb/aspeed-udc.c         | 236 ++++++++++++++++++++++++++++++++++++
 hw/arm/Kconfig              |   1 +
 hw/usb/Kconfig              |   3 +
 hw/usb/meson.build          |   1 +
 hw/usb/trace-events         |   7 ++
 6 files changed, 297 insertions(+)
 create mode 100644 include/hw/usb/aspeed-udc.h
 create mode 100644 hw/usb/aspeed-udc.c

diff --git a/include/hw/usb/aspeed-udc.h b/include/hw/usb/aspeed-udc.h
new file mode 100644
index 0000000000..58fed5f9a2
--- /dev/null
+++ b/include/hw/usb/aspeed-udc.h
@@ -0,0 +1,49 @@
+/*
+ * ASPEED USB Device Controller (UDC)
+ *
+ * Copyright (c) 2026 ASPEED Technology Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_USB_ASPEED_UDC_H
+#define HW_USB_ASPEED_UDC_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_ASPEED_UDC "aspeed.udc"
+OBJECT_DECLARE_SIMPLE_TYPE(AspeedUDCState, ASPEED_UDC)
+
+/*
+ * Register map: root/global block at 0x000 - 0x087, then one 0x10 byte bank
+ * per programmable endpoint from 0x200.
+ */
+#define ASPEED_UDC_MEM_SIZE     0x300
+#define ASPEED_UDC_ROOT_NR_REGS (0x88 >> 2)
+#define ASPEED_UDC_EP_REG_BASE  0x200
+#define ASPEED_UDC_EP_NR_REGS   (0x10 >> 2)
+
+/*
+ * EP0 (control) is served through the root registers (UDC_EP0_*), so only
+ * the 4 programmable endpoints get their own register bank / ep[] entry.
+ */
+#define ASPEED_UDC_NUM_EP   4
+
+typedef struct AspeedUDCEP {
+    MemoryRegion mr;
+    uint32_t regs[ASPEED_UDC_EP_NR_REGS];
+    int index;
+} AspeedUDCEP;
+
+struct AspeedUDCState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion udc_container;
+    MemoryRegion root_mr;
+    uint32_t regs[ASPEED_UDC_ROOT_NR_REGS];
+    AspeedUDCEP ep[ASPEED_UDC_NUM_EP];
+    qemu_irq irq;
+};
+
+#endif /* HW_USB_ASPEED_UDC_H */
diff --git a/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c
new file mode 100644
index 0000000000..cd773e979b
--- /dev/null
+++ b/hw/usb/aspeed-udc.c
@@ -0,0 +1,236 @@
+/*
+ * ASPEED USB Device Controller (UDC)
+ *
+ * Copyright (c) 2026 ASPEED Technology Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Models the ASPEED USB Device Controller (UDC). It implements one control
+ * endpoint (EP0) and 4 programmable endpoints.
+ *
+ * This file is the system-bus side of the controller: the MMIO register map,
+ * the interrupt and the soft reset. The gadget USB device presented to a host
+ * controller (and the endpoint data path) is added on top of this.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/irq.h"
+#include "hw/core/registerfields.h"
+#include "hw/usb/aspeed-udc.h"
+#include "qemu/module.h"
+#include "trace.h"
+
+/* Root / Global registers (offset from the controller base) */
+REG32(UDC_FUNC_CTRL, 0x00)
+    FIELD(UDC_FUNC_CTRL, UPSTREAM_EN,       0, 1)
+REG32(UDC_IER, 0x08)
+REG32(UDC_ISR, 0x0C)
+    FIELD(UDC_ISR, EP_POOL_ACK,            16, 1)
+    FIELD(UDC_ISR, BUS_RESET,               6, 1)
+    FIELD(UDC_ISR, EP0_IN_ACK,              3, 1)
+    FIELD(UDC_ISR, EP0_OUT_ACK,             1, 1)
+    FIELD(UDC_ISR, EP0_SETUP,               0, 1)
+REG32(UDC_EP_ACK_IER, 0x10)
+REG32(UDC_EP_NAK_IER, 0x14)
+REG32(UDC_EP_ACK_ISR, 0x18)
+REG32(UDC_EP_NAK_ISR, 0x1C)
+REG32(UDC_DEV_RESET, 0x20)
+    FIELD(UDC_DEV_RESET, EP_POOL,           9, 1)
+    FIELD(UDC_DEV_RESET, DMA,               8, 1)
+    FIELD(UDC_DEV_RESET, ROOT,              0, 1)
+REG32(UDC_STS, 0x24)
+    FIELD(UDC_STS, HIGHSPEED,              27, 1)
+REG32(UDC_EP0_CTRL, 0x30)
+    FIELD(UDC_EP0_CTRL, RX_LEN,            16, 7)
+    FIELD(UDC_EP0_CTRL, TX_LEN,             8, 7)
+    FIELD(UDC_EP0_CTRL, RX_RDY,             2, 1)
+    FIELD(UDC_EP0_CTRL, TX_RDY,             1, 1)
+    FIELD(UDC_EP0_CTRL, STALL,              0, 1)
+REG32(UDC_EP0_DATA_BUFF, 0x34)
+/* EP0 SETUP packet buffer: SETUP0 = bytes 0...3, SETUP1 = bytes 4...7 */
+REG32(UDC_SETUP0, 0x80)
+REG32(UDC_SETUP1, 0x84)
+
+/* Per programmable-endpoint registers (offset from the EP register base) */
+REG32(EP_CONFIG, 0x00)
+    FIELD(EP_CONFIG, MAX_PKT,              16, 10)
+    FIELD(EP_CONFIG, EP_NUM,                8, 4)
+    FIELD(EP_CONFIG, DIR_OUT,               4, 1)
+    FIELD(EP_CONFIG, ENABLE,                0, 1)
+REG32(EP_DMA_CTRL, 0x04)
+    FIELD(EP_DMA_CTRL, PROC_STS,            4, 4)
+    FIELD(EP_DMA_CTRL, DESC_OP_EN,          0, 1)
+REG32(EP_DMA_BUFF, 0x08)
+REG32(EP_DMA_STS, 0x0C)
+    FIELD(EP_DMA_STS, PKT_SIZE,            16, 11)
+    FIELD(EP_DMA_STS, RPTR,                 8, 8)
+    FIELD(EP_DMA_STS, WPTR,                 0, 8)
+
+static void aspeed_udc_update_irq(AspeedUDCState *s)
+{
+    bool level;
+
+    level = (s->regs[R_UDC_ISR] & s->regs[R_UDC_IER]) ||
+            (s->regs[R_UDC_EP_ACK_ISR] & s->regs[R_UDC_EP_ACK_IER]) ||
+            (s->regs[R_UDC_EP_NAK_ISR] & s->regs[R_UDC_EP_NAK_IER]);
+
+    trace_aspeed_udc_irq(s->regs[R_UDC_ISR], s->regs[R_UDC_IER], level);
+    qemu_set_irq(s->irq, level);
+}
+
+static uint64_t aspeed_udc_read(void *opaque, hwaddr offset, unsigned size)
+{
+    AspeedUDCState *s = ASPEED_UDC(opaque);
+    uint32_t reg = offset >> 2;
+    uint32_t val;
+
+    val = s->regs[reg];
+    trace_aspeed_udc_read(offset, val);
+
+    return val;
+}
+
+static void aspeed_udc_write(void *opaque, hwaddr offset, uint64_t data,
+                             unsigned size)
+{
+    AspeedUDCState *s = ASPEED_UDC(opaque);
+    uint32_t reg = offset >> 2;
+    uint32_t val = data;
+
+    trace_aspeed_udc_write(offset, val);
+
+    switch (reg) {
+    case R_UDC_IER:
+    case R_UDC_EP_ACK_IER:
+    case R_UDC_EP_NAK_IER:
+        s->regs[reg] = val;
+        aspeed_udc_update_irq(s);
+        break;
+    case R_UDC_ISR:
+    case R_UDC_EP_ACK_ISR:
+    case R_UDC_EP_NAK_ISR:
+        s->regs[reg] &= ~val;
+        aspeed_udc_update_irq(s);
+        break;
+    default:
+        s->regs[reg] = val;
+        break;
+    }
+}
+
+static const MemoryRegionOps aspeed_udc_ops = {
+    .read = aspeed_udc_read,
+    .write = aspeed_udc_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static uint64_t aspeed_udc_ep_read(void *opaque, hwaddr offset, unsigned size)
+{
+    AspeedUDCEP *e = opaque;
+    uint32_t reg = offset >> 2;
+    uint32_t val;
+
+    val = e->regs[reg];
+    trace_aspeed_udc_ep_read(e->index, offset, val);
+
+    return val;
+}
+
+static void aspeed_udc_ep_write(void *opaque, hwaddr offset, uint64_t data,
+                                unsigned size)
+{
+    AspeedUDCEP *e = opaque;
+    uint32_t reg = offset >> 2;
+
+    trace_aspeed_udc_ep_write(e->index, offset, data);
+    e->regs[reg] = data;
+}
+
+static const MemoryRegionOps aspeed_udc_ep_ops = {
+    .read = aspeed_udc_ep_read,
+    .write = aspeed_udc_ep_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static void aspeed_udc_reset_hold(Object *obj, ResetType type)
+{
+    AspeedUDCState *s = ASPEED_UDC(obj);
+    int i;
+
+    memset(s->regs, 0, sizeof(s->regs));
+    for (i = 0; i < ASPEED_UDC_NUM_EP; i++) {
+        memset(s->ep[i].regs, 0, sizeof(s->ep[i].regs));
+    }
+
+    /* Device-reset default: root, DMA and EP-pool soft-reset bits set */
+    s->regs[R_UDC_DEV_RESET] = (R_UDC_DEV_RESET_ROOT_MASK |
+                                R_UDC_DEV_RESET_DMA_MASK |
+                                R_UDC_DEV_RESET_EP_POOL_MASK);
+}
+
+static void aspeed_udc_realize(DeviceState *dev, Error **errp)
+{
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    AspeedUDCState *s = ASPEED_UDC(dev);
+    int i;
+
+    memory_region_init(&s->udc_container, OBJECT(s), TYPE_ASPEED_UDC,
+                       ASPEED_UDC_MEM_SIZE);
+    memory_region_init_io(&s->root_mr, OBJECT(s), &aspeed_udc_ops, s,
+                          TYPE_ASPEED_UDC ".root",
+                          ASPEED_UDC_ROOT_NR_REGS << 2);
+    memory_region_add_subregion(&s->udc_container, 0, &s->root_mr);
+
+    /* Each programmable endpoint has its own register bank */
+    for (i = 0; i < ASPEED_UDC_NUM_EP; i++) {
+        g_autofree char *name = g_strdup_printf(TYPE_ASPEED_UDC ".ep%d", i);
+
+        s->ep[i].index = i;
+        memory_region_init_io(&s->ep[i].mr, OBJECT(s), &aspeed_udc_ep_ops,
+                              &s->ep[i], name, ASPEED_UDC_EP_NR_REGS << 2);
+        memory_region_add_subregion(&s->udc_container,
+                                    ASPEED_UDC_EP_REG_BASE +
+                                    i * (ASPEED_UDC_EP_NR_REGS << 2),
+                                    &s->ep[i].mr);
+    }
+
+    sysbus_init_mmio(sbd, &s->udc_container);
+    sysbus_init_irq(sbd, &s->irq);
+}
+
+static void aspeed_udc_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->desc = "ASPEED USB Device Controller";
+    dc->realize = aspeed_udc_realize;
+    rc->phases.hold = aspeed_udc_reset_hold;
+}
+
+static const TypeInfo aspeed_udc_types[] = {
+    {
+        .name          = TYPE_ASPEED_UDC,
+        .parent        = TYPE_SYS_BUS_DEVICE,
+        .instance_size = sizeof(AspeedUDCState),
+        .class_init    = aspeed_udc_class_init,
+    },
+};
+
+DEFINE_TYPES(aspeed_udc_types)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 260d2f0751..146ed1a0be 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -536,6 +536,7 @@ config ASPEED_SOC
     imply GENERIC_LOADER
     imply PCI_DEVICES
     imply E1000E_PCI_EXPRESS
+    select ASPEED_UDC
     select DS1338
     select FTGMAC100
     select I2C
diff --git a/hw/usb/Kconfig b/hw/usb/Kconfig
index de95686720..e4df15fe7c 100644
--- a/hw/usb/Kconfig
+++ b/hw/usb/Kconfig
@@ -146,3 +146,6 @@ config XLNX_USB_SUBSYS
 config USB_CHIPIDEA
     bool
     select USB_EHCI_SYSBUS
+
+config ASPEED_UDC
+    bool
diff --git a/hw/usb/meson.build b/hw/usb/meson.build
index ba55c28ef6..d4ba60a91c 100644
--- a/hw/usb/meson.build
+++ b/hw/usb/meson.build
@@ -27,6 +27,7 @@ system_ss.add(when: 'CONFIG_USB_XHCI_NEC', if_true: files('hcd-xhci-nec.c'))
 system_ss.add(when: 'CONFIG_USB_DWC2', if_true: files('hcd-dwc2.c'))
 system_ss.add(when: 'CONFIG_USB_DWC3', if_true: files('hcd-dwc3.c'))
 system_ss.add(when: 'CONFIG_USB_CHIPIDEA', if_true: files('chipidea.c'))
+system_ss.add(when: 'CONFIG_ASPEED_UDC', if_true: files('aspeed-udc.c'))
 
 system_ss.add(when: 'CONFIG_IMX_USBPHY', if_true: files('imx-usb-phy.c'))
 system_ss.add(when: 'CONFIG_VT82C686', if_true: files('vt82c686-uhci-pci.c'))
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index 67249d69c2..ed05304520 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -377,3 +377,10 @@ canokey_handle_data_out(uint8_t ep_out, uint32_t out_len) "ep %d len %d"
 canokey_handle_data_in(uint8_t ep_in, uint32_t in_len) "ep %d len %d"
 canokey_realize(void)
 canokey_unrealize(void)
+
+# aspeed-udc.c
+aspeed_udc_read(uint64_t offset, uint32_t value) "offset 0x%" PRIx64 " value 0x%x"
+aspeed_udc_write(uint64_t offset, uint32_t value) "offset 0x%" PRIx64 " value 0x%x"
+aspeed_udc_ep_read(int ep, uint64_t offset, uint32_t value) "ep %d, offset 0x%" PRIx64 " value 0x%x"
+aspeed_udc_ep_write(int ep, uint64_t offset, uint32_t value) "ep %d, offset 0x%" PRIx64 " value 0x%x"
+aspeed_udc_irq(uint32_t isr, uint32_t ier, int level) "isr 0x%x, ier 0x%x, level %d"
-- 
2.53.0


  reply	other threads:[~2026-09-01  2:44 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-01  2:44 [PATCH v4 0/4] hw/usb: Add ASPEED USB Device Controller (UDC) Jamin Lin
2026-09-01  2:44 ` Jamin Lin [this message]
2026-09-01  6:18   ` [PATCH v4 1/4] hw/usb/aspeed-udc: Add ASPEED UDC device controller Cédric Le Goater
2026-09-01  2:44 ` [PATCH v4 2/4] hw/usb/aspeed-udc: Add ASPEED UDC gadget USB device Jamin Lin
2026-09-01  2:44 ` [PATCH v4 3/4] hw/usb/aspeed-udc: Add programmable endpoint DMA transfers Jamin Lin
2026-09-01  6:17   ` Cédric Le Goater
2026-09-01  9:16     ` Jamin Lin
2026-09-01  2:44 ` [PATCH v4 4/4] hw/arm/aspeed_ast2600: Wire up the UDC Jamin Lin
2026-09-01  6:17   ` Cédric Le Goater

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260901024400.3488429-2-jamin_lin@aspeedtech.com \
    --to=jamin_lin@aspeedtech.com \
    --cc=andrew@codeconstruct.com.au \
    --cc=clg@kaod.org \
    --cc=joel@jms.id.au \
    --cc=kane_chen@aspeedtech.com \
    --cc=leetroy@gmail.com \
    --cc=pbonzini@redhat.com \
    --cc=peter.maydell@linaro.org \
    --cc=philmd@oss.qualcomm.com \
    --cc=qemu-arm@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=steven_lee@aspeedtech.com \
    --cc=troy_lee@aspeedtech.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.