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 <[email protected]> --- include/hw/usb/aspeed-udc.h | 49 ++++++++ hw/usb/aspeed-udc.c | 240 ++++++++++++++++++++++++++++++++++++ hw/arm/Kconfig | 1 + hw/usb/Kconfig | 4 + hw/usb/meson.build | 1 + hw/usb/trace-events | 7 ++ 6 files changed, 302 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..85786a2e12 --- /dev/null +++ b/hw/usb/aspeed-udc.c @@ -0,0 +1,240 @@ +/* + * 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_CONFIG, 0x04) + FIELD(UDC_CONFIG, DEV_ADDR, 0, 7) +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_EP_DATA, 0x28) +REG32(UDC_ISO_TX_FAIL, 0x2C) +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..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/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
