@@ -20,7 +20,9 @@
#include "qemu/osdep.h"
#include "hw/irq.h"
+#include "hw/usb/uhci-regs.h"
#include "qapi/error.h"
+#include "qemu/log.h"
#include "qemu/module.h"
#include "qemu/timer.h"
#include "hw/usb.h"
@@ -88,6 +90,99 @@ static void uhci_sysbus_class_init(ObjectClass *klass, void *data)
dc->reset = uhci_sysbus_reset_sysbus;
}
+static hwaddr aspeed_uhci_chip_to_uhci(hwaddr addr)
+{
+ switch (addr) {
+ case 0x00:
+ return UHCI_USBCMD;
+ case 0x04:
+ return UHCI_USBSTS;
+ case 0x08:
+ return UHCI_USBINTR;
+ case 0x0c:
+ return UHCI_USBFLBASEADD;
+ case 0x80:
+ return UHCI_USBFRNUM;
+ case 0x84:
+ return UHCI_USBSOF;
+ case 0x88:
+ return UHCI_USBPORTSC1;
+ case 0x8c:
+ return UHCI_USBPORTSC2;
+ case 0x90:
+ return UHCI_USBPORTSC3;
+ case 0x94:
+ return UHCI_USBPORTSC4;
+ default: /* unimplemented */
+ qemu_log_mask(LOG_UNIMP, "Unimplemented Aspeed UHCI register 0x%lx\n",
+ addr);
+ return 0x20;
+ }
+}
+
+/*
+ * Aspeed UHCI registers are 32 bit wide.
+ * Convert to 16 bit to access standard UHCI code.
+ */
+static uint64_t aspeed_uhci_port_read(void *opaque, hwaddr addr, unsigned size)
+{
+ UHCIState *uhci = opaque;
+ MemoryRegion *mr = &uhci->mem;
+ hwaddr uaddr = aspeed_uhci_chip_to_uhci(addr);
+
+ if (uaddr == UHCI_USBFLBASEADD) {
+ return mr->ops->read(opaque, uaddr, 2) |
+ mr->ops->read(opaque, uaddr + 2, 2) << 16;
+ }
+ return mr->ops->read(opaque, uaddr, 2);
+}
+
+static void aspeed_uhci_port_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ UHCIState *uhci = opaque;
+ MemoryRegion *mr = &uhci->mem;
+ hwaddr uaddr = aspeed_uhci_chip_to_uhci(addr);
+
+ if (uaddr == UHCI_USBFLBASEADD) {
+ mr->ops->write(opaque, uaddr, val & 0xffff, 2);
+ mr->ops->write(opaque, uaddr + 2, val >> 16, 2);
+ } else {
+ mr->ops->write(opaque, uaddr, val, 2);
+ }
+}
+
+static const MemoryRegionOps aspeed_uhci_mmio_ops = {
+ .read = aspeed_uhci_port_read,
+ .write = aspeed_uhci_port_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void uhci_sysbus_aspeed_realize(DeviceState *dev, Error **errp)
+{
+ UHCISysBusState *s = SYSBUS_UHCI(dev);
+ ASPEEDUHCIState *f = ASPEED_UHCI(dev);
+ UHCIState *uhci = &s->uhci;
+
+ uhci_sysbus_realize(dev, errp);
+
+ memory_region_init_io(&f->mem_aspeed, OBJECT(f), &aspeed_uhci_mmio_ops,
+ uhci, "aspeed", 0x100);
+ memory_region_add_subregion(&uhci->mem, 0, &f->mem_aspeed);
+}
+
+static void uhci_sysbus_aspeed_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = uhci_sysbus_aspeed_realize;
+ set_bit(DEVICE_CATEGORY_USB, dc->categories);
+ dc->desc = "ASPEED UHCI USB Controller";
+ dc->reset = uhci_sysbus_reset_sysbus;
+}
+
static const TypeInfo uhci_sysbus_types[] = {
{
.name = TYPE_SYSBUS_UHCI,
@@ -95,6 +190,12 @@ static const TypeInfo uhci_sysbus_types[] = {
.instance_size = sizeof(UHCISysBusState),
.class_init = uhci_sysbus_class_init,
},
+ {
+ .name = TYPE_ASPEED_UHCI,
+ .parent = TYPE_SYSBUS_UHCI,
+ .instance_size = sizeof(ASPEEDUHCIState),
+ .class_init = uhci_sysbus_aspeed_class_init,
+ },
};
DEFINE_TYPES(uhci_sysbus_types);
@@ -4,6 +4,7 @@
#include "hcd-uhci.h"
#define TYPE_SYSBUS_UHCI "sysbus-uhci"
+#define TYPE_ASPEED_UHCI "aspeed-uhci"
OBJECT_DECLARE_SIMPLE_TYPE(UHCISysBusState, SYSBUS_UHCI)
@@ -20,4 +21,14 @@ struct UHCISysBusState {
uint32_t num_ports;
};
+OBJECT_DECLARE_SIMPLE_TYPE(ASPEEDUHCIState, ASPEED_UHCI)
+
+struct ASPEEDUHCIState {
+ /*< private >*/
+ UHCISysBusState parent_obj;
+ /*< public >*/
+
+ MemoryRegion mem_aspeed;
+};
+
#endif /* HW_USB_HCD_UHCI_SYSBUS_H */
Aspeed uses non-standard UHCI register addresses. On top of that, registers are 32 bit wide instead of 16 bit. Map Aspeed UHCI addresses to standard UHCI addresses and where needed combine/split 32 bit accesses to solve the problem. Signed-off-by: Guenter Roeck <linux@roeck-us.net> --- hw/usb/hcd-uhci-sysbus.c | 101 +++++++++++++++++++++++++++++++++++++++ hw/usb/hcd-uhci-sysbus.h | 11 +++++ 2 files changed, 112 insertions(+)