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>
Subject: [PATCH v1 1/3] hw/usb/aspeed-udc: Add ASPEED UDC device controller
Date: Fri, 3 Jul 2026 07:43:34 +0000 [thread overview]
Message-ID: <20260703074332.1049473-2-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20260703074332.1049473-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, driven by the Linux
"aspeed_udc" gadget driver.
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_DEV) that a host
controller enumerates, which is added in the next patch.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
hw/arm/Kconfig | 1 +
hw/usb/Kconfig | 4 +
hw/usb/aspeed-udc.c | 264 ++++++++++++++++++++++++++++++++++++
hw/usb/meson.build | 1 +
hw/usb/trace-events | 7 +
include/hw/usb/aspeed-udc.h | 54 ++++++++
6 files changed, 331 insertions(+)
create mode 100644 hw/usb/aspeed-udc.c
create mode 100644 include/hw/usb/aspeed-udc.h
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index fb798ccbee..8fcf9f48c9 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -532,6 +532,7 @@ config ASPEED_SOC
default y
depends on TCG && ARM
imply PCI_DEVICES
+ select ASPEED_UDC
select DS1338
select FTGMAC100
select I2C
diff --git a/hw/usb/Kconfig b/hw/usb/Kconfig
index de95686720..e8c00f813a 100644
--- a/hw/usb/Kconfig
+++ b/hw/usb/Kconfig
@@ -146,3 +146,7 @@ config XLNX_USB_SUBSYS
config USB_CHIPIDEA
bool
select USB_EHCI_SYSBUS
+
+config ASPEED_UDC
+ bool
+ select USB
diff --git a/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c
new file mode 100644
index 0000000000..9be9cfcf13
--- /dev/null
+++ b/hw/usb/aspeed-udc.c
@@ -0,0 +1,264 @@
+/*
+ * 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), driven by the Linux
+ * "aspeed_udc" gadget driver. 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)
+ FIELD(UDC_FUNC_CTRL, UPSTREAM_FS, 1, 1)
+ FIELD(UDC_FUNC_CTRL, STOP_CLK_SUSPEND, 2, 1)
+ FIELD(UDC_FUNC_CTRL, AUTO_REMOTE_WKUP, 3, 1)
+ FIELD(UDC_FUNC_CTRL, REMOTE_WKUP_EN, 4, 1)
+ FIELD(UDC_FUNC_CTRL, TEST_MODE, 8, 3)
+ FIELD(UDC_FUNC_CTRL, PHY_RESET_DIS, 11, 1)
+ FIELD(UDC_FUNC_CTRL, EP_LONG_DESC, 18, 1)
+ FIELD(UDC_FUNC_CTRL, PHY_CLK_EN, 31, 1)
+REG32(UDC_CONFIG, 0x04)
+ FIELD(UDC_CONFIG, DEV_ADDR, 0, 7)
+REG32(UDC_IER, 0x08)
+REG32(UDC_ISR, 0x0C)
+ FIELD(UDC_ISR, EP0_SETUP, 0, 1)
+ FIELD(UDC_ISR, EP0_OUT_ACK, 1, 1)
+ FIELD(UDC_ISR, EP0_OUT_NAK, 2, 1)
+ FIELD(UDC_ISR, EP0_IN_ACK, 3, 1)
+ FIELD(UDC_ISR, EP0_IN_NAK, 4, 1)
+ FIELD(UDC_ISR, BUS_RESET, 6, 1)
+ FIELD(UDC_ISR, SUSPEND, 7, 1)
+ FIELD(UDC_ISR, RESUME, 8, 1)
+ FIELD(UDC_ISR, EP_POOL_ACK, 16, 1)
+ FIELD(UDC_ISR, EP_POOL_NAK, 17, 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, ROOT, 0, 1)
+ FIELD(UDC_DEV_RESET, DMA, 8, 1)
+ FIELD(UDC_DEV_RESET, EP_POOL, 9, 1)
+REG32(UDC_STS, 0x24)
+ FIELD(UDC_STS, HIGHSPEED, 27, 1)
+REG32(UDC_EP_DATA, 0x28)
+REG32(UDC_ISO_TX_FAIL, 0x2C)
+REG32(UDC_EP0_CTRL, 0x30)
+ FIELD(UDC_EP0_CTRL, STALL, 0, 1)
+ FIELD(UDC_EP0_CTRL, TX_RDY, 1, 1)
+ FIELD(UDC_EP0_CTRL, RX_RDY, 2, 1)
+ FIELD(UDC_EP0_CTRL, TX_LEN, 8, 7)
+ FIELD(UDC_EP0_CTRL, RX_LEN, 16, 7)
+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, ENABLE, 0, 1)
+ FIELD(EP_CONFIG, DIR_OUT, 4, 1)
+ FIELD(EP_CONFIG, TYPE, 5, 2)
+ FIELD(EP_CONFIG, EP_NUM, 8, 4)
+ FIELD(EP_CONFIG, STALL, 12, 1)
+ FIELD(EP_CONFIG, AUTO_TOGGLE_DIS, 13, 1)
+ FIELD(EP_CONFIG, MAX_PKT, 16, 10)
+REG32(EP_DMA_CTRL, 0x04)
+ FIELD(EP_DMA_CTRL, DESC_OP_EN, 0, 1)
+ FIELD(EP_DMA_CTRL, SINGLE_STAGE, 1, 1)
+ FIELD(EP_DMA_CTRL, RESET, 2, 1)
+ FIELD(EP_DMA_CTRL, IN_LONG_MODE, 3, 1)
+ FIELD(EP_DMA_CTRL, PROC_STS, 4, 4)
+REG32(EP_DMA_BUFF, 0x08)
+REG32(EP_DMA_STS, 0x0C)
+ FIELD(EP_DMA_STS, WPTR, 0, 8)
+ FIELD(EP_DMA_STS, RPTR, 8, 8)
+ FIELD(EP_DMA_STS, TX_SIZE, 16, 11)
+
+/* Device-reset default: root, DMA and EP-pool soft-reset bits set (0x301) */
+#define UDC_DEV_RESET_DEFAULT \
+ (R_UDC_DEV_RESET_ROOT_MASK | R_UDC_DEV_RESET_DMA_MASK | \
+ R_UDC_DEV_RESET_EP_POOL_MASK)
+
+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);
+ uint64_t val = s->regs[offset >> 2];
+
+ 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_ISR:
+ case R_UDC_EP_ACK_ISR:
+ case R_UDC_EP_NAK_ISR:
+ /* Status registers are write-1-to-clear */
+ s->regs[reg] &= ~val;
+ break;
+ default:
+ s->regs[reg] = val;
+ break;
+ }
+
+ aspeed_udc_update_irq(s);
+}
+
+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;
+ uint64_t val = e->regs[offset >> 2];
+
+ 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;
+
+ trace_aspeed_udc_ep_write(e->index, offset, data);
+ e->regs[offset >> 2] = 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_realize(DeviceState *dev, Error **errp)
+{
+ AspeedUDCState *s = ASPEED_UDC(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ int i;
+
+ s->regs = g_new0(uint32_t, ASPEED_UDC_NR_REGS);
+
+ memory_region_init(&s->iomem, OBJECT(s), TYPE_ASPEED_UDC,
+ ASPEED_UDC_REG_SIZE);
+
+ /* Root/global registers occupy the low part of the window */
+ memory_region_init_io(&s->reg_mr, OBJECT(s), &aspeed_udc_ops, s,
+ TYPE_ASPEED_UDC ".regs", ASPEED_UDC_NR_REGS << 2);
+ memory_region_add_subregion(&s->iomem, 0, &s->reg_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;
+ s->ep[i].regs = g_new0(uint32_t, ASPEED_UDC_EP_NR_REGS);
+ 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->iomem, ASPEED_UDC_EP_REG_BASE +
+ i * ASPEED_UDC_EP_REG_SIZE, &s->ep[i].mr);
+ }
+
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+}
+
+static void aspeed_udc_reset_hold(Object *obj, ResetType type)
+{
+ AspeedUDCState *s = ASPEED_UDC(obj);
+ int i;
+
+ memset(s->regs, 0, ASPEED_UDC_NR_REGS * sizeof(uint32_t));
+ for (i = 0; i < ASPEED_UDC_NUM_EP; i++) {
+ memset(s->ep[i].regs, 0, ASPEED_UDC_EP_NR_REGS * sizeof(uint32_t));
+ }
+ s->regs[R_UDC_DEV_RESET] = UDC_DEV_RESET_DEFAULT;
+}
+
+static void aspeed_udc_unrealize(DeviceState *dev)
+{
+ AspeedUDCState *s = ASPEED_UDC(dev);
+ int i;
+
+ for (i = 0; i < ASPEED_UDC_NUM_EP; i++) {
+ g_free(s->ep[i].regs);
+ }
+ g_free(s->regs);
+}
+
+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;
+ dc->unrealize = aspeed_udc_unrealize;
+ 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/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 0d4318dcf1..fb79e24fba 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, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64
+aspeed_udc_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64
+aspeed_udc_ep_read(int ep, uint64_t offset, uint64_t value) "ep %d, offset 0x%" PRIx64 " value 0x%" PRIx64
+aspeed_udc_ep_write(int ep, uint64_t offset, uint64_t value) "ep %d, offset 0x%" PRIx64 " value 0x%" PRIx64
+aspeed_udc_irq(uint32_t isr, uint32_t ier, int level) "isr 0x%x, ier 0x%x, level %d"
diff --git a/include/hw/usb/aspeed-udc.h b/include/hw/usb/aspeed-udc.h
new file mode 100644
index 0000000000..eb279dd9c3
--- /dev/null
+++ b/include/hw/usb/aspeed-udc.h
@@ -0,0 +1,54 @@
+/*
+ * 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)
+
+/*
+ * 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
+/* 32-bit registers per programmable endpoint */
+#define ASPEED_UDC_EP_NR_REGS 4
+
+/*
+ * The root/global register block spans 0x000...0x087: the SETUP data buffer
+ * ends at 0x84. Size the backing array to cover the whole block.
+ */
+#define ASPEED_UDC_NR_REGS (0x88 >> 2)
+
+/* MMIO window: root registers below EP_REG_BASE, then the per-EP banks */
+#define ASPEED_UDC_REG_SIZE 0x300
+#define ASPEED_UDC_EP_REG_BASE 0x200
+#define ASPEED_UDC_EP_REG_SIZE 0x10
+
+typedef struct AspeedUDCEP {
+ MemoryRegion mr;
+ int index;
+ uint32_t *regs;
+} AspeedUDCEP;
+
+struct AspeedUDCState {
+ SysBusDevice parent_obj;
+
+ /* container: root registers + per-endpoint banks */
+ MemoryRegion iomem;
+ MemoryRegion reg_mr;
+ qemu_irq irq;
+ uint32_t *regs;
+ AspeedUDCEP ep[ASPEED_UDC_NUM_EP];
+};
+
+#endif /* HW_USB_ASPEED_UDC_H */
--
2.53.0
next prev parent reply other threads:[~2026-07-03 7:45 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-03 7:43 [PATCH v1 0/3] hw/usb: Add ASPEED USB Device Controller (UDC) Jamin Lin
2026-07-03 7:43 ` Jamin Lin [this message]
2026-07-09 21:03 ` [PATCH v1 1/3] hw/usb/aspeed-udc: Add ASPEED UDC device controller Philippe Mathieu-Daudé
2026-07-03 7:43 ` [PATCH v1 2/3] hw/usb/aspeed-udc: Add ASPEED UDC gadget USB device Jamin Lin
2026-07-03 7:43 ` [PATCH v1 3/3] hw/arm/aspeed_ast2600: Wire up the UDC Jamin Lin
2026-07-09 21:10 ` Philippe Mathieu-Daudé
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=20260703074332.1049473-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=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.