/*
 * USB UHCI controller emulation
 * PCI code
 *
 * Copyright (c) 2005 Fabrice Bellard
 *
 * Copyright (c) 2008 Max Krasnyansky
 *     Magor rewrite of the UHCI data structures parser and frame processor
 *     Support for fully async operation and multiple outstanding transactions
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "qemu/osdep.h"
#include "hw/irq.h"
#include "hw/usb.h"
#include "migration/vmstate.h"
#include "hw/pci/pci.h"
#include "hw/qdev-properties.h"
#include "qapi/error.h"
#include "qemu/main-loop.h"
#include "qemu/module.h"
#include "qom/object.h"
#include "hcd-uhci-pci.h"

struct UHCIPCIDeviceClass {
    PCIDeviceClass parent_class;
    UHCIPCIInfo info;
};

static const VMStateDescription vmstate_uhci = {
    .name = "pci_uhci",
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (const VMStateField[]) {
        VMSTATE_PCI_DEVICE(dev, UHCIPCIState),
        VMSTATE_STRUCT(state, UHCIPCIState, 1, vmstate_uhci_state, UHCIState),
        VMSTATE_END_OF_LIST()
    }
};

static void uhci_pci_reset(UHCIState *uhci)
{
    UHCIPCIState *pstate = container_of(uhci, UHCIPCIState, state);
    PCIDevice *d = &pstate->dev;

    d->config[0x6a] = 0x01; /* usb clock */
    d->config[0x6b] = 0x00;

    uhci_state_reset(uhci);
}

void usb_uhci_common_realize_pci(PCIDevice *dev, Error **errp)
{
    Error *err = NULL;
    UHCIPCIDeviceClass *u = UHCI_PCI_GET_CLASS(dev);
    UHCIPCIState *uhci = UHCI_PCI(dev);
    UHCIState *s = &uhci->state;
    uint8_t *pci_conf = dev->config;

    pci_conf[PCI_CLASS_PROG] = 0x00;
    /* TODO: reset value should be 0. */
    pci_conf[USB_SBRN] = USB_RELEASE_1; /* release number */
    pci_config_set_interrupt_pin(pci_conf, u->info.irq_pin + 1);

    s->irq = pci_allocate_irq(dev);
    s->masterbus = uhci->masterbus;
    s->firstport = uhci->firstport;
    s->maxframes = uhci->maxframes;
    s->frame_bandwidth = uhci->frame_bandwidth;
    s->as = pci_get_address_space(dev);
    s->uhci_reset = uhci_pci_reset;

    usb_uhci_init(s, DEVICE(dev), &err);

    /*
     * Use region 4 for consistency with real hardware.  BSD guests seem
     * to rely on this.
     */
    pci_register_bar(dev, 4, PCI_BASE_ADDRESS_SPACE_IO, &s->mem);
}

static void uhci_pci_reset_pci(DeviceState *dev)
{
    PCIDevice *d = PCI_DEVICE(dev);
    UHCIPCIState *uhci = UHCI_PCI(d);

    uhci_pci_reset(&uhci->state);
}

static void usb_uhci_pci_exit(PCIDevice *dev)
{
    UHCIPCIState *uhci = UHCI_PCI(dev);
    UHCIState *s = &uhci->state;

    usb_uhci_exit(s);

    qemu_free_irq(s->irq);
}

static Property uhci_properties_companion[] = {
    DEFINE_PROP_STRING("masterbus", UHCIPCIState, masterbus),
    DEFINE_PROP_UINT32("firstport", UHCIPCIState, firstport, 0),
    DEFINE_PROP_UINT32("bandwidth", UHCIPCIState, frame_bandwidth, 1280),
    DEFINE_PROP_UINT32("maxframes", UHCIPCIState, maxframes, 128),
    DEFINE_PROP_END_OF_LIST(),
};
static Property uhci_properties_standalone[] = {
    DEFINE_PROP_UINT32("bandwidth", UHCIPCIState, frame_bandwidth, 1280),
    DEFINE_PROP_UINT32("maxframes", UHCIPCIState, maxframes, 128),
    DEFINE_PROP_END_OF_LIST(),
};

static void uhci_pci_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);

    k->class_id  = PCI_CLASS_SERIAL_USB;
    dc->vmsd = &vmstate_uhci;
    device_class_set_legacy_reset(dc, uhci_pci_reset_pci);
    set_bit(DEVICE_CATEGORY_USB, dc->categories);
}

