/*
* 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/module.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)