From 01fe6b6076c5a90e1a5f100f67968fcada8daff8 Mon Sep 17 00:00:00 2001 From: Shannon Zhao Date: Tue, 14 Jun 2016 15:59:12 +0100 Subject: hw/arm/virt: Add PMU node for virt machine Add a virtual PMU device for virt machine while use PPI 7 for PMU overflow interrupt number. Signed-off-by: Shannon Zhao Reviewed-by: Andrew Jones Message-id: 1465267577-1808-3-git-send-email-zhaoshenglong@huawei.com Signed-off-by: Peter Maydell --- include/hw/arm/virt.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'include/hw') diff --git a/include/hw/arm/virt.h b/include/hw/arm/virt.h index 82703d2ecd..9650193253 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, -- cgit 1.4.1 From 056fca7b51d949aa0e18e0eb647838874a53bcbe Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Tue, 14 Jun 2016 15:59:14 +0100 Subject: i2c: Factor our send() and recv() common logic Most of the control flow logic between send and recv (error checking etc) is the same. Factor this out into a common send_recv() API. This is then usable by clients, where the control logic for send and receive differs only by a boolean. E.g. if (send) i2c_send(...): else i2c_recv(...); becomes: i2c_send_recv(... , send); Signed-off-by: Peter Crosthwaite Reviewed-by: Alistair Francis Signed-off-by: KONRAD Frederic Message-id: 1465833014-21982-4-git-send-email-fred.konrad@greensocs.com Changes from FK: * Rebased on master. * Rebased on my i2c broadcast patch. Signed-off-by: KONRAD Frederic Reviewed-by: Alistair Francis Signed-off-by: Peter Maydell --- hw/i2c/core.c | 52 ++++++++++++++++++++++++++++++++++------------------ include/hw/i2c/i2c.h | 1 + 2 files changed, 35 insertions(+), 18 deletions(-) (limited to 'include/hw') diff --git a/hw/i2c/core.c b/hw/i2c/core.c index 1fa8268c30..abb3efb1db 100644 --- a/hw/i2c/core.c +++ b/hw/i2c/core.c @@ -149,36 +149,52 @@ void i2c_end_transfer(I2CBus *bus) bus->broadcast = false; } -int i2c_send(I2CBus *bus, uint8_t data) +int i2c_send_recv(I2CBus *bus, uint8_t *data, bool send) { I2CSlaveClass *sc; I2CNode *node; int ret = 0; - 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; + 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; } + + 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; } - return ret ? -1 : 0; } -int i2c_recv(I2CBus *bus) +int i2c_send(I2CBus *bus, uint8_t data) { - I2CSlaveClass *sc; + return i2c_send_recv(bus, &data, true); +} - if ((QLIST_EMPTY(&bus->current_devs)) || (bus->broadcast)) { - return -1; - } +int i2c_recv(I2CBus *bus) +{ + uint8_t data; + int ret = i2c_send_recv(bus, &data, false); - sc = I2C_SLAVE_GET_CLASS(QLIST_FIRST(&bus->current_devs)->elt); - if (sc->recv) { - return sc->recv(QLIST_FIRST(&bus->current_devs)->elt); - } - return -1; + return ret < 0 ? ret : data; } void i2c_nack(I2CBus *bus) diff --git a/include/hw/i2c/i2c.h b/include/hw/i2c/i2c.h index 4986ebc73c..c4085aa366 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); -- cgit 1.4.1 From 6fc7f77fd21f15f773bb18c1c4011949be9b4118 Mon Sep 17 00:00:00 2001 From: KONRAD Frederic Date: Tue, 14 Jun 2016 15:59:15 +0100 Subject: introduce aux-bus This introduces a new bus: aux-bus. It contains an address space for aux slaves devices and a bridge to an I2C bus for I2C through AUX transactions. Signed-off-by: KONRAD Frederic Tested-By: Hyun Kwon Reviewed-by: Alistair Francis Message-id: 1465833014-21982-5-git-send-email-fred.konrad@greensocs.com Signed-off-by: Peter Maydell --- default-configs/aarch64-softmmu.mak | 1 + hw/misc/Makefile.objs | 1 + hw/misc/aux.c | 292 ++++++++++++++++++++++++++++++++++++ include/hw/misc/aux.h | 128 ++++++++++++++++ 4 files changed, 422 insertions(+) create mode 100644 hw/misc/aux.c create mode 100644 include/hw/misc/aux.h (limited to 'include/hw') diff --git a/default-configs/aarch64-softmmu.mak b/default-configs/aarch64-softmmu.mak index 96dd994b3c..d3a2665911 100644 --- a/default-configs/aarch64-softmmu.mak +++ b/default-configs/aarch64-softmmu.mak @@ -3,4 +3,5 @@ # We support all the 32 bit boards so need all their config include arm-softmmu.mak +CONFIG_AUX=y CONFIG_XLNX_ZYNQMP=y diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs index bc0dd2cc75..ffb49c11ac 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/aux.c b/hw/misc/aux.c new file mode 100644 index 0000000000..25d7712398 --- /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 + * + * 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 . + * + */ + +/* + * 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/include/hw/misc/aux.h b/include/hw/misc/aux.h new file mode 100644 index 0000000000..759c3bf20c --- /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 + * + * 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 . + * + */ + +#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 */ -- cgit 1.4.1 From e27ed1bdd33402c9b5187bc0b30f8fd36b9e5202 Mon Sep 17 00:00:00 2001 From: KONRAD Frederic Date: Tue, 14 Jun 2016 15:59:15 +0100 Subject: introduce dpcd module This introduces dpcd module. It wires on a aux-bus and can be accessed by the driver to get lane-speed, etc. Signed-off-by: KONRAD Frederic Reviewed-by: Alistair Francis Reviewed-by: Peter Crosthwaite Tested-By: Hyun Kwon Message-id: 1465833014-21982-6-git-send-email-fred.konrad@greensocs.com Signed-off-by: Peter Maydell --- default-configs/aarch64-softmmu.mak | 1 + hw/display/Makefile.objs | 1 + hw/display/dpcd.c | 173 ++++++++++++++++++++++++++++++++++++ include/hw/display/dpcd.h | 105 ++++++++++++++++++++++ 4 files changed, 280 insertions(+) create mode 100644 hw/display/dpcd.c create mode 100644 include/hw/display/dpcd.h (limited to 'include/hw') diff --git a/default-configs/aarch64-softmmu.mak b/default-configs/aarch64-softmmu.mak index d3a2665911..87165b755b 100644 --- a/default-configs/aarch64-softmmu.mak +++ b/default-configs/aarch64-softmmu.mak @@ -4,4 +4,5 @@ include arm-softmmu.mak CONFIG_AUX=y +CONFIG_DPCD=y CONFIG_XLNX_ZYNQMP=y diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs index d99780eeb9..ddf3275d92 100644 --- a/hw/display/Makefile.objs +++ b/hw/display/Makefile.objs @@ -43,3 +43,4 @@ 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 diff --git a/hw/display/dpcd.c b/hw/display/dpcd.c new file mode 100644 index 0000000000..5a36855240 --- /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 + * + * 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 . + * + */ + +/* + * 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/include/hw/display/dpcd.h b/include/hw/display/dpcd.h new file mode 100644 index 0000000000..274dc2e42d --- /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 + * + * 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 . + * + */ + +#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 */ -- cgit 1.4.1 From 78c71af8049c40657b646d9dd722867fa15c0f1b Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Tue, 14 Jun 2016 15:59:15 +0100 Subject: hw/i2c-ddc.c: Implement DDC I2C slave Implement an I2C slave which implements DDC and returns the EDID data for an attached monitor. Signed-off-by: Peter Maydell Reviewed-by: Alistair Francis Tested-by: Hyun Kwon Signed-off-by: KONRAD Frederic Message-id: 1465833014-21982-7-git-send-email-fred.konrad@greensocs.com - Rebased on the current master. - Modified for QOM. Signed-off-by: KONRAD Frederic Reviewed-by: Alistair Francis Tested-By: Hyun Kwon [PMM: actually wire up the vmstate to dc->vmsd] Signed-off-by: Peter Maydell --- default-configs/aarch64-softmmu.mak | 1 + hw/i2c/Makefile.objs | 1 + hw/i2c/i2c-ddc.c | 308 ++++++++++++++++++++++++++++++++++++ include/hw/i2c/i2c-ddc.h | 38 +++++ 4 files changed, 348 insertions(+) create mode 100644 hw/i2c/i2c-ddc.c create mode 100644 include/hw/i2c/i2c-ddc.h (limited to 'include/hw') diff --git a/default-configs/aarch64-softmmu.mak b/default-configs/aarch64-softmmu.mak index 87165b755b..24494832cf 100644 --- a/default-configs/aarch64-softmmu.mak +++ b/default-configs/aarch64-softmmu.mak @@ -4,5 +4,6 @@ include arm-softmmu.mak CONFIG_AUX=y +CONFIG_DDC=y CONFIG_DPCD=y CONFIG_XLNX_ZYNQMP=y diff --git a/hw/i2c/Makefile.objs b/hw/i2c/Makefile.objs index 1fd54edf4c..a081b8ef2c 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/i2c-ddc.c b/hw/i2c/i2c-ddc.c new file mode 100644 index 0000000000..1227212934 --- /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 . + */ + +#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/include/hw/i2c/i2c-ddc.h b/include/hw/i2c/i2c-ddc.h new file mode 100644 index 0000000000..cb8e62d622 --- /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 . + */ + +#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 */ -- cgit 1.4.1 From d3c6369a96a8741523a617295306665773ca02e5 Mon Sep 17 00:00:00 2001 From: KONRAD Frederic Date: Tue, 14 Jun 2016 15:59:15 +0100 Subject: introduce xlnx-dpdma This is the implementation of the DPDMA. Signed-off-by: KONRAD Frederic Reviewed-by: Alistair Francis Tested-By: Hyun Kwon Message-id: 1465833014-21982-8-git-send-email-fred.konrad@greensocs.com Signed-off-by: Peter Maydell --- hw/dma/Makefile.objs | 1 + hw/dma/xlnx_dpdma.c | 786 ++++++++++++++++++++++++++++++++++++++++++++ include/hw/dma/xlnx_dpdma.h | 85 +++++ 3 files changed, 872 insertions(+) create mode 100644 hw/dma/xlnx_dpdma.c create mode 100644 include/hw/dma/xlnx_dpdma.h (limited to 'include/hw') diff --git a/hw/dma/Makefile.objs b/hw/dma/Makefile.objs index a1abbcf746..8b0823e593 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/xlnx_dpdma.c b/hw/dma/xlnx_dpdma.c new file mode 100644 index 0000000000..8ceb21ddb3 --- /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 + * + * 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 . + * + */ + +#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/include/hw/dma/xlnx_dpdma.h b/include/hw/dma/xlnx_dpdma.h new file mode 100644 index 0000000000..ae571a0b2f --- /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 + * + * 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 . + * + */ + +#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 */ -- cgit 1.4.1 From 58ac482a66de09a7590f705e53fc6a3fb8a055e8 Mon Sep 17 00:00:00 2001 From: KONRAD Frederic Date: Tue, 14 Jun 2016 15:59:15 +0100 Subject: introduce xlnx-dp This is the implementation of the DisplayPort. It has an aux-bus to access dpcd and edid. Graphic plane is connected to the channel 3. Video plane is connected to the channel 0. Audio stream are connected to the channels 4 and 5. Signed-off-by: KONRAD Frederic Tested-By: Hyun Kwon Reviewed-by: Alistair Francis Message-id: 1465833014-21982-9-git-send-email-fred.konrad@greensocs.com [PMM: fixed format strings] Signed-off-by: Peter Maydell --- hw/display/Makefile.objs | 1 + hw/display/xlnx_dp.c | 1336 ++++++++++++++++++++++++++++++++++++++++++ include/hw/display/xlnx_dp.h | 109 ++++ 3 files changed, 1446 insertions(+) create mode 100644 hw/display/xlnx_dp.c create mode 100644 include/hw/display/xlnx_dp.h (limited to 'include/hw') diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs index ddf3275d92..063889beaf 100644 --- a/hw/display/Makefile.objs +++ b/hw/display/Makefile.objs @@ -44,3 +44,4 @@ 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/xlnx_dp.c b/hw/display/xlnx_dp.c new file mode 100644 index 0000000000..be53b756c3 --- /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 + * + * 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 . + * + */ + +#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/include/hw/display/xlnx_dp.h b/include/hw/display/xlnx_dp.h new file mode 100644 index 0000000000..d3a03f118c --- /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 + * + * 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 . + * + */ + +#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 */ -- cgit 1.4.1 From b93dbcdd5993dd0800d18d494f60821a398fee8b Mon Sep 17 00:00:00 2001 From: KONRAD Frederic Date: Tue, 14 Jun 2016 15:59:15 +0100 Subject: arm: xlnx-zynqmp: Add xlnx-dp and xlnx-dpdma This adds the DP and the DPDMA to the Zynq MP platform. Signed-off-by: KONRAD Frederic Reviewed-by: Peter Crosthwaite Reviewed-by: Alistair Francis Tested-By: Hyun Kwon Message-id: 1465833014-21982-10-git-send-email-fred.konrad@greensocs.com Signed-off-by: Peter Maydell --- hw/arm/xlnx-zynqmp.c | 32 +++++++++++++++++++++++++++++++- include/hw/arm/xlnx-zynqmp.h | 4 ++++ 2 files changed, 35 insertions(+), 1 deletion(-) (limited to 'include/hw') diff --git a/hw/arm/xlnx-zynqmp.c b/hw/arm/xlnx-zynqmp.c index 308d6770c0..23c7199867 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/include/hw/arm/xlnx-zynqmp.h b/include/hw/arm/xlnx-zynqmp.h index 68f6eb0c4d..c2931bf39c 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; -- cgit 1.4.1