diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2016-06-14 16:04:25 +0100 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2016-06-14 16:04:25 +0100 |
commit | 1be08a0946b1a189ac72822182c37367e8cd3d87 (patch) | |
tree | 2c41b2e3abf67957ea1b75e7d5330ab2b1dff4fe | |
parent | 7474f1be701f136b224af5e1abe55e97dc3f29a5 (diff) | |
parent | fe8fcf3d642b4de1369841bf6acac13e0ec8770d (diff) | |
download | qemu-1be08a0946b1a189ac72822182c37367e8cd3d87.zip qemu-1be08a0946b1a189ac72822182c37367e8cd3d87.tar.gz qemu-1be08a0946b1a189ac72822182c37367e8cd3d87.tar.bz2 |
Merge remote-tracking branch 'remotes/pmaydell/tags/pull-target-arm-20160614-2' into staging
target-arm queue:
* add PMU support for virt machine under KVM
* fix reset and migration of TTBCR(S)
* add virt-2.7 machine type
* QOMify various ARM devices
* implement xilinx DisplayPort device
* don't permit ARMv8-only Neon insns to work on ARMv7
# gpg: Signature made Tue 14 Jun 2016 16:01:45 BST
# gpg: using RSA key 0x3C2525ED14360CDE
# gpg: Good signature from "Peter Maydell <peter.maydell@linaro.org>"
# gpg: aka "Peter Maydell <pmaydell@gmail.com>"
# gpg: aka "Peter Maydell <pmaydell@chiark.greenend.org.uk>"
# Primary key fingerprint: E1A5 C593 CD41 9DE2 8E83 15CF 3C25 25ED 1436 0CDE
* remotes/pmaydell/tags/pull-target-arm-20160614-2: (30 commits)
target-arm: Don't permit ARMv8-only Neon insns on ARMv7
arm: xlnx-zynqmp: Add xlnx-dp and xlnx-dpdma
introduce xlnx-dp
introduce xlnx-dpdma
hw/i2c-ddc.c: Implement DDC I2C slave
introduce dpcd module
introduce aux-bus
i2c: Factor our send() and recv() common logic
i2c: implement broadcast write
i2cbus: remove unused dev field
hw/sd: QOM'ify pl181.c
hw/dma: QOM'ify pxa2xx_dma.c
hw/misc: QOM'ify mst_fpga.c
hw/misc: QOM'ify exynos4210_pmu.c
hw/misc: QOM'ify arm_l2x0.c
hw/gpio: QOM'ify zaurus.c
hw/gpio: QOM'ify pl061.c
hw/gpio: QOM'ify omap_gpio.c
hw/i2c: QOM'ify versatile_i2c.c
hw/i2c: QOM'ify omap_i2c.c
...
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
40 files changed, 3830 insertions, 223 deletions
diff --git a/default-configs/aarch64-softmmu.mak b/default-configs/aarch64-softmmu.mak index 96dd994..2449483 100644 --- a/default-configs/aarch64-softmmu.mak +++ b/default-configs/aarch64-softmmu.mak @@ -3,4 +3,7 @@ # We support all the 32 bit boards so need all their config include arm-softmmu.mak +CONFIG_AUX=y +CONFIG_DDC=y +CONFIG_DPCD=y CONFIG_XLNX_ZYNQMP=y diff --git a/hw/arm/virt-acpi-build.c b/hw/arm/virt-acpi-build.c index 735ab86..1fa0581 100644 --- a/hw/arm/virt-acpi-build.c +++ b/hw/arm/virt-acpi-build.c @@ -538,6 +538,10 @@ build_madt(GArray *table_data, BIOSLinker *linker, VirtGuestInfo *guest_info) gicc->arm_mpidr = armcpu->mp_affinity; gicc->uid = i; gicc->flags = cpu_to_le32(ACPI_GICC_ENABLED); + + if (armcpu->has_pmu) { + gicc->performance_interrupt = cpu_to_le32(PPI(VIRTUAL_PMU_IRQ)); + } } if (guest_info->gic_version == 3) { diff --git a/hw/arm/virt.c b/hw/arm/virt.c index 73113cf..c5c125e 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -42,6 +42,7 @@ #include "sysemu/sysemu.h" #include "sysemu/kvm.h" #include "hw/boards.h" +#include "hw/compat.h" #include "hw/loader.h" #include "exec/address-spaces.h" #include "qemu/bitops.h" @@ -98,6 +99,36 @@ typedef struct { #define VIRT_MACHINE_CLASS(klass) \ OBJECT_CLASS_CHECK(VirtMachineClass, klass, TYPE_VIRT_MACHINE) + +#define DEFINE_VIRT_MACHINE_LATEST(major, minor, latest) \ + static void virt_##major##_##minor##_class_init(ObjectClass *oc, \ + void *data) \ + { \ + MachineClass *mc = MACHINE_CLASS(oc); \ + virt_machine_##major##_##minor##_options(mc); \ + mc->desc = "QEMU " # major "." # minor " ARM Virtual Machine"; \ + if (latest) { \ + mc->alias = "virt"; \ + } \ + } \ + static const TypeInfo machvirt_##major##_##minor##_info = { \ + .name = MACHINE_TYPE_NAME("virt-" # major "." # minor), \ + .parent = TYPE_VIRT_MACHINE, \ + .instance_init = virt_##major##_##minor##_instance_init, \ + .class_init = virt_##major##_##minor##_class_init, \ + }; \ + static void machvirt_machine_##major##_##minor##_init(void) \ + { \ + type_register_static(&machvirt_##major##_##minor##_info); \ + } \ + type_init(machvirt_machine_##major##_##minor##_init); + +#define DEFINE_VIRT_MACHINE_AS_LATEST(major, minor) \ + DEFINE_VIRT_MACHINE_LATEST(major, minor, true) +#define DEFINE_VIRT_MACHINE(major, minor) \ + DEFINE_VIRT_MACHINE_LATEST(major, minor, false) + + /* RAM limit in GB. Since VIRT_MEM starts at the 1GB mark, this means * RAM can go up to the 256GB mark, leaving 256GB of the physical * address space unallocated and free for future use between 256G and 512G. @@ -436,6 +467,37 @@ static void fdt_add_gic_node(VirtBoardInfo *vbi, int type) qemu_fdt_setprop_cell(vbi->fdt, "/intc", "phandle", vbi->gic_phandle); } +static void fdt_add_pmu_nodes(const VirtBoardInfo *vbi, int gictype) +{ + CPUState *cpu; + ARMCPU *armcpu; + uint32_t irqflags = GIC_FDT_IRQ_FLAGS_LEVEL_HI; + + CPU_FOREACH(cpu) { + armcpu = ARM_CPU(cpu); + if (!armcpu->has_pmu || + !kvm_arm_pmu_create(cpu, PPI(VIRTUAL_PMU_IRQ))) { + return; + } + } + + if (gictype == 2) { + irqflags = deposit32(irqflags, GIC_FDT_IRQ_PPI_CPU_START, + GIC_FDT_IRQ_PPI_CPU_WIDTH, + (1 << vbi->smp_cpus) - 1); + } + + armcpu = ARM_CPU(qemu_get_cpu(0)); + qemu_fdt_add_subnode(vbi->fdt, "/pmu"); + if (arm_feature(&armcpu->env, ARM_FEATURE_V8)) { + const char compat[] = "arm,armv8-pmuv3"; + qemu_fdt_setprop(vbi->fdt, "/pmu", "compatible", + compat, sizeof(compat)); + qemu_fdt_setprop_cells(vbi->fdt, "/pmu", "interrupts", + GIC_FDT_IRQ_TYPE_PPI, VIRTUAL_PMU_IRQ, irqflags); + } +} + static void create_v2m(VirtBoardInfo *vbi, qemu_irq *pic) { int i; @@ -1259,6 +1321,8 @@ static void machvirt_init(MachineState *machine) create_gic(vbi, pic, gic_version, vms->secure); + fdt_add_pmu_nodes(vbi, gic_version); + create_uart(vbi, pic, VIRT_UART, sysmem, serial_hds[0]); if (vms->secure) { @@ -1387,7 +1451,13 @@ static const TypeInfo virt_machine_info = { .class_init = virt_machine_class_init, }; -static void virt_2_6_instance_init(Object *obj) +static void machvirt_machine_init(void) +{ + type_register_static(&virt_machine_info); +} +type_init(machvirt_machine_init); + +static void virt_2_7_instance_init(Object *obj) { VirtMachineState *vms = VIRT_MACHINE(obj); @@ -1420,25 +1490,22 @@ static void virt_2_6_instance_init(Object *obj) "Valid values are 2, 3 and host", NULL); } -static void virt_2_6_class_init(ObjectClass *oc, void *data) +static void virt_machine_2_7_options(MachineClass *mc) { - MachineClass *mc = MACHINE_CLASS(oc); - - mc->desc = "QEMU 2.6 ARM Virtual Machine"; - mc->alias = "virt"; } +DEFINE_VIRT_MACHINE_AS_LATEST(2, 7) -static const TypeInfo machvirt_info = { - .name = MACHINE_TYPE_NAME("virt-2.6"), - .parent = TYPE_VIRT_MACHINE, - .instance_init = virt_2_6_instance_init, - .class_init = virt_2_6_class_init, -}; +#define VIRT_COMPAT_2_6 \ + HW_COMPAT_2_6 -static void machvirt_machine_init(void) +static void virt_2_6_instance_init(Object *obj) { - type_register_static(&virt_machine_info); - type_register_static(&machvirt_info); + virt_2_7_instance_init(obj); } -type_init(machvirt_machine_init); +static void virt_machine_2_6_options(MachineClass *mc) +{ + virt_machine_2_7_options(mc); + SET_MACHINE_COMPAT(mc, VIRT_COMPAT_2_6); +} +DEFINE_VIRT_MACHINE(2, 6) diff --git a/hw/arm/xlnx-zynqmp.c b/hw/arm/xlnx-zynqmp.c index 308d677..23c7199 100644 --- a/hw/arm/xlnx-zynqmp.c +++ b/hw/arm/xlnx-zynqmp.c @@ -38,6 +38,12 @@ #define SATA_ADDR 0xFD0C0000 #define SATA_NUM_PORTS 2 +#define DP_ADDR 0xfd4a0000 +#define DP_IRQ 113 + +#define DPDMA_ADDR 0xfd4c0000 +#define DPDMA_IRQ 116 + static const uint64_t gem_addr[XLNX_ZYNQMP_NUM_GEMS] = { 0xFF0B0000, 0xFF0C0000, 0xFF0D0000, 0xFF0E0000, }; @@ -165,6 +171,12 @@ static void xlnx_zynqmp_init(Object *obj) TYPE_XILINX_SPIPS); qdev_set_parent_bus(DEVICE(&s->spi[i]), sysbus_get_default()); } + + object_initialize(&s->dp, sizeof(s->dp), TYPE_XLNX_DP); + qdev_set_parent_bus(DEVICE(&s->dp), sysbus_get_default()); + + object_initialize(&s->dpdma, sizeof(s->dpdma), TYPE_XLNX_DPDMA); + qdev_set_parent_bus(DEVICE(&s->dpdma), sysbus_get_default()); } static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp) @@ -388,8 +400,26 @@ static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp) object_property_add_alias(OBJECT(s), bus_name, OBJECT(&s->spi[i]), "spi0", &error_abort); - g_free(bus_name); + g_free(bus_name); + } + + object_property_set_bool(OBJECT(&s->dp), true, "realized", &err); + if (err) { + error_propagate(errp, err); + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->dp), 0, DP_ADDR); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->dp), 0, gic_spi[DP_IRQ]); + + object_property_set_bool(OBJECT(&s->dpdma), true, "realized", &err); + if (err) { + error_propagate(errp, err); + return; } + object_property_set_link(OBJECT(&s->dp), OBJECT(&s->dpdma), "dpdma", + &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->dpdma), 0, DPDMA_ADDR); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->dpdma), 0, gic_spi[DPDMA_IRQ]); } static Property xlnx_zynqmp_props[] = { diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs index d99780e..063889b 100644 --- a/hw/display/Makefile.objs +++ b/hw/display/Makefile.objs @@ -43,3 +43,5 @@ virtio-gpu.o-cflags := $(VIRGL_CFLAGS) virtio-gpu.o-libs += $(VIRGL_LIBS) virtio-gpu-3d.o-cflags := $(VIRGL_CFLAGS) virtio-gpu-3d.o-libs += $(VIRGL_LIBS) +obj-$(CONFIG_DPCD) += dpcd.o +obj-$(CONFIG_XLNX_ZYNQMP) += xlnx_dp.o diff --git a/hw/display/dpcd.c b/hw/display/dpcd.c new file mode 100644 index 0000000..5a36855 --- /dev/null +++ b/hw/display/dpcd.c @@ -0,0 +1,173 @@ +/* + * dpcd.c + * + * Copyright (C) 2015 : GreenSocs Ltd + * http://www.greensocs.com/ , email: info@greensocs.com + * + * Developed by : + * Frederic Konrad <fred.konrad@greensocs.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option)any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +/* + * This is a simple AUX slave which emulates a connected screen. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/misc/aux.h" +#include "hw/display/dpcd.h" + +#ifndef DEBUG_DPCD +#define DEBUG_DPCD 0 +#endif + +#define DPRINTF(fmt, ...) do { \ + if (DEBUG_DPCD) { \ + qemu_log("dpcd: " fmt, ## __VA_ARGS__); \ + } \ +} while (0); + +#define DPCD_READABLE_AREA 0x600 + +struct DPCDState { + /*< private >*/ + AUXSlave parent_obj; + + /*< public >*/ + /* + * The DCPD is 0x7FFFF length but read as 0 after offset 0x5FF. + */ + uint8_t dpcd_info[DPCD_READABLE_AREA]; + + MemoryRegion iomem; +}; + +static uint64_t dpcd_read(void *opaque, hwaddr offset, unsigned size) +{ + uint8_t ret; + DPCDState *e = DPCD(opaque); + + if (offset < DPCD_READABLE_AREA) { + ret = e->dpcd_info[offset]; + } else { + qemu_log_mask(LOG_GUEST_ERROR, "dpcd: Bad offset 0x%" HWADDR_PRIX "\n", + offset); + ret = 0; + } + + DPRINTF("read 0x%" PRIX8 " @0x%" HWADDR_PRIX "\n", ret, offset); + return ret; +} + +static void dpcd_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + DPCDState *e = DPCD(opaque); + + DPRINTF("write 0x%" PRIX8 " @0x%" HWADDR_PRIX "\n", (uint8_t)value, offset); + + if (offset < DPCD_READABLE_AREA) { + e->dpcd_info[offset] = value; + } else { + qemu_log_mask(LOG_GUEST_ERROR, "dpcd: Bad offset 0x%" HWADDR_PRIX "\n", + offset); + } +} + +static const MemoryRegionOps aux_ops = { + .read = dpcd_read, + .write = dpcd_write, + .valid = { + .min_access_size = 1, + .max_access_size = 1, + }, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, +}; + +static void dpcd_reset(DeviceState *dev) +{ + DPCDState *s = DPCD(dev); + + memset(&(s->dpcd_info), 0, sizeof(s->dpcd_info)); + + s->dpcd_info[DPCD_REVISION] = DPCD_REV_1_0; + s->dpcd_info[DPCD_MAX_LINK_RATE] = DPCD_5_4GBPS; + s->dpcd_info[DPCD_MAX_LANE_COUNT] = DPCD_FOUR_LANES; + s->dpcd_info[DPCD_RECEIVE_PORT0_CAP_0] = DPCD_EDID_PRESENT; + /* buffer size */ + s->dpcd_info[DPCD_RECEIVE_PORT0_CAP_1] = 0xFF; + + s->dpcd_info[DPCD_LANE0_1_STATUS] = DPCD_LANE0_CR_DONE + | DPCD_LANE0_CHANNEL_EQ_DONE + | DPCD_LANE0_SYMBOL_LOCKED + | DPCD_LANE1_CR_DONE + | DPCD_LANE1_CHANNEL_EQ_DONE + | DPCD_LANE1_SYMBOL_LOCKED; + s->dpcd_info[DPCD_LANE2_3_STATUS] = DPCD_LANE2_CR_DONE + | DPCD_LANE2_CHANNEL_EQ_DONE + | DPCD_LANE2_SYMBOL_LOCKED + | DPCD_LANE3_CR_DONE + | DPCD_LANE3_CHANNEL_EQ_DONE + | DPCD_LANE3_SYMBOL_LOCKED; + + s->dpcd_info[DPCD_LANE_ALIGN_STATUS_UPDATED] = DPCD_INTERLANE_ALIGN_DONE; + s->dpcd_info[DPCD_SINK_STATUS] = DPCD_RECEIVE_PORT_0_STATUS; +} + +static void dpcd_init(Object *obj) +{ + DPCDState *s = DPCD(obj); + + memory_region_init_io(&s->iomem, obj, &aux_ops, s, TYPE_DPCD, 0x7FFFF); + aux_init_mmio(AUX_SLAVE(obj), &s->iomem); +} + +static const VMStateDescription vmstate_dpcd = { + .name = TYPE_DPCD, + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT8_ARRAY_V(dpcd_info, DPCDState, DPCD_READABLE_AREA, 0), + VMSTATE_END_OF_LIST() + } +}; + +static void dpcd_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->reset = dpcd_reset; + dc->vmsd = &vmstate_dpcd; +} + +static const TypeInfo dpcd_info = { + .name = TYPE_DPCD, + .parent = TYPE_AUX_SLAVE, + .instance_size = sizeof(DPCDState), + .class_init = dpcd_class_init, + .instance_init = dpcd_init, +}; + +static void dpcd_register_types(void) +{ + type_register_static(&dpcd_info); +} + +type_init(dpcd_register_types) diff --git a/hw/display/xlnx_dp.c b/hw/display/xlnx_dp.c new file mode 100644 index 0000000..be53b75 --- /dev/null +++ b/hw/display/xlnx_dp.c @@ -0,0 +1,1336 @@ +/* + * xlnx_dp.c + * + * Copyright (C) 2015 : GreenSocs Ltd + * http://www.greensocs.com/ , email: info@greensocs.com + * + * Developed by : + * Frederic Konrad <fred.konrad@greensocs.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option)any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/display/xlnx_dp.h" + +#ifndef DEBUG_DP +#define DEBUG_DP 0 +#endif + +#define DPRINTF(fmt, ...) do { \ + if (DEBUG_DP) { \ + qemu_log("xlnx_dp: " fmt , ## __VA_ARGS__); \ + } \ +} while (0); + +/* + * Register offset for DP. + */ +#define DP_LINK_BW_SET (0x0000 >> 2) +#define DP_LANE_COUNT_SET (0x0004 >> 2) +#define DP_ENHANCED_FRAME_EN (0x0008 >> 2) +#define DP_TRAINING_PATTERN_SET (0x000C >> 2) +#define DP_LINK_QUAL_PATTERN_SET (0x0010 >> 2) +#define DP_SCRAMBLING_DISABLE (0x0014 >> 2) +#define DP_DOWNSPREAD_CTRL (0x0018 >> 2) +#define DP_SOFTWARE_RESET (0x001C >> 2) +#define DP_TRANSMITTER_ENABLE (0x0080 >> 2) +#define DP_MAIN_STREAM_ENABLE (0x0084 >> 2) +#define DP_FORCE_SCRAMBLER_RESET (0x00C0 >> 2) +#define DP_VERSION_REGISTER (0x00F8 >> 2) +#define DP_CORE_ID (0x00FC >> 2) + +#define DP_AUX_COMMAND_REGISTER (0x0100 >> 2) +#define AUX_ADDR_ONLY_MASK (0x1000) +#define AUX_COMMAND_MASK (0x0F00) +#define AUX_COMMAND_SHIFT (8) +#define AUX_COMMAND_NBYTES (0x000F) + +#define DP_AUX_WRITE_FIFO (0x0104 >> 2) +#define DP_AUX_ADDRESS (0x0108 >> 2) +#define DP_AUX_CLOCK_DIVIDER (0x010C >> 2) +#define DP_TX_USER_FIFO_OVERFLOW (0x0110 >> 2) +#define DP_INTERRUPT_SIGNAL_STATE (0x0130 >> 2) +#define DP_AUX_REPLY_DATA (0x0134 >> 2) +#define DP_AUX_REPLY_CODE (0x0138 >> 2) +#define DP_AUX_REPLY_COUNT (0x013C >> 2) +#define DP_REPLY_DATA_COUNT (0x0148 >> 2) +#define DP_REPLY_STATUS (0x014C >> 2) +#define DP_HPD_DURATION (0x0150 >> 2) +#define DP_MAIN_STREAM_HTOTAL (0x0180 >> 2) +#define DP_MAIN_STREAM_VTOTAL (0x0184 >> 2) +#define DP_MAIN_STREAM_POLARITY (0x0188 >> 2) +#define DP_MAIN_STREAM_HSWIDTH (0x018C >> 2) +#define DP_MAIN_STREAM_VSWIDTH (0x0190 >> 2) +#define DP_MAIN_STREAM_HRES (0x0194 >> 2) +#define DP_MAIN_STREAM_VRES (0x0198 >> 2) +#define DP_MAIN_STREAM_HSTART (0x019C >> 2) +#define DP_MAIN_STREAM_VSTART (0x01A0 >> 2) +#define DP_MAIN_STREAM_MISC0 (0x01A4 >> 2) +#define DP_MAIN_STREAM_MISC1 (0x01A8 >> 2) +#define DP_MAIN_STREAM_M_VID (0x01AC >> 2) +#define DP_MSA_TRANSFER_UNIT_SIZE (0x01B0 >> 2) +#define DP_MAIN_STREAM_N_VID (0x01B4 >> 2) +#define DP_USER_DATA_COUNT_PER_LANE (0x01BC >> 2) +#define DP_MIN_BYTES_PER_TU (0x01C4 >> 2) +#define DP_FRAC_BYTES_PER_TU (0x01C8 >> 2) +#define DP_INIT_WAIT (0x01CC >> 2) +#define DP_PHY_RESET (0x0200 >> 2) +#define DP_PHY_VOLTAGE_DIFF_LANE_0 (0x0220 >> 2) +#define DP_PHY_VOLTAGE_DIFF_LANE_1 (0x0224 >> 2) +#define DP_TRANSMIT_PRBS7 (0x0230 >> 2) +#define DP_PHY_CLOCK_SELECT (0x0234 >> 2) +#define DP_TX_PHY_POWER_DOWN (0x0238 >> 2) +#define DP_PHY_PRECURSOR_LANE_0 (0x023C >> 2) +#define DP_PHY_PRECURSOR_LANE_1 (0x0240 >> 2) +#define DP_PHY_POSTCURSOR_LANE_0 (0x024C >> 2) +#define DP_PHY_POSTCURSOR_LANE_1 (0x0250 >> 2) +#define DP_PHY_STATUS (0x0280 >> 2) + +#define DP_TX_AUDIO_CONTROL (0x0300 >> 2) +#define DP_TX_AUD_CTRL (1) + +#define DP_TX_AUDIO_CHANNELS (0x0304 >> 2) +#define DP_TX_AUDIO_INFO_DATA(n) ((0x0308 + 4 * n) >> 2) +#define DP_TX_M_AUD (0x0328 >> 2) +#define DP_TX_N_AUD (0x032C >> 2) +#define DP_TX_AUDIO_EXT_DATA(n) ((0x0330 + 4 * n) >> 2) +#define DP_INT_STATUS (0x03A0 >> 2) +#define DP_INT_MASK (0x03A4 >> 2) +#define DP_INT_EN (0x03A8 >> 2) +#define DP_INT_DS (0x03AC >> 2) + +/* + * Registers offset for Audio Video Buffer configuration. + */ +#define V_BLEND_OFFSET (0xA000) +#define V_BLEND_BG_CLR_0 (0x0000 >> 2) +#define V_BLEND_BG_CLR_1 (0x0004 >> 2) +#define V_BLEND_BG_CLR_2 (0x0008 >> 2) +#define V_BLEND_SET_GLOBAL_ALPHA_REG (0x000C >> 2) +#define V_BLEND_OUTPUT_VID_FORMAT (0x0014 >> 2) +#define V_BLEND_LAYER0_CONTROL (0x0018 >> 2) +#define V_BLEND_LAYER1_CONTROL (0x001C >> 2) + +#define V_BLEND_RGB2YCBCR_COEFF(n) ((0x0020 + 4 * n) >> 2) +#define V_BLEND_IN1CSC_COEFF(n) ((0x0044 + 4 * n) >> 2) + +#define V_BLEND_LUMA_IN1CSC_OFFSET (0x0068 >> 2) +#define V_BLEND_CR_IN1CSC_OFFSET (0x006C >> 2) +#define V_BLEND_CB_IN1CSC_OFFSET (0x0070 >> 2) +#define V_BLEND_LUMA_OUTCSC_OFFSET (0x0074 >> 2) +#define V_BLEND_CR_OUTCSC_OFFSET (0x0078 >> 2) +#define V_BLEND_CB_OUTCSC_OFFSET (0x007C >> 2) + +#define V_BLEND_IN2CSC_COEFF(n) ((0x0080 + 4 * n) >> 2) + +#define V_BLEND_LUMA_IN2CSC_OFFSET (0x00A4 >> 2) +#define V_BLEND_CR_IN2CSC_OFFSET (0x00A8 >> 2) +#define V_BLEND_CB_IN2CSC_OFFSET (0x00AC >> 2) +#define V_BLEND_CHROMA_KEY_ENABLE (0x01D0 >> 2) +#define V_BLEND_CHROMA_KEY_COMP1 (0x01D4 >> 2) +#define V_BLEND_CHROMA_KEY_COMP2 (0x01D8 >> 2) +#define V_BLEND_CHROMA_KEY_COMP3 (0x01DC >> 2) + +/* + * Registers offset for Audio Video Buffer configuration. + */ +#define AV_BUF_MANAGER_OFFSET (0xB000) +#define AV_BUF_FORMAT (0x0000 >> 2) +#define AV_BUF_NON_LIVE_LATENCY (0x0008 >> 2) +#define AV_CHBUF0 (0x0010 >> 2) +#define AV_CHBUF1 (0x0014 >> 2) +#define AV_CHBUF2 (0x0018 >> 2) +#define AV_CHBUF3 (0x001C >> 2) +#define AV_CHBUF4 (0x0020 >> 2) +#define AV_CHBUF5 (0x0024 >> 2) +#define AV_BUF_STC_CONTROL (0x002C >> 2) +#define AV_BUF_STC_INIT_VALUE0 (0x0030 >> 2) +#define AV_BUF_STC_INIT_VALUE1 (0x0034 >> 2) +#define AV_BUF_STC_ADJ (0x0038 >> 2) +#define AV_BUF_STC_VIDEO_VSYNC_TS_REG0 (0x003C >> 2) +#define AV_BUF_STC_VIDEO_VSYNC_TS_REG1 (0x0040 >> 2) +#define AV_BUF_STC_EXT_VSYNC_TS_REG0 (0x0044 >> 2) +#define AV_BUF_STC_EXT_VSYNC_TS_REG1 (0x0048 >> 2) +#define AV_BUF_STC_CUSTOM_EVENT_TS_REG0 (0x004C >> 2) +#define AV_BUF_STC_CUSTOM_EVENT_TS_REG1 (0x0050 >> 2) +#define AV_BUF_STC_CUSTOM_EVENT2_TS_REG0 (0x0054 >> 2) +#define AV_BUF_STC_CUSTOM_EVENT2_TS_REG1 (0x0058 >> 2) +#define AV_BUF_STC_SNAPSHOT0 (0x0060 >> 2) +#define AV_BUF_STC_SNAPSHOT1 (0x0064 >> 2) +#define AV_BUF_OUTPUT_AUDIO_VIDEO_SELECT (0x0070 >> 2) +#define AV_BUF_HCOUNT_VCOUNT_INT0 (0x0074 >> 2) +#define AV_BUF_HCOUNT_VCOUNT_INT1 (0x0078 >> 2) +#define AV_BUF_DITHER_CONFIG (0x007C >> 2) +#define AV_BUF_DITHER_CONFIG_MAX (0x008C >> 2) +#define AV_BUF_DITHER_CONFIG_MIN (0x0090 >> 2) +#define AV_BUF_PATTERN_GEN_SELECT (0x0100 >> 2) +#define AV_BUF_AUD_VID_CLK_SOURCE (0x0120 >> 2) +#define AV_BUF_SRST_REG (0x0124 >> 2) +#define AV_BUF_AUDIO_RDY_INTERVAL (0x0128 >> 2) +#define AV_BUF_AUDIO_CH_CONFIG (0x012C >> 2) + +#define AV_BUF_GRAPHICS_COMP_SCALE_FACTOR(n)((0x0200 + 4 * n) >> 2) + +#define AV_BUF_VIDEO_COMP_SCALE_FACTOR(n) ((0x020C + 4 * n) >> 2) + +#define AV_BUF_LIVE_VIDEO_COMP_SF(n) ((0x0218 + 4 * n) >> 2) + +#define AV_BUF_LIVE_VID_CONFIG (0x0224 >> 2) + +#define AV_BUF_LIVE_GFX_COMP_SF(n) ((0x0228 + 4 * n) >> 2) + +#define AV_BUF_LIVE_GFX_CONFIG (0x0234 >> 2) + +#define AUDIO_MIXER_REGISTER_OFFSET (0xC000) +#define AUDIO_MIXER_VOLUME_CONTROL (0x0000 >> 2) +#define AUDIO_MIXER_META_DATA (0x0004 >> 2) +#define AUD_CH_STATUS_REG(n) ((0x0008 + 4 * n) >> 2) +#define AUD_CH_A_DATA_REG(n) ((0x0020 + 4 * n) >> 2) +#define AUD_CH_B_DATA_REG(n) ((0x0038 + 4 * n) >> 2) + +#define DP_AUDIO_DMA_CHANNEL(n) (4 + n) +#define DP_GRAPHIC_DMA_CHANNEL (3) +#define DP_VIDEO_DMA_CHANNEL (0) + +enum DPGraphicFmt { + DP_GRAPHIC_RGBA8888 = 0 << 8, + DP_GRAPHIC_ABGR8888 = 1 << 8, + DP_GRAPHIC_RGB888 = 2 << 8, + DP_GRAPHIC_BGR888 = 3 << 8, + DP_GRAPHIC_RGBA5551 = 4 << 8, + DP_GRAPHIC_RGBA4444 = 5 << 8, + DP_GRAPHIC_RGB565 = 6 << 8, + DP_GRAPHIC_8BPP = 7 << 8, + DP_GRAPHIC_4BPP = 8 << 8, + DP_GRAPHIC_2BPP = 9 << 8, + DP_GRAPHIC_1BPP = 10 << 8, + DP_GRAPHIC_MASK = 0xF << 8 +}; + +enum DPVideoFmt { + DP_NL_VID_CB_Y0_CR_Y1 = 0, + DP_NL_VID_CR_Y0_CB_Y1 = 1, + DP_NL_VID_Y0_CR_Y1_CB = 2, + DP_NL_VID_Y0_CB_Y1_CR = 3, + DP_NL_VID_YV16 = 4, + DP_NL_VID_YV24 = 5, + DP_NL_VID_YV16CL = 6, + DP_NL_VID_MONO = 7, + DP_NL_VID_YV16CL2 = 8, + DP_NL_VID_YUV444 = 9, + DP_NL_VID_RGB888 = 10, + DP_NL_VID_RGBA8880 = 11, + DP_NL_VID_RGB888_10BPC = 12, + DP_NL_VID_YUV444_10BPC = 13, + DP_NL_VID_YV16CL2_10BPC = 14, + DP_NL_VID_YV16CL_10BPC = 15, + DP_NL_VID_YV16_10BPC = 16, + DP_NL_VID_YV24_10BPC = 17, + DP_NL_VID_Y_ONLY_10BPC = 18, + DP_NL_VID_YV16_420 = 19, + DP_NL_VID_YV16CL_420 = 20, + DP_NL_VID_YV16CL2_420 = 21, + DP_NL_VID_YV16_420_10BPC = 22, + DP_NL_VID_YV16CL_420_10BPC = 23, + DP_NL_VID_YV16CL2_420_10BPC = 24, + DP_NL_VID_FMT_MASK = 0x1F +}; + +typedef enum DPGraphicFmt DPGraphicFmt; +typedef enum DPVideoFmt DPVideoFmt; + +static const VMStateDescription vmstate_dp = { + .name = TYPE_XLNX_DP, + .version_id = 1, + .fields = (VMStateField[]){ + VMSTATE_UINT32_ARRAY(core_registers, XlnxDPState, + DP_CORE_REG_ARRAY_SIZE), + VMSTATE_UINT32_ARRAY(avbufm_registers, XlnxDPState, + DP_AVBUF_REG_ARRAY_SIZE), + VMSTATE_UINT32_ARRAY(vblend_registers, XlnxDPState, + DP_VBLEND_REG_ARRAY_SIZE), + VMSTATE_UINT32_ARRAY(audio_registers, XlnxDPState, + DP_AUDIO_REG_ARRAY_SIZE), + VMSTATE_END_OF_LIST() + } +}; + +static void xlnx_dp_update_irq(XlnxDPState *s); + +static uint64_t xlnx_dp_audio_read(void *opaque, hwaddr offset, unsigned size) +{ + XlnxDPState *s = XLNX_DP(opaque); + + offset = offset >> 2; + return s->audio_registers[offset]; +} + +static void xlnx_dp_audio_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + XlnxDPState *s = XLNX_DP(opaque); + + offset = offset >> 2; + + switch (offset) { + case AUDIO_MIXER_META_DATA: + s->audio_registers[offset] = value & 0x00000001; + break; + default: + s->audio_registers[offset] = value; + break; + } +} + +static const MemoryRegionOps audio_ops = { + .read = xlnx_dp_audio_read, + .write = xlnx_dp_audio_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static inline uint32_t xlnx_dp_audio_get_volume(XlnxDPState *s, + uint8_t channel) +{ + switch (channel) { + case 0: + return extract32(s->audio_registers[AUDIO_MIXER_VOLUME_CONTROL], 0, 16); + case 1: + return extract32(s->audio_registers[AUDIO_MIXER_VOLUME_CONTROL], 16, + 16); + default: + return 0; + } +} + +static inline void xlnx_dp_audio_activate(XlnxDPState *s) +{ + bool activated = ((s->core_registers[DP_TX_AUDIO_CONTROL] + & DP_TX_AUD_CTRL) != 0); + AUD_set_active_out(s->amixer_output_stream, activated); + xlnx_dpdma_set_host_data_location(s->dpdma, DP_AUDIO_DMA_CHANNEL(0), + &s->audio_buffer_0); + xlnx_dpdma_set_host_data_location(s->dpdma, DP_AUDIO_DMA_CHANNEL(1), + &s->audio_buffer_1); +} + +static inline void xlnx_dp_audio_mix_buffer(XlnxDPState *s) +{ + /* + * Audio packets are signed and have this shape: + * | 16 | 16 | 16 | 16 | 16 | 16 | 16 | 16 | + * | R3 | L3 | R2 | L2 | R1 | L1 | R0 | L0 | + * + * Output audio is 16bits saturated. + */ + int i; + + if ((s->audio_data_available[0]) && (xlnx_dp_audio_get_volume(s, 0))) { + for (i = 0; i < s->audio_data_available[0] / 2; i++) { + s->temp_buffer[i] = (int64_t)(s->audio_buffer_0[i]) + * xlnx_dp_audio_get_volume(s, 0) / 8192; + } + s->byte_left = s->audio_data_available[0]; + } else { + memset(s->temp_buffer, 0, s->audio_data_available[1] / 2); + } + + if ((s->audio_data_available[1]) && (xlnx_dp_audio_get_volume(s, 1))) { + if ((s->audio_data_available[0] == 0) + || (s->audio_data_available[1] == s->audio_data_available[0])) { + for (i = 0; i < s->audio_data_available[1] / 2; i++) { + s->temp_buffer[i] += (int64_t)(s->audio_buffer_1[i]) + * xlnx_dp_audio_get_volume(s, 1) / 8192; + } + s->byte_left = s->audio_data_available[1]; + } + } + + for (i = 0; i < s->byte_left / 2; i++) { + s->out_buffer[i] = MAX(-32767, MIN(s->temp_buffer[i], 32767)); + } + + s->data_ptr = 0; +} + +static void xlnx_dp_audio_callback(void *opaque, int avail) +{ + /* + * Get some data from the DPDMA and compute these datas. + * Then wait for QEMU's audio subsystem to call this callback. + */ + XlnxDPState *s = XLNX_DP(opaque); + size_t written = 0; + + /* If there are already some data don't get more data. */ + if (s->byte_left == 0) { + s->audio_data_available[0] = xlnx_dpdma_start_operation(s->dpdma, 4, + true); + s->audio_data_available[1] = xlnx_dpdma_start_operation(s->dpdma, 5, + true); + xlnx_dp_audio_mix_buffer(s); + } + + /* Send the buffer through the audio. */ + if (s->byte_left <= MAX_QEMU_BUFFER_SIZE) { + if (s->byte_left != 0) { + written = AUD_write(s->amixer_output_stream, + &s->out_buffer[s->data_ptr], s->byte_left); + } else { + /* + * There is nothing to play.. We don't have any data! Fill the + * buffer with zero's and send it. + */ + written = 0; + memset(s->out_buffer, 0, 1024); + AUD_write(s->amixer_output_stream, s->out_buffer, 1024); + } + } else { + written = AUD_write(s->amixer_output_stream, + &s->out_buffer[s->data_ptr], MAX_QEMU_BUFFER_SIZE); + } + s->byte_left -= written; + s->data_ptr += written; +} + +/* + * AUX channel related function. + */ +static void xlnx_dp_aux_clear_rx_fifo(XlnxDPState *s) +{ + fifo8_reset(&s->rx_fifo); +} + +static void xlnx_dp_aux_push_rx_fifo(XlnxDPState *s, uint8_t *buf, size_t len) +{ + DPRINTF("Push %u data in rx_fifo\n", (unsigned)len); + fifo8_push_all(&s->rx_fifo, buf, len); +} + +static uint8_t xlnx_dp_aux_pop_rx_fifo(XlnxDPState *s) +{ + uint8_t ret; + + if (fifo8_is_empty(&s->rx_fifo)) { + DPRINTF("rx_fifo underflow..\n"); + abort(); + } + ret = fifo8_pop(&s->rx_fifo); + DPRINTF("pop 0x%" PRIX8 " from rx_fifo.\n", ret); + return ret; +} + +static void xlnx_dp_aux_clear_tx_fifo(XlnxDPState *s) +{ + fifo8_reset(&s->tx_fifo); +} + +static void xlnx_dp_aux_push_tx_fifo(XlnxDPState *s, uint8_t val, size_t len) +{ + DPRINTF("Push %u data in tx_fifo\n", (unsigned)len); + fifo8_push_all(&s->tx_fifo, &val, len); +} + +static uint8_t xlnx_dp_aux_pop_tx_fifo(XlnxDPState *s) +{ + uint8_t ret; + + if (fifo8_is_empty(&s->tx_fifo)) { + DPRINTF("tx_fifo underflow..\n"); + abort(); + } + ret = fifo8_pop(&s->tx_fifo); + DPRINTF("pop 0x%2.2X from tx_fifo.\n", ret); + return ret; +} + +static uint32_t xlnx_dp_aux_get_address(XlnxDPState *s) +{ + return s->core_registers[DP_AUX_ADDRESS]; +} + +/* + * Get command from the register. + */ +static void xlnx_dp_aux_set_command(XlnxDPState *s, uint32_t value) +{ + bool address_only = (value & AUX_ADDR_ONLY_MASK) != 0; + AUXCommand cmd = (value & AUX_COMMAND_MASK) >> AUX_COMMAND_SHIFT; + uint8_t nbytes = (value & AUX_COMMAND_NBYTES) + 1; + uint8_t buf[16]; + int i; + + /* + * When an address_only command is executed nothing happen to the fifo, so + * just make nbytes = 0. + */ + if (address_only) { + nbytes = 0; + } + + switch (cmd) { + case READ_AUX: + case READ_I2C: + case READ_I2C_MOT: + s->core_registers[DP_AUX_REPLY_CODE] = aux_request(s->aux_bus, cmd, + xlnx_dp_aux_get_address(s), + nbytes, buf); + s->core_registers[DP_REPLY_DATA_COUNT] = nbytes; + + if (s->core_registers[DP_AUX_REPLY_CODE] == AUX_I2C_ACK) { + xlnx_dp_aux_push_rx_fifo(s, buf, nbytes); + } + break; + case WRITE_AUX: + case WRITE_I2C: + case WRITE_I2C_MOT: + for (i = 0; i < nbytes; i++) { + buf[i] = xlnx_dp_aux_pop_tx_fifo(s); + } + s->core_registers[DP_AUX_REPLY_CODE] = aux_request(s->aux_bus, cmd, + xlnx_dp_aux_get_address(s), + nbytes, buf); + xlnx_dp_aux_clear_tx_fifo(s); + break; + case WRITE_I2C_STATUS: + qemu_log_mask(LOG_UNIMP, "xlnx_dp: Write i2c status not implemented\n"); + break; + default: + abort(); + } + + s->core_registers[DP_INTERRUPT_SIGNAL_STATE] |= 0x04; +} + +static void xlnx_dp_set_dpdma(Object *obj, const char *name, Object *val, + Error **errp) +{ + XlnxDPState *s = XLNX_DP(obj); + if (s->console) { + DisplaySurface *surface = qemu_console_surface(s->console); + XlnxDPDMAState *dma = XLNX_DPDMA(val); + xlnx_dpdma_set_host_data_location(dma, DP_GRAPHIC_DMA_CHANNEL, + surface_data(surface)); + } +} + +static inline uint8_t xlnx_dp_global_alpha_value(XlnxDPState *s) +{ + return (s->vblend_registers[V_BLEND_SET_GLOBAL_ALPHA_REG] & 0x1FE) >> 1; +} + +static inline bool xlnx_dp_global_alpha_enabled(XlnxDPState *s) +{ + /* + * If the alpha is totally opaque (255) we consider the alpha is disabled to + * reduce CPU consumption. + */ + return ((xlnx_dp_global_alpha_value(s) != 0xFF) && + ((s->vblend_registers[V_BLEND_SET_GLOBAL_ALPHA_REG] & 0x01) != 0)); +} + +static void xlnx_dp_recreate_surface(XlnxDPState *s) +{ + /* + * Two possibilities, if blending is enabled the console displays + * bout_plane, if not g_plane is displayed. + */ + uint16_t width = s->core_registers[DP_MAIN_STREAM_HRES]; + uint16_t height = s->core_registers[DP_MAIN_STREAM_VRES]; + DisplaySurface *current_console_surface = qemu_console_surface(s->console); + + if ((width != 0) && (height != 0)) { + /* + * As dpy_gfx_replace_surface calls qemu_free_displaysurface on the + * surface we need to be carefull and don't free the surface associated + * to the console or double free will happen. + */ + if (s->bout_plane.surface != current_console_surface) { + qemu_free_displaysurface(s->bout_plane.surface); + } + if (s->v_plane.surface != current_console_surface) { + qemu_free_displaysurface(s->v_plane.surface); + } + if (s->g_plane.surface != current_console_surface) { + qemu_free_displaysurface(s->g_plane.surface); + } + + s->g_plane.surface + = qemu_create_displaysurface_from(width, height, + s->g_plane.format, 0, NULL); + s->v_plane.surface + = qemu_create_displaysurface_from(width, height, + s->v_plane.format, 0, NULL); + if (xlnx_dp_global_alpha_enabled(s)) { + s->bout_plane.surface = + qemu_create_displaysurface_from(width, + height, + s->g_plane.format, + 0, NULL); + dpy_gfx_replace_surface(s->console, s->bout_plane.surface); + } else { + s->bout_plane.surface = NULL; + dpy_gfx_replace_surface(s->console, s->g_plane.surface); + } + + xlnx_dpdma_set_host_data_location(s->dpdma, DP_GRAPHIC_DMA_CHANNEL, + surface_data(s->g_plane.surface)); + xlnx_dpdma_set_host_data_location(s->dpdma, DP_VIDEO_DMA_CHANNEL, + surface_data(s->v_plane.surface)); + } +} + +/* + * Change the graphic format of the surface. + */ +static void xlnx_dp_change_graphic_fmt(XlnxDPState *s) +{ + switch (s->avbufm_registers[AV_BUF_FORMAT] & DP_GRAPHIC_MASK) { + case DP_GRAPHIC_RGBA8888: + s->g_plane.format = PIXMAN_r8g8b8a8; + break; + case DP_GRAPHIC_ABGR8888: + s->g_plane.format = PIXMAN_a8b8g8r8; + break; + case DP_GRAPHIC_RGB565: + s->g_plane.format = PIXMAN_r5g6b5; + break; + case DP_GRAPHIC_RGB888: + s->g_plane.format = PIXMAN_r8g8b8; + break; + case DP_GRAPHIC_BGR888: + s->g_plane.format = PIXMAN_b8g8r8; + break; + default: + DPRINTF("error: unsupported graphic format %u.\n", + s->avbufm_registers[AV_BUF_FORMAT] & DP_GRAPHIC_MASK); + abort(); + } + + switch (s->avbufm_registers[AV_BUF_FORMAT] & DP_NL_VID_FMT_MASK) { + case 0: + s->v_plane.format = PIXMAN_x8b8g8r8; + break; + case DP_NL_VID_RGBA8880: + s->v_plane.format = PIXMAN_x8b8g8r8; + break; + default: + DPRINTF("error: unsupported video format %u.\n", + s->avbufm_registers[AV_BUF_FORMAT] & DP_NL_VID_FMT_MASK); + abort(); + } + + xlnx_dp_recreate_surface(s); +} + +static void xlnx_dp_update_irq(XlnxDPState *s) +{ + uint32_t flags; + + flags = s->core_registers[DP_INT_STATUS] & ~s->core_registers[DP_INT_MASK]; + DPRINTF("update IRQ value = %" PRIx32 "\n", flags); + qemu_set_irq(s->irq, flags != 0); +} + +static uint64_t xlnx_dp_read(void *opaque, hwaddr offset, unsigned size) +{ + XlnxDPState *s = XLNX_DP(opaque); + uint64_t ret = 0; + + offset = offset >> 2; + + switch (offset) { + case DP_TX_USER_FIFO_OVERFLOW: + /* This register is cleared after a read */ + ret = s->core_registers[DP_TX_USER_FIFO_OVERFLOW]; + s->core_registers[DP_TX_USER_FIFO_OVERFLOW] = 0; + break; + case DP_AUX_REPLY_DATA: + ret = xlnx_dp_aux_pop_rx_fifo(s); + break; + case DP_INTERRUPT_SIGNAL_STATE: + /* + * XXX: Not sure it is the right thing to do actually. + * The register is not written by the device driver so it's stuck + * to 0x04. + */ + ret = s->core_registers[DP_INTERRUPT_SIGNAL_STATE]; + s->core_registers[DP_INTERRUPT_SIGNAL_STATE] &= ~0x04; + break; + case DP_AUX_WRITE_FIFO: + case DP_TX_AUDIO_INFO_DATA(0): + case DP_TX_AUDIO_INFO_DATA(1): + case DP_TX_AUDIO_INFO_DATA(2): + case DP_TX_AUDIO_INFO_DATA(3): + case DP_TX_AUDIO_INFO_DATA(4): + case DP_TX_AUDIO_INFO_DATA(5): + case DP_TX_AUDIO_INFO_DATA(6): + case DP_TX_AUDIO_INFO_DATA(7): + case DP_TX_AUDIO_EXT_DATA(0): + case DP_TX_AUDIO_EXT_DATA(1): + case DP_TX_AUDIO_EXT_DATA(2): + case DP_TX_AUDIO_EXT_DATA(3): + case DP_TX_AUDIO_EXT_DATA(4): + case DP_TX_AUDIO_EXT_DATA(5): + case DP_TX_AUDIO_EXT_DATA(6): + case DP_TX_AUDIO_EXT_DATA(7): + case DP_TX_AUDIO_EXT_DATA(8): + /* write only registers */ + ret = 0; + break; + default: + assert(offset <= (0x3AC >> 2)); + ret = s->core_registers[offset]; + break; + } + + DPRINTF("core read @%" PRIx64 " = 0x%8.8" PRIX64 "\n", offset << 2, ret); + return ret; +} + +static void xlnx_dp_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + XlnxDPState *s = XLNX_DP(opaque); + + DPRINTF("core write @%" PRIx64 " = 0x%8.8" PRIX64 "\n", offset, value); + + offset = offset >> 2; + + switch (offset) { + /* + * Only special write case are handled. + */ + case DP_LINK_BW_SET: + s->core_registers[offset] = value & 0x000000FF; + break; + case DP_LANE_COUNT_SET: + case DP_MAIN_STREAM_MISC0: + s->core_registers[offset] = value & 0x0000000F; + break; + case DP_TRAINING_PATTERN_SET: + case DP_LINK_QUAL_PATTERN_SET: + case DP_MAIN_STREAM_POLARITY: + case DP_PHY_VOLTAGE_DIFF_LANE_0: + case DP_PHY_VOLTAGE_DIFF_LANE_1: + s->core_registers[offset] = value & 0x00000003; + break; + case DP_ENHANCED_FRAME_EN: + case DP_SCRAMBLING_DISABLE: + case DP_DOWNSPREAD_CTRL: + case DP_MAIN_STREAM_ENABLE: + case DP_TRANSMIT_PRBS7: + s->core_registers[offset] = value & 0x00000001; + break; + case DP_PHY_CLOCK_SELECT: + s->core_registers[offset] = value & 0x00000007; + break; + case DP_SOFTWARE_RESET: + /* + * No need to update this bit as it's read '0'. + */ + /* + * TODO: reset IP. + */ + break; + case DP_TRANSMITTER_ENABLE: + s->core_registers[offset] = value & 0x01; + break; + case DP_FORCE_SCRAMBLER_RESET: + /* + * No need to update this bit as it's read '0'. + */ + /* + * TODO: force a scrambler reset?? + */ + break; + case DP_AUX_COMMAND_REGISTER: + s->core_registers[offset] = value & 0x00001F0F; + xlnx_dp_aux_set_command(s, s->core_registers[offset]); + break; + case DP_MAIN_STREAM_HTOTAL: + case DP_MAIN_STREAM_VTOTAL: + case DP_MAIN_STREAM_HSTART: + case DP_MAIN_STREAM_VSTART: + s->core_registers[offset] = value & 0x0000FFFF; + break; + case DP_MAIN_STREAM_HRES: + case DP_MAIN_STREAM_VRES: + s->core_registers[offset] = value & 0x0000FFFF; + xlnx_dp_recreate_surface(s); + break; + case DP_MAIN_STREAM_HSWIDTH: + case DP_MAIN_STREAM_VSWIDTH: + s->core_registers[offset] = value & 0x00007FFF; + break; + case DP_MAIN_STREAM_MISC1: + s->core_registers[offset] = value & 0x00000086; + break; + case DP_MAIN_STREAM_M_VID: + case DP_MAIN_STREAM_N_VID: + s->core_registers[offset] = value & 0x00FFFFFF; + break; + case DP_MSA_TRANSFER_UNIT_SIZE: + case DP_MIN_BYTES_PER_TU: + case DP_INIT_WAIT: + s->core_registers[offset] = value & 0x00000007; + break; + case DP_USER_DATA_COUNT_PER_LANE: + s->core_registers[offset] = value & 0x0003FFFF; + break; + case DP_FRAC_BYTES_PER_TU: + s->core_registers[offset] = value & 0x000003FF; + break; + case DP_PHY_RESET: + s->core_registers[offset] = value & 0x00010003; + /* + * TODO: Reset something? + */ + break; + case DP_TX_PHY_POWER_DOWN: + s->core_registers[offset] = value & 0x0000000F; + /* + * TODO: Power down things? + */ + break; + case DP_AUX_WRITE_FIFO: + xlnx_dp_aux_push_tx_fifo(s, value, 1); + break; + case DP_AUX_CLOCK_DIVIDER: + break; + case DP_AUX_REPLY_COUNT: + /* + * Writing to this register clear the counter. + */ + s->core_registers[offset] = 0x00000000; + break; + case DP_AUX_ADDRESS: + s->core_registers[offset] = value & 0x000FFFFF; + break; + case DP_VERSION_REGISTER: + case DP_CORE_ID: + case DP_TX_USER_FIFO_OVERFLOW: + case DP_AUX_REPLY_DATA: + case DP_AUX_REPLY_CODE: + case DP_REPLY_DATA_COUNT: + case DP_REPLY_STATUS: + case DP_HPD_DURATION: + /* + * Write to read only location.. + */ + break; + case DP_TX_AUDIO_CONTROL: + s->core_registers[offset] = value & 0x00000001; + xlnx_dp_audio_activate(s); + break; + case DP_TX_AUDIO_CHANNELS: + s->core_registers[offset] = value & 0x00000007; + xlnx_dp_audio_activate(s); + break; + case DP_INT_STATUS: + s->core_registers[DP_INT_STATUS] &= ~value; + xlnx_dp_update_irq(s); + break; + case DP_INT_EN: + s->core_registers[DP_INT_MASK] &= ~value; + xlnx_dp_update_irq(s); + break; + case DP_INT_DS: + s->core_registers[DP_INT_MASK] |= ~value; + xlnx_dp_update_irq(s); + break; + default: + assert(offset <= (0x504C >> 2)); + s->core_registers[offset] = value; + break; + } +} + +static const MemoryRegionOps dp_ops = { + .read = xlnx_dp_read, + .write = xlnx_dp_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +/* + * This is to handle Read/Write to the Video Blender. + */ +static void xlnx_dp_vblend_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + XlnxDPState *s = XLNX_DP(opaque); + bool alpha_was_enabled; + + DPRINTF("vblend: write @0x%" HWADDR_PRIX " = 0x%" PRIX32 "\n", offset, + (uint32_t)value); + offset = offset >> 2; + + switch (offset) { + case V_BLEND_BG_CLR_0: + case V_BLEND_BG_CLR_1: + case V_BLEND_BG_CLR_2: + s->vblend_registers[offset] = value & 0x00000FFF; + break; + case V_BLEND_SET_GLOBAL_ALPHA_REG: + /* + * A write to this register can enable or disable blending. Thus we need + * to recreate the surfaces. + */ + alpha_was_enabled = xlnx_dp_global_alpha_enabled(s); + s->vblend_registers[offset] = value & 0x000001FF; + if (xlnx_dp_global_alpha_enabled(s) != alpha_was_enabled) { + xlnx_dp_recreate_surface(s); + } + break; + case V_BLEND_OUTPUT_VID_FORMAT: + s->vblend_registers[offset] = value & 0x00000017; + break; + case V_BLEND_LAYER0_CONTROL: + case V_BLEND_LAYER1_CONTROL: + s->vblend_registers[offset] = value & 0x00000103; + break; + case V_BLEND_RGB2YCBCR_COEFF(0): + case V_BLEND_RGB2YCBCR_COEFF(1): + case V_BLEND_RGB2YCBCR_COEFF(2): + case V_BLEND_RGB2YCBCR_COEFF(3): + case V_BLEND_RGB2YCBCR_COEFF(4): + case V_BLEND_RGB2YCBCR_COEFF(5): + case V_BLEND_RGB2YCBCR_COEFF(6): + case V_BLEND_RGB2YCBCR_COEFF(7): + case V_BLEND_RGB2YCBCR_COEFF(8): + case V_BLEND_IN1CSC_COEFF(0): + case V_BLEND_IN1CSC_COEFF(1): + case V_BLEND_IN1CSC_COEFF(2): + case V_BLEND_IN1CSC_COEFF(3): + case V_BLEND_IN1CSC_COEFF(4): + case V_BLEND_IN1CSC_COEFF(5): + case V_BLEND_IN1CSC_COEFF(6): + case V_BLEND_IN1CSC_COEFF(7): + case V_BLEND_IN1CSC_COEFF(8): + case V_BLEND_IN2CSC_COEFF(0): + case V_BLEND_IN2CSC_COEFF(1): + case V_BLEND_IN2CSC_COEFF(2): + case V_BLEND_IN2CSC_COEFF(3): + case V_BLEND_IN2CSC_COEFF(4): + case V_BLEND_IN2CSC_COEFF(5): + case V_BLEND_IN2CSC_COEFF(6): + case V_BLEND_IN2CSC_COEFF(7): + case V_BLEND_IN2CSC_COEFF(8): + s->vblend_registers[offset] = value & 0x0000FFFF; + break; + case V_BLEND_LUMA_IN1CSC_OFFSET: + case V_BLEND_CR_IN1CSC_OFFSET: + case V_BLEND_CB_IN1CSC_OFFSET: + case V_BLEND_LUMA_IN2CSC_OFFSET: + case V_BLEND_CR_IN2CSC_OFFSET: + case V_BLEND_CB_IN2CSC_OFFSET: + case V_BLEND_LUMA_OUTCSC_OFFSET: + case V_BLEND_CR_OUTCSC_OFFSET: + case V_BLEND_CB_OUTCSC_OFFSET: + s->vblend_registers[offset] = value & 0x3FFF7FFF; + break; + case V_BLEND_CHROMA_KEY_ENABLE: + s->vblend_registers[offset] = value & 0x00000003; + break; + case V_BLEND_CHROMA_KEY_COMP1: + case V_BLEND_CHROMA_KEY_COMP2: + case V_BLEND_CHROMA_KEY_COMP3: + s->vblend_registers[offset] = value & 0x0FFF0FFF; + break; + default: + s->vblend_registers[offset] = value; + break; + } +} + +static uint64_t xlnx_dp_vblend_read(void *opaque, hwaddr offset, + unsigned size) +{ + XlnxDPState *s = XLNX_DP(opaque); + + DPRINTF("vblend: read @0x%" HWADDR_PRIX " = 0x%" PRIX32 "\n", offset, + s->vblend_registers[offset >> 2]); + return s->vblend_registers[offset >> 2]; +} + +static const MemoryRegionOps vblend_ops = { + .read = xlnx_dp_vblend_read, + .write = xlnx_dp_vblend_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +/* + * This is to handle Read/Write to the Audio Video buffer manager. + */ +static void xlnx_dp_avbufm_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + XlnxDPState *s = XLNX_DP(opaque); + + DPRINTF("avbufm: write @0x%" HWADDR_PRIX " = 0x%" PRIX32 "\n", offset, + (uint32_t)value); + offset = offset >> 2; + + switch (offset) { + case AV_BUF_FORMAT: + s->avbufm_registers[offset] = value & 0x00000FFF; + xlnx_dp_change_graphic_fmt(s); + break; + case AV_CHBUF0: + case AV_CHBUF1: + case AV_CHBUF2: + case AV_CHBUF3: + case AV_CHBUF4: + case AV_CHBUF5: + s->avbufm_registers[offset] = value & 0x0000007F; + break; + case AV_BUF_OUTPUT_AUDIO_VIDEO_SELECT: + s->avbufm_registers[offset] = value & 0x0000007F; + break; + case AV_BUF_DITHER_CONFIG: + s->avbufm_registers[offset] = value & 0x000007FF; + break; + case AV_BUF_DITHER_CONFIG_MAX: + case AV_BUF_DITHER_CONFIG_MIN: + s->avbufm_registers[offset] = value & 0x00000FFF; + break; + case AV_BUF_PATTERN_GEN_SELECT: + s->avbufm_registers[offset] = value & 0xFFFFFF03; + break; + case AV_BUF_AUD_VID_CLK_SOURCE: + s->avbufm_registers[offset] = value & 0x00000007; + break; + case AV_BUF_SRST_REG: + s->avbufm_registers[offset] = value & 0x00000002; + break; + case AV_BUF_AUDIO_CH_CONFIG: + s->avbufm_registers[offset] = value & 0x00000003; + break; + case AV_BUF_GRAPHICS_COMP_SCALE_FACTOR(0): + case AV_BUF_GRAPHICS_COMP_SCALE_FACTOR(1): + case AV_BUF_GRAPHICS_COMP_SCALE_FACTOR(2): + case AV_BUF_VIDEO_COMP_SCALE_FACTOR(0): + case AV_BUF_VIDEO_COMP_SCALE_FACTOR(1): + case AV_BUF_VIDEO_COMP_SCALE_FACTOR(2): + s->avbufm_registers[offset] = value & 0x0000FFFF; + break; + case AV_BUF_LIVE_VIDEO_COMP_SF(0): + case AV_BUF_LIVE_VIDEO_COMP_SF(1): + case AV_BUF_LIVE_VIDEO_COMP_SF(2): + case AV_BUF_LIVE_VID_CONFIG: + case AV_BUF_LIVE_GFX_COMP_SF(0): + case AV_BUF_LIVE_GFX_COMP_SF(1): + case AV_BUF_LIVE_GFX_COMP_SF(2): + case AV_BUF_LIVE_GFX_CONFIG: + case AV_BUF_NON_LIVE_LATENCY: + case AV_BUF_STC_CONTROL: + case AV_BUF_STC_INIT_VALUE0: + case AV_BUF_STC_INIT_VALUE1: + case AV_BUF_STC_ADJ: + case AV_BUF_STC_VIDEO_VSYNC_TS_REG0: + case AV_BUF_STC_VIDEO_VSYNC_TS_REG1: + case AV_BUF_STC_EXT_VSYNC_TS_REG0: + case AV_BUF_STC_EXT_VSYNC_TS_REG1: + case AV_BUF_STC_CUSTOM_EVENT_TS_REG0: + case AV_BUF_STC_CUSTOM_EVENT_TS_REG1: + case AV_BUF_STC_CUSTOM_EVENT2_TS_REG0: + case AV_BUF_STC_CUSTOM_EVENT2_TS_REG1: + case AV_BUF_STC_SNAPSHOT0: + case AV_BUF_STC_SNAPSHOT1: + case AV_BUF_HCOUNT_VCOUNT_INT0: + case AV_BUF_HCOUNT_VCOUNT_INT1: + qemu_log_mask(LOG_UNIMP, "avbufm: unimplmented"); + break; + default: + s->avbufm_registers[offset] = value; + break; + } +} + +static uint64_t xlnx_dp_avbufm_read(void *opaque, hwaddr offset, + unsigned size) +{ + XlnxDPState *s = XLNX_DP(opaque); + + offset = offset >> 2; + return s->avbufm_registers[offset]; +} + +static const MemoryRegionOps avbufm_ops = { + .read = xlnx_dp_avbufm_read, + .write = xlnx_dp_avbufm_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +/* + * This is a global alpha blending using pixman. + * Both graphic and video planes are multiplied with the global alpha + * coefficient and added. + */ +static inline void xlnx_dp_blend_surface(XlnxDPState *s) +{ + pixman_fixed_t alpha1[] = { pixman_double_to_fixed(1), + pixman_double_to_fixed(1), + pixman_double_to_fixed(1.0) }; + pixman_fixed_t alpha2[] = { pixman_double_to_fixed(1), + pixman_double_to_fixed(1), + pixman_double_to_fixed(1.0) }; + + if ((surface_width(s->g_plane.surface) + != surface_width(s->v_plane.surface)) || + (surface_height(s->g_plane.surface) + != surface_height(s->v_plane.surface))) { + return; + } + + alpha1[2] = pixman_double_to_fixed((double)(xlnx_dp_global_alpha_value(s)) + / 256.0); + alpha2[2] = pixman_double_to_fixed((255.0 + - (double)xlnx_dp_global_alpha_value(s)) + / 256.0); + + pixman_image_set_filter(s->g_plane.surface->image, + PIXMAN_FILTER_CONVOLUTION, alpha1, 3); + pixman_image_composite(PIXMAN_OP_SRC, s->g_plane.surface->image, 0, + s->bout_plane.surface->image, 0, 0, 0, 0, 0, 0, + surface_width(s->g_plane.surface), + surface_height(s->g_plane.surface)); + pixman_image_set_filter(s->v_plane.surface->image, + PIXMAN_FILTER_CONVOLUTION, alpha2, 3); + pixman_image_composite(PIXMAN_OP_ADD, s->v_plane.surface->image, 0, + s->bout_plane.surface->image, 0, 0, 0, 0, 0, 0, + surface_width(s->g_plane.surface), + surface_height(s->g_plane.surface)); +} + +static void xlnx_dp_update_display(void *opaque) +{ + XlnxDPState *s = XLNX_DP(opaque); + + if ((s->core_registers[DP_TRANSMITTER_ENABLE] & 0x01) == 0) { + return; + } + + s->core_registers[DP_INT_STATUS] |= (1 << 13); + xlnx_dp_update_irq(s); + + xlnx_dpdma_trigger_vsync_irq(s->dpdma); + + /* + * Trigger the DMA channel. + */ + if (!xlnx_dpdma_start_operation(s->dpdma, 3, false)) { + /* + * An error occured don't do anything with the data.. + * Trigger an underflow interrupt. + */ + s->core_registers[DP_INT_STATUS] |= (1 << 21); + xlnx_dp_update_irq(s); + return; + } + + if (xlnx_dp_global_alpha_enabled(s)) { + if (!xlnx_dpdma_start_operation(s->dpdma, 0, false)) { + s->core_registers[DP_INT_STATUS] |= (1 << 21); + xlnx_dp_update_irq(s); + return; + } + xlnx_dp_blend_surface(s); + } + + /* + * XXX: We might want to update only what changed. + */ + dpy_gfx_update(s->console, 0, 0, surface_width(s->g_plane.surface), + surface_height(s->g_plane.surface)); +} + +static const GraphicHwOps xlnx_dp_gfx_ops = { + .gfx_update = xlnx_dp_update_display, +}; + +static void xlnx_dp_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + XlnxDPState *s = XLNX_DP(obj); + + memory_region_init(&s->container, obj, TYPE_XLNX_DP, 0xC050); + + memory_region_init_io(&s->core_iomem, obj, &dp_ops, s, TYPE_XLNX_DP + ".core", 0x3AF); + memory_region_add_subregion(&s->container, 0x0000, &s->core_iomem); + + memory_region_init_io(&s->vblend_iomem, obj, &vblend_ops, s, TYPE_XLNX_DP + ".v_blend", 0x1DF); + memory_region_add_subregion(&s->container, 0xA000, &s->vblend_iomem); + + memory_region_init_io(&s->avbufm_iomem, obj, &avbufm_ops, s, TYPE_XLNX_DP + ".av_buffer_manager", 0x238); + memory_region_add_subregion(&s->container, 0xB000, &s->avbufm_iomem); + + memory_region_init_io(&s->audio_iomem, obj, &audio_ops, s, TYPE_XLNX_DP + ".audio", sizeof(s->audio_registers)); + memory_region_add_subregion(&s->container, 0xC000, &s->audio_iomem); + + sysbus_init_mmio(sbd, &s->container); + sysbus_init_irq(sbd, &s->irq); + + object_property_add_link(obj, "dpdma", TYPE_XLNX_DPDMA, + (Object **) &s->dpdma, + xlnx_dp_set_dpdma, + OBJ_PROP_LINK_UNREF_ON_RELEASE, + &error_abort); + + /* + * Initialize AUX Bus. + */ + s->aux_bus = aux_init_bus(DEVICE(obj), "aux"); + + /* + * Initialize DPCD and EDID.. + */ + s->dpcd = DPCD(aux_create_slave(s->aux_bus, "dpcd", 0x00000)); + s->edid = I2CDDC(qdev_create(BUS(aux_get_i2c_bus(s->aux_bus)), "i2c-ddc")); + i2c_set_slave_address(I2C_SLAVE(s->edid), 0x50); + + fifo8_create(&s->rx_fifo, 16); + fifo8_create(&s->tx_fifo, 16); +} + +static void xlnx_dp_realize(DeviceState *dev, Error **errp) +{ + XlnxDPState *s = XLNX_DP(dev); + DisplaySurface *surface; + struct audsettings as; + + s->console = graphic_console_init(dev, 0, &xlnx_dp_gfx_ops, s); + surface = qemu_console_surface(s->console); + xlnx_dpdma_set_host_data_location(s->dpdma, DP_GRAPHIC_DMA_CHANNEL, + surface_data(surface)); + + as.freq = 44100; + as.nchannels = 2; + as.fmt = AUD_FMT_S16; + as.endianness = 0; + + AUD_register_card("xlnx_dp.audio", &s->aud_card); + + s->amixer_output_stream = AUD_open_out(&s->aud_card, + s->amixer_output_stream, + "xlnx_dp.audio.out", + s, + xlnx_dp_audio_callback, + &as); + AUD_set_volume_out(s->amixer_output_stream, 0, 255, 255); + xlnx_dp_audio_activate(s); +} + +static void xlnx_dp_reset(DeviceState *dev) +{ + XlnxDPState *s = XLNX_DP(dev); + + memset(s->core_registers, 0, sizeof(s->core_registers)); + s->core_registers[DP_VERSION_REGISTER] = 0x04010000; + s->core_registers[DP_CORE_ID] = 0x01020000; + s->core_registers[DP_REPLY_STATUS] = 0x00000010; + s->core_registers[DP_MSA_TRANSFER_UNIT_SIZE] = 0x00000040; + s->core_registers[DP_INIT_WAIT] = 0x00000020; + s->core_registers[DP_PHY_RESET] = 0x00010003; + s->core_registers[DP_INT_MASK] = 0xFFFFF03F; + s->core_registers[DP_PHY_STATUS] = 0x00000043; + s->core_registers[DP_INTERRUPT_SIGNAL_STATE] = 0x00000001; + + s->vblend_registers[V_BLEND_RGB2YCBCR_COEFF(0)] = 0x00001000; + s->vblend_registers[V_BLEND_RGB2YCBCR_COEFF(4)] = 0x00001000; + s->vblend_registers[V_BLEND_RGB2YCBCR_COEFF(8)] = 0x00001000; + s->vblend_registers[V_BLEND_IN1CSC_COEFF(0)] = 0x00001000; + s->vblend_registers[V_BLEND_IN1CSC_COEFF(4)] = 0x00001000; + s->vblend_registers[V_BLEND_IN1CSC_COEFF(8)] = 0x00001000; + s->vblend_registers[V_BLEND_IN2CSC_COEFF(0)] = 0x00001000; + s->vblend_registers[V_BLEND_IN2CSC_COEFF(4)] = 0x00001000; + s->vblend_registers[V_BLEND_IN2CSC_COEFF(8)] = 0x00001000; + + s->avbufm_registers[AV_BUF_NON_LIVE_LATENCY] = 0x00000180; + s->avbufm_registers[AV_BUF_OUTPUT_AUDIO_VIDEO_SELECT] = 0x00000008; + s->avbufm_registers[AV_BUF_DITHER_CONFIG_MAX] = 0x00000FFF; + s->avbufm_registers[AV_BUF_GRAPHICS_COMP_SCALE_FACTOR(0)] = 0x00010101; + s->avbufm_registers[AV_BUF_GRAPHICS_COMP_SCALE_FACTOR(1)] = 0x00010101; + s->avbufm_registers[AV_BUF_GRAPHICS_COMP_SCALE_FACTOR(2)] = 0x00010101; + s->avbufm_registers[AV_BUF_VIDEO_COMP_SCALE_FACTOR(0)] = 0x00010101; + s->avbufm_registers[AV_BUF_VIDEO_COMP_SCALE_FACTOR(1)] = 0x00010101; + s->avbufm_registers[AV_BUF_VIDEO_COMP_SCALE_FACTOR(2)] = 0x00010101; + s->avbufm_registers[AV_BUF_LIVE_VIDEO_COMP_SF(0)] = 0x00010101; + s->avbufm_registers[AV_BUF_LIVE_VIDEO_COMP_SF(1)] = 0x00010101; + s->avbufm_registers[AV_BUF_LIVE_VIDEO_COMP_SF(2)] = 0x00010101; + s->avbufm_registers[AV_BUF_LIVE_GFX_COMP_SF(0)] = 0x00010101; + s->avbufm_registers[AV_BUF_LIVE_GFX_COMP_SF(1)] = 0x00010101; + s->avbufm_registers[AV_BUF_LIVE_GFX_COMP_SF(2)] = 0x00010101; + + memset(s->audio_registers, 0, sizeof(s->audio_registers)); + s->byte_left = 0; + + xlnx_dp_aux_clear_rx_fifo(s); + xlnx_dp_change_graphic_fmt(s); + xlnx_dp_update_irq(s); +} + +static void xlnx_dp_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = xlnx_dp_realize; + dc->vmsd = &vmstate_dp; + dc->reset = xlnx_dp_reset; +} + +static const TypeInfo xlnx_dp_info = { + .name = TYPE_XLNX_DP, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(XlnxDPState), + .instance_init = xlnx_dp_init, + .class_init = xlnx_dp_class_init, +}; + +static void xlnx_dp_register_types(void) +{ + type_register_static(&xlnx_dp_info); +} + +type_init(xlnx_dp_register_types) diff --git a/hw/dma/Makefile.objs b/hw/dma/Makefile.objs index a1abbcf..8b0823e 100644 --- a/hw/dma/Makefile.objs +++ b/hw/dma/Makefile.objs @@ -8,6 +8,7 @@ common-obj-$(CONFIG_XILINX_AXI) += xilinx_axidma.o common-obj-$(CONFIG_ETRAXFS) += etraxfs_dma.o common-obj-$(CONFIG_STP2000) += sparc32_dma.o common-obj-$(CONFIG_SUN4M) += sun4m_iommu.o +obj-$(CONFIG_XLNX_ZYNQMP) += xlnx_dpdma.o obj-$(CONFIG_OMAP) += omap_dma.o soc_dma.o obj-$(CONFIG_PXA2XX) += pxa2xx_dma.o diff --git a/hw/dma/pxa2xx_dma.c b/hw/dma/pxa2xx_dma.c index 2306abc..634a432 100644 --- a/hw/dma/pxa2xx_dma.c +++ b/hw/dma/pxa2xx_dma.c @@ -12,6 +12,7 @@ #include "hw/hw.h" #include "hw/arm/pxa.h" #include "hw/sysbus.h" +#include "qapi/error.h" #define PXA255_DMA_NUM_CHANNELS 16 #define PXA27X_DMA_NUM_CHANNELS 32 @@ -450,31 +451,36 @@ static void pxa2xx_dma_request(void *opaque, int req_num, int on) } } -static int pxa2xx_dma_init(SysBusDevice *sbd) +static void pxa2xx_dma_init(Object *obj) +{ + DeviceState *dev = DEVICE(obj); + PXA2xxDMAState *s = PXA2XX_DMA(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memset(s->req, 0, sizeof(uint8_t) * PXA2XX_DMA_NUM_REQUESTS); + + qdev_init_gpio_in(dev, pxa2xx_dma_request, PXA2XX_DMA_NUM_REQUESTS); + + memory_region_init_io(&s->iomem, obj, &pxa2xx_dma_ops, s, + "pxa2xx.dma", 0x00010000); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); +} + +static void pxa2xx_dma_realize(DeviceState *dev, Error **errp) { - DeviceState *dev = DEVICE(sbd); PXA2xxDMAState *s = PXA2XX_DMA(dev); int i; if (s->channels <= 0) { - return -1; + error_setg(errp, "channels value invalid"); + return; } s->chan = g_new0(PXA2xxDMAChannel, s->channels); for (i = 0; i < s->channels; i ++) s->chan[i].state = DCSR_STOPINTR; - - memset(s->req, 0, sizeof(uint8_t) * PXA2XX_DMA_NUM_REQUESTS); - - qdev_init_gpio_in(dev, pxa2xx_dma_request, PXA2XX_DMA_NUM_REQUESTS); - - memory_region_init_io(&s->iomem, OBJECT(s), &pxa2xx_dma_ops, s, - "pxa2xx.dma", 0x00010000); - sysbus_init_mmio(sbd, &s->iomem); - sysbus_init_irq(sbd, &s->irq); - - return 0; } DeviceState *pxa27x_dma_init(hwaddr base, qemu_irq irq) @@ -553,18 +559,18 @@ static Property pxa2xx_dma_properties[] = { static void pxa2xx_dma_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); - k->init = pxa2xx_dma_init; dc->desc = "PXA2xx DMA controller"; dc->vmsd = &vmstate_pxa2xx_dma; dc->props = pxa2xx_dma_properties; + dc->realize = pxa2xx_dma_realize; } static const TypeInfo pxa2xx_dma_info = { .name = TYPE_PXA2XX_DMA, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(PXA2xxDMAState), + .instance_init = pxa2xx_dma_init, .class_init = pxa2xx_dma_class_init, }; diff --git a/hw/dma/xlnx_dpdma.c b/hw/dma/xlnx_dpdma.c new file mode 100644 index 0000000..8ceb21d --- /dev/null +++ b/hw/dma/xlnx_dpdma.c @@ -0,0 +1,786 @@ +/* + * xlnx_dpdma.c + * + * Copyright (C) 2015 : GreenSocs Ltd + * http://www.greensocs.com/ , email: info@greensocs.com + * + * Developed by : + * Frederic Konrad <fred.konrad@greensocs.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/dma/xlnx_dpdma.h" + +#ifndef DEBUG_DPDMA +#define DEBUG_DPDMA 0 +#endif + +#define DPRINTF(fmt, ...) do { \ + if (DEBUG_DPDMA) { \ + qemu_log("xlnx_dpdma: " fmt , ## __VA_ARGS__); \ + } \ +} while (0); + +/* + * Registers offset for DPDMA. + */ +#define DPDMA_ERR_CTRL (0x0000) +#define DPDMA_ISR (0x0004 >> 2) +#define DPDMA_IMR (0x0008 >> 2) +#define DPDMA_IEN (0x000C >> 2) +#define DPDMA_IDS (0x0010 >> 2) +#define DPDMA_EISR (0x0014 >> 2) +#define DPDMA_EIMR (0x0018 >> 2) +#define DPDMA_EIEN (0x001C >> 2) +#define DPDMA_EIDS (0x0020 >> 2) +#define DPDMA_CNTL (0x0100 >> 2) + +#define DPDMA_GBL (0x0104 >> 2) +#define DPDMA_GBL_TRG_CH(n) (1 << n) +#define DPDMA_GBL_RTRG_CH(n) (1 << 6 << n) + +#define DPDMA_ALC0_CNTL (0x0108 >> 2) +#define DPDMA_ALC0_STATUS (0x010C >> 2) +#define DPDMA_ALC0_MAX (0x0110 >> 2) +#define DPDMA_ALC0_MIN (0x0114 >> 2) +#define DPDMA_ALC0_ACC (0x0118 >> 2) +#define DPDMA_ALC0_ACC_TRAN (0x011C >> 2) +#define DPDMA_ALC1_CNTL (0x0120 >> 2) +#define DPDMA_ALC1_STATUS (0x0124 >> 2) +#define DPDMA_ALC1_MAX (0x0128 >> 2) +#define DPDMA_ALC1_MIN (0x012C >> 2) +#define DPDMA_ALC1_ACC (0x0130 >> 2) +#define DPDMA_ALC1_ACC_TRAN (0x0134 >> 2) + +#define DPDMA_DSCR_STRT_ADDRE_CH(n) ((0x0200 + n * 0x100) >> 2) +#define DPDMA_DSCR_STRT_ADDR_CH(n) ((0x0204 + n * 0x100) >> 2) +#define DPDMA_DSCR_NEXT_ADDRE_CH(n) ((0x0208 + n * 0x100) >> 2) +#define DPDMA_DSCR_NEXT_ADDR_CH(n) ((0x020C + n * 0x100) >> 2) +#define DPDMA_PYLD_CUR_ADDRE_CH(n) ((0x0210 + n * 0x100) >> 2) +#define DPDMA_PYLD_CUR_ADDR_CH(n) ((0x0214 + n * 0x100) >> 2) + +#define DPDMA_CNTL_CH(n) ((0x0218 + n * 0x100) >> 2) +#define DPDMA_CNTL_CH_EN (1) +#define DPDMA_CNTL_CH_PAUSED (1 << 1) + +#define DPDMA_STATUS_CH(n) ((0x021C + n * 0x100) >> 2) +#define DPDMA_STATUS_BURST_TYPE (1 << 4) +#define DPDMA_STATUS_MODE (1 << 5) +#define DPDMA_STATUS_EN_CRC (1 << 6) +#define DPDMA_STATUS_LAST_DSCR (1 << 7) +#define DPDMA_STATUS_LDSCR_FRAME (1 << 8) +#define DPDMA_STATUS_IGNR_DONE (1 << 9) +#define DPDMA_STATUS_DSCR_DONE (1 << 10) +#define DPDMA_STATUS_EN_DSCR_UP (1 << 11) +#define DPDMA_STATUS_EN_DSCR_INTR (1 << 12) +#define DPDMA_STATUS_PREAMBLE_OFF (13) + +#define DPDMA_VDO_CH(n) ((0x0220 + n * 0x100) >> 2) +#define DPDMA_PYLD_SZ_CH(n) ((0x0224 + n * 0x100) >> 2) +#define DPDMA_DSCR_ID_CH(n) ((0x0228 + n * 0x100) >> 2) + +/* + * Descriptor control field. + */ +#define CONTROL_PREAMBLE_VALUE 0xA5 + +#define DSCR_CTRL_PREAMBLE 0xFF +#define DSCR_CTRL_EN_DSCR_DONE_INTR (1 << 8) +#define DSCR_CTRL_EN_DSCR_UPDATE (1 << 9) +#define DSCR_CTRL_IGNORE_DONE (1 << 10) +#define DSCR_CTRL_AXI_BURST_TYPE (1 << 11) +#define DSCR_CTRL_AXCACHE (0x0F << 12) +#define DSCR_CTRL_AXPROT (0x2 << 16) +#define DSCR_CTRL_DESCRIPTOR_MODE (1 << 18) +#define DSCR_CTRL_LAST_DESCRIPTOR (1 << 19) +#define DSCR_CTRL_ENABLE_CRC (1 << 20) +#define DSCR_CTRL_LAST_DESCRIPTOR_OF_FRAME (1 << 21) + +/* + * Descriptor timestamp field. + */ +#define STATUS_DONE (1 << 31) + +#define DPDMA_FRAG_MAX_SZ (4096) + +enum DPDMABurstType { + DPDMA_INCR = 0, + DPDMA_FIXED = 1 +}; + +enum DPDMAMode { + DPDMA_CONTIGOUS = 0, + DPDMA_FRAGMENTED = 1 +}; + +struct DPDMADescriptor { + uint32_t control; + uint32_t descriptor_id; + /* transfer size in byte. */ + uint32_t xfer_size; + uint32_t line_size_stride; + uint32_t timestamp_lsb; + uint32_t timestamp_msb; + /* contains extension for both descriptor and source. */ + uint32_t address_extension; + uint32_t next_descriptor; + uint32_t source_address; + uint32_t address_extension_23; + uint32_t address_extension_45; + uint32_t source_address2; + uint32_t source_address3; + uint32_t source_address4; + uint32_t source_address5; + uint32_t crc; +}; + +typedef enum DPDMABurstType DPDMABurstType; +typedef enum DPDMAMode DPDMAMode; +typedef struct DPDMADescriptor DPDMADescriptor; + +static bool xlnx_dpdma_desc_is_last(DPDMADescriptor *desc) +{ + return ((desc->control & DSCR_CTRL_LAST_DESCRIPTOR) != 0); +} + +static bool xlnx_dpdma_desc_is_last_of_frame(DPDMADescriptor *desc) +{ + return ((desc->control & DSCR_CTRL_LAST_DESCRIPTOR_OF_FRAME) != 0); +} + +static uint64_t xlnx_dpdma_desc_get_source_address(DPDMADescriptor *desc, + uint8_t frag) +{ + uint64_t addr = 0; + assert(frag < 5); + + switch (frag) { + case 0: + addr = desc->source_address + + (extract32(desc->address_extension, 16, 12) << 20); + break; + case 1: + addr = desc->source_address2 + + (extract32(desc->address_extension_23, 0, 12) << 8); + break; + case 2: + addr = desc->source_address3 + + (extract32(desc->address_extension_23, 16, 12) << 20); + break; + case 3: + addr = desc->source_address4 + + (extract32(desc->address_extension_45, 0, 12) << 8); + break; + case 4: + addr = desc->source_address5 + + (extract32(desc->address_extension_45, 16, 12) << 20); + break; + default: + addr = 0; + break; + } + + return addr; +} + +static uint32_t xlnx_dpdma_desc_get_transfer_size(DPDMADescriptor *desc) +{ + return desc->xfer_size; +} + +static uint32_t xlnx_dpdma_desc_get_line_size(DPDMADescriptor *desc) +{ + return extract32(desc->line_size_stride, 0, 18); +} + +static uint32_t xlnx_dpdma_desc_get_line_stride(DPDMADescriptor *desc) +{ + return extract32(desc->line_size_stride, 18, 14) * 16; +} + +static inline bool xlnx_dpdma_desc_crc_enabled(DPDMADescriptor *desc) +{ + return (desc->control & DSCR_CTRL_ENABLE_CRC) != 0; +} + +static inline bool xlnx_dpdma_desc_check_crc(DPDMADescriptor *desc) +{ + uint32_t *p = (uint32_t *)desc; + uint32_t crc = 0; + uint8_t i; + + /* + * CRC is calculated on the whole descriptor except the last 32bits word + * using 32bits addition. + */ + for (i = 0; i < 15; i++) { + crc += p[i]; + } + + return crc == desc->crc; +} + +static inline bool xlnx_dpdma_desc_completion_interrupt(DPDMADescriptor *desc) +{ + return (desc->control & DSCR_CTRL_EN_DSCR_DONE_INTR) != 0; +} + +static inline bool xlnx_dpdma_desc_is_valid(DPDMADescriptor *desc) +{ + return (desc->control & DSCR_CTRL_PREAMBLE) == CONTROL_PREAMBLE_VALUE; +} + +static inline bool xlnx_dpdma_desc_is_contiguous(DPDMADescriptor *desc) +{ + return (desc->control & DSCR_CTRL_DESCRIPTOR_MODE) == 0; +} + +static inline bool xlnx_dpdma_desc_update_enabled(DPDMADescriptor *desc) +{ + return (desc->control & DSCR_CTRL_EN_DSCR_UPDATE) != 0; +} + +static inline void xlnx_dpdma_desc_set_done(DPDMADescriptor *desc) +{ + desc->timestamp_msb |= STATUS_DONE; +} + +static inline bool xlnx_dpdma_desc_is_already_done(DPDMADescriptor *desc) +{ + return (desc->timestamp_msb & STATUS_DONE) != 0; +} + +static inline bool xlnx_dpdma_desc_ignore_done_bit(DPDMADescriptor *desc) +{ + return (desc->control & DSCR_CTRL_IGNORE_DONE) != 0; +} + +static const VMStateDescription vmstate_xlnx_dpdma = { + .name = TYPE_XLNX_DPDMA, + .version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(registers, XlnxDPDMAState, + XLNX_DPDMA_REG_ARRAY_SIZE), + VMSTATE_BOOL_ARRAY(operation_finished, XlnxDPDMAState, 6), + VMSTATE_END_OF_LIST() + } +}; + +static void xlnx_dpdma_update_irq(XlnxDPDMAState *s) +{ + bool flags; + + flags = ((s->registers[DPDMA_ISR] & (~s->registers[DPDMA_IMR])) + || (s->registers[DPDMA_EISR] & (~s->registers[DPDMA_EIMR]))); + qemu_set_irq(s->irq, flags); +} + +static uint64_t xlnx_dpdma_descriptor_start_address(XlnxDPDMAState *s, + uint8_t channel) +{ + return (s->registers[DPDMA_DSCR_STRT_ADDRE_CH(channel)] << 16) + + s->registers[DPDMA_DSCR_STRT_ADDR_CH(channel)]; +} + +static uint64_t xlnx_dpdma_descriptor_next_address(XlnxDPDMAState *s, + uint8_t channel) +{ + return ((uint64_t)s->registers[DPDMA_DSCR_NEXT_ADDRE_CH(channel)] << 32) + + s->registers[DPDMA_DSCR_NEXT_ADDR_CH(channel)]; +} + +static bool xlnx_dpdma_is_channel_enabled(XlnxDPDMAState *s, + uint8_t channel) +{ + return (s->registers[DPDMA_CNTL_CH(channel)] & DPDMA_CNTL_CH_EN) != 0; +} + +static bool xlnx_dpdma_is_channel_paused(XlnxDPDMAState *s, + uint8_t channel) +{ + return (s->registers[DPDMA_CNTL_CH(channel)] & DPDMA_CNTL_CH_PAUSED) != 0; +} + +static inline bool xlnx_dpdma_is_channel_retriggered(XlnxDPDMAState *s, + uint8_t channel) +{ + /* Clear the retriggered bit after reading it. */ + bool channel_is_retriggered = s->registers[DPDMA_GBL] + & DPDMA_GBL_RTRG_CH(channel); + s->registers[DPDMA_GBL] &= ~DPDMA_GBL_RTRG_CH(channel); + return channel_is_retriggered; +} + +static inline bool xlnx_dpdma_is_channel_triggered(XlnxDPDMAState *s, + uint8_t channel) +{ + return s->registers[DPDMA_GBL] & DPDMA_GBL_TRG_CH(channel); +} + +static void xlnx_dpdma_update_desc_info(XlnxDPDMAState *s, uint8_t channel, + DPDMADescriptor *desc) +{ + s->registers[DPDMA_DSCR_NEXT_ADDRE_CH(channel)] = + extract32(desc->address_extension, 0, 16); + s->registers[DPDMA_DSCR_NEXT_ADDR_CH(channel)] = desc->next_descriptor; + s->registers[DPDMA_PYLD_CUR_ADDRE_CH(channel)] = + extract32(desc->address_extension, 16, 16); + s->registers[DPDMA_PYLD_CUR_ADDR_CH(channel)] = desc->source_address; + s->registers[DPDMA_VDO_CH(channel)] = + extract32(desc->line_size_stride, 18, 14) + + (extract32(desc->line_size_stride, 0, 18) + << 14); + s->registers[DPDMA_PYLD_SZ_CH(channel)] = desc->xfer_size; + s->registers[DPDMA_DSCR_ID_CH(channel)] = desc->descriptor_id; + + /* Compute the status register with the descriptor information. */ + s->registers[DPDMA_STATUS_CH(channel)] = + extract32(desc->control, 0, 8) << 13; + if ((desc->control & DSCR_CTRL_EN_DSCR_DONE_INTR) != 0) { + s->registers[DPDMA_STATUS_CH(channel)] |= DPDMA_STATUS_EN_DSCR_INTR; + } + if ((desc->control & DSCR_CTRL_EN_DSCR_UPDATE) != 0) { + s->registers[DPDMA_STATUS_CH(channel)] |= DPDMA_STATUS_EN_DSCR_UP; + } + if ((desc->timestamp_msb & STATUS_DONE) != 0) { + s->registers[DPDMA_STATUS_CH(channel)] |= DPDMA_STATUS_DSCR_DONE; + } + if ((desc->control & DSCR_CTRL_IGNORE_DONE) != 0) { + s->registers[DPDMA_STATUS_CH(channel)] |= DPDMA_STATUS_IGNR_DONE; + } + if ((desc->control & DSCR_CTRL_LAST_DESCRIPTOR_OF_FRAME) != 0) { + s->registers[DPDMA_STATUS_CH(channel)] |= DPDMA_STATUS_LDSCR_FRAME; + } + if ((desc->control & DSCR_CTRL_LAST_DESCRIPTOR) != 0) { + s->registers[DPDMA_STATUS_CH(channel)] |= DPDMA_STATUS_LAST_DSCR; + } + if ((desc->control & DSCR_CTRL_ENABLE_CRC) != 0) { + s->registers[DPDMA_STATUS_CH(channel)] |= DPDMA_STATUS_EN_CRC; + } + if ((desc->control & DSCR_CTRL_DESCRIPTOR_MODE) != 0) { + s->registers[DPDMA_STATUS_CH(channel)] |= DPDMA_STATUS_MODE; + } + if ((desc->control & DSCR_CTRL_AXI_BURST_TYPE) != 0) { + s->registers[DPDMA_STATUS_CH(channel)] |= DPDMA_STATUS_BURST_TYPE; + } +} + +static void xlnx_dpdma_dump_descriptor(DPDMADescriptor *desc) +{ + if (DEBUG_DPDMA) { + qemu_log("DUMP DESCRIPTOR:\n"); + qemu_hexdump((char *)desc, stdout, "", sizeof(DPDMADescriptor)); + } +} + +static uint64_t xlnx_dpdma_read(void *opaque, hwaddr offset, + unsigned size) +{ + XlnxDPDMAState *s = XLNX_DPDMA(opaque); + + DPRINTF("read @%" HWADDR_PRIx "\n", offset); + offset = offset >> 2; + + switch (offset) { + /* + * Trying to read a write only register. + */ + case DPDMA_GBL: + return 0; + default: + assert(offset <= (0xFFC >> 2)); + return s->registers[offset]; + } + return 0; +} + +static void xlnx_dpdma_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + XlnxDPDMAState *s = XLNX_DPDMA(opaque); + + DPRINTF("write @%" HWADDR_PRIx " = %" PRIx64 "\n", offset, value); + offset = offset >> 2; + + switch (offset) { + case DPDMA_ISR: + s->registers[DPDMA_ISR] &= ~value; + xlnx_dpdma_update_irq(s); + break; + case DPDMA_IEN: + s->registers[DPDMA_IMR] &= ~value; + break; + case DPDMA_IDS: + s->registers[DPDMA_IMR] |= value; + break; + case DPDMA_EISR: + s->registers[DPDMA_EISR] &= ~value; + xlnx_dpdma_update_irq(s); + break; + case DPDMA_EIEN: + s->registers[DPDMA_EIMR] &= ~value; + break; + case DPDMA_EIDS: + s->registers[DPDMA_EIMR] |= value; + break; + case DPDMA_IMR: + case DPDMA_EIMR: + case DPDMA_DSCR_NEXT_ADDRE_CH(0): + case DPDMA_DSCR_NEXT_ADDRE_CH(1): + case DPDMA_DSCR_NEXT_ADDRE_CH(2): + case DPDMA_DSCR_NEXT_ADDRE_CH(3): + case DPDMA_DSCR_NEXT_ADDRE_CH(4): + case DPDMA_DSCR_NEXT_ADDRE_CH(5): + case DPDMA_DSCR_NEXT_ADDR_CH(0): + case DPDMA_DSCR_NEXT_ADDR_CH(1): + case DPDMA_DSCR_NEXT_ADDR_CH(2): + case DPDMA_DSCR_NEXT_ADDR_CH(3): + case DPDMA_DSCR_NEXT_ADDR_CH(4): + case DPDMA_DSCR_NEXT_ADDR_CH(5): + case DPDMA_PYLD_CUR_ADDRE_CH(0): + case DPDMA_PYLD_CUR_ADDRE_CH(1): + case DPDMA_PYLD_CUR_ADDRE_CH(2): + case DPDMA_PYLD_CUR_ADDRE_CH(3): + case DPDMA_PYLD_CUR_ADDRE_CH(4): + case DPDMA_PYLD_CUR_ADDRE_CH(5): + case DPDMA_PYLD_CUR_ADDR_CH(0): + case DPDMA_PYLD_CUR_ADDR_CH(1): + case DPDMA_PYLD_CUR_ADDR_CH(2): + case DPDMA_PYLD_CUR_ADDR_CH(3): + case DPDMA_PYLD_CUR_ADDR_CH(4): + case DPDMA_PYLD_CUR_ADDR_CH(5): + case DPDMA_STATUS_CH(0): + case DPDMA_STATUS_CH(1): + case DPDMA_STATUS_CH(2): + case DPDMA_STATUS_CH(3): + case DPDMA_STATUS_CH(4): + case DPDMA_STATUS_CH(5): + case DPDMA_VDO_CH(0): + case DPDMA_VDO_CH(1): + case DPDMA_VDO_CH(2): + case DPDMA_VDO_CH(3): + case DPDMA_VDO_CH(4): + case DPDMA_VDO_CH(5): + case DPDMA_PYLD_SZ_CH(0): + case DPDMA_PYLD_SZ_CH(1): + case DPDMA_PYLD_SZ_CH(2): + case DPDMA_PYLD_SZ_CH(3): + case DPDMA_PYLD_SZ_CH(4): + case DPDMA_PYLD_SZ_CH(5): + case DPDMA_DSCR_ID_CH(0): + case DPDMA_DSCR_ID_CH(1): + case DPDMA_DSCR_ID_CH(2): + case DPDMA_DSCR_ID_CH(3): + case DPDMA_DSCR_ID_CH(4): + case DPDMA_DSCR_ID_CH(5): + /* + * Trying to write to a read only register.. + */ + break; + case DPDMA_GBL: + /* + * This is a write only register so it's read as zero in the read + * callback. + * We store the value anyway so we can know if the channel is + * enabled. + */ + s->registers[offset] |= value & 0x00000FFF; + break; + case DPDMA_DSCR_STRT_ADDRE_CH(0): + case DPDMA_DSCR_STRT_ADDRE_CH(1): + case DPDMA_DSCR_STRT_ADDRE_CH(2): + case DPDMA_DSCR_STRT_ADDRE_CH(3): + case DPDMA_DSCR_STRT_ADDRE_CH(4): + case DPDMA_DSCR_STRT_ADDRE_CH(5): + value &= 0x0000FFFF; + s->registers[offset] = value; + break; + case DPDMA_CNTL_CH(0): + s->registers[DPDMA_GBL] &= ~DPDMA_GBL_TRG_CH(0); + value &= 0x3FFFFFFF; + s->registers[offset] = value; + break; + case DPDMA_CNTL_CH(1): + s->registers[DPDMA_GBL] &= ~DPDMA_GBL_TRG_CH(1); + value &= 0x3FFFFFFF; + s->registers[offset] = value; + break; + case DPDMA_CNTL_CH(2): + s->registers[DPDMA_GBL] &= ~DPDMA_GBL_TRG_CH(2); + value &= 0x3FFFFFFF; + s->registers[offset] = value; + break; + case DPDMA_CNTL_CH(3): + s->registers[DPDMA_GBL] &= ~DPDMA_GBL_TRG_CH(3); + value &= 0x3FFFFFFF; + s->registers[offset] = value; + break; + case DPDMA_CNTL_CH(4): + s->registers[DPDMA_GBL] &= ~DPDMA_GBL_TRG_CH(4); + value &= 0x3FFFFFFF; + s->registers[offset] = value; + break; + case DPDMA_CNTL_CH(5): + s->registers[DPDMA_GBL] &= ~DPDMA_GBL_TRG_CH(5); + value &= 0x3FFFFFFF; + s->registers[offset] = value; + break; + default: + assert(offset <= (0xFFC >> 2)); + s->registers[offset] = value; + break; + } +} + +static const MemoryRegionOps dma_ops = { + .read = xlnx_dpdma_read, + .write = xlnx_dpdma_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void xlnx_dpdma_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + XlnxDPDMAState *s = XLNX_DPDMA(obj); + + memory_region_init_io(&s->iomem, obj, &dma_ops, s, + TYPE_XLNX_DPDMA, 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); +} + +static void xlnx_dpdma_reset(DeviceState *dev) +{ + XlnxDPDMAState *s = XLNX_DPDMA(dev); + size_t i; + + memset(s->registers, 0, sizeof(s->registers)); + s->registers[DPDMA_IMR] = 0x07FFFFFF; + s->registers[DPDMA_EIMR] = 0xFFFFFFFF; + s->registers[DPDMA_ALC0_MIN] = 0x0000FFFF; + s->registers[DPDMA_ALC1_MIN] = 0x0000FFFF; + + for (i = 0; i < 6; i++) { + s->data[i] = NULL; + s->operation_finished[i] = true; + } +} + +static void xlnx_dpdma_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->vmsd = &vmstate_xlnx_dpdma; + dc->reset = xlnx_dpdma_reset; +} + +static const TypeInfo xlnx_dpdma_info = { + .name = TYPE_XLNX_DPDMA, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(XlnxDPDMAState), + .instance_init = xlnx_dpdma_init, + .class_init = xlnx_dpdma_class_init, +}; + +static void xlnx_dpdma_register_types(void) +{ + type_register_static(&xlnx_dpdma_info); +} + +size_t xlnx_dpdma_start_operation(XlnxDPDMAState *s, uint8_t channel, + bool one_desc) +{ + uint64_t desc_addr; + uint64_t source_addr[6]; + DPDMADescriptor desc; + bool done = false; + size_t ptr = 0; + + assert(channel <= 5); + + DPRINTF("start dpdma channel 0x%" PRIX8 "\n", channel); + + if (!xlnx_dpdma_is_channel_triggered(s, channel)) { + DPRINTF("Channel isn't triggered..\n"); + return 0; + } + + if (!xlnx_dpdma_is_channel_enabled(s, channel)) { + DPRINTF("Channel isn't enabled..\n"); + return 0; + } + + if (xlnx_dpdma_is_channel_paused(s, channel)) { + DPRINTF("Channel is paused..\n"); + return 0; + } + + do { + if ((s->operation_finished[channel]) + || xlnx_dpdma_is_channel_retriggered(s, channel)) { + desc_addr = xlnx_dpdma_descriptor_start_address(s, channel); + s->operation_finished[channel] = false; + } else { + desc_addr = xlnx_dpdma_descriptor_next_address(s, channel); + } + + if (dma_memory_read(&address_space_memory, desc_addr, &desc, + sizeof(DPDMADescriptor))) { + s->registers[DPDMA_EISR] |= ((1 << 1) << channel); + xlnx_dpdma_update_irq(s); + s->operation_finished[channel] = true; + DPRINTF("Can't get the descriptor.\n"); + break; + } + + xlnx_dpdma_update_desc_info(s, channel, &desc); + +#ifdef DEBUG_DPDMA + xlnx_dpdma_dump_descriptor(&desc); +#endif + + DPRINTF("location of the descriptor: %" PRIx64 "\n", desc_addr); + if (!xlnx_dpdma_desc_is_valid(&desc)) { + s->registers[DPDMA_EISR] |= ((1 << 7) << channel); + xlnx_dpdma_update_irq(s); + s->operation_finished[channel] = true; + DPRINTF("Invalid descriptor..\n"); + break; + } + + if (xlnx_dpdma_desc_crc_enabled(&desc) + && !xlnx_dpdma_desc_check_crc(&desc)) { + s->registers[DPDMA_EISR] |= ((1 << 13) << channel); + xlnx_dpdma_update_irq(s); + s->operation_finished[channel] = true; + DPRINTF("Bad CRC for descriptor..\n"); + break; + } + + if (xlnx_dpdma_desc_is_already_done(&desc) + && !xlnx_dpdma_desc_ignore_done_bit(&desc)) { + /* We are trying to process an already processed descriptor. */ + s->registers[DPDMA_EISR] |= ((1 << 25) << channel); + xlnx_dpdma_update_irq(s); + s->operation_finished[channel] = true; + DPRINTF("Already processed descriptor..\n"); + break; + } + + done = xlnx_dpdma_desc_is_last(&desc) + || xlnx_dpdma_desc_is_last_of_frame(&desc); + + s->operation_finished[channel] = done; + if (s->data[channel]) { + int64_t transfer_len = xlnx_dpdma_desc_get_transfer_size(&desc); + uint32_t line_size = xlnx_dpdma_desc_get_line_size(&desc); + uint32_t line_stride = xlnx_dpdma_desc_get_line_stride(&desc); + if (xlnx_dpdma_desc_is_contiguous(&desc)) { + source_addr[0] = xlnx_dpdma_desc_get_source_address(&desc, 0); + while (transfer_len != 0) { + if (dma_memory_read(&address_space_memory, + source_addr[0], + &s->data[channel][ptr], + line_size)) { + s->registers[DPDMA_ISR] |= ((1 << 12) << channel); + xlnx_dpdma_update_irq(s); + DPRINTF("Can't get data.\n"); + break; + } + ptr += line_size; + transfer_len -= line_size; + source_addr[0] += line_stride; + } + } else { + DPRINTF("Source address:\n"); + int frag; + for (frag = 0; frag < 5; frag++) { + source_addr[frag] = + xlnx_dpdma_desc_get_source_address(&desc, frag); + DPRINTF("Fragment %u: %" PRIx64 "\n", frag + 1, + source_addr[frag]); + } + + frag = 0; + while ((transfer_len < 0) && (frag < 5)) { + size_t fragment_len = DPDMA_FRAG_MAX_SZ + - (source_addr[frag] % DPDMA_FRAG_MAX_SZ); + + if (dma_memory_read(&address_space_memory, + source_addr[frag], + &(s->data[channel][ptr]), + fragment_len)) { + s->registers[DPDMA_ISR] |= ((1 << 12) << channel); + xlnx_dpdma_update_irq(s); + DPRINTF("Can't get data.\n"); + break; + } + ptr += fragment_len; + transfer_len -= fragment_len; + frag += 1; + } + } + } + + if (xlnx_dpdma_desc_update_enabled(&desc)) { + /* The descriptor need to be updated when it's completed. */ + DPRINTF("update the descriptor with the done flag set.\n"); + xlnx_dpdma_desc_set_done(&desc); + dma_memory_write(&address_space_memory, desc_addr, &desc, + sizeof(DPDMADescriptor)); + } + + if (xlnx_dpdma_desc_completion_interrupt(&desc)) { + DPRINTF("completion interrupt enabled!\n"); + s->registers[DPDMA_ISR] |= (1 << channel); + xlnx_dpdma_update_irq(s); + } + + } while (!done && !one_desc); + + return ptr; +} + +void xlnx_dpdma_set_host_data_location(XlnxDPDMAState *s, uint8_t channel, + void *p) +{ + if (!s) { + qemu_log_mask(LOG_UNIMP, "DPDMA client not attached to valid DPDMA" + " instance\n"); + return; + } + + assert(channel <= 5); + s->data[channel] = p; +} + +void xlnx_dpdma_trigger_vsync_irq(XlnxDPDMAState *s) +{ + s->registers[DPDMA_ISR] |= (1 << 27); + xlnx_dpdma_update_irq(s); +} + +type_init(xlnx_dpdma_register_types) diff --git a/hw/gpio/omap_gpio.c b/hw/gpio/omap_gpio.c index 9b1b004..dabef4a 100644 --- a/hw/gpio/omap_gpio.c +++ b/hw/gpio/omap_gpio.c @@ -23,6 +23,7 @@ #include "hw/arm/omap.h" #include "hw/sysbus.h" #include "qemu/error-report.h" +#include "qapi/error.h" struct omap_gpio_s { qemu_irq irq; @@ -678,48 +679,46 @@ static const MemoryRegionOps omap2_gpif_top_ops = { .endianness = DEVICE_NATIVE_ENDIAN, }; -static int omap_gpio_init(SysBusDevice *sbd) +static void omap_gpio_init(Object *obj) { - DeviceState *dev = DEVICE(sbd); - struct omap_gpif_s *s = OMAP1_GPIO(dev); + DeviceState *dev = DEVICE(obj); + struct omap_gpif_s *s = OMAP1_GPIO(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); - if (!s->clk) { - error_report("omap-gpio: clk not connected"); - return -1; - } qdev_init_gpio_in(dev, omap_gpio_set, 16); qdev_init_gpio_out(dev, s->omap1.handler, 16); sysbus_init_irq(sbd, &s->omap1.irq); - memory_region_init_io(&s->iomem, OBJECT(s), &omap_gpio_ops, &s->omap1, + memory_region_init_io(&s->iomem, obj, &omap_gpio_ops, &s->omap1, "omap.gpio", 0x1000); sysbus_init_mmio(sbd, &s->iomem); - return 0; } -static int omap2_gpio_init(SysBusDevice *sbd) +static void omap_gpio_realize(DeviceState *dev, Error **errp) +{ + struct omap_gpif_s *s = OMAP1_GPIO(dev); + + if (!s->clk) { + error_setg(errp, "omap-gpio: clk not connected"); + } +} + +static void omap2_gpio_realize(DeviceState *dev, Error **errp) { - DeviceState *dev = DEVICE(sbd); struct omap2_gpif_s *s = OMAP2_GPIO(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); int i; if (!s->iclk) { - error_report("omap2-gpio: iclk not connected"); - return -1; + error_setg(errp, "omap2-gpio: iclk not connected"); + return; } s->modulecount = s->mpu_model < omap2430 ? 4 - : s->mpu_model < omap3430 ? 5 - : 6; - - for (i = 0; i < s->modulecount; i++) { - if (!s->fclk[i]) { - error_report("omap2-gpio: fclk%d not connected", i); - return -1; - } - } + : s->mpu_model < omap3430 ? 5 + : 6; if (s->mpu_model < omap3430) { - memory_region_init_io(&s->iomem, OBJECT(s), &omap2_gpif_top_ops, s, + memory_region_init_io(&s->iomem, OBJECT(dev), &omap2_gpif_top_ops, s, "omap2.gpio", 0x1000); sysbus_init_mmio(sbd, &s->iomem); } @@ -732,17 +731,20 @@ static int omap2_gpio_init(SysBusDevice *sbd) for (i = 0; i < s->modulecount; i++) { struct omap2_gpio_s *m = &s->modules[i]; + if (!s->fclk[i]) { + error_setg(errp, "omap2-gpio: fclk%d not connected", i); + return; + } + m->revision = (s->mpu_model < omap3430) ? 0x18 : 0x25; m->handler = &s->handler[i * 32]; sysbus_init_irq(sbd, &m->irq[0]); /* mpu irq */ sysbus_init_irq(sbd, &m->irq[1]); /* dsp irq */ sysbus_init_irq(sbd, &m->wkup); - memory_region_init_io(&m->iomem, OBJECT(s), &omap2_gpio_module_ops, m, + memory_region_init_io(&m->iomem, OBJECT(dev), &omap2_gpio_module_ops, m, "omap.gpio-module", 0x1000); sysbus_init_mmio(sbd, &m->iomem); } - - return 0; } /* Using qdev pointer properties for the clocks is not ideal. @@ -766,9 +768,8 @@ static Property omap_gpio_properties[] = { static void omap_gpio_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); - k->init = omap_gpio_init; + dc->realize = omap_gpio_realize; dc->reset = omap_gpif_reset; dc->props = omap_gpio_properties; /* Reason: pointer property "clk" */ @@ -779,6 +780,7 @@ static const TypeInfo omap_gpio_info = { .name = TYPE_OMAP1_GPIO, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(struct omap_gpif_s), + .instance_init = omap_gpio_init, .class_init = omap_gpio_class_init, }; @@ -797,9 +799,8 @@ static Property omap2_gpio_properties[] = { static void omap2_gpio_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); - k->init = omap2_gpio_init; + dc->realize = omap2_gpio_realize; dc->reset = omap2_gpif_reset; dc->props = omap2_gpio_properties; /* Reason: pointer properties "iclk", "fclk0", ..., "fclk5" */ diff --git a/hw/gpio/pl061.c b/hw/gpio/pl061.c index 44faeb2..4ae2aa1 100644 --- a/hw/gpio/pl061.c +++ b/hw/gpio/pl061.c @@ -341,20 +341,6 @@ static const MemoryRegionOps pl061_ops = { .endianness = DEVICE_NATIVE_ENDIAN, }; -static int pl061_initfn(SysBusDevice *sbd) -{ - DeviceState *dev = DEVICE(sbd); - PL061State *s = PL061(dev); - - memory_region_init_io(&s->iomem, OBJECT(s), &pl061_ops, s, "pl061", 0x1000); - sysbus_init_mmio(sbd, &s->iomem); - sysbus_init_irq(sbd, &s->irq); - qdev_init_gpio_in(dev, pl061_set_irq, 8); - qdev_init_gpio_out(dev, s->out, 8); - - return 0; -} - static void pl061_luminary_init(Object *obj) { PL061State *s = PL061(obj); @@ -366,17 +352,23 @@ static void pl061_luminary_init(Object *obj) static void pl061_init(Object *obj) { PL061State *s = PL061(obj); + DeviceState *dev = DEVICE(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); s->id = pl061_id; s->rsvd_start = 0x424; + + memory_region_init_io(&s->iomem, obj, &pl061_ops, s, "pl061", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); + qdev_init_gpio_in(dev, pl061_set_irq, 8); + qdev_init_gpio_out(dev, s->out, 8); } static void pl061_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); - k->init = pl061_initfn; dc->vmsd = &vmstate_pl061; dc->reset = &pl061_reset; } diff --git a/hw/gpio/zaurus.c b/hw/gpio/zaurus.c index 555da28..15865e1 100644 --- a/hw/gpio/zaurus.c +++ b/hw/gpio/zaurus.c @@ -167,19 +167,18 @@ static void scoop_gpio_set(void *opaque, int line, int level) s->gpio_level &= ~(1 << line); } -static int scoop_init(SysBusDevice *sbd) +static void scoop_init(Object *obj) { - DeviceState *dev = DEVICE(sbd); - ScoopInfo *s = SCOOP(dev); + DeviceState *dev = DEVICE(obj); + ScoopInfo *s = SCOOP(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); s->status = 0x02; qdev_init_gpio_out(dev, s->handler, 16); qdev_init_gpio_in(dev, scoop_gpio_set, 16); - memory_region_init_io(&s->iomem, OBJECT(s), &scoop_ops, s, "scoop", 0x1000); + memory_region_init_io(&s->iomem, obj, &scoop_ops, s, "scoop", 0x1000); sysbus_init_mmio(sbd, &s->iomem); - - return 0; } static int scoop_post_load(void *opaque, int version_id) @@ -239,9 +238,7 @@ static const VMStateDescription vmstate_scoop_regs = { static void scoop_sysbus_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); - k->init = scoop_init; dc->desc = "Scoop2 Sharp custom ASIC"; dc->vmsd = &vmstate_scoop_regs; } @@ -250,6 +247,7 @@ static const TypeInfo scoop_sysbus_info = { .name = TYPE_SCOOP, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(ScoopInfo), + .instance_init = scoop_init, .class_init = scoop_sysbus_class_init, }; diff --git a/hw/i2c/Makefile.objs b/hw/i2c/Makefile.objs index 1fd54ed..a081b8e 100644 --- a/hw/i2c/Makefile.objs +++ b/hw/i2c/Makefile.objs @@ -1,4 +1,5 @@ common-obj-y += core.o smbus.o smbus_eeprom.o +common-obj-$(CONFIG_DDC) += i2c-ddc.o common-obj-$(CONFIG_VERSATILE_I2C) += versatile_i2c.o common-obj-$(CONFIG_ACPI_X86) += smbus_ich9.o common-obj-$(CONFIG_APM) += pm_smbus.o diff --git a/hw/i2c/bitbang_i2c.c b/hw/i2c/bitbang_i2c.c index 6ed2060..d3a2989 100644 --- a/hw/i2c/bitbang_i2c.c +++ b/hw/i2c/bitbang_i2c.c @@ -210,13 +210,14 @@ static void bitbang_i2c_gpio_set(void *opaque, int irq, int level) } } -static int gpio_i2c_init(SysBusDevice *sbd) +static void gpio_i2c_init(Object *obj) { - DeviceState *dev = DEVICE(sbd); - GPIOI2CState *s = GPIO_I2C(dev); + DeviceState *dev = DEVICE(obj); + GPIOI2CState *s = GPIO_I2C(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); I2CBus *bus; - memory_region_init(&s->dummy_iomem, OBJECT(s), "gpio_i2c", 0); + memory_region_init(&s->dummy_iomem, obj, "gpio_i2c", 0); sysbus_init_mmio(sbd, &s->dummy_iomem); bus = i2c_init_bus(dev, "i2c"); @@ -224,16 +225,12 @@ static int gpio_i2c_init(SysBusDevice *sbd) qdev_init_gpio_in(dev, bitbang_i2c_gpio_set, 2); qdev_init_gpio_out(dev, &s->out, 1); - - return 0; } static void gpio_i2c_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); - k->init = gpio_i2c_init; set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); dc->desc = "Virtual GPIO to I2C bridge"; } @@ -242,6 +239,7 @@ static const TypeInfo gpio_i2c_info = { .name = TYPE_GPIO_I2C, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(GPIOI2CState), + .instance_init = gpio_i2c_init, .class_init = gpio_i2c_class_init, }; diff --git a/hw/i2c/core.c b/hw/i2c/core.c index ba22104..abb3efb 100644 --- a/hw/i2c/core.c +++ b/hw/i2c/core.c @@ -10,12 +10,19 @@ #include "qemu/osdep.h" #include "hw/i2c/i2c.h" +typedef struct I2CNode I2CNode; + +struct I2CNode { + I2CSlave *elt; + QLIST_ENTRY(I2CNode) next; +}; + struct I2CBus { BusState qbus; - I2CSlave *current_dev; - I2CSlave *dev; + QLIST_HEAD(, I2CNode) current_devs; uint8_t saved_address; + bool broadcast; }; static Property i2c_props[] = { @@ -36,17 +43,12 @@ static void i2c_bus_pre_save(void *opaque) { I2CBus *bus = opaque; - bus->saved_address = bus->current_dev ? bus->current_dev->address : -1; -} - -static int i2c_bus_post_load(void *opaque, int version_id) -{ - I2CBus *bus = opaque; - - /* The bus is loaded before attached devices, so load and save the - current device id. Devices will check themselves as loaded. */ - bus->current_dev = NULL; - return 0; + bus->saved_address = -1; + if (!QLIST_EMPTY(&bus->current_devs)) { + if (!bus->broadcast) { + bus->saved_address = QLIST_FIRST(&bus->current_devs)->elt->address; + } + } } static const VMStateDescription vmstate_i2c_bus = { @@ -54,9 +56,9 @@ static const VMStateDescription vmstate_i2c_bus = { .version_id = 1, .minimum_version_id = 1, .pre_save = i2c_bus_pre_save, - .post_load = i2c_bus_post_load, .fields = (VMStateField[]) { VMSTATE_UINT8(saved_address, I2CBus), + VMSTATE_BOOL(broadcast, I2CBus), VMSTATE_END_OF_LIST() } }; @@ -67,6 +69,7 @@ I2CBus *i2c_init_bus(DeviceState *parent, const char *name) I2CBus *bus; bus = I2C_BUS(qbus_create(TYPE_I2C_BUS, parent, name)); + QLIST_INIT(&bus->current_devs); vmstate_register(NULL, -1, &vmstate_i2c_bus, bus); return bus; } @@ -79,7 +82,7 @@ void i2c_set_slave_address(I2CSlave *dev, uint8_t address) /* Return nonzero if bus is busy. */ int i2c_bus_busy(I2CBus *bus) { - return bus->current_dev != NULL; + return !QLIST_EMPTY(&bus->current_devs); } /* Returns non-zero if the address is not valid. */ @@ -87,95 +90,127 @@ int i2c_bus_busy(I2CBus *bus) int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv) { BusChild *kid; - I2CSlave *slave = NULL; I2CSlaveClass *sc; + I2CNode *node; + + if (address == 0x00) { + /* + * This is a broadcast, the current_devs will be all the devices of the + * bus. + */ + bus->broadcast = true; + } QTAILQ_FOREACH(kid, &bus->qbus.children, sibling) { DeviceState *qdev = kid->child; I2CSlave *candidate = I2C_SLAVE(qdev); - if (candidate->address == address) { - slave = candidate; - break; + if ((candidate->address == address) || (bus->broadcast)) { + node = g_malloc(sizeof(struct I2CNode)); + node->elt = candidate; + QLIST_INSERT_HEAD(&bus->current_devs, node, next); + if (!bus->broadcast) { + break; + } } } - if (!slave) { + if (QLIST_EMPTY(&bus->current_devs)) { return 1; } - sc = I2C_SLAVE_GET_CLASS(slave); - /* If the bus is already busy, assume this is a repeated - start condition. */ - bus->current_dev = slave; - if (sc->event) { - sc->event(slave, recv ? I2C_START_RECV : I2C_START_SEND); + QLIST_FOREACH(node, &bus->current_devs, next) { + sc = I2C_SLAVE_GET_CLASS(node->elt); + /* If the bus is already busy, assume this is a repeated + start condition. */ + if (sc->event) { + sc->event(node->elt, recv ? I2C_START_RECV : I2C_START_SEND); + } } return 0; } void i2c_end_transfer(I2CBus *bus) { - I2CSlave *dev = bus->current_dev; I2CSlaveClass *sc; + I2CNode *node, *next; - if (!dev) { + if (QLIST_EMPTY(&bus->current_devs)) { return; } - sc = I2C_SLAVE_GET_CLASS(dev); - if (sc->event) { - sc->event(dev, I2C_FINISH); + QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) { + sc = I2C_SLAVE_GET_CLASS(node->elt); + if (sc->event) { + sc->event(node->elt, I2C_FINISH); + } + QLIST_REMOVE(node, next); + g_free(node); } - - bus->current_dev = NULL; + bus->broadcast = false; } -int i2c_send(I2CBus *bus, uint8_t data) +int i2c_send_recv(I2CBus *bus, uint8_t *data, bool send) { - I2CSlave *dev = bus->current_dev; I2CSlaveClass *sc; + I2CNode *node; + int ret = 0; + + if (send) { + QLIST_FOREACH(node, &bus->current_devs, next) { + sc = I2C_SLAVE_GET_CLASS(node->elt); + if (sc->send) { + ret = ret || sc->send(node->elt, *data); + } else { + ret = -1; + } + } + return ret ? -1 : 0; + } else { + if ((QLIST_EMPTY(&bus->current_devs)) || (bus->broadcast)) { + return -1; + } - if (!dev) { + sc = I2C_SLAVE_GET_CLASS(QLIST_FIRST(&bus->current_devs)->elt); + if (sc->recv) { + ret = sc->recv(QLIST_FIRST(&bus->current_devs)->elt); + if (ret < 0) { + return ret; + } else { + *data = ret; + return 0; + } + } return -1; } +} - sc = I2C_SLAVE_GET_CLASS(dev); - if (sc->send) { - return sc->send(dev, data); - } - - return -1; +int i2c_send(I2CBus *bus, uint8_t data) +{ + return i2c_send_recv(bus, &data, true); } int i2c_recv(I2CBus *bus) { - I2CSlave *dev = bus->current_dev; - I2CSlaveClass *sc; - - if (!dev) { - return -1; - } + uint8_t data; + int ret = i2c_send_recv(bus, &data, false); - sc = I2C_SLAVE_GET_CLASS(dev); - if (sc->recv) { - return sc->recv(dev); - } - - return -1; + return ret < 0 ? ret : data; } void i2c_nack(I2CBus *bus) { - I2CSlave *dev = bus->current_dev; I2CSlaveClass *sc; + I2CNode *node; - if (!dev) { + if (QLIST_EMPTY(&bus->current_devs)) { return; } - sc = I2C_SLAVE_GET_CLASS(dev); - if (sc->event) { - sc->event(dev, I2C_NACK); + QLIST_FOREACH(node, &bus->current_devs, next) { + sc = I2C_SLAVE_GET_CLASS(node->elt); + if (sc->event) { + sc->event(node->elt, I2C_NACK); + } } } @@ -183,9 +218,13 @@ static int i2c_slave_post_load(void *opaque, int version_id) { I2CSlave *dev = opaque; I2CBus *bus; + I2CNode *node; + bus = I2C_BUS(qdev_get_parent_bus(DEVICE(dev))); - if (bus->saved_address == dev->address) { - bus->current_dev = dev; + if ((bus->saved_address == dev->address) || (bus->broadcast)) { + node = g_malloc(sizeof(struct I2CNode)); + node->elt = dev; + QLIST_INSERT_HEAD(&bus->current_devs, node, next); } return 0; } diff --git a/hw/i2c/exynos4210_i2c.c b/hw/i2c/exynos4210_i2c.c index 8c2a2c1..c96fa7d 100644 --- a/hw/i2c/exynos4210_i2c.c +++ b/hw/i2c/exynos4210_i2c.c @@ -299,33 +299,32 @@ static void exynos4210_i2c_reset(DeviceState *d) s->scl_free = true; } -static int exynos4210_i2c_realize(SysBusDevice *sbd) +static void exynos4210_i2c_init(Object *obj) { - DeviceState *dev = DEVICE(sbd); - Exynos4210I2CState *s = EXYNOS4_I2C(dev); + DeviceState *dev = DEVICE(obj); + Exynos4210I2CState *s = EXYNOS4_I2C(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); - memory_region_init_io(&s->iomem, OBJECT(s), &exynos4210_i2c_ops, s, + memory_region_init_io(&s->iomem, obj, &exynos4210_i2c_ops, s, TYPE_EXYNOS4_I2C, EXYNOS4_I2C_MEM_SIZE); sysbus_init_mmio(sbd, &s->iomem); sysbus_init_irq(sbd, &s->irq); s->bus = i2c_init_bus(dev, "i2c"); - return 0; } static void exynos4210_i2c_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - SysBusDeviceClass *sbdc = SYS_BUS_DEVICE_CLASS(klass); dc->vmsd = &exynos4210_i2c_vmstate; dc->reset = exynos4210_i2c_reset; - sbdc->init = exynos4210_i2c_realize; } static const TypeInfo exynos4210_i2c_type_info = { .name = TYPE_EXYNOS4_I2C, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(Exynos4210I2CState), + .instance_init = exynos4210_i2c_init, .class_init = exynos4210_i2c_class_init, }; diff --git a/hw/i2c/i2c-ddc.c b/hw/i2c/i2c-ddc.c new file mode 100644 index 0000000..1227212 --- /dev/null +++ b/hw/i2c/i2c-ddc.c @@ -0,0 +1,308 @@ +/* A simple I2C slave for returning monitor EDID data via DDC. + * + * Copyright (c) 2011 Linaro Limited + * Written by Peter Maydell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/i2c/i2c.h" +#include "hw/i2c/i2c-ddc.h" + +#ifndef DEBUG_I2CDDC +#define DEBUG_I2CDDC 0 +#endif + +#define DPRINTF(fmt, ...) do { \ + if (DEBUG_I2CDDC) { \ + qemu_log("i2c-ddc: " fmt , ## __VA_ARGS__); \ + } \ +} while (0); + +/* Structure defining a monitor's characteristics in a + * readable format: this should be passed to build_edid_blob() + * to convert it into the 128 byte binary EDID blob. + * Not all bits of the EDID are customisable here. + */ +struct EDIDData { + char manuf_id[3]; /* three upper case letters */ + uint16_t product_id; + uint32_t serial_no; + uint8_t manuf_week; + int manuf_year; + uint8_t h_cm; + uint8_t v_cm; + uint8_t gamma; + char monitor_name[14]; + char serial_no_string[14]; + /* Range limits */ + uint8_t vmin; /* Hz */ + uint8_t vmax; /* Hz */ + uint8_t hmin; /* kHz */ + uint8_t hmax; /* kHz */ + uint8_t pixclock; /* MHz / 10 */ + uint8_t timing_data[18]; +}; + +typedef struct EDIDData EDIDData; + +/* EDID data for a simple LCD monitor */ +static const EDIDData lcd_edid = { + /* The manuf_id ought really to be an assigned EISA ID */ + .manuf_id = "QMU", + .product_id = 0, + .serial_no = 1, + .manuf_week = 1, + .manuf_year = 2011, + .h_cm = 40, + .v_cm = 30, + .gamma = 0x78, + .monitor_name = "QEMU monitor", + .serial_no_string = "1", + .vmin = 40, + .vmax = 120, + .hmin = 30, + .hmax = 100, + .pixclock = 18, + .timing_data = { + /* Borrowed from a 21" LCD */ + 0x48, 0x3f, 0x40, 0x30, 0x62, 0xb0, 0x32, 0x40, 0x40, + 0xc0, 0x13, 0x00, 0x98, 0x32, 0x11, 0x00, 0x00, 0x1e + } +}; + +static uint8_t manuf_char_to_int(char c) +{ + return (c - 'A') & 0x1f; +} + +static void write_ascii_descriptor_block(uint8_t *descblob, uint8_t blocktype, + const char *string) +{ + /* Write an EDID Descriptor Block of the "ascii string" type */ + int i; + descblob[0] = descblob[1] = descblob[2] = descblob[4] = 0; + descblob[3] = blocktype; + /* The rest is 13 bytes of ASCII; if less then the rest must + * be filled with newline then spaces + */ + for (i = 5; i < 19; i++) { + descblob[i] = string[i - 5]; + if (!descblob[i]) { + break; + } + } + if (i < 19) { + descblob[i++] = '\n'; + } + for ( ; i < 19; i++) { + descblob[i] = ' '; + } +} + +static void write_range_limits_descriptor(const EDIDData *edid, + uint8_t *descblob) +{ + int i; + descblob[0] = descblob[1] = descblob[2] = descblob[4] = 0; + descblob[3] = 0xfd; + descblob[5] = edid->vmin; + descblob[6] = edid->vmax; + descblob[7] = edid->hmin; + descblob[8] = edid->hmax; + descblob[9] = edid->pixclock; + descblob[10] = 0; + descblob[11] = 0xa; + for (i = 12; i < 19; i++) { + descblob[i] = 0x20; + } +} + +static void build_edid_blob(const EDIDData *edid, uint8_t *blob) +{ + /* Write an EDID 1.3 format blob (128 bytes) based + * on the EDIDData structure. + */ + int i; + uint8_t cksum; + + /* 00-07 : header */ + blob[0] = blob[7] = 0; + for (i = 1 ; i < 7; i++) { + blob[i] = 0xff; + } + /* 08-09 : manufacturer ID */ + blob[8] = (manuf_char_to_int(edid->manuf_id[0]) << 2) + | (manuf_char_to_int(edid->manuf_id[1]) >> 3); + blob[9] = (manuf_char_to_int(edid->manuf_id[1]) << 5) + | manuf_char_to_int(edid->manuf_id[2]); + /* 10-11 : product ID code */ + blob[10] = edid->product_id; + blob[11] = edid->product_id >> 8; + blob[12] = edid->serial_no; + blob[13] = edid->serial_no >> 8; + blob[14] = edid->serial_no >> 16; + blob[15] = edid->serial_no >> 24; + /* 16 : week of manufacture */ + blob[16] = edid->manuf_week; + /* 17 : year of manufacture - 1990 */ + blob[17] = edid->manuf_year - 1990; + /* 18, 19 : EDID version and revision */ + blob[18] = 1; + blob[19] = 3; + /* 20 - 24 : basic display parameters */ + /* We are always a digital display */ + blob[20] = 0x80; + /* 21, 22 : max h/v size in cm */ + blob[21] = edid->h_cm; + blob[22] = edid->v_cm; + /* 23 : gamma (divide by 100 then add 1 for actual value) */ + blob[23] = edid->gamma; + /* 24 feature support: no power management, RGB, preferred timing mode, + * standard colour space + */ + blob[24] = 0x0e; + /* 25 - 34 : chromaticity coordinates. These are the + * standard sRGB chromaticity values + */ + blob[25] = 0xee; + blob[26] = 0x91; + blob[27] = 0xa3; + blob[28] = 0x54; + blob[29] = 0x4c; + blob[30] = 0x99; + blob[31] = 0x26; + blob[32] = 0x0f; + blob[33] = 0x50; + blob[34] = 0x54; + /* 35, 36 : Established timings: claim to support everything */ + blob[35] = blob[36] = 0xff; + /* 37 : manufacturer's reserved timing: none */ + blob[37] = 0; + /* 38 - 53 : standard timing identification + * don't claim anything beyond what the 'established timings' + * already provide. Unused slots must be (0x1, 0x1) + */ + for (i = 38; i < 54; i++) { + blob[i] = 0x1; + } + /* 54 - 71 : descriptor block 1 : must be preferred timing data */ + memcpy(blob + 54, edid->timing_data, 18); + /* 72 - 89, 90 - 107, 108 - 125 : descriptor block 2, 3, 4 + * Order not important, but we must have a monitor name and a + * range limits descriptor. + */ + write_range_limits_descriptor(edid, blob + 72); + write_ascii_descriptor_block(blob + 90, 0xfc, edid->monitor_name); + write_ascii_descriptor_block(blob + 108, 0xff, edid->serial_no_string); + + /* 126 : extension flag */ + blob[126] = 0; + + cksum = 0; + for (i = 0; i < 127; i++) { + cksum += blob[i]; + } + /* 127 : checksum */ + blob[127] = -cksum; + if (DEBUG_I2CDDC) { + qemu_hexdump((char *)blob, stdout, "", 128); + } +} + +static void i2c_ddc_reset(DeviceState *ds) +{ + I2CDDCState *s = I2CDDC(ds); + + s->firstbyte = false; + s->reg = 0; +} + +static void i2c_ddc_event(I2CSlave *i2c, enum i2c_event event) +{ + I2CDDCState *s = I2CDDC(i2c); + + if (event == I2C_START_SEND) { + s->firstbyte = true; + } +} + +static int i2c_ddc_rx(I2CSlave *i2c) +{ + I2CDDCState *s = I2CDDC(i2c); + + int value; + value = s->edid_blob[s->reg]; + s->reg++; + return value; +} + +static int i2c_ddc_tx(I2CSlave *i2c, uint8_t data) +{ + I2CDDCState *s = I2CDDC(i2c); + if (s->firstbyte) { + s->reg = data; + s->firstbyte = false; + DPRINTF("[EDID] Written new pointer: %u\n", data); + return 1; + } + + /* Ignore all writes */ + s->reg++; + return 1; +} + +static void i2c_ddc_init(Object *obj) +{ + I2CDDCState *s = I2CDDC(obj); + build_edid_blob(&lcd_edid, s->edid_blob); +} + +static const VMStateDescription vmstate_i2c_ddc = { + .name = TYPE_I2CDDC, + .version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_BOOL(firstbyte, I2CDDCState), + VMSTATE_UINT8(reg, I2CDDCState), + VMSTATE_END_OF_LIST() + } +}; + +static void i2c_ddc_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + I2CSlaveClass *isc = I2C_SLAVE_CLASS(oc); + + dc->reset = i2c_ddc_reset; + dc->vmsd = &vmstate_i2c_ddc; + isc->event = i2c_ddc_event; + isc->recv = i2c_ddc_rx; + isc->send = i2c_ddc_tx; +} + +static TypeInfo i2c_ddc_info = { + .name = TYPE_I2CDDC, + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(I2CDDCState), + .instance_init = i2c_ddc_init, + .class_init = i2c_ddc_class_init +}; + +static void ddc_register_devices(void) +{ + type_register_static(&i2c_ddc_info); +} + +type_init(ddc_register_devices); diff --git a/hw/i2c/omap_i2c.c b/hw/i2c/omap_i2c.c index 67fbbff..f7c92ea 100644 --- a/hw/i2c/omap_i2c.c +++ b/hw/i2c/omap_i2c.c @@ -22,6 +22,7 @@ #include "hw/arm/omap.h" #include "hw/sysbus.h" #include "qemu/error-report.h" +#include "qapi/error.h" #define TYPE_OMAP_I2C "omap_i2c" #define OMAP_I2C(obj) OBJECT_CHECK(OMAPI2CState, (obj), TYPE_OMAP_I2C) @@ -445,29 +446,35 @@ static const MemoryRegionOps omap_i2c_ops = { .endianness = DEVICE_NATIVE_ENDIAN, }; -static int omap_i2c_init(SysBusDevice *sbd) +static void omap_i2c_init(Object *obj) +{ + DeviceState *dev = DEVICE(obj); + OMAPI2CState *s = OMAP_I2C(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + sysbus_init_irq(sbd, &s->irq); + sysbus_init_irq(sbd, &s->drq[0]); + sysbus_init_irq(sbd, &s->drq[1]); + sysbus_init_mmio(sbd, &s->iomem); + s->bus = i2c_init_bus(dev, NULL); +} + +static void omap_i2c_realize(DeviceState *dev, Error **errp) { - DeviceState *dev = DEVICE(sbd); OMAPI2CState *s = OMAP_I2C(dev); + memory_region_init_io(&s->iomem, OBJECT(dev), &omap_i2c_ops, s, "omap.i2c", + (s->revision < OMAP2_INTR_REV) ? 0x800 : 0x1000); + if (!s->fclk) { - error_report("omap_i2c: fclk not connected"); - return -1; + error_setg(errp, "omap_i2c: fclk not connected"); + return; } if (s->revision >= OMAP2_INTR_REV && !s->iclk) { /* Note that OMAP1 doesn't have a separate interface clock */ - error_report("omap_i2c: iclk not connected"); - return -1; + error_setg(errp, "omap_i2c: iclk not connected"); + return; } - - sysbus_init_irq(sbd, &s->irq); - sysbus_init_irq(sbd, &s->drq[0]); - sysbus_init_irq(sbd, &s->drq[1]); - memory_region_init_io(&s->iomem, OBJECT(s), &omap_i2c_ops, s, "omap.i2c", - (s->revision < OMAP2_INTR_REV) ? 0x800 : 0x1000); - sysbus_init_mmio(sbd, &s->iomem); - s->bus = i2c_init_bus(dev, NULL); - return 0; } static Property omap_i2c_properties[] = { @@ -480,18 +487,19 @@ static Property omap_i2c_properties[] = { static void omap_i2c_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); - k->init = omap_i2c_init; + dc->props = omap_i2c_properties; dc->reset = omap_i2c_reset; /* Reason: pointer properties "iclk", "fclk" */ dc->cannot_instantiate_with_device_add_yet = true; + dc->realize = omap_i2c_realize; } static const TypeInfo omap_i2c_info = { .name = TYPE_OMAP_I2C, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(OMAPI2CState), + .instance_init = omap_i2c_init, .class_init = omap_i2c_class_init, }; diff --git a/hw/i2c/versatile_i2c.c b/hw/i2c/versatile_i2c.c index 0bce524..da9f298 100644 --- a/hw/i2c/versatile_i2c.c +++ b/hw/i2c/versatile_i2c.c @@ -79,32 +79,25 @@ static const MemoryRegionOps versatile_i2c_ops = { .endianness = DEVICE_NATIVE_ENDIAN, }; -static int versatile_i2c_init(SysBusDevice *sbd) +static void versatile_i2c_init(Object *obj) { - DeviceState *dev = DEVICE(sbd); - VersatileI2CState *s = VERSATILE_I2C(dev); + DeviceState *dev = DEVICE(obj); + VersatileI2CState *s = VERSATILE_I2C(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); I2CBus *bus; bus = i2c_init_bus(dev, "i2c"); s->bitbang = bitbang_i2c_init(bus); - memory_region_init_io(&s->iomem, OBJECT(s), &versatile_i2c_ops, s, + memory_region_init_io(&s->iomem, obj, &versatile_i2c_ops, s, "versatile_i2c", 0x1000); sysbus_init_mmio(sbd, &s->iomem); - return 0; -} - -static void versatile_i2c_class_init(ObjectClass *klass, void *data) -{ - SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); - - k->init = versatile_i2c_init; } static const TypeInfo versatile_i2c_info = { .name = TYPE_VERSATILE_I2C, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(VersatileI2CState), - .class_init = versatile_i2c_class_init, + .instance_init = versatile_i2c_init, }; static void versatile_i2c_register_types(void) diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs index bc0dd2c..ffb49c1 100644 --- a/hw/misc/Makefile.objs +++ b/hw/misc/Makefile.objs @@ -51,3 +51,4 @@ obj-$(CONFIG_MIPS_ITU) += mips_itu.o obj-$(CONFIG_PVPANIC) += pvpanic.o obj-$(CONFIG_EDU) += edu.o obj-$(CONFIG_HYPERV_TESTDEV) += hyperv_testdev.o +obj-$(CONFIG_AUX) += aux.o diff --git a/hw/misc/arm_l2x0.c b/hw/misc/arm_l2x0.c index 4442227..66a0787 100644 --- a/hw/misc/arm_l2x0.c +++ b/hw/misc/arm_l2x0.c @@ -159,14 +159,14 @@ static const MemoryRegionOps l2x0_mem_ops = { .endianness = DEVICE_NATIVE_ENDIAN, }; -static int l2x0_priv_init(SysBusDevice *dev) +static void l2x0_priv_init(Object *obj) { - L2x0State *s = ARM_L2X0(dev); + L2x0State *s = ARM_L2X0(obj); + SysBusDevice *dev = SYS_BUS_DEVICE(obj); - memory_region_init_io(&s->iomem, OBJECT(dev), &l2x0_mem_ops, s, + memory_region_init_io(&s->iomem, obj, &l2x0_mem_ops, s, "l2x0_cc", 0x1000); sysbus_init_mmio(dev, &s->iomem); - return 0; } static Property l2x0_properties[] = { @@ -176,10 +176,8 @@ static Property l2x0_properties[] = { static void l2x0_class_init(ObjectClass *klass, void *data) { - SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); - k->init = l2x0_priv_init; dc->vmsd = &vmstate_l2x0; dc->props = l2x0_properties; dc->reset = l2x0_priv_reset; @@ -189,6 +187,7 @@ static const TypeInfo l2x0_info = { .name = TYPE_ARM_L2X0, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(L2x0State), + .instance_init = l2x0_priv_init, .class_init = l2x0_class_init, }; diff --git a/hw/misc/aux.c b/hw/misc/aux.c new file mode 100644 index 0000000..25d7712 --- /dev/null +++ b/hw/misc/aux.c @@ -0,0 +1,292 @@ +/* + * aux.c + * + * Copyright 2015 : GreenSocs Ltd + * http://www.greensocs.com/ , email: info@greensocs.com + * + * Developed by : + * Frederic Konrad <fred.konrad@greensocs.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option)any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +/* + * This is an implementation of the AUX bus for VESA Display Port v1.1a. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/misc/aux.h" +#include "hw/i2c/i2c.h" +#include "monitor/monitor.h" + +#ifndef DEBUG_AUX +#define DEBUG_AUX 0 +#endif + +#define DPRINTF(fmt, ...) do { \ + if (DEBUG_AUX) { \ + qemu_log("aux: " fmt , ## __VA_ARGS__); \ + } \ +} while (0); + +#define TYPE_AUXTOI2C "aux-to-i2c-bridge" +#define AUXTOI2C(obj) OBJECT_CHECK(AUXTOI2CState, (obj), TYPE_AUXTOI2C) + +static void aux_slave_dev_print(Monitor *mon, DeviceState *dev, int indent); +static inline I2CBus *aux_bridge_get_i2c_bus(AUXTOI2CState *bridge); + +/* aux-bus implementation (internal not public) */ +static void aux_bus_class_init(ObjectClass *klass, void *data) +{ + BusClass *k = BUS_CLASS(klass); + + /* AUXSlave has an MMIO so we need to change the way we print information + * in monitor. + */ + k->print_dev = aux_slave_dev_print; +} + +AUXBus *aux_init_bus(DeviceState *parent, const char *name) +{ + AUXBus *bus; + + bus = AUX_BUS(qbus_create(TYPE_AUX_BUS, parent, name)); + bus->bridge = AUXTOI2C(qdev_create(BUS(bus), TYPE_AUXTOI2C)); + + /* Memory related. */ + bus->aux_io = g_malloc(sizeof(*bus->aux_io)); + memory_region_init(bus->aux_io, OBJECT(bus), "aux-io", (1 << 20)); + address_space_init(&bus->aux_addr_space, bus->aux_io, "aux-io"); + return bus; +} + +static void aux_bus_map_device(AUXBus *bus, AUXSlave *dev, hwaddr addr) +{ + memory_region_add_subregion(bus->aux_io, addr, dev->mmio); +} + +static bool aux_bus_is_bridge(AUXBus *bus, DeviceState *dev) +{ + return (dev == DEVICE(bus->bridge)); +} + +I2CBus *aux_get_i2c_bus(AUXBus *bus) +{ + return aux_bridge_get_i2c_bus(bus->bridge); +} + +AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address, + uint8_t len, uint8_t *data) +{ + AUXReply ret = AUX_NACK; + I2CBus *i2c_bus = aux_get_i2c_bus(bus); + size_t i; + bool is_write = false; + + DPRINTF("request at address 0x%" PRIX32 ", command %u, len %u\n", address, + cmd, len); + + switch (cmd) { + /* + * Forward the request on the AUX bus.. + */ + case WRITE_AUX: + case READ_AUX: + is_write = cmd == READ_AUX ? false : true; + for (i = 0; i < len; i++) { + if (!address_space_rw(&bus->aux_addr_space, address++, + MEMTXATTRS_UNSPECIFIED, data++, 1, + is_write)) { + ret = AUX_I2C_ACK; + } else { + ret = AUX_NACK; + break; + } + } + break; + /* + * Classic I2C transactions.. + */ + case READ_I2C: + case WRITE_I2C: + is_write = cmd == READ_I2C ? false : true; + if (i2c_bus_busy(i2c_bus)) { + i2c_end_transfer(i2c_bus); + } + + if (i2c_start_transfer(i2c_bus, address, is_write)) { + ret = AUX_I2C_NACK; + break; + } + + ret = AUX_I2C_ACK; + while (len > 0) { + if (i2c_send_recv(i2c_bus, data++, is_write) < 0) { + ret = AUX_I2C_NACK; + break; + } + len--; + } + i2c_end_transfer(i2c_bus); + break; + /* + * I2C MOT transactions. + * + * Here we send a start when: + * - We didn't start transaction yet. + * - We had a READ and we do a WRITE. + * - We changed the address. + */ + case WRITE_I2C_MOT: + case READ_I2C_MOT: + is_write = cmd == READ_I2C_MOT ? false : true; + if (!i2c_bus_busy(i2c_bus)) { + /* + * No transactions started.. + */ + if (i2c_start_transfer(i2c_bus, address, is_write)) { + ret = AUX_I2C_NACK; + break; + } + } else if ((address != bus->last_i2c_address) || + (bus->last_transaction != cmd)) { + /* + * Transaction started but we need to restart.. + */ + i2c_end_transfer(i2c_bus); + if (i2c_start_transfer(i2c_bus, address, is_write)) { + ret = AUX_I2C_NACK; + break; + } + } + + while (len > 0) { + if (i2c_send_recv(i2c_bus, data++, is_write) < 0) { + ret = AUX_I2C_NACK; + i2c_end_transfer(i2c_bus); + break; + } + len--; + } + bus->last_transaction = cmd; + bus->last_i2c_address = address; + ret = AUX_I2C_ACK; + break; + default: + DPRINTF("Not implemented!\n"); + return AUX_NACK; + } + + DPRINTF("reply: %u\n", ret); + return ret; +} + +static const TypeInfo aux_bus_info = { + .name = TYPE_AUX_BUS, + .parent = TYPE_BUS, + .instance_size = sizeof(AUXBus), + .class_init = aux_bus_class_init +}; + +/* aux-i2c implementation (internal not public) */ +struct AUXTOI2CState { + /*< private >*/ + DeviceState parent_obj; + + /*< public >*/ + I2CBus *i2c_bus; +}; + +static void aux_bridge_init(Object *obj) +{ + AUXTOI2CState *s = AUXTOI2C(obj); + + s->i2c_bus = i2c_init_bus(DEVICE(obj), "aux-i2c"); +} + +static inline I2CBus *aux_bridge_get_i2c_bus(AUXTOI2CState *bridge) +{ + return bridge->i2c_bus; +} + +static const TypeInfo aux_to_i2c_type_info = { + .name = TYPE_AUXTOI2C, + .parent = TYPE_DEVICE, + .instance_size = sizeof(AUXTOI2CState), + .instance_init = aux_bridge_init +}; + +/* aux-slave implementation */ +static void aux_slave_dev_print(Monitor *mon, DeviceState *dev, int indent) +{ + AUXBus *bus = AUX_BUS(qdev_get_parent_bus(dev)); + AUXSlave *s; + + /* Don't print anything if the device is I2C "bridge". */ + if (aux_bus_is_bridge(bus, dev)) { + return; + } + + s = AUX_SLAVE(dev); + + monitor_printf(mon, "%*smemory " TARGET_FMT_plx "/" TARGET_FMT_plx "\n", + indent, "", + object_property_get_int(OBJECT(s->mmio), "addr", NULL), + memory_region_size(s->mmio)); +} + +DeviceState *aux_create_slave(AUXBus *bus, const char *type, uint32_t addr) +{ + DeviceState *dev; + + dev = DEVICE(object_new(type)); + assert(dev); + qdev_set_parent_bus(dev, &bus->qbus); + qdev_init_nofail(dev); + aux_bus_map_device(AUX_BUS(qdev_get_parent_bus(dev)), AUX_SLAVE(dev), addr); + return dev; +} + +void aux_init_mmio(AUXSlave *aux_slave, MemoryRegion *mmio) +{ + assert(!aux_slave->mmio); + aux_slave->mmio = mmio; +} + +static void aux_slave_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + + set_bit(DEVICE_CATEGORY_MISC, k->categories); + k->bus_type = TYPE_AUX_BUS; +} + +static const TypeInfo aux_slave_type_info = { + .name = TYPE_AUX_SLAVE, + .parent = TYPE_DEVICE, + .instance_size = sizeof(AUXSlave), + .abstract = true, + .class_init = aux_slave_class_init, +}; + +static void aux_register_types(void) +{ + type_register_static(&aux_bus_info); + type_register_static(&aux_slave_type_info); + type_register_static(&aux_to_i2c_type_info); +} + +type_init(aux_register_types) diff --git a/hw/misc/exynos4210_pmu.c b/hw/misc/exynos4210_pmu.c index 889abad..e30dbc7 100644 --- a/hw/misc/exynos4210_pmu.c +++ b/hw/misc/exynos4210_pmu.c @@ -457,15 +457,15 @@ static void exynos4210_pmu_reset(DeviceState *dev) } } -static int exynos4210_pmu_init(SysBusDevice *dev) +static void exynos4210_pmu_init(Object *obj) { - Exynos4210PmuState *s = EXYNOS4210_PMU(dev); + Exynos4210PmuState *s = EXYNOS4210_PMU(obj); + SysBusDevice *dev = SYS_BUS_DEVICE(obj); /* memory mapping */ - memory_region_init_io(&s->iomem, OBJECT(dev), &exynos4210_pmu_ops, s, + memory_region_init_io(&s->iomem, obj, &exynos4210_pmu_ops, s, "exynos4210.pmu", EXYNOS4210_PMU_REGS_MEM_SIZE); sysbus_init_mmio(dev, &s->iomem); - return 0; } static const VMStateDescription exynos4210_pmu_vmstate = { @@ -481,9 +481,7 @@ static const VMStateDescription exynos4210_pmu_vmstate = { static void exynos4210_pmu_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); - k->init = exynos4210_pmu_init; dc->reset = exynos4210_pmu_reset; dc->vmsd = &exynos4210_pmu_vmstate; } @@ -492,6 +490,7 @@ static const TypeInfo exynos4210_pmu_info = { .name = TYPE_EXYNOS4210_PMU, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(Exynos4210PmuState), + .instance_init = exynos4210_pmu_init, .class_init = exynos4210_pmu_class_init, }; diff --git a/hw/misc/mst_fpga.c b/hw/misc/mst_fpga.c index 48d7dfb..a10f049 100644 --- a/hw/misc/mst_fpga.c +++ b/hw/misc/mst_fpga.c @@ -200,10 +200,11 @@ static int mst_fpga_post_load(void *opaque, int version_id) return 0; } -static int mst_fpga_init(SysBusDevice *sbd) +static void mst_fpga_init(Object *obj) { - DeviceState *dev = DEVICE(sbd); - mst_irq_state *s = MAINSTONE_FPGA(dev); + DeviceState *dev = DEVICE(obj); + mst_irq_state *s = MAINSTONE_FPGA(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); s->pcmcia0 = MST_PCMCIAx_READY | MST_PCMCIAx_nCD; s->pcmcia1 = MST_PCMCIAx_READY | MST_PCMCIAx_nCD; @@ -213,10 +214,9 @@ static int mst_fpga_init(SysBusDevice *sbd) /* alloc the external 16 irqs */ qdev_init_gpio_in(dev, mst_fpga_set_irq, MST_NUM_IRQS); - memory_region_init_io(&s->iomem, OBJECT(s), &mst_fpga_ops, s, + memory_region_init_io(&s->iomem, obj, &mst_fpga_ops, s, "fpga", 0x00100000); sysbus_init_mmio(sbd, &s->iomem); - return 0; } static VMStateDescription vmstate_mst_fpga_regs = { @@ -245,9 +245,7 @@ static VMStateDescription vmstate_mst_fpga_regs = { static void mst_fpga_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); - k->init = mst_fpga_init; dc->desc = "Mainstone II FPGA"; dc->vmsd = &vmstate_mst_fpga_regs; } @@ -256,6 +254,7 @@ static const TypeInfo mst_fpga_info = { .name = TYPE_MAINSTONE_FPGA, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(mst_irq_state), + .instance_init = mst_fpga_init, .class_init = mst_fpga_class_init, }; diff --git a/hw/sd/pl181.c b/hw/sd/pl181.c index eb783c6..82c63a4 100644 --- a/hw/sd/pl181.c +++ b/hw/sd/pl181.c @@ -13,6 +13,7 @@ #include "hw/sysbus.h" #include "hw/sd/sd.h" #include "qemu/log.h" +#include "qapi/error.h" //#define DEBUG_PL181 1 @@ -481,43 +482,48 @@ static void pl181_reset(DeviceState *d) sd_set_cb(s->card, s->cardstatus[0], s->cardstatus[1]); } -static int pl181_init(SysBusDevice *sbd) +static void pl181_init(Object *obj) { - DeviceState *dev = DEVICE(sbd); - PL181State *s = PL181(dev); - DriveInfo *dinfo; + DeviceState *dev = DEVICE(obj); + PL181State *s = PL181(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); - memory_region_init_io(&s->iomem, OBJECT(s), &pl181_ops, s, "pl181", 0x1000); + memory_region_init_io(&s->iomem, obj, &pl181_ops, s, "pl181", 0x1000); sysbus_init_mmio(sbd, &s->iomem); sysbus_init_irq(sbd, &s->irq[0]); sysbus_init_irq(sbd, &s->irq[1]); qdev_init_gpio_out(dev, s->cardstatus, 2); +} + +static void pl181_realize(DeviceState *dev, Error **errp) +{ + PL181State *s = PL181(dev); + DriveInfo *dinfo; + /* FIXME use a qdev drive property instead of drive_get_next() */ dinfo = drive_get_next(IF_SD); s->card = sd_init(dinfo ? blk_by_legacy_dinfo(dinfo) : NULL, false); if (s->card == NULL) { - return -1; + error_setg(errp, "sd_init failed"); } - - return 0; } static void pl181_class_init(ObjectClass *klass, void *data) { - SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); DeviceClass *k = DEVICE_CLASS(klass); - sdc->init = pl181_init; k->vmsd = &vmstate_pl181; k->reset = pl181_reset; /* Reason: init() method uses drive_get_next() */ k->cannot_instantiate_with_device_add_yet = true; + k->realize = pl181_realize; } static const TypeInfo pl181_info = { .name = TYPE_PL181, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(PL181State), + .instance_init = pl181_init, .class_init = pl181_class_init, }; diff --git a/include/hw/arm/virt.h b/include/hw/arm/virt.h index 82703d2..9650193 100644 --- a/include/hw/arm/virt.h +++ b/include/hw/arm/virt.h @@ -41,6 +41,10 @@ #define ARCH_TIMER_NS_EL1_IRQ 14 #define ARCH_TIMER_NS_EL2_IRQ 10 +#define VIRTUAL_PMU_IRQ 7 + +#define PPI(irq) ((irq) + 16) + enum { VIRT_FLASH, VIRT_MEM, diff --git a/include/hw/arm/xlnx-zynqmp.h b/include/hw/arm/xlnx-zynqmp.h index 68f6eb0..c2931bf 100644 --- a/include/hw/arm/xlnx-zynqmp.h +++ b/include/hw/arm/xlnx-zynqmp.h @@ -26,6 +26,8 @@ #include "hw/ide/ahci.h" #include "hw/sd/sdhci.h" #include "hw/ssi/xilinx_spips.h" +#include "hw/dma/xlnx_dpdma.h" +#include "hw/display/xlnx_dp.h" #define TYPE_XLNX_ZYNQMP "xlnx,zynqmp" #define XLNX_ZYNQMP(obj) OBJECT_CHECK(XlnxZynqMPState, (obj), \ @@ -81,6 +83,8 @@ typedef struct XlnxZynqMPState { SysbusAHCIState sata; SDHCIState sdhci[XLNX_ZYNQMP_NUM_SDHCI]; XilinxSPIPS spi[XLNX_ZYNQMP_NUM_SPIS]; + XlnxDPState dp; + XlnxDPDMAState dpdma; char *boot_cpu; ARMCPU *boot_cpu_ptr; diff --git a/include/hw/display/dpcd.h b/include/hw/display/dpcd.h new file mode 100644 index 0000000..274dc2e --- /dev/null +++ b/include/hw/display/dpcd.h @@ -0,0 +1,105 @@ +/* + * dpcd.h + * + * Copyright (C)2015 : GreenSocs Ltd + * http://www.greensocs.com/ , email: info@greensocs.com + * + * Developed by : + * Frederic Konrad <fred.konrad@greensocs.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option)any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef DPCD_H +#define DPCD_H + +typedef struct DPCDState DPCDState; + +#define TYPE_DPCD "dpcd" +#define DPCD(obj) OBJECT_CHECK(DPCDState, (obj), TYPE_DPCD) + +/* DCPD Revision. */ +#define DPCD_REVISION 0x00 +#define DPCD_REV_1_0 0x10 +#define DPCD_REV_1_1 0x11 + +/* DCPD Max Link Rate. */ +#define DPCD_MAX_LINK_RATE 0x01 +#define DPCD_1_62GBPS 0x06 +#define DPCD_2_7GBPS 0x0A +#define DPCD_5_4GBPS 0x14 + +#define DPCD_MAX_LANE_COUNT 0x02 +#define DPCD_ONE_LANE 0x01 +#define DPCD_TWO_LANES 0x02 +#define DPCD_FOUR_LANES 0x04 + +/* DCPD Max down spread. */ +#define DPCD_UP_TO_0_5 0x01 +#define DPCD_NO_AUX_HANDSHAKE_LINK_TRAINING 0x40 + +/* DCPD Downstream port type. */ +#define DPCD_DISPLAY_PORT 0x00 +#define DPCD_ANALOG 0x02 +#define DPCD_DVI_HDMI 0x04 +#define DPCD_OTHER 0x06 + +/* DPCD Format conversion. */ +#define DPCD_FORMAT_CONVERSION 0x08 + +/* Main link channel coding. */ +#define DPCD_ANSI_8B_10B 0x01 + +/* Down stream port count. */ +#define DPCD_OUI_SUPPORTED 0x80 + +/* Receiver port capability. */ +#define DPCD_RECEIVE_PORT0_CAP_0 0x08 +#define DPCD_RECEIVE_PORT0_CAP_1 0x09 +#define DPCD_EDID_PRESENT 0x02 +#define DPCD_ASSOCIATED_TO_PRECEDING_PORT 0x04 + +/* Down stream port capability. */ +#define DPCD_CAP_DISPLAY_PORT 0x000 +#define DPCD_CAP_ANALOG_VGA 0x001 +#define DPCD_CAP_DVI 0x002 +#define DPCD_CAP_HDMI 0x003 +#define DPCD_CAP_OTHER 0x100 + +#define DPCD_LANE0_1_STATUS 0x202 +#define DPCD_LANE0_CR_DONE (1 << 0) +#define DPCD_LANE0_CHANNEL_EQ_DONE (1 << 1) +#define DPCD_LANE0_SYMBOL_LOCKED (1 << 2) +#define DPCD_LANE1_CR_DONE (1 << 4) +#define DPCD_LANE1_CHANNEL_EQ_DONE (1 << 5) +#define DPCD_LANE1_SYMBOL_LOCKED (1 << 6) + +#define DPCD_LANE2_3_STATUS 0x203 +#define DPCD_LANE2_CR_DONE (1 << 0) +#define DPCD_LANE2_CHANNEL_EQ_DONE (1 << 1) +#define DPCD_LANE2_SYMBOL_LOCKED (1 << 2) +#define DPCD_LANE3_CR_DONE (1 << 4) +#define DPCD_LANE3_CHANNEL_EQ_DONE (1 << 5) +#define DPCD_LANE3_SYMBOL_LOCKED (1 << 6) + +#define DPCD_LANE_ALIGN_STATUS_UPDATED 0x204 +#define DPCD_INTERLANE_ALIGN_DONE 0x01 +#define DPCD_DOWNSTREAM_PORT_STATUS_CHANGED 0x40 +#define DPCD_LINK_STATUS_UPDATED 0x80 + +#define DPCD_SINK_STATUS 0x205 +#define DPCD_RECEIVE_PORT_0_STATUS 0x01 + +#endif /* !DPCD_H */ diff --git a/include/hw/display/xlnx_dp.h b/include/hw/display/xlnx_dp.h new file mode 100644 index 0000000..d3a03f1 --- /dev/null +++ b/include/hw/display/xlnx_dp.h @@ -0,0 +1,109 @@ +/* + * xlnx_dp.h + * + * Copyright (C) 2015 : GreenSocs Ltd + * http://www.greensocs.com/ , email: info@greensocs.com + * + * Developed by : + * Frederic Konrad <fred.konrad@greensocs.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "hw/sysbus.h" +#include "ui/console.h" +#include "hw/misc/aux.h" +#include "hw/i2c/i2c.h" +#include "hw/display/dpcd.h" +#include "hw/i2c/i2c-ddc.h" +#include "qemu/fifo8.h" +#include "hw/dma/xlnx_dpdma.h" +#include "audio/audio.h" + +#ifndef XLNX_DP_H +#define XLNX_DP_H + +#define AUD_CHBUF_MAX_DEPTH 32768 +#define MAX_QEMU_BUFFER_SIZE 4096 + +#define DP_CORE_REG_ARRAY_SIZE (0x3AF >> 2) +#define DP_AVBUF_REG_ARRAY_SIZE (0x238 >> 2) +#define DP_VBLEND_REG_ARRAY_SIZE (0x1DF >> 2) +#define DP_AUDIO_REG_ARRAY_SIZE (0x50 >> 2) + +struct PixmanPlane { + pixman_format_code_t format; + DisplaySurface *surface; +}; + +typedef struct XlnxDPState { + /*< private >*/ + SysBusDevice parent_obj; + + /* < public >*/ + MemoryRegion container; + + uint32_t core_registers[DP_CORE_REG_ARRAY_SIZE]; + MemoryRegion core_iomem; + + uint32_t avbufm_registers[DP_AVBUF_REG_ARRAY_SIZE]; + MemoryRegion avbufm_iomem; + + uint32_t vblend_registers[DP_VBLEND_REG_ARRAY_SIZE]; + MemoryRegion vblend_iomem; + + uint32_t audio_registers[DP_AUDIO_REG_ARRAY_SIZE]; + MemoryRegion audio_iomem; + + QemuConsole *console; + + /* + * This is the planes used to display in console. When the blending is + * enabled bout_plane is displayed in console else it's g_plane. + */ + struct PixmanPlane g_plane; + struct PixmanPlane v_plane; + struct PixmanPlane bout_plane; + + QEMUSoundCard aud_card; + SWVoiceOut *amixer_output_stream; + int16_t audio_buffer_0[AUD_CHBUF_MAX_DEPTH]; + int16_t audio_buffer_1[AUD_CHBUF_MAX_DEPTH]; + size_t audio_data_available[2]; + int64_t temp_buffer[AUD_CHBUF_MAX_DEPTH]; + int16_t out_buffer[AUD_CHBUF_MAX_DEPTH]; + size_t byte_left; /* byte available in out_buffer. */ + size_t data_ptr; /* next byte to be sent to QEMU. */ + + /* Associated DPDMA controller. */ + XlnxDPDMAState *dpdma; + + qemu_irq irq; + + AUXBus *aux_bus; + Fifo8 rx_fifo; + Fifo8 tx_fifo; + + /* + * XXX: This should be in an other module. + */ + DPCDState *dpcd; + I2CDDCState *edid; +} XlnxDPState; + +#define TYPE_XLNX_DP "xlnx.v-dp" +#define XLNX_DP(obj) OBJECT_CHECK(XlnxDPState, (obj), TYPE_XLNX_DP) + +#endif /* !XLNX_DP_H */ diff --git a/include/hw/dma/xlnx_dpdma.h b/include/hw/dma/xlnx_dpdma.h new file mode 100644 index 0000000..ae571a0 --- /dev/null +++ b/include/hw/dma/xlnx_dpdma.h @@ -0,0 +1,85 @@ +/* + * xlnx_dpdma.h + * + * Copyright (C) 2015 : GreenSocs Ltd + * http://www.greensocs.com/ , email: info@greensocs.com + * + * Developed by : + * Frederic Konrad <fred.konrad@greensocs.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef XLNX_DPDMA_H +#define XLNX_DPDMA_H + +#include "hw/sysbus.h" +#include "ui/console.h" +#include "sysemu/dma.h" + +#define XLNX_DPDMA_REG_ARRAY_SIZE (0x1000 >> 2) + +struct XlnxDPDMAState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + MemoryRegion iomem; + uint32_t registers[XLNX_DPDMA_REG_ARRAY_SIZE]; + uint8_t *data[6]; + bool operation_finished[6]; + qemu_irq irq; +}; + +typedef struct XlnxDPDMAState XlnxDPDMAState; + +#define TYPE_XLNX_DPDMA "xlnx.dpdma" +#define XLNX_DPDMA(obj) OBJECT_CHECK(XlnxDPDMAState, (obj), TYPE_XLNX_DPDMA) + +/* + * xlnx_dpdma_start_operation: Start the operation on the specified channel. The + * DPDMA gets the current descriptor and retrieves + * data to the buffer specified by + * dpdma_set_host_data_location(). + * + * Returns The number of bytes transfered by the DPDMA or 0 if an error occured. + * + * @s The DPDMA state. + * @channel The channel to start. + */ +size_t xlnx_dpdma_start_operation(XlnxDPDMAState *s, uint8_t channel, + bool one_desc); + +/* + * xlnx_dpdma_set_host_data_location: Set the location in the host memory where + * to store the data out from the dma + * channel. + * + * @s The DPDMA state. + * @channel The channel associated to the pointer. + * @p The buffer where to store the data. + */ +/* XXX: add a maximum size arg and send an interrupt in case of overflow. */ +void xlnx_dpdma_set_host_data_location(XlnxDPDMAState *s, uint8_t channel, + void *p); + +/* + * xlnx_dpdma_trigger_vsync_irq: Trigger a VSYNC IRQ when the display is + * updated. + * + * @s The DPDMA state. + */ +void xlnx_dpdma_trigger_vsync_irq(XlnxDPDMAState *s); + +#endif /* !XLNX_DPDMA_H */ diff --git a/include/hw/i2c/i2c-ddc.h b/include/hw/i2c/i2c-ddc.h new file mode 100644 index 0000000..cb8e62d --- /dev/null +++ b/include/hw/i2c/i2c-ddc.h @@ -0,0 +1,38 @@ +/* A simple I2C slave for returning monitor EDID data via DDC. + * + * Copyright (c) 2011 Linaro Limited + * Written by Peter Maydell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef I2C_DDC +#define I2C_DDC + +/* A simple I2C slave which just returns the contents of its EDID blob. */ + +struct I2CDDCState { + /*< private >*/ + I2CSlave i2c; + /*< public >*/ + bool firstbyte; + uint8_t reg; + uint8_t edid_blob[128]; +}; + +typedef struct I2CDDCState I2CDDCState; + +#define TYPE_I2CDDC "i2c-ddc" +#define I2CDDC(obj) OBJECT_CHECK(I2CDDCState, (obj), TYPE_I2CDDC) + +#endif /* !I2C_DDC */ diff --git a/include/hw/i2c/i2c.h b/include/hw/i2c/i2c.h index 4986ebc..c4085aa 100644 --- a/include/hw/i2c/i2c.h +++ b/include/hw/i2c/i2c.h @@ -56,6 +56,7 @@ int i2c_bus_busy(I2CBus *bus); int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv); void i2c_end_transfer(I2CBus *bus); void i2c_nack(I2CBus *bus); +int i2c_send_recv(I2CBus *bus, uint8_t *data, bool send); int i2c_send(I2CBus *bus, uint8_t data); int i2c_recv(I2CBus *bus); diff --git a/include/hw/misc/aux.h b/include/hw/misc/aux.h new file mode 100644 index 0000000..759c3bf --- /dev/null +++ b/include/hw/misc/aux.h @@ -0,0 +1,128 @@ +/* + * aux.h + * + * Copyright (C)2014 : GreenSocs Ltd + * http://www.greensocs.com/ , email: info@greensocs.com + * + * Developed by : + * Frederic Konrad <fred.konrad@greensocs.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option)any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef QEMU_AUX_H +#define QEMU_AUX_H + +#include "hw/qdev.h" + +typedef struct AUXBus AUXBus; +typedef struct AUXSlave AUXSlave; +typedef enum AUXCommand AUXCommand; +typedef enum AUXReply AUXReply; +typedef struct AUXTOI2CState AUXTOI2CState; + +enum AUXCommand { + WRITE_I2C = 0, + READ_I2C = 1, + WRITE_I2C_STATUS = 2, + WRITE_I2C_MOT = 4, + READ_I2C_MOT = 5, + WRITE_AUX = 8, + READ_AUX = 9 +}; + +enum AUXReply { + AUX_I2C_ACK = 0, + AUX_NACK = 1, + AUX_DEFER = 2, + AUX_I2C_NACK = 4, + AUX_I2C_DEFER = 8 +}; + +#define TYPE_AUX_BUS "aux-bus" +#define AUX_BUS(obj) OBJECT_CHECK(AUXBus, (obj), TYPE_AUX_BUS) + +struct AUXBus { + /* < private > */ + BusState qbus; + + /* < public > */ + AUXSlave *current_dev; + AUXSlave *dev; + uint32_t last_i2c_address; + AUXCommand last_transaction; + + AUXTOI2CState *bridge; + + MemoryRegion *aux_io; + AddressSpace aux_addr_space; +}; + +#define TYPE_AUX_SLAVE "aux-slave" +#define AUX_SLAVE(obj) \ + OBJECT_CHECK(AUXSlave, (obj), TYPE_AUX_SLAVE) + +struct AUXSlave { + /* < private > */ + DeviceState parent_obj; + + /* < public > */ + MemoryRegion *mmio; +}; + +/** + * aux_init_bus: Initialize an AUX bus. + * + * Returns the new AUX bus created. + * + * @parent The device where this bus is located. + * @name The name of the bus. + */ +AUXBus *aux_init_bus(DeviceState *parent, const char *name); + +/* + * aux_request: Make a request on the bus. + * + * Returns the reply of the request. + * + * @bus Ths bus where the request happen. + * @cmd The command requested. + * @address The 20bits address of the slave. + * @len The length of the read or write. + * @data The data array which will be filled or read during transfer. + */ +AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address, + uint8_t len, uint8_t *data); + +/* + * aux_get_i2c_bus: Get the i2c bus for I2C over AUX command. + * + * Returns the i2c bus associated to this AUX bus. + * + * @bus The AUX bus. + */ +I2CBus *aux_get_i2c_bus(AUXBus *bus); + +/* + * aux_init_mmio: Init an mmio for an AUX slave. + * + * @aux_slave The AUX slave. + * @mmio The mmio to be registered. + */ +void aux_init_mmio(AUXSlave *aux_slave, MemoryRegion *mmio); + +DeviceState *aux_create_slave(AUXBus *bus, const char *name, uint32_t addr); + +#endif /* !QEMU_AUX_H */ diff --git a/target-arm/cpu.h b/target-arm/cpu.h index 17d8051..942aa36 100644 --- a/target-arm/cpu.h +++ b/target-arm/cpu.h @@ -572,6 +572,8 @@ struct ARMCPU { bool powered_off; /* CPU has security extension */ bool has_el3; + /* CPU has PMU (Performance Monitor Unit) */ + bool has_pmu; /* CPU has memory protection unit */ bool has_mpu; diff --git a/target-arm/helper.c b/target-arm/helper.c index 862e780..c9730d6 100644 --- a/target-arm/helper.c +++ b/target-arm/helper.c @@ -3765,8 +3765,11 @@ static const ARMCPRegInfo el3_cp_reginfo[] = { .opc0 = 3, .opc1 = 6, .crn = 2, .crm = 0, .opc2 = 2, .access = PL3_RW, /* no .writefn needed as this can't cause an ASID change; - * no .raw_writefn or .resetfn needed as we never use mask/base_mask + * we must provide a .raw_writefn and .resetfn because we handle + * reset and migration for the AArch32 TTBCR(S), which might be + * using mask and base_mask. */ + .resetfn = vmsa_ttbcr_reset, .raw_writefn = vmsa_ttbcr_raw_write, .fieldoffset = offsetof(CPUARMState, cp15.tcr_el[3]) }, { .name = "ELR_EL3", .state = ARM_CP_STATE_AA64, .type = ARM_CP_ALIAS, diff --git a/target-arm/kvm32.c b/target-arm/kvm32.c index c03e3e5..c35c676 100644 --- a/target-arm/kvm32.c +++ b/target-arm/kvm32.c @@ -522,3 +522,9 @@ bool kvm_arm_hw_debug_active(CPUState *cs) { return false; } + +int kvm_arm_pmu_create(CPUState *cs, int irq) +{ + qemu_log_mask(LOG_UNIMP, "%s: not implemented\n", __func__); + return 0; +} diff --git a/target-arm/kvm64.c b/target-arm/kvm64.c index e2a34f6..2d6a310 100644 --- a/target-arm/kvm64.c +++ b/target-arm/kvm64.c @@ -382,6 +382,47 @@ static CPUWatchpoint *find_hw_watchpoint(CPUState *cpu, target_ulong addr) return NULL; } +static bool kvm_arm_pmu_support_ctrl(CPUState *cs, struct kvm_device_attr *attr) +{ + return kvm_vcpu_ioctl(cs, KVM_HAS_DEVICE_ATTR, attr) == 0; +} + +int kvm_arm_pmu_create(CPUState *cs, int irq) +{ + int err; + + struct kvm_device_attr attr = { + .group = KVM_ARM_VCPU_PMU_V3_CTRL, + .addr = (intptr_t)&irq, + .attr = KVM_ARM_VCPU_PMU_V3_IRQ, + .flags = 0, + }; + + if (!kvm_arm_pmu_support_ctrl(cs, &attr)) { + return 0; + } + + err = kvm_vcpu_ioctl(cs, KVM_SET_DEVICE_ATTR, &attr); + if (err < 0) { + fprintf(stderr, "KVM_SET_DEVICE_ATTR failed: %s\n", + strerror(-err)); + abort(); + } + + attr.group = KVM_ARM_VCPU_PMU_V3_CTRL; + attr.attr = KVM_ARM_VCPU_PMU_V3_INIT; + attr.addr = 0; + attr.flags = 0; + + err = kvm_vcpu_ioctl(cs, KVM_SET_DEVICE_ATTR, &attr); + if (err < 0) { + fprintf(stderr, "KVM_SET_DEVICE_ATTR failed: %s\n", + strerror(-err)); + abort(); + } + + return 1; +} static inline void set_feature(uint64_t *features, int feature) { @@ -461,6 +502,11 @@ int kvm_arch_init_vcpu(CPUState *cs) if (!arm_feature(&cpu->env, ARM_FEATURE_AARCH64)) { cpu->kvm_init_features[0] |= 1 << KVM_ARM_VCPU_EL1_32BIT; } + if (kvm_irqchip_in_kernel() && + kvm_check_extension(cs->kvm_state, KVM_CAP_ARM_PMU_V3)) { + cpu->has_pmu = true; + cpu->kvm_init_features[0] |= 1 << KVM_ARM_VCPU_PMU_V3; + } /* Do KVM_ARM_VCPU_INIT ioctl */ ret = kvm_arm_vcpu_init(cs); diff --git a/target-arm/kvm_arm.h b/target-arm/kvm_arm.h index 345233c..a419368 100644 --- a/target-arm/kvm_arm.h +++ b/target-arm/kvm_arm.h @@ -194,6 +194,8 @@ int kvm_arm_sync_mpstate_to_qemu(ARMCPU *cpu); int kvm_arm_vgic_probe(void); +int kvm_arm_pmu_create(CPUState *cs, int irq); + #else static inline int kvm_arm_vgic_probe(void) @@ -201,6 +203,11 @@ static inline int kvm_arm_vgic_probe(void) return 0; } +static inline int kvm_arm_pmu_create(CPUState *cs, int irq) +{ + return 0; +} + #endif static inline const char *gic_class_name(void) diff --git a/target-arm/translate.c b/target-arm/translate.c index 6815bc1a..3e71467 100644 --- a/target-arm/translate.c +++ b/target-arm/translate.c @@ -5311,6 +5311,30 @@ static int neon_2rm_is_float_op(int op) op >= NEON_2RM_VRECPE_F); } +static bool neon_2rm_is_v8_op(int op) +{ + /* Return true if this neon 2reg-misc op is ARMv8 and up */ + switch (op) { + case NEON_2RM_VRINTN: + case NEON_2RM_VRINTA: + case NEON_2RM_VRINTM: + case NEON_2RM_VRINTP: + case NEON_2RM_VRINTZ: + case NEON_2RM_VRINTX: + case NEON_2RM_VCVTAU: + case NEON_2RM_VCVTAS: + case NEON_2RM_VCVTNU: + case NEON_2RM_VCVTNS: + case NEON_2RM_VCVTPU: + case NEON_2RM_VCVTPS: + case NEON_2RM_VCVTMU: + case NEON_2RM_VCVTMS: + return true; + default: + return false; + } +} + /* Each entry in this array has bit n set if the insn allows * size value n (otherwise it will UNDEF). Since unallocated * op values will have no bits set they always UNDEF. @@ -6798,6 +6822,10 @@ static int disas_neon_data_insn(DisasContext *s, uint32_t insn) if ((neon_2rm_sizes[op] & (1 << size)) == 0) { return 1; } + if (neon_2rm_is_v8_op(op) && + !arm_dc_feature(s, ARM_FEATURE_V8)) { + return 1; + } if ((op != NEON_2RM_VMOVN && op != NEON_2RM_VQMOVN) && q && ((rm | rd) & 1)) { return 1; |