static const TypeInfo uhci_pci_type_info = {
    .name = TYPE_UHCI_PCI,
    .parent = TYPE_PCI_DEVICE,
    .instance_size = sizeof(UHCIPCIState),
    .class_size    = sizeof(UHCIPCIDeviceClass),
    .class_init    = uhci_pci_class_init,
    .interfaces = (InterfaceInfo[]) {
        { INTERFACE_CONVENTIONAL_PCI_DEVICE },
        { },
    },
};

void uhci_pci_data_class_init(ObjectClass *klass, void *data)
{
    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
    DeviceClass *dc = DEVICE_CLASS(klass);
    UHCIPCIDeviceClass *u = UHCI_PCI_CLASS(klass);
    UHCIPCIInfo *info = data;

    k->realize = info->realize ? info->realize : usb_uhci_common_realize_pci;
    k->exit = info->unplug ? usb_uhci_pci_exit : NULL;
    k->vendor_id = info->vendor_id;
    k->device_id = info->device_id;
    k->revision  = info->revision;
    if (!info->unplug) {
        /* uhci controllers in companion setups can't be hotplugged */
        dc->hotpluggable = false;
        device_class_set_props(dc, uhci_properties_companion);
    } else {
        device_class_set_props(dc, uhci_properties_standalone);
    }
    if (info->notuser) {
        dc->user_creatable = false;
    }
    u->info = *info;
}

static UHCIPCIInfo uhci_pci_info[] = {
    {
        .name      = TYPE_PIIX3_USB_UHCI,
        .vendor_id = PCI_VENDOR_ID_INTEL,
        .device_id = PCI_DEVICE_ID_INTEL_82371SB_2,
        .revision  = 0x01,
        .irq_pin   = 3,
        .unplug    = true,
    },{
        .name      = TYPE_PIIX4_USB_UHCI,
        .vendor_id = PCI_VENDOR_ID_INTEL,
        .device_id = PCI_DEVICE_ID_INTEL_82371AB_2,
        .revision  = 0x01,
        .irq_pin   = 3,
        .unplug    = true,
    },{
        .name      = TYPE_ICH9_USB_UHCI(1), /* 00:1d.0 */
        .vendor_id = PCI_VENDOR_ID_INTEL,
        .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI1,
        .revision  = 0x03,
        .irq_pin   = 0,
        .unplug    = false,
    },{
        .name      = TYPE_ICH9_USB_UHCI(2), /* 00:1d.1 */
        .vendor_id = PCI_VENDOR_ID_INTEL,
        .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI2,
        .revision  = 0x03,
        .irq_pin   = 1,
        .unplug    = false,
    },{
        .name      = TYPE_ICH9_USB_UHCI(3), /* 00:1d.2 */
        .vendor_id = PCI_VENDOR_ID_INTEL,
        .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI3,
        .revision  = 0x03,
        .irq_pin   = 2,
        .unplug    = false,
    },{
        .name      = TYPE_ICH9_USB_UHCI(4), /* 00:1a.0 */
        .vendor_id = PCI_VENDOR_ID_INTEL,
        .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI4,
        .revision  = 0x03,
        .irq_pin   = 0,
        .unplug    = false,
    },{
        .name      = TYPE_ICH9_USB_UHCI(5), /* 00:1a.1 */
        .vendor_id = PCI_VENDOR_ID_INTEL,
        .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI5,
        .revision  = 0x03,
        .irq_pin   = 1,
        .unplug    = false,
    },{
        .name      = TYPE_ICH9_USB_UHCI(6), /* 00:1a.2 */
        .vendor_id = PCI_VENDOR_ID_INTEL,
        .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI6,
        .revision  = 0x03,
        .irq_pin   = 2,
        .unplug    = false,
    }
};

static void uhci_pci_register_types(void)
{
    TypeInfo type_info = {
        .parent        = TYPE_UHCI_PCI,
        .class_init    = uhci_pci_data_class_init,
    };
    int i;

    type_register_static(&uhci_pci_type_info);

    for (i = 0; i < ARRAY_SIZE(uhci_pci_info); i++) {
        type_info.name = uhci_pci_info[i].name;
        type_info.class_data = uhci_pci_info + i;
        type_register(&type_info);
    }
}

type_init(uhci_pci_register_types)