diff options
Diffstat (limited to 'hw/usb')
-rw-r--r-- | hw/usb/Kconfig | 5 | ||||
-rw-r--r-- | hw/usb/Makefile.objs | 2 | ||||
-rw-r--r-- | hw/usb/imx-usb-phy.c | 225 |
3 files changed, 232 insertions, 0 deletions
diff --git a/hw/usb/Kconfig b/hw/usb/Kconfig index 5e70ed5f7b..464348ba14 100644 --- a/hw/usb/Kconfig +++ b/hw/usb/Kconfig @@ -91,3 +91,8 @@ config USB_STORAGE_MTP bool default y depends on USB + +config IMX_USBPHY + bool + default y + depends on USB diff --git a/hw/usb/Makefile.objs b/hw/usb/Makefile.objs index 2b10868937..66835e5bf7 100644 --- a/hw/usb/Makefile.objs +++ b/hw/usb/Makefile.objs @@ -61,3 +61,5 @@ common-obj-$(CONFIG_XEN) += xen-usb.o xen-usb.o-cflags := $(LIBUSB_CFLAGS) xen-usb.o-libs := $(LIBUSB_LIBS) endif + +common-obj-$(CONFIG_IMX_USBPHY) += imx-usb-phy.o diff --git a/hw/usb/imx-usb-phy.c b/hw/usb/imx-usb-phy.c new file mode 100644 index 0000000000..e705a03a1f --- /dev/null +++ b/hw/usb/imx-usb-phy.c @@ -0,0 +1,225 @@ +/* + * i.MX USB PHY + * + * Copyright (c) 2020 Guenter Roeck <linux@roeck-us.net> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * We need to implement basic reset control in the PHY control register. + * For everything else, it is sufficient to set whatever is written. + */ + +#include "qemu/osdep.h" +#include "hw/usb/imx-usb-phy.h" +#include "migration/vmstate.h" +#include "qemu/log.h" +#include "qemu/module.h" + +static const VMStateDescription vmstate_imx_usbphy = { + .name = TYPE_IMX_USBPHY, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(usbphy, IMXUSBPHYState, USBPHY_MAX), + VMSTATE_END_OF_LIST() + }, +}; + +static void imx_usbphy_softreset(IMXUSBPHYState *s) +{ + s->usbphy[USBPHY_PWD] = 0x001e1c00; + s->usbphy[USBPHY_TX] = 0x10060607; + s->usbphy[USBPHY_RX] = 0x00000000; + s->usbphy[USBPHY_CTRL] = 0xc0200000; +} + +static void imx_usbphy_reset(DeviceState *dev) +{ + IMXUSBPHYState *s = IMX_USBPHY(dev); + + s->usbphy[USBPHY_STATUS] = 0x00000000; + s->usbphy[USBPHY_DEBUG] = 0x7f180000; + s->usbphy[USBPHY_DEBUG0_STATUS] = 0x00000000; + s->usbphy[USBPHY_DEBUG1] = 0x00001000; + s->usbphy[USBPHY_VERSION] = 0x04020000; + + imx_usbphy_softreset(s); +} + +static uint64_t imx_usbphy_read(void *opaque, hwaddr offset, unsigned size) +{ + IMXUSBPHYState *s = (IMXUSBPHYState *)opaque; + uint32_t index = offset >> 2; + uint32_t value; + + switch (index) { + case USBPHY_PWD_SET: + case USBPHY_TX_SET: + case USBPHY_RX_SET: + case USBPHY_CTRL_SET: + case USBPHY_DEBUG_SET: + case USBPHY_DEBUG1_SET: + /* + * All REG_NAME_SET register access are in fact targeting the + * REG_NAME register. + */ + value = s->usbphy[index - 1]; + break; + case USBPHY_PWD_CLR: + case USBPHY_TX_CLR: + case USBPHY_RX_CLR: + case USBPHY_CTRL_CLR: + case USBPHY_DEBUG_CLR: + case USBPHY_DEBUG1_CLR: + /* + * All REG_NAME_CLR register access are in fact targeting the + * REG_NAME register. + */ + value = s->usbphy[index - 2]; + break; + case USBPHY_PWD_TOG: + case USBPHY_TX_TOG: + case USBPHY_RX_TOG: + case USBPHY_CTRL_TOG: + case USBPHY_DEBUG_TOG: + case USBPHY_DEBUG1_TOG: + /* + * All REG_NAME_TOG register access are in fact targeting the + * REG_NAME register. + */ + value = s->usbphy[index - 3]; + break; + default: + value = s->usbphy[index]; + break; + } + return (uint64_t)value; +} + +static void imx_usbphy_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + IMXUSBPHYState *s = (IMXUSBPHYState *)opaque; + uint32_t index = offset >> 2; + + switch (index) { + case USBPHY_CTRL: + s->usbphy[index] = value; + if (value & USBPHY_CTRL_SFTRST) { + imx_usbphy_softreset(s); + } + break; + case USBPHY_PWD: + case USBPHY_TX: + case USBPHY_RX: + case USBPHY_STATUS: + case USBPHY_DEBUG: + case USBPHY_DEBUG1: + s->usbphy[index] = value; + break; + case USBPHY_CTRL_SET: + s->usbphy[index - 1] |= value; + if (value & USBPHY_CTRL_SFTRST) { + imx_usbphy_softreset(s); + } + break; + case USBPHY_PWD_SET: + case USBPHY_TX_SET: + case USBPHY_RX_SET: + case USBPHY_DEBUG_SET: + case USBPHY_DEBUG1_SET: + /* + * All REG_NAME_SET register access are in fact targeting the + * REG_NAME register. So we change the value of the REG_NAME + * register, setting bits passed in the value. + */ + s->usbphy[index - 1] |= value; + break; + case USBPHY_PWD_CLR: + case USBPHY_TX_CLR: + case USBPHY_RX_CLR: + case USBPHY_CTRL_CLR: + case USBPHY_DEBUG_CLR: + case USBPHY_DEBUG1_CLR: + /* + * All REG_NAME_CLR register access are in fact targeting the + * REG_NAME register. So we change the value of the REG_NAME + * register, unsetting bits passed in the value. + */ + s->usbphy[index - 2] &= ~value; + break; + case USBPHY_CTRL_TOG: + s->usbphy[index - 3] ^= value; + if ((value & USBPHY_CTRL_SFTRST) && + (s->usbphy[index - 3] & USBPHY_CTRL_SFTRST)) { + imx_usbphy_softreset(s); + } + break; + case USBPHY_PWD_TOG: + case USBPHY_TX_TOG: + case USBPHY_RX_TOG: + case USBPHY_DEBUG_TOG: + case USBPHY_DEBUG1_TOG: + /* + * All REG_NAME_TOG register access are in fact targeting the + * REG_NAME register. So we change the value of the REG_NAME + * register, toggling bits passed in the value. + */ + s->usbphy[index - 3] ^= value; + break; + default: + /* Other registers are read-only */ + break; + } +} + +static const struct MemoryRegionOps imx_usbphy_ops = { + .read = imx_usbphy_read, + .write = imx_usbphy_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + /* + * 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 imx_usbphy_realize(DeviceState *dev, Error **errp) +{ + IMXUSBPHYState *s = IMX_USBPHY(dev); + + memory_region_init_io(&s->iomem, OBJECT(s), &imx_usbphy_ops, s, + "imx-usbphy", 0x1000); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); +} + +static void imx_usbphy_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = imx_usbphy_reset; + dc->vmsd = &vmstate_imx_usbphy; + dc->desc = "i.MX USB PHY Module"; + dc->realize = imx_usbphy_realize; +} + +static const TypeInfo imx_usbphy_info = { + .name = TYPE_IMX_USBPHY, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IMXUSBPHYState), + .class_init = imx_usbphy_class_init, +}; + +static void imx_usbphy_register_types(void) +{ + type_register_static(&imx_usbphy_info); +} + +type_init(imx_usbphy_register_types) |