| /* |
| * Copyright (c) 2018, Impinj, Inc. |
| * |
| * Chipidea USB block emulation code |
| * |
| * Author: Andrey Smirnov <andrew.smirnov@gmail.com> |
| * |
| * This work is licensed under the terms of the GNU GPL, version 2 or later. |
| * See the COPYING file in the top-level directory. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "hw/usb/hcd-ehci.h" |
| #include "hw/usb/chipidea.h" |
| #include "qemu/log.h" |
| |
| enum { |
| CHIPIDEA_USBx_DCIVERSION = 0x000, |
| CHIPIDEA_USBx_DCCPARAMS = 0x004, |
| CHIPIDEA_USBx_DCCPARAMS_HC = BIT(8), |
| }; |
| |
| static uint64_t chipidea_read(void *opaque, hwaddr offset, |
| unsigned size) |
| { |
| return 0; |
| } |
| |
| static void chipidea_write(void *opaque, hwaddr offset, |
| uint64_t value, unsigned size) |
| { |
| } |
| |
| static const struct MemoryRegionOps chipidea_ops = { |
| .read = chipidea_read, |
| .write = chipidea_write, |
| .endianness = DEVICE_NATIVE_ENDIAN, |
| .impl = { |
| /* |
| * Our device would not work correctly if the guest was doing |
| * unaligned access. This might not be a limitation on the |
| * real device but in practice there is no reason for a guest |
| * to access this device unaligned. |
| */ |
| .min_access_size = 4, |
| .max_access_size = 4, |
| .unaligned = false, |
| }, |
| }; |
| |
| static uint64_t chipidea_dc_read(void *opaque, hwaddr offset, |
| unsigned size) |
| { |
| switch (offset) { |
| case CHIPIDEA_USBx_DCIVERSION: |
| return 0x1; |
| case CHIPIDEA_USBx_DCCPARAMS: |
| /* |
| * Real hardware (at least i.MX7) will also report the |
| * controller as "Device Capable" (and 8 supported endpoints), |
| * but there doesn't seem to be much point in doing so, since |
| * we don't emulate that part. |
| */ |
| return CHIPIDEA_USBx_DCCPARAMS_HC; |
| } |
| |
| return 0; |
| } |
| |
| static void chipidea_dc_write(void *opaque, hwaddr offset, |
| uint64_t value, unsigned size) |
| { |
| } |
| |
| static const struct MemoryRegionOps chipidea_dc_ops = { |
| .read = chipidea_dc_read, |
| .write = chipidea_dc_write, |
| .endianness = DEVICE_NATIVE_ENDIAN, |
| .impl = { |
| /* |
| * Our device would not work correctly if the guest was doing |
| * unaligned access. This might not be a limitation on the real |
| * device but in practice there is no reason for a guest to access |
| * this device unaligned. |
| */ |
| .min_access_size = 4, |
| .max_access_size = 4, |
| .unaligned = false, |
| }, |
| }; |
| |
| static void chipidea_init(Object *obj) |
| { |
| EHCIState *ehci = &SYS_BUS_EHCI(obj)->ehci; |
| ChipideaState *ci = CHIPIDEA(obj); |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(ci->iomem); i++) { |
| const struct { |
| const char *name; |
| hwaddr offset; |
| uint64_t size; |
| const struct MemoryRegionOps *ops; |
| } regions[ARRAY_SIZE(ci->iomem)] = { |
| /* |
| * Registers located between offsets 0x000 and 0xFC |
| */ |
| { |
| .name = TYPE_CHIPIDEA ".misc", |
| .offset = 0x000, |
| .size = 0x100, |
| .ops = &chipidea_ops, |
| }, |
| /* |
| * Registers located between offsets 0x1A4 and 0x1DC |
| */ |
| { |
| .name = TYPE_CHIPIDEA ".endpoints", |
| .offset = 0x1A4, |
| .size = 0x1DC - 0x1A4 + 4, |
| .ops = &chipidea_ops, |
| }, |
| /* |
| * USB_x_DCIVERSION and USB_x_DCCPARAMS |
| */ |
| { |
| .name = TYPE_CHIPIDEA ".dc", |
| .offset = 0x120, |
| .size = 8, |
| .ops = &chipidea_dc_ops, |
| }, |
| }; |
| |
| memory_region_init_io(&ci->iomem[i], |
| obj, |
| regions[i].ops, |
| ci, |
| regions[i].name, |
| regions[i].size); |
| |
| memory_region_add_subregion(&ehci->mem, |
| regions[i].offset, |
| &ci->iomem[i]); |
| } |
| } |
| |
| static void chipidea_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(klass); |
| |
| /* |
| * Offsets used were taken from i.MX7Dual Applications Processor |
| * Reference Manual, Rev 0.1, p. 3177, Table 11-59 |
| */ |
| sec->capsbase = 0x100; |
| sec->opregbase = 0x140; |
| sec->portnr = 1; |
| |
| set_bit(DEVICE_CATEGORY_USB, dc->categories); |
| dc->desc = "Chipidea USB Module"; |
| } |
| |
| static const TypeInfo chipidea_info = { |
| .name = TYPE_CHIPIDEA, |
| .parent = TYPE_SYS_BUS_EHCI, |
| .instance_size = sizeof(ChipideaState), |
| .instance_init = chipidea_init, |
| .class_init = chipidea_class_init, |
| }; |
| |
| static void chipidea_register_type(void) |
| { |
| type_register_static(&chipidea_info); |
| } |
| type_init(chipidea_register_type) |