* [Qemu-devel] [PATCH V8 1/9] i2cbus: remove unused dev field
2016-06-06 14:21 [Qemu-devel] [PATCH V8 0/9] Xilinx DisplayPort fred.konrad
@ 2016-06-06 14:21 ` fred.konrad
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 2/9] i2c: implement broadcast write fred.konrad
` (7 subsequent siblings)
8 siblings, 0 replies; 14+ messages in thread
From: fred.konrad @ 2016-06-06 14:21 UTC (permalink / raw)
To: qemu-devel
Cc: peter.maydell, crosthwaitepeter, alistair.francis,
edgar.iglesias, hyunk, guillaume.delbergue, mark.burton,
fred.konrad
From: KONRAD Frederic <fred.konrad@greensocs.com>
The dev field in i2cbus is not used.
So just drop it.
Signed-off-by: KONRAD Frederic <fred.konrad@greensocs.com>
Reviewed-by: Alistair Francis <alistair.francis@xilinx.com>
Reviewed-by: Peter Crosthwaite <crosthwaite.peter@gmail.com>
Tested-By: Hyun Kwon <hyun.kwon@xilinx.com>
---
hw/i2c/core.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/hw/i2c/core.c b/hw/i2c/core.c
index ba22104..013ff68 100644
--- a/hw/i2c/core.c
+++ b/hw/i2c/core.c
@@ -14,7 +14,6 @@ struct I2CBus
{
BusState qbus;
I2CSlave *current_dev;
- I2CSlave *dev;
uint8_t saved_address;
};
--
1.8.3.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [Qemu-devel] [PATCH V8 2/9] i2c: implement broadcast write
2016-06-06 14:21 [Qemu-devel] [PATCH V8 0/9] Xilinx DisplayPort fred.konrad
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 1/9] i2cbus: remove unused dev field fred.konrad
@ 2016-06-06 14:21 ` fred.konrad
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 3/9] i2c: Factor our send() and recv() common logic fred.konrad
` (6 subsequent siblings)
8 siblings, 0 replies; 14+ messages in thread
From: fred.konrad @ 2016-06-06 14:21 UTC (permalink / raw)
To: qemu-devel
Cc: peter.maydell, crosthwaitepeter, alistair.francis,
edgar.iglesias, hyunk, guillaume.delbergue, mark.burton,
fred.konrad
From: KONRAD Frederic <fred.konrad@greensocs.com>
This does a write to every slaves when the I2C bus get a write to address 0.
Signed-off-by: KONRAD Frederic <fred.konrad@greensocs.com>
Reviewed-by: Alistair Francis <alistair.francis@xilinx.com>
Reviewed-by: Peter Crosthwaite <crosthwaite.peter@gmail.com>
Tested-By: Hyun Kwon <hyun.kwon@xilinx.com>
---
hw/i2c/core.c | 129 ++++++++++++++++++++++++++++++++++------------------------
1 file changed, 75 insertions(+), 54 deletions(-)
diff --git a/hw/i2c/core.c b/hw/i2c/core.c
index 013ff68..a3921d9 100644
--- a/hw/i2c/core.c
+++ b/hw/i2c/core.c
@@ -10,11 +10,19 @@
#include "qemu/osdep.h"
#include "hw/i2c/i2c.h"
+typedef struct I2CNode I2CNode;
+
+struct I2CNode {
+ I2CSlave *elt;
+ QLIST_ENTRY(I2CNode) next;
+};
+
struct I2CBus
{
BusState qbus;
- I2CSlave *current_dev;
+ QLIST_HEAD(, I2CNode) current_devs;
uint8_t saved_address;
+ bool broadcast;
};
static Property i2c_props[] = {
@@ -35,17 +43,12 @@ static void i2c_bus_pre_save(void *opaque)
{
I2CBus *bus = opaque;
- bus->saved_address = bus->current_dev ? bus->current_dev->address : -1;
-}
-
-static int i2c_bus_post_load(void *opaque, int version_id)
-{
- I2CBus *bus = opaque;
-
- /* The bus is loaded before attached devices, so load and save the
- current device id. Devices will check themselves as loaded. */
- bus->current_dev = NULL;
- return 0;
+ bus->saved_address = -1;
+ if (!QLIST_EMPTY(&bus->current_devs)) {
+ if (!bus->broadcast) {
+ bus->saved_address = QLIST_FIRST(&bus->current_devs)->elt->address;
+ }
+ }
}
static const VMStateDescription vmstate_i2c_bus = {
@@ -53,9 +56,9 @@ static const VMStateDescription vmstate_i2c_bus = {
.version_id = 1,
.minimum_version_id = 1,
.pre_save = i2c_bus_pre_save,
- .post_load = i2c_bus_post_load,
.fields = (VMStateField[]) {
VMSTATE_UINT8(saved_address, I2CBus),
+ VMSTATE_BOOL(broadcast, I2CBus),
VMSTATE_END_OF_LIST()
}
};
@@ -78,7 +81,7 @@ void i2c_set_slave_address(I2CSlave *dev, uint8_t address)
/* Return nonzero if bus is busy. */
int i2c_bus_busy(I2CBus *bus)
{
- return bus->current_dev != NULL;
+ return !QLIST_EMPTY(&bus->current_devs);
}
/* Returns non-zero if the address is not valid. */
@@ -86,95 +89,109 @@ int i2c_bus_busy(I2CBus *bus)
int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv)
{
BusChild *kid;
- I2CSlave *slave = NULL;
I2CSlaveClass *sc;
+ I2CNode *node;
+
+ if (address == 0x00) {
+ /*
+ * This is a broadcast, the current_devs will be all the devices of the
+ * bus.
+ */
+ bus->broadcast = true;
+ }
QTAILQ_FOREACH(kid, &bus->qbus.children, sibling) {
DeviceState *qdev = kid->child;
I2CSlave *candidate = I2C_SLAVE(qdev);
- if (candidate->address == address) {
- slave = candidate;
- break;
+ if ((candidate->address == address) || (bus->broadcast)) {
+ node = g_malloc(sizeof(struct I2CNode));
+ node->elt = candidate;
+ QLIST_INSERT_HEAD(&bus->current_devs, node, next);
+ if (!bus->broadcast) {
+ break;
+ }
}
}
- if (!slave) {
+ if (QLIST_EMPTY(&bus->current_devs)) {
return 1;
}
- sc = I2C_SLAVE_GET_CLASS(slave);
- /* If the bus is already busy, assume this is a repeated
- start condition. */
- bus->current_dev = slave;
- if (sc->event) {
- sc->event(slave, recv ? I2C_START_RECV : I2C_START_SEND);
+ QLIST_FOREACH(node, &bus->current_devs, next) {
+ sc = I2C_SLAVE_GET_CLASS(node->elt);
+ /* If the bus is already busy, assume this is a repeated
+ start condition. */
+ if (sc->event) {
+ sc->event(node->elt, recv ? I2C_START_RECV : I2C_START_SEND);
+ }
}
return 0;
}
void i2c_end_transfer(I2CBus *bus)
{
- I2CSlave *dev = bus->current_dev;
I2CSlaveClass *sc;
+ I2CNode *node;
- if (!dev) {
+ if (QLIST_EMPTY(&bus->current_devs)) {
return;
}
- sc = I2C_SLAVE_GET_CLASS(dev);
- if (sc->event) {
- sc->event(dev, I2C_FINISH);
+ QLIST_FOREACH(node, &bus->current_devs, next) {
+ sc = I2C_SLAVE_GET_CLASS(node->elt);
+ if (sc->event) {
+ sc->event(node->elt, I2C_FINISH);
+ }
+ QLIST_REMOVE(node, next);
+ g_free(node);
}
-
- bus->current_dev = NULL;
+ bus->broadcast = false;
}
int i2c_send(I2CBus *bus, uint8_t data)
{
- I2CSlave *dev = bus->current_dev;
I2CSlaveClass *sc;
+ I2CNode *node;
+ int ret = -1;
- if (!dev) {
- return -1;
- }
-
- sc = I2C_SLAVE_GET_CLASS(dev);
- if (sc->send) {
- return sc->send(dev, data);
+ QLIST_FOREACH(node, &bus->current_devs, next) {
+ sc = I2C_SLAVE_GET_CLASS(node->elt);
+ if (sc->send) {
+ ret |= sc->send(node->elt, data);
+ }
}
-
- return -1;
+ return ret;
}
int i2c_recv(I2CBus *bus)
{
- I2CSlave *dev = bus->current_dev;
I2CSlaveClass *sc;
- if (!dev) {
+ if ((QLIST_EMPTY(&bus->current_devs)) || (bus->broadcast)) {
return -1;
}
- sc = I2C_SLAVE_GET_CLASS(dev);
+ sc = I2C_SLAVE_GET_CLASS(QLIST_FIRST(&bus->current_devs)->elt);
if (sc->recv) {
- return sc->recv(dev);
+ return sc->recv(QLIST_FIRST(&bus->current_devs)->elt);
}
-
return -1;
}
void i2c_nack(I2CBus *bus)
{
- I2CSlave *dev = bus->current_dev;
I2CSlaveClass *sc;
+ I2CNode *node;
- if (!dev) {
+ if (QLIST_EMPTY(&bus->current_devs)) {
return;
}
- sc = I2C_SLAVE_GET_CLASS(dev);
- if (sc->event) {
- sc->event(dev, I2C_NACK);
+ QLIST_FOREACH(node, &bus->current_devs, next) {
+ sc = I2C_SLAVE_GET_CLASS(node->elt);
+ if (sc->event) {
+ sc->event(node->elt, I2C_NACK);
+ }
}
}
@@ -182,9 +199,13 @@ static int i2c_slave_post_load(void *opaque, int version_id)
{
I2CSlave *dev = opaque;
I2CBus *bus;
+ I2CNode *node;
+
bus = I2C_BUS(qdev_get_parent_bus(DEVICE(dev)));
- if (bus->saved_address == dev->address) {
- bus->current_dev = dev;
+ if ((bus->saved_address == dev->address) || (bus->broadcast)) {
+ node = g_malloc(sizeof(struct I2CNode));
+ node->elt = dev;
+ QLIST_INSERT_HEAD(&bus->current_devs, node, next);
}
return 0;
}
--
1.8.3.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [Qemu-devel] [PATCH V8 3/9] i2c: Factor our send() and recv() common logic
2016-06-06 14:21 [Qemu-devel] [PATCH V8 0/9] Xilinx DisplayPort fred.konrad
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 1/9] i2cbus: remove unused dev field fred.konrad
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 2/9] i2c: implement broadcast write fred.konrad
@ 2016-06-06 14:21 ` fred.konrad
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 4/9] introduce aux-bus fred.konrad
` (5 subsequent siblings)
8 siblings, 0 replies; 14+ messages in thread
From: fred.konrad @ 2016-06-06 14:21 UTC (permalink / raw)
To: qemu-devel
Cc: peter.maydell, crosthwaitepeter, alistair.francis,
edgar.iglesias, hyunk, guillaume.delbergue, mark.burton,
fred.konrad, Peter Crosthwaite
From: Peter Crosthwaite <crosthwaitepeter@gmail.com>
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 <crosthwaite.peter@gmail.com>
Changes from FK:
* Rebased on master.
* Rebased on my i2c broadcast patch.
Signed-off-by: KONRAD Frederic <fred.konrad@greensocs.com>
Reviewed-by: Alistair Francis <alistair.francis@xilinx.com>
---
hw/i2c/core.c | 48 ++++++++++++++++++++++++++++++++----------------
include/hw/i2c/i2c.h | 1 +
2 files changed, 33 insertions(+), 16 deletions(-)
diff --git a/hw/i2c/core.c b/hw/i2c/core.c
index a3921d9..a49138f 100644
--- a/hw/i2c/core.c
+++ b/hw/i2c/core.c
@@ -148,34 +148,50 @@ 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 = -1;
- QLIST_FOREACH(node, &bus->current_devs, next) {
- sc = I2C_SLAVE_GET_CLASS(node->elt);
- if (sc->send) {
- ret |= sc->send(node->elt, data);
+ if (send) {
+ QLIST_FOREACH(node, &bus->current_devs, next) {
+ sc = I2C_SLAVE_GET_CLASS(node->elt);
+ if (sc->send) {
+ ret |= sc->send(node->elt, *data);
+ }
+ }
+ return ret;
+ } 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;
}
-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 4986ebc..c4085aa 100644
--- a/include/hw/i2c/i2c.h
+++ b/include/hw/i2c/i2c.h
@@ -56,6 +56,7 @@ int i2c_bus_busy(I2CBus *bus);
int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv);
void i2c_end_transfer(I2CBus *bus);
void i2c_nack(I2CBus *bus);
+int i2c_send_recv(I2CBus *bus, uint8_t *data, bool send);
int i2c_send(I2CBus *bus, uint8_t data);
int i2c_recv(I2CBus *bus);
--
1.8.3.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [Qemu-devel] [PATCH V8 4/9] introduce aux-bus
2016-06-06 14:21 [Qemu-devel] [PATCH V8 0/9] Xilinx DisplayPort fred.konrad
` (2 preceding siblings ...)
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 3/9] i2c: Factor our send() and recv() common logic fred.konrad
@ 2016-06-06 14:21 ` fred.konrad
2016-06-06 18:41 ` Alistair Francis
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 5/9] introduce dpcd module fred.konrad
` (4 subsequent siblings)
8 siblings, 1 reply; 14+ messages in thread
From: fred.konrad @ 2016-06-06 14:21 UTC (permalink / raw)
To: qemu-devel
Cc: peter.maydell, crosthwaitepeter, alistair.francis,
edgar.iglesias, hyunk, guillaume.delbergue, mark.burton,
fred.konrad
From: KONRAD Frederic <fred.konrad@greensocs.com>
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 <fred.konrad@greensocs.com>
Tested-By: Hyun Kwon <hyun.kwon@xilinx.com>
---
default-configs/aarch64-softmmu.mak | 1 +
hw/misc/Makefile.objs | 1 +
hw/misc/aux.c | 297 ++++++++++++++++++++++++++++++++++++
include/hw/misc/aux.h | 125 +++++++++++++++
4 files changed, 424 insertions(+)
create mode 100644 hw/misc/aux.c
create mode 100644 include/hw/misc/aux.h
diff --git a/default-configs/aarch64-softmmu.mak b/default-configs/aarch64-softmmu.mak
index 96dd994..d3a2665 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 bc0dd2c..ffb49c1 100644
--- a/hw/misc/Makefile.objs
+++ b/hw/misc/Makefile.objs
@@ -51,3 +51,4 @@ obj-$(CONFIG_MIPS_ITU) += mips_itu.o
obj-$(CONFIG_PVPANIC) += pvpanic.o
obj-$(CONFIG_EDU) += edu.o
obj-$(CONFIG_HYPERV_TESTDEV) += hyperv_testdev.o
+obj-$(CONFIG_AUX) += aux.o
diff --git a/hw/misc/aux.c b/hw/misc/aux.c
new file mode 100644
index 0000000..6605224
--- /dev/null
+++ b/hw/misc/aux.c
@@ -0,0 +1,297 @@
+/*
+ * aux.c
+ *
+ * Copyright 2015 : GreenSocs Ltd
+ * http://www.greensocs.com/ , email: info@greensocs.com
+ *
+ * Developed by :
+ * Frederic Konrad <fred.konrad@greensocs.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option)any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * This is an implementation of the AUX bus for VESA Display Port v1.1a.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/misc/aux.h"
+#include "hw/i2c/i2c.h"
+#include "monitor/monitor.h"
+
+#ifndef DEBUG_AUX
+#define DEBUG_AUX 0
+#endif
+
+#define DPRINTF(fmt, ...) do { \
+ if (DEBUG_AUX) { \
+ qemu_log("aux: " fmt , ## __VA_ARGS__); \
+ } \
+} while (0);
+
+#define TYPE_AUXTOI2C "aux-to-i2c-bridge"
+#define AUXTOI2C(obj) OBJECT_CHECK(AUXTOI2CState, (obj), TYPE_AUXTOI2C)
+
+#define TYPE_AUX_BUS "aux-bus"
+#define AUX_BUS(obj) OBJECT_CHECK(AUXBus, (obj), TYPE_AUX_BUS)
+
+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:
+ is_write = true;
+ /* fallthrough */
+ case READ_AUX:
+ 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");
+ ret = AUX_NACK;
+ break;
+ }
+
+ 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 0000000..2cc30c2
--- /dev/null
+++ b/include/hw/misc/aux.h
@@ -0,0 +1,125 @@
+/*
+ * aux.h
+ *
+ * Copyright (C)2014 : GreenSocs Ltd
+ * http://www.greensocs.com/ , email: info@greensocs.com
+ *
+ * Developed by :
+ * Frederic Konrad <fred.konrad@greensocs.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option)any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QEMU_AUX_H
+#define QEMU_AUX_H
+
+#include "hw/qdev.h"
+
+typedef struct AUXBus AUXBus;
+typedef struct AUXSlave AUXSlave;
+typedef enum AUXCommand AUXCommand;
+typedef enum AUXReply AUXReply;
+typedef struct AUXTOI2CState AUXTOI2CState;
+
+enum AUXCommand {
+ WRITE_I2C = 0,
+ READ_I2C = 1,
+ WRITE_I2C_STATUS = 2,
+ WRITE_I2C_MOT = 4,
+ READ_I2C_MOT = 5,
+ WRITE_AUX = 8,
+ READ_AUX = 9
+};
+
+enum AUXReply {
+ AUX_I2C_ACK = 0,
+ AUX_NACK = 1,
+ AUX_DEFER = 2,
+ AUX_I2C_NACK = 4,
+ AUX_I2C_DEFER = 8
+};
+
+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 */
--
1.8.3.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [Qemu-devel] [PATCH V8 4/9] introduce aux-bus
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 4/9] introduce aux-bus fred.konrad
@ 2016-06-06 18:41 ` Alistair Francis
2016-06-07 7:02 ` KONRAD Frederic
0 siblings, 1 reply; 14+ messages in thread
From: Alistair Francis @ 2016-06-06 18:41 UTC (permalink / raw)
To: KONRAD Frédéric
Cc: qemu-devel@nongnu.org Developers, Edgar Iglesias, Peter Maydell,
hyunk, Mark Burton, Alistair Francis, Peter Crosthwaite,
Guillaume Delbergue
On Mon, Jun 6, 2016 at 7:21 AM, <fred.konrad@greensocs.com> wrote:
> From: KONRAD Frederic <fred.konrad@greensocs.com>
>
> 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 <fred.konrad@greensocs.com>
> Tested-By: Hyun Kwon <hyun.kwon@xilinx.com>
> ---
> default-configs/aarch64-softmmu.mak | 1 +
> hw/misc/Makefile.objs | 1 +
> hw/misc/aux.c | 297 ++++++++++++++++++++++++++++++++++++
> include/hw/misc/aux.h | 125 +++++++++++++++
> 4 files changed, 424 insertions(+)
> create mode 100644 hw/misc/aux.c
> create mode 100644 include/hw/misc/aux.h
>
> diff --git a/default-configs/aarch64-softmmu.mak b/default-configs/aarch64-softmmu.mak
> index 96dd994..d3a2665 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 bc0dd2c..ffb49c1 100644
> --- a/hw/misc/Makefile.objs
> +++ b/hw/misc/Makefile.objs
> @@ -51,3 +51,4 @@ obj-$(CONFIG_MIPS_ITU) += mips_itu.o
> obj-$(CONFIG_PVPANIC) += pvpanic.o
> obj-$(CONFIG_EDU) += edu.o
> obj-$(CONFIG_HYPERV_TESTDEV) += hyperv_testdev.o
> +obj-$(CONFIG_AUX) += aux.o
> diff --git a/hw/misc/aux.c b/hw/misc/aux.c
> new file mode 100644
> index 0000000..6605224
> --- /dev/null
> +++ b/hw/misc/aux.c
> @@ -0,0 +1,297 @@
> +/*
> + * aux.c
> + *
> + * Copyright 2015 : GreenSocs Ltd
> + * http://www.greensocs.com/ , email: info@greensocs.com
> + *
> + * Developed by :
> + * Frederic Konrad <fred.konrad@greensocs.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation, either version 2 of the License, or
> + * (at your option)any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +/*
> + * This is an implementation of the AUX bus for VESA Display Port v1.1a.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "hw/misc/aux.h"
> +#include "hw/i2c/i2c.h"
> +#include "monitor/monitor.h"
> +
> +#ifndef DEBUG_AUX
> +#define DEBUG_AUX 0
> +#endif
> +
> +#define DPRINTF(fmt, ...) do { \
> + if (DEBUG_AUX) { \
> + qemu_log("aux: " fmt , ## __VA_ARGS__); \
> + } \
> +} while (0);
> +
> +#define TYPE_AUXTOI2C "aux-to-i2c-bridge"
> +#define AUXTOI2C(obj) OBJECT_CHECK(AUXTOI2CState, (obj), TYPE_AUXTOI2C)
> +
> +#define TYPE_AUX_BUS "aux-bus"
> +#define AUX_BUS(obj) OBJECT_CHECK(AUXBus, (obj), TYPE_AUX_BUS)
This should be in the header file where the struct is.
> +
> +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:
> + is_write = true;
> + /* fallthrough */
Can you use the conditional setting like you do with the others?
> + case READ_AUX:
> + 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");
> + ret = AUX_NACK;
> + break;
This should be a return, not a break. Otherwise you are printing the
reply line as well.
Looks good otherwise, if you fix the comments I made:
Reviewed-by: Alistair Francis <alistair.francis@xilinx.com>
Thanks,
Alistair
> + }
> +
> + 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 0000000..2cc30c2
> --- /dev/null
> +++ b/include/hw/misc/aux.h
> @@ -0,0 +1,125 @@
> +/*
> + * aux.h
> + *
> + * Copyright (C)2014 : GreenSocs Ltd
> + * http://www.greensocs.com/ , email: info@greensocs.com
> + *
> + * Developed by :
> + * Frederic Konrad <fred.konrad@greensocs.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation, either version 2 of the License, or
> + * (at your option)any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#ifndef QEMU_AUX_H
> +#define QEMU_AUX_H
> +
> +#include "hw/qdev.h"
> +
> +typedef struct AUXBus AUXBus;
> +typedef struct AUXSlave AUXSlave;
> +typedef enum AUXCommand AUXCommand;
> +typedef enum AUXReply AUXReply;
> +typedef struct AUXTOI2CState AUXTOI2CState;
> +
> +enum AUXCommand {
> + WRITE_I2C = 0,
> + READ_I2C = 1,
> + WRITE_I2C_STATUS = 2,
> + WRITE_I2C_MOT = 4,
> + READ_I2C_MOT = 5,
> + WRITE_AUX = 8,
> + READ_AUX = 9
> +};
> +
> +enum AUXReply {
> + AUX_I2C_ACK = 0,
> + AUX_NACK = 1,
> + AUX_DEFER = 2,
> + AUX_I2C_NACK = 4,
> + AUX_I2C_DEFER = 8
> +};
> +
> +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 */
> --
> 1.8.3.1
>
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [Qemu-devel] [PATCH V8 4/9] introduce aux-bus
2016-06-06 18:41 ` Alistair Francis
@ 2016-06-07 7:02 ` KONRAD Frederic
2016-06-07 20:21 ` Alistair Francis
0 siblings, 1 reply; 14+ messages in thread
From: KONRAD Frederic @ 2016-06-07 7:02 UTC (permalink / raw)
To: Alistair Francis
Cc: qemu-devel@nongnu.org Developers, Edgar Iglesias, Peter Maydell,
hyunk, Mark Burton, Peter Crosthwaite, Guillaume Delbergue
Le 06/06/2016 à 20:41, Alistair Francis a écrit :
> On Mon, Jun 6, 2016 at 7:21 AM, <fred.konrad@greensocs.com> wrote:
>> From: KONRAD Frederic <fred.konrad@greensocs.com>
>>
>> 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 <fred.konrad@greensocs.com>
>> Tested-By: Hyun Kwon <hyun.kwon@xilinx.com>
>> ---
>> default-configs/aarch64-softmmu.mak | 1 +
>> hw/misc/Makefile.objs | 1 +
>> hw/misc/aux.c | 297 ++++++++++++++++++++++++++++++++++++
>> include/hw/misc/aux.h | 125 +++++++++++++++
>> 4 files changed, 424 insertions(+)
>> create mode 100644 hw/misc/aux.c
>> create mode 100644 include/hw/misc/aux.h
>>
>> diff --git a/default-configs/aarch64-softmmu.mak b/default-configs/aarch64-softmmu.mak
>> index 96dd994..d3a2665 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 bc0dd2c..ffb49c1 100644
>> --- a/hw/misc/Makefile.objs
>> +++ b/hw/misc/Makefile.objs
>> @@ -51,3 +51,4 @@ obj-$(CONFIG_MIPS_ITU) += mips_itu.o
>> obj-$(CONFIG_PVPANIC) += pvpanic.o
>> obj-$(CONFIG_EDU) += edu.o
>> obj-$(CONFIG_HYPERV_TESTDEV) += hyperv_testdev.o
>> +obj-$(CONFIG_AUX) += aux.o
>> diff --git a/hw/misc/aux.c b/hw/misc/aux.c
>> new file mode 100644
>> index 0000000..6605224
>> --- /dev/null
>> +++ b/hw/misc/aux.c
>> @@ -0,0 +1,297 @@
>> +/*
>> + * aux.c
>> + *
>> + * Copyright 2015 : GreenSocs Ltd
>> + * http://www.greensocs.com/ , email: info@greensocs.com
>> + *
>> + * Developed by :
>> + * Frederic Konrad <fred.konrad@greensocs.com>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation, either version 2 of the License, or
>> + * (at your option)any later version.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> + * GNU General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU General Public License along
>> + * with this program; if not, see <http://www.gnu.org/licenses/>.
>> + *
>> + */
>> +
>> +/*
>> + * This is an implementation of the AUX bus for VESA Display Port v1.1a.
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "qemu/log.h"
>> +#include "hw/misc/aux.h"
>> +#include "hw/i2c/i2c.h"
>> +#include "monitor/monitor.h"
>> +
>> +#ifndef DEBUG_AUX
>> +#define DEBUG_AUX 0
>> +#endif
>> +
>> +#define DPRINTF(fmt, ...) do { \
>> + if (DEBUG_AUX) { \
>> + qemu_log("aux: " fmt , ## __VA_ARGS__); \
>> + } \
>> +} while (0);
>> +
>> +#define TYPE_AUXTOI2C "aux-to-i2c-bridge"
>> +#define AUXTOI2C(obj) OBJECT_CHECK(AUXTOI2CState, (obj), TYPE_AUXTOI2C)
>> +
>> +#define TYPE_AUX_BUS "aux-bus"
>> +#define AUX_BUS(obj) OBJECT_CHECK(AUXBus, (obj), TYPE_AUX_BUS)
>
> This should be in the header file where the struct is.
Ok
>
>> +
>> +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:
>> + is_write = true;
>> + /* fallthrough */
>
> Can you use the conditional setting like you do with the others?
yes sorry I missed this one.
>
>> + case READ_AUX:
>> + 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");
>> + ret = AUX_NACK;
>> + break;
>
> This should be a return, not a break. Otherwise you are printing the
> reply line as well.
Yes, I don't think it is an issue as I return AUX_NACK?
Fred
>
> Looks good otherwise, if you fix the comments I made:
>
> Reviewed-by: Alistair Francis <alistair.francis@xilinx.com>
>
> Thanks,
>
> Alistair
>
>> + }
>> +
>> + 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 0000000..2cc30c2
>> --- /dev/null
>> +++ b/include/hw/misc/aux.h
>> @@ -0,0 +1,125 @@
>> +/*
>> + * aux.h
>> + *
>> + * Copyright (C)2014 : GreenSocs Ltd
>> + * http://www.greensocs.com/ , email: info@greensocs.com
>> + *
>> + * Developed by :
>> + * Frederic Konrad <fred.konrad@greensocs.com>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation, either version 2 of the License, or
>> + * (at your option)any later version.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> + * GNU General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU General Public License along
>> + * with this program; if not, see <http://www.gnu.org/licenses/>.
>> + *
>> + */
>> +
>> +#ifndef QEMU_AUX_H
>> +#define QEMU_AUX_H
>> +
>> +#include "hw/qdev.h"
>> +
>> +typedef struct AUXBus AUXBus;
>> +typedef struct AUXSlave AUXSlave;
>> +typedef enum AUXCommand AUXCommand;
>> +typedef enum AUXReply AUXReply;
>> +typedef struct AUXTOI2CState AUXTOI2CState;
>> +
>> +enum AUXCommand {
>> + WRITE_I2C = 0,
>> + READ_I2C = 1,
>> + WRITE_I2C_STATUS = 2,
>> + WRITE_I2C_MOT = 4,
>> + READ_I2C_MOT = 5,
>> + WRITE_AUX = 8,
>> + READ_AUX = 9
>> +};
>> +
>> +enum AUXReply {
>> + AUX_I2C_ACK = 0,
>> + AUX_NACK = 1,
>> + AUX_DEFER = 2,
>> + AUX_I2C_NACK = 4,
>> + AUX_I2C_DEFER = 8
>> +};
>> +
>> +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 */
>> --
>> 1.8.3.1
>>
>>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [Qemu-devel] [PATCH V8 4/9] introduce aux-bus
2016-06-07 7:02 ` KONRAD Frederic
@ 2016-06-07 20:21 ` Alistair Francis
0 siblings, 0 replies; 14+ messages in thread
From: Alistair Francis @ 2016-06-07 20:21 UTC (permalink / raw)
To: KONRAD Frederic
Cc: Alistair Francis, Edgar Iglesias, Peter Maydell, hyunk,
Mark Burton, qemu-devel@nongnu.org Developers, Peter Crosthwaite,
Guillaume Delbergue
On Tue, Jun 7, 2016 at 12:02 AM, KONRAD Frederic
<fred.konrad@greensocs.com> wrote:
>
>
> Le 06/06/2016 à 20:41, Alistair Francis a écrit :
>>
>> On Mon, Jun 6, 2016 at 7:21 AM, <fred.konrad@greensocs.com> wrote:
>>>
>>> From: KONRAD Frederic <fred.konrad@greensocs.com>
>>>
>>> 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 <fred.konrad@greensocs.com>
>>> Tested-By: Hyun Kwon <hyun.kwon@xilinx.com>
>>> ---
>>> default-configs/aarch64-softmmu.mak | 1 +
>>> hw/misc/Makefile.objs | 1 +
>>> hw/misc/aux.c | 297
>>> ++++++++++++++++++++++++++++++++++++
>>> include/hw/misc/aux.h | 125 +++++++++++++++
>>> 4 files changed, 424 insertions(+)
>>> create mode 100644 hw/misc/aux.c
>>> create mode 100644 include/hw/misc/aux.h
>>>
>>> diff --git a/default-configs/aarch64-softmmu.mak
>>> b/default-configs/aarch64-softmmu.mak
>>> index 96dd994..d3a2665 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 bc0dd2c..ffb49c1 100644
>>> --- a/hw/misc/Makefile.objs
>>> +++ b/hw/misc/Makefile.objs
>>> @@ -51,3 +51,4 @@ obj-$(CONFIG_MIPS_ITU) += mips_itu.o
>>> obj-$(CONFIG_PVPANIC) += pvpanic.o
>>> obj-$(CONFIG_EDU) += edu.o
>>> obj-$(CONFIG_HYPERV_TESTDEV) += hyperv_testdev.o
>>> +obj-$(CONFIG_AUX) += aux.o
>>> diff --git a/hw/misc/aux.c b/hw/misc/aux.c
>>> new file mode 100644
>>> index 0000000..6605224
>>> --- /dev/null
>>> +++ b/hw/misc/aux.c
>>> @@ -0,0 +1,297 @@
>>> +/*
>>> + * aux.c
>>> + *
>>> + * Copyright 2015 : GreenSocs Ltd
>>> + * http://www.greensocs.com/ , email: info@greensocs.com
>>> + *
>>> + * Developed by :
>>> + * Frederic Konrad <fred.konrad@greensocs.com>
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify
>>> + * it under the terms of the GNU General Public License as published by
>>> + * the Free Software Foundation, either version 2 of the License, or
>>> + * (at your option)any later version.
>>> + *
>>> + * This program is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>>> + * GNU General Public License for more details.
>>> + *
>>> + * You should have received a copy of the GNU General Public License
>>> along
>>> + * with this program; if not, see <http://www.gnu.org/licenses/>.
>>> + *
>>> + */
>>> +
>>> +/*
>>> + * This is an implementation of the AUX bus for VESA Display Port v1.1a.
>>> + */
>>> +
>>> +#include "qemu/osdep.h"
>>> +#include "qemu/log.h"
>>> +#include "hw/misc/aux.h"
>>> +#include "hw/i2c/i2c.h"
>>> +#include "monitor/monitor.h"
>>> +
>>> +#ifndef DEBUG_AUX
>>> +#define DEBUG_AUX 0
>>> +#endif
>>> +
>>> +#define DPRINTF(fmt, ...) do {
>>> \
>>> + if (DEBUG_AUX) {
>>> \
>>> + qemu_log("aux: " fmt , ## __VA_ARGS__);
>>> \
>>> + }
>>> \
>>> +} while (0);
>>> +
>>> +#define TYPE_AUXTOI2C "aux-to-i2c-bridge"
>>> +#define AUXTOI2C(obj) OBJECT_CHECK(AUXTOI2CState, (obj), TYPE_AUXTOI2C)
>>> +
>>> +#define TYPE_AUX_BUS "aux-bus"
>>> +#define AUX_BUS(obj) OBJECT_CHECK(AUXBus, (obj), TYPE_AUX_BUS)
>>
>>
>> This should be in the header file where the struct is.
>
>
> Ok
>
>>
>>> +
>>> +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:
>>> + is_write = true;
>>> + /* fallthrough */
>>
>>
>> Can you use the conditional setting like you do with the others?
>
> yes sorry I missed this one.
>
>
>>
>>> + case READ_AUX:
>>> + 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");
>>> + ret = AUX_NACK;
>>> + break;
>>
>>
>> This should be a return, not a break. Otherwise you are printing the
>> reply line as well.
>
> Yes, I don't think it is an issue as I return AUX_NACK?
I don't there is any problem functionality wise. I was just thinking
that the print statements might be a bit confusing.
On second thought though it isn't a big deal. You can leave it as is,
it's your call.
Thanks,
Alistair
>
> Fred
>
>
>>
>> Looks good otherwise, if you fix the comments I made:
>>
>> Reviewed-by: Alistair Francis <alistair.francis@xilinx.com>
>>
>> Thanks,
>>
>> Alistair
^ permalink raw reply [flat|nested] 14+ messages in thread
* [Qemu-devel] [PATCH V8 5/9] introduce dpcd module
2016-06-06 14:21 [Qemu-devel] [PATCH V8 0/9] Xilinx DisplayPort fred.konrad
` (3 preceding siblings ...)
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 4/9] introduce aux-bus fred.konrad
@ 2016-06-06 14:21 ` fred.konrad
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 6/9] hw/i2c-ddc.c: Implement DDC I2C slave fred.konrad
` (3 subsequent siblings)
8 siblings, 0 replies; 14+ messages in thread
From: fred.konrad @ 2016-06-06 14:21 UTC (permalink / raw)
To: qemu-devel
Cc: peter.maydell, crosthwaitepeter, alistair.francis,
edgar.iglesias, hyunk, guillaume.delbergue, mark.burton,
fred.konrad
From: KONRAD Frederic <fred.konrad@greensocs.com>
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 <fred.konrad@greensocs.com>
Reviewed-by: Alistair Francis <alistair.francis@xilinx.com>
Reviewed-by: Peter Crosthwaite <crosthwaite.peter@gmail.com>
Tested-By: Hyun Kwon <hyun.kwon@xilinx.com>
---
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
diff --git a/default-configs/aarch64-softmmu.mak b/default-configs/aarch64-softmmu.mak
index d3a2665..87165b7 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 d99780e..ddf3275 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 0000000..5a36855
--- /dev/null
+++ b/hw/display/dpcd.c
@@ -0,0 +1,173 @@
+/*
+ * dpcd.c
+ *
+ * Copyright (C) 2015 : GreenSocs Ltd
+ * http://www.greensocs.com/ , email: info@greensocs.com
+ *
+ * Developed by :
+ * Frederic Konrad <fred.konrad@greensocs.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option)any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * This is a simple AUX slave which emulates a connected screen.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/misc/aux.h"
+#include "hw/display/dpcd.h"
+
+#ifndef DEBUG_DPCD
+#define DEBUG_DPCD 0
+#endif
+
+#define DPRINTF(fmt, ...) do { \
+ if (DEBUG_DPCD) { \
+ qemu_log("dpcd: " fmt, ## __VA_ARGS__); \
+ } \
+} while (0);
+
+#define DPCD_READABLE_AREA 0x600
+
+struct DPCDState {
+ /*< private >*/
+ AUXSlave parent_obj;
+
+ /*< public >*/
+ /*
+ * The DCPD is 0x7FFFF length but read as 0 after offset 0x5FF.
+ */
+ uint8_t dpcd_info[DPCD_READABLE_AREA];
+
+ MemoryRegion iomem;
+};
+
+static uint64_t dpcd_read(void *opaque, hwaddr offset, unsigned size)
+{
+ uint8_t ret;
+ DPCDState *e = DPCD(opaque);
+
+ if (offset < DPCD_READABLE_AREA) {
+ ret = e->dpcd_info[offset];
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "dpcd: Bad offset 0x%" HWADDR_PRIX "\n",
+ offset);
+ ret = 0;
+ }
+
+ DPRINTF("read 0x%" PRIX8 " @0x%" HWADDR_PRIX "\n", ret, offset);
+ return ret;
+}
+
+static void dpcd_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ DPCDState *e = DPCD(opaque);
+
+ DPRINTF("write 0x%" PRIX8 " @0x%" HWADDR_PRIX "\n", (uint8_t)value, offset);
+
+ if (offset < DPCD_READABLE_AREA) {
+ e->dpcd_info[offset] = value;
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "dpcd: Bad offset 0x%" HWADDR_PRIX "\n",
+ offset);
+ }
+}
+
+static const MemoryRegionOps aux_ops = {
+ .read = dpcd_read,
+ .write = dpcd_write,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void dpcd_reset(DeviceState *dev)
+{
+ DPCDState *s = DPCD(dev);
+
+ memset(&(s->dpcd_info), 0, sizeof(s->dpcd_info));
+
+ s->dpcd_info[DPCD_REVISION] = DPCD_REV_1_0;
+ s->dpcd_info[DPCD_MAX_LINK_RATE] = DPCD_5_4GBPS;
+ s->dpcd_info[DPCD_MAX_LANE_COUNT] = DPCD_FOUR_LANES;
+ s->dpcd_info[DPCD_RECEIVE_PORT0_CAP_0] = DPCD_EDID_PRESENT;
+ /* buffer size */
+ s->dpcd_info[DPCD_RECEIVE_PORT0_CAP_1] = 0xFF;
+
+ s->dpcd_info[DPCD_LANE0_1_STATUS] = DPCD_LANE0_CR_DONE
+ | DPCD_LANE0_CHANNEL_EQ_DONE
+ | DPCD_LANE0_SYMBOL_LOCKED
+ | DPCD_LANE1_CR_DONE
+ | DPCD_LANE1_CHANNEL_EQ_DONE
+ | DPCD_LANE1_SYMBOL_LOCKED;
+ s->dpcd_info[DPCD_LANE2_3_STATUS] = DPCD_LANE2_CR_DONE
+ | DPCD_LANE2_CHANNEL_EQ_DONE
+ | DPCD_LANE2_SYMBOL_LOCKED
+ | DPCD_LANE3_CR_DONE
+ | DPCD_LANE3_CHANNEL_EQ_DONE
+ | DPCD_LANE3_SYMBOL_LOCKED;
+
+ s->dpcd_info[DPCD_LANE_ALIGN_STATUS_UPDATED] = DPCD_INTERLANE_ALIGN_DONE;
+ s->dpcd_info[DPCD_SINK_STATUS] = DPCD_RECEIVE_PORT_0_STATUS;
+}
+
+static void dpcd_init(Object *obj)
+{
+ DPCDState *s = DPCD(obj);
+
+ memory_region_init_io(&s->iomem, obj, &aux_ops, s, TYPE_DPCD, 0x7FFFF);
+ aux_init_mmio(AUX_SLAVE(obj), &s->iomem);
+}
+
+static const VMStateDescription vmstate_dpcd = {
+ .name = TYPE_DPCD,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY_V(dpcd_info, DPCDState, DPCD_READABLE_AREA, 0),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void dpcd_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->reset = dpcd_reset;
+ dc->vmsd = &vmstate_dpcd;
+}
+
+static const TypeInfo dpcd_info = {
+ .name = TYPE_DPCD,
+ .parent = TYPE_AUX_SLAVE,
+ .instance_size = sizeof(DPCDState),
+ .class_init = dpcd_class_init,
+ .instance_init = dpcd_init,
+};
+
+static void dpcd_register_types(void)
+{
+ type_register_static(&dpcd_info);
+}
+
+type_init(dpcd_register_types)
diff --git a/include/hw/display/dpcd.h b/include/hw/display/dpcd.h
new file mode 100644
index 0000000..274dc2e
--- /dev/null
+++ b/include/hw/display/dpcd.h
@@ -0,0 +1,105 @@
+/*
+ * dpcd.h
+ *
+ * Copyright (C)2015 : GreenSocs Ltd
+ * http://www.greensocs.com/ , email: info@greensocs.com
+ *
+ * Developed by :
+ * Frederic Konrad <fred.konrad@greensocs.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option)any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef DPCD_H
+#define DPCD_H
+
+typedef struct DPCDState DPCDState;
+
+#define TYPE_DPCD "dpcd"
+#define DPCD(obj) OBJECT_CHECK(DPCDState, (obj), TYPE_DPCD)
+
+/* DCPD Revision. */
+#define DPCD_REVISION 0x00
+#define DPCD_REV_1_0 0x10
+#define DPCD_REV_1_1 0x11
+
+/* DCPD Max Link Rate. */
+#define DPCD_MAX_LINK_RATE 0x01
+#define DPCD_1_62GBPS 0x06
+#define DPCD_2_7GBPS 0x0A
+#define DPCD_5_4GBPS 0x14
+
+#define DPCD_MAX_LANE_COUNT 0x02
+#define DPCD_ONE_LANE 0x01
+#define DPCD_TWO_LANES 0x02
+#define DPCD_FOUR_LANES 0x04
+
+/* DCPD Max down spread. */
+#define DPCD_UP_TO_0_5 0x01
+#define DPCD_NO_AUX_HANDSHAKE_LINK_TRAINING 0x40
+
+/* DCPD Downstream port type. */
+#define DPCD_DISPLAY_PORT 0x00
+#define DPCD_ANALOG 0x02
+#define DPCD_DVI_HDMI 0x04
+#define DPCD_OTHER 0x06
+
+/* DPCD Format conversion. */
+#define DPCD_FORMAT_CONVERSION 0x08
+
+/* Main link channel coding. */
+#define DPCD_ANSI_8B_10B 0x01
+
+/* Down stream port count. */
+#define DPCD_OUI_SUPPORTED 0x80
+
+/* Receiver port capability. */
+#define DPCD_RECEIVE_PORT0_CAP_0 0x08
+#define DPCD_RECEIVE_PORT0_CAP_1 0x09
+#define DPCD_EDID_PRESENT 0x02
+#define DPCD_ASSOCIATED_TO_PRECEDING_PORT 0x04
+
+/* Down stream port capability. */
+#define DPCD_CAP_DISPLAY_PORT 0x000
+#define DPCD_CAP_ANALOG_VGA 0x001
+#define DPCD_CAP_DVI 0x002
+#define DPCD_CAP_HDMI 0x003
+#define DPCD_CAP_OTHER 0x100
+
+#define DPCD_LANE0_1_STATUS 0x202
+#define DPCD_LANE0_CR_DONE (1 << 0)
+#define DPCD_LANE0_CHANNEL_EQ_DONE (1 << 1)
+#define DPCD_LANE0_SYMBOL_LOCKED (1 << 2)
+#define DPCD_LANE1_CR_DONE (1 << 4)
+#define DPCD_LANE1_CHANNEL_EQ_DONE (1 << 5)
+#define DPCD_LANE1_SYMBOL_LOCKED (1 << 6)
+
+#define DPCD_LANE2_3_STATUS 0x203
+#define DPCD_LANE2_CR_DONE (1 << 0)
+#define DPCD_LANE2_CHANNEL_EQ_DONE (1 << 1)
+#define DPCD_LANE2_SYMBOL_LOCKED (1 << 2)
+#define DPCD_LANE3_CR_DONE (1 << 4)
+#define DPCD_LANE3_CHANNEL_EQ_DONE (1 << 5)
+#define DPCD_LANE3_SYMBOL_LOCKED (1 << 6)
+
+#define DPCD_LANE_ALIGN_STATUS_UPDATED 0x204
+#define DPCD_INTERLANE_ALIGN_DONE 0x01
+#define DPCD_DOWNSTREAM_PORT_STATUS_CHANGED 0x40
+#define DPCD_LINK_STATUS_UPDATED 0x80
+
+#define DPCD_SINK_STATUS 0x205
+#define DPCD_RECEIVE_PORT_0_STATUS 0x01
+
+#endif /* !DPCD_H */
--
1.8.3.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [Qemu-devel] [PATCH V8 6/9] hw/i2c-ddc.c: Implement DDC I2C slave
2016-06-06 14:21 [Qemu-devel] [PATCH V8 0/9] Xilinx DisplayPort fred.konrad
` (4 preceding siblings ...)
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 5/9] introduce dpcd module fred.konrad
@ 2016-06-06 14:21 ` fred.konrad
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 7/9] introduce xlnx-dpdma fred.konrad
` (2 subsequent siblings)
8 siblings, 0 replies; 14+ messages in thread
From: fred.konrad @ 2016-06-06 14:21 UTC (permalink / raw)
To: qemu-devel
Cc: peter.maydell, crosthwaitepeter, alistair.francis,
edgar.iglesias, hyunk, guillaume.delbergue, mark.burton,
fred.konrad
From: Peter Maydell <peter.maydell@linaro.org>
Implement an I2C slave which implements DDC and returns the
EDID data for an attached monitor.
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
- Rebased on the current master.
- Modified for QOM.
Signed-off-by: KONRAD Frederic <fred.konrad@greensocs.com>
Reviewed-by: Alistair Francis <alistair.francis@xilinx.com>
Tested-By: Hyun Kwon <hyun.kwon@xilinx.com>
---
default-configs/aarch64-softmmu.mak | 1 +
hw/i2c/Makefile.objs | 1 +
hw/i2c/i2c-ddc.c | 307 ++++++++++++++++++++++++++++++++++++
include/hw/i2c/i2c-ddc.h | 38 +++++
4 files changed, 347 insertions(+)
create mode 100644 hw/i2c/i2c-ddc.c
create mode 100644 include/hw/i2c/i2c-ddc.h
diff --git a/default-configs/aarch64-softmmu.mak b/default-configs/aarch64-softmmu.mak
index 87165b7..2449483 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 aeb8f38..6dd7b6c 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 0000000..02cd374
--- /dev/null
+++ b/hw/i2c/i2c-ddc.c
@@ -0,0 +1,307 @@
+/* A simple I2C slave for returning monitor EDID data via DDC.
+ *
+ * Copyright (c) 2011 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/i2c/i2c.h"
+#include "hw/i2c/i2c-ddc.h"
+
+#ifndef DEBUG_I2CDDC
+#define DEBUG_I2CDDC 0
+#endif
+
+#define DPRINTF(fmt, ...) do { \
+ if (DEBUG_I2CDDC) { \
+ qemu_log("i2c-ddc: " fmt , ## __VA_ARGS__); \
+ } \
+} while (0);
+
+/* Structure defining a monitor's characteristics in a
+ * readable format: this should be passed to build_edid_blob()
+ * to convert it into the 128 byte binary EDID blob.
+ * Not all bits of the EDID are customisable here.
+ */
+struct EDIDData {
+ char manuf_id[3]; /* three upper case letters */
+ uint16_t product_id;
+ uint32_t serial_no;
+ uint8_t manuf_week;
+ int manuf_year;
+ uint8_t h_cm;
+ uint8_t v_cm;
+ uint8_t gamma;
+ char monitor_name[14];
+ char serial_no_string[14];
+ /* Range limits */
+ uint8_t vmin; /* Hz */
+ uint8_t vmax; /* Hz */
+ uint8_t hmin; /* kHz */
+ uint8_t hmax; /* kHz */
+ uint8_t pixclock; /* MHz / 10 */
+ uint8_t timing_data[18];
+};
+
+typedef struct EDIDData EDIDData;
+
+/* EDID data for a simple LCD monitor */
+static const EDIDData lcd_edid = {
+ /* The manuf_id ought really to be an assigned EISA ID */
+ .manuf_id = "QMU",
+ .product_id = 0,
+ .serial_no = 1,
+ .manuf_week = 1,
+ .manuf_year = 2011,
+ .h_cm = 40,
+ .v_cm = 30,
+ .gamma = 0x78,
+ .monitor_name = "QEMU monitor",
+ .serial_no_string = "1",
+ .vmin = 40,
+ .vmax = 120,
+ .hmin = 30,
+ .hmax = 100,
+ .pixclock = 18,
+ .timing_data = {
+ /* Borrowed from a 21" LCD */
+ 0x48, 0x3f, 0x40, 0x30, 0x62, 0xb0, 0x32, 0x40, 0x40,
+ 0xc0, 0x13, 0x00, 0x98, 0x32, 0x11, 0x00, 0x00, 0x1e
+ }
+};
+
+static uint8_t manuf_char_to_int(char c)
+{
+ return (c - 'A') & 0x1f;
+}
+
+static void write_ascii_descriptor_block(uint8_t *descblob, uint8_t blocktype,
+ const char *string)
+{
+ /* Write an EDID Descriptor Block of the "ascii string" type */
+ int i;
+ descblob[0] = descblob[1] = descblob[2] = descblob[4] = 0;
+ descblob[3] = blocktype;
+ /* The rest is 13 bytes of ASCII; if less then the rest must
+ * be filled with newline then spaces
+ */
+ for (i = 5; i < 19; i++) {
+ descblob[i] = string[i - 5];
+ if (!descblob[i]) {
+ break;
+ }
+ }
+ if (i < 19) {
+ descblob[i++] = '\n';
+ }
+ for ( ; i < 19; i++) {
+ descblob[i] = ' ';
+ }
+}
+
+static void write_range_limits_descriptor(const EDIDData *edid,
+ uint8_t *descblob)
+{
+ int i;
+ descblob[0] = descblob[1] = descblob[2] = descblob[4] = 0;
+ descblob[3] = 0xfd;
+ descblob[5] = edid->vmin;
+ descblob[6] = edid->vmax;
+ descblob[7] = edid->hmin;
+ descblob[8] = edid->hmax;
+ descblob[9] = edid->pixclock;
+ descblob[10] = 0;
+ descblob[11] = 0xa;
+ for (i = 12; i < 19; i++) {
+ descblob[i] = 0x20;
+ }
+}
+
+static void build_edid_blob(const EDIDData *edid, uint8_t *blob)
+{
+ /* Write an EDID 1.3 format blob (128 bytes) based
+ * on the EDIDData structure.
+ */
+ int i;
+ uint8_t cksum;
+
+ /* 00-07 : header */
+ blob[0] = blob[7] = 0;
+ for (i = 1 ; i < 7; i++) {
+ blob[i] = 0xff;
+ }
+ /* 08-09 : manufacturer ID */
+ blob[8] = (manuf_char_to_int(edid->manuf_id[0]) << 2)
+ | (manuf_char_to_int(edid->manuf_id[1]) >> 3);
+ blob[9] = (manuf_char_to_int(edid->manuf_id[1]) << 5)
+ | manuf_char_to_int(edid->manuf_id[2]);
+ /* 10-11 : product ID code */
+ blob[10] = edid->product_id;
+ blob[11] = edid->product_id >> 8;
+ blob[12] = edid->serial_no;
+ blob[13] = edid->serial_no >> 8;
+ blob[14] = edid->serial_no >> 16;
+ blob[15] = edid->serial_no >> 24;
+ /* 16 : week of manufacture */
+ blob[16] = edid->manuf_week;
+ /* 17 : year of manufacture - 1990 */
+ blob[17] = edid->manuf_year - 1990;
+ /* 18, 19 : EDID version and revision */
+ blob[18] = 1;
+ blob[19] = 3;
+ /* 20 - 24 : basic display parameters */
+ /* We are always a digital display */
+ blob[20] = 0x80;
+ /* 21, 22 : max h/v size in cm */
+ blob[21] = edid->h_cm;
+ blob[22] = edid->v_cm;
+ /* 23 : gamma (divide by 100 then add 1 for actual value) */
+ blob[23] = edid->gamma;
+ /* 24 feature support: no power management, RGB, preferred timing mode,
+ * standard colour space
+ */
+ blob[24] = 0x0e;
+ /* 25 - 34 : chromaticity coordinates. These are the
+ * standard sRGB chromaticity values
+ */
+ blob[25] = 0xee;
+ blob[26] = 0x91;
+ blob[27] = 0xa3;
+ blob[28] = 0x54;
+ blob[29] = 0x4c;
+ blob[30] = 0x99;
+ blob[31] = 0x26;
+ blob[32] = 0x0f;
+ blob[33] = 0x50;
+ blob[34] = 0x54;
+ /* 35, 36 : Established timings: claim to support everything */
+ blob[35] = blob[36] = 0xff;
+ /* 37 : manufacturer's reserved timing: none */
+ blob[37] = 0;
+ /* 38 - 53 : standard timing identification
+ * don't claim anything beyond what the 'established timings'
+ * already provide. Unused slots must be (0x1, 0x1)
+ */
+ for (i = 38; i < 54; i++) {
+ blob[i] = 0x1;
+ }
+ /* 54 - 71 : descriptor block 1 : must be preferred timing data */
+ memcpy(blob + 54, edid->timing_data, 18);
+ /* 72 - 89, 90 - 107, 108 - 125 : descriptor block 2, 3, 4
+ * Order not important, but we must have a monitor name and a
+ * range limits descriptor.
+ */
+ write_range_limits_descriptor(edid, blob + 72);
+ write_ascii_descriptor_block(blob + 90, 0xfc, edid->monitor_name);
+ write_ascii_descriptor_block(blob + 108, 0xff, edid->serial_no_string);
+
+ /* 126 : extension flag */
+ blob[126] = 0;
+
+ cksum = 0;
+ for (i = 0; i < 127; i++) {
+ cksum += blob[i];
+ }
+ /* 127 : checksum */
+ blob[127] = -cksum;
+ if (DEBUG_I2CDDC) {
+ qemu_hexdump((char *)blob, stdout, "", 128);
+ }
+}
+
+static void i2c_ddc_reset(DeviceState *ds)
+{
+ I2CDDCState *s = I2CDDC(ds);
+
+ s->firstbyte = false;
+ s->reg = 0;
+}
+
+static void i2c_ddc_event(I2CSlave *i2c, enum i2c_event event)
+{
+ I2CDDCState *s = I2CDDC(i2c);
+
+ if (event == I2C_START_SEND) {
+ s->firstbyte = true;
+ }
+}
+
+static int i2c_ddc_rx(I2CSlave *i2c)
+{
+ I2CDDCState *s = I2CDDC(i2c);
+
+ int value;
+ value = s->edid_blob[s->reg];
+ s->reg++;
+ return value;
+}
+
+static int i2c_ddc_tx(I2CSlave *i2c, uint8_t data)
+{
+ I2CDDCState *s = I2CDDC(i2c);
+ if (s->firstbyte) {
+ s->reg = data;
+ s->firstbyte = false;
+ DPRINTF("[EDID] Written new pointer: %u\n", data);
+ return 1;
+ }
+
+ /* Ignore all writes */
+ s->reg++;
+ return 1;
+}
+
+static void i2c_ddc_init(Object *obj)
+{
+ I2CDDCState *s = I2CDDC(obj);
+ build_edid_blob(&lcd_edid, s->edid_blob);
+}
+
+static 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;
+ isc->event = i2c_ddc_event;
+ isc->recv = i2c_ddc_rx;
+ isc->send = i2c_ddc_tx;
+}
+
+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 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 0000000..cb8e62d
--- /dev/null
+++ b/include/hw/i2c/i2c-ddc.h
@@ -0,0 +1,38 @@
+/* A simple I2C slave for returning monitor EDID data via DDC.
+ *
+ * Copyright (c) 2011 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef I2C_DDC
+#define I2C_DDC
+
+/* A simple I2C slave which just returns the contents of its EDID blob. */
+
+struct I2CDDCState {
+ /*< private >*/
+ I2CSlave i2c;
+ /*< public >*/
+ bool firstbyte;
+ uint8_t reg;
+ uint8_t edid_blob[128];
+};
+
+typedef struct I2CDDCState I2CDDCState;
+
+#define TYPE_I2CDDC "i2c-ddc"
+#define I2CDDC(obj) OBJECT_CHECK(I2CDDCState, (obj), TYPE_I2CDDC)
+
+#endif /* !I2C_DDC */
--
1.8.3.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [Qemu-devel] [PATCH V8 7/9] introduce xlnx-dpdma
2016-06-06 14:21 [Qemu-devel] [PATCH V8 0/9] Xilinx DisplayPort fred.konrad
` (5 preceding siblings ...)
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 6/9] hw/i2c-ddc.c: Implement DDC I2C slave fred.konrad
@ 2016-06-06 14:21 ` fred.konrad
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 8/9] introduce xlnx-dp fred.konrad
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 9/9] arm: xlnx-zynqmp: Add xlnx-dp and xlnx-dpdma fred.konrad
8 siblings, 0 replies; 14+ messages in thread
From: fred.konrad @ 2016-06-06 14:21 UTC (permalink / raw)
To: qemu-devel
Cc: peter.maydell, crosthwaitepeter, alistair.francis,
edgar.iglesias, hyunk, guillaume.delbergue, mark.burton,
fred.konrad
From: KONRAD Frederic <fred.konrad@greensocs.com>
This is the implementation of the DPDMA.
Signed-off-by: KONRAD Frederic <fred.konrad@greensocs.com>
Reviewed-by: Alistair Francis <alistair.francis@xilinx.com>
Tested-By: Hyun Kwon <hyun.kwon@xilinx.com>
---
hw/dma/Makefile.objs | 1 +
hw/dma/xlnx_dpdma.c | 794 ++++++++++++++++++++++++++++++++++++++++++++
include/hw/dma/xlnx_dpdma.h | 85 +++++
3 files changed, 880 insertions(+)
create mode 100644 hw/dma/xlnx_dpdma.c
create mode 100644 include/hw/dma/xlnx_dpdma.h
diff --git a/hw/dma/Makefile.objs b/hw/dma/Makefile.objs
index a1abbcf..8b0823e 100644
--- a/hw/dma/Makefile.objs
+++ b/hw/dma/Makefile.objs
@@ -8,6 +8,7 @@ common-obj-$(CONFIG_XILINX_AXI) += xilinx_axidma.o
common-obj-$(CONFIG_ETRAXFS) += etraxfs_dma.o
common-obj-$(CONFIG_STP2000) += sparc32_dma.o
common-obj-$(CONFIG_SUN4M) += sun4m_iommu.o
+obj-$(CONFIG_XLNX_ZYNQMP) += xlnx_dpdma.o
obj-$(CONFIG_OMAP) += omap_dma.o soc_dma.o
obj-$(CONFIG_PXA2XX) += pxa2xx_dma.o
diff --git a/hw/dma/xlnx_dpdma.c b/hw/dma/xlnx_dpdma.c
new file mode 100644
index 0000000..97a5da7
--- /dev/null
+++ b/hw/dma/xlnx_dpdma.c
@@ -0,0 +1,794 @@
+/*
+ * xlnx_dpdma.c
+ *
+ * Copyright (C) 2015 : GreenSocs Ltd
+ * http://www.greensocs.com/ , email: info@greensocs.com
+ *
+ * Developed by :
+ * Frederic Konrad <fred.konrad@greensocs.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/dma/xlnx_dpdma.h"
+
+#ifndef DEBUG_DPDMA
+#define DEBUG_DPDMA 0
+#endif
+
+#define DPRINTF(fmt, ...) do { \
+ if (DEBUG_DPDMA) { \
+ qemu_log("xlnx_dpdma: " fmt , ## __VA_ARGS__); \
+ } \
+} while (0);
+
+/*
+ * Registers offset for DPDMA.
+ */
+#define DPDMA_ERR_CTRL (0x0000)
+#define DPDMA_ISR (0x0004 >> 2)
+#define DPDMA_IMR (0x0008 >> 2)
+#define DPDMA_IEN (0x000C >> 2)
+#define DPDMA_IDS (0x0010 >> 2)
+#define DPDMA_EISR (0x0014 >> 2)
+#define DPDMA_EIMR (0x0018 >> 2)
+#define DPDMA_EIEN (0x001C >> 2)
+#define DPDMA_EIDS (0x0020 >> 2)
+#define DPDMA_CNTL (0x0100 >> 2)
+
+#define DPDMA_GBL (0x0104 >> 2)
+#define DPDMA_GBL_TRG_CH(n) (1 << n)
+#define DPDMA_GBL_RTRG_CH(n) (1 << 6 << n)
+
+#define DPDMA_ALC0_CNTL (0x0108 >> 2)
+#define DPDMA_ALC0_STATUS (0x010C >> 2)
+#define DPDMA_ALC0_MAX (0x0110 >> 2)
+#define DPDMA_ALC0_MIN (0x0114 >> 2)
+#define DPDMA_ALC0_ACC (0x0118 >> 2)
+#define DPDMA_ALC0_ACC_TRAN (0x011C >> 2)
+#define DPDMA_ALC1_CNTL (0x0120 >> 2)
+#define DPDMA_ALC1_STATUS (0x0124 >> 2)
+#define DPDMA_ALC1_MAX (0x0128 >> 2)
+#define DPDMA_ALC1_MIN (0x012C >> 2)
+#define DPDMA_ALC1_ACC (0x0130 >> 2)
+#define DPDMA_ALC1_ACC_TRAN (0x0134 >> 2)
+
+#define DPDMA_DSCR_STRT_ADDRE_CH(n) ((0x0200 + n * 0x100) >> 2)
+#define DPDMA_DSCR_STRT_ADDR_CH(n) ((0x0204 + n * 0x100) >> 2)
+#define DPDMA_DSCR_NEXT_ADDRE_CH(n) ((0x0208 + n * 0x100) >> 2)
+#define DPDMA_DSCR_NEXT_ADDR_CH(n) ((0x020C + n * 0x100) >> 2)
+#define DPDMA_PYLD_CUR_ADDRE_CH(n) ((0x0210 + n * 0x100) >> 2)
+#define DPDMA_PYLD_CUR_ADDR_CH(n) ((0x0214 + n * 0x100) >> 2)
+
+#define DPDMA_CNTL_CH(n) ((0x0218 + n * 0x100) >> 2)
+#define DPDMA_CNTL_CH_EN (1)
+#define DPDMA_CNTL_CH_PAUSED (1 << 1)
+
+#define DPDMA_STATUS_CH(n) ((0x021C + n * 0x100) >> 2)
+#define DPDMA_STATUS_BURST_TYPE (1 << 4)
+#define DPDMA_STATUS_MODE (1 << 5)
+#define DPDMA_STATUS_EN_CRC (1 << 6)
+#define DPDMA_STATUS_LAST_DSCR (1 << 7)
+#define DPDMA_STATUS_LDSCR_FRAME (1 << 8)
+#define DPDMA_STATUS_IGNR_DONE (1 << 9)
+#define DPDMA_STATUS_DSCR_DONE (1 << 10)
+#define DPDMA_STATUS_EN_DSCR_UP (1 << 11)
+#define DPDMA_STATUS_EN_DSCR_INTR (1 << 12)
+#define DPDMA_STATUS_PREAMBLE_OFF (13)
+
+#define DPDMA_VDO_CH(n) ((0x0220 + n * 0x100) >> 2)
+#define DPDMA_PYLD_SZ_CH(n) ((0x0224 + n * 0x100) >> 2)
+#define DPDMA_DSCR_ID_CH(n) ((0x0228 + n * 0x100) >> 2)
+
+/*
+ * Descriptor control field.
+ */
+#define CONTROL_PREAMBLE_VALUE 0xA5
+
+#define DSCR_CTRL_PREAMBLE 0xFF
+#define DSCR_CTRL_EN_DSCR_DONE_INTR (1 << 8)
+#define DSCR_CTRL_EN_DSCR_UPDATE (1 << 9)
+#define DSCR_CTRL_IGNORE_DONE (1 << 10)
+#define DSCR_CTRL_AXI_BURST_TYPE (1 << 11)
+#define DSCR_CTRL_AXCACHE (0x0F << 12)
+#define DSCR_CTRL_AXPROT (0x2 << 16)
+#define DSCR_CTRL_DESCRIPTOR_MODE (1 << 18)
+#define DSCR_CTRL_LAST_DESCRIPTOR (1 << 19)
+#define DSCR_CTRL_ENABLE_CRC (1 << 20)
+#define DSCR_CTRL_LAST_DESCRIPTOR_OF_FRAME (1 << 21)
+
+/*
+ * Descriptor timestamp field.
+ */
+#define STATUS_DONE (1 << 31)
+
+#define DPDMA_FRAG_MAX_SZ (4096)
+
+enum DPDMABurstType {
+ DPDMA_INCR = 0,
+ DPDMA_FIXED = 1
+};
+
+enum DPDMAMode {
+ DPDMA_CONTIGOUS = 0,
+ DPDMA_FRAGMENTED = 1
+};
+
+struct DPDMADescriptor {
+ uint32_t control;
+ uint32_t descriptor_id;
+ /* transfer size in byte. */
+ uint32_t xfer_size;
+ uint32_t line_size_stride;
+ uint32_t timestamp_lsb;
+ uint32_t timestamp_msb;
+ /* contains extension for both descriptor and source. */
+ uint32_t address_extension;
+ uint32_t next_descriptor;
+ uint32_t source_address;
+ uint32_t address_extension_23;
+ uint32_t address_extension_45;
+ uint32_t source_address2;
+ uint32_t source_address3;
+ uint32_t source_address4;
+ uint32_t source_address5;
+ uint32_t crc;
+};
+
+typedef enum DPDMABurstType DPDMABurstType;
+typedef enum DPDMAMode DPDMAMode;
+typedef struct DPDMADescriptor DPDMADescriptor;
+
+static bool xlnx_dpdma_desc_is_last(DPDMADescriptor *desc)
+{
+ return ((desc->control & DSCR_CTRL_LAST_DESCRIPTOR) != 0);
+}
+
+static bool xlnx_dpdma_desc_is_last_of_frame(DPDMADescriptor *desc)
+{
+ return ((desc->control & DSCR_CTRL_LAST_DESCRIPTOR_OF_FRAME) != 0);
+}
+
+static uint64_t xlnx_dpdma_desc_get_source_address(DPDMADescriptor *desc,
+ uint8_t frag)
+{
+ uint64_t addr = 0;
+ assert(frag < 5);
+
+ switch (frag) {
+ case 0:
+ addr = desc->source_address
+ + (extract32(desc->address_extension, 16, 12) << 20);
+ break;
+ case 1:
+ addr = desc->source_address2
+ + (extract32(desc->address_extension_23, 0, 12) << 8);
+ break;
+ case 2:
+ addr = desc->source_address3
+ + (extract32(desc->address_extension_23, 16, 12) << 20);
+ break;
+ case 3:
+ addr = desc->source_address4
+ + (extract32(desc->address_extension_45, 0, 12) << 8);
+ break;
+ case 4:
+ addr = desc->source_address5
+ + (extract32(desc->address_extension_45, 16, 12) << 20);
+ break;
+ default:
+ addr = 0;
+ break;
+ }
+
+ return addr;
+}
+
+static uint32_t xlnx_dpdma_desc_get_transfer_size(DPDMADescriptor *desc)
+{
+ return desc->xfer_size;
+}
+
+static uint32_t xlnx_dpdma_desc_get_line_size(DPDMADescriptor *desc)
+{
+ return extract32(desc->line_size_stride, 0, 18);
+}
+
+static uint32_t xlnx_dpdma_desc_get_line_stride(DPDMADescriptor *desc)
+{
+ return extract32(desc->line_size_stride, 18, 14) * 16;
+}
+
+static inline bool xlnx_dpdma_desc_crc_enabled(DPDMADescriptor *desc)
+{
+ return (desc->control & DSCR_CTRL_ENABLE_CRC) != 0;
+}
+
+static inline bool xlnx_dpdma_desc_check_crc(DPDMADescriptor *desc)
+{
+ uint32_t *p = (uint32_t *)desc;
+ uint32_t crc = 0;
+ uint8_t i;
+
+ /*
+ * CRC is calculated on the whole descriptor except the last 32bits word
+ * using 32bits addition.
+ */
+ for (i = 0; i < 15; i++) {
+ crc += p[i];
+ }
+
+ return crc == desc->crc;
+}
+
+static inline bool xlnx_dpdma_desc_completion_interrupt(DPDMADescriptor *desc)
+{
+ return (desc->control & DSCR_CTRL_EN_DSCR_DONE_INTR) != 0;
+}
+
+static inline bool xlnx_dpdma_desc_is_valid(DPDMADescriptor *desc)
+{
+ return (desc->control & DSCR_CTRL_PREAMBLE) == CONTROL_PREAMBLE_VALUE;
+}
+
+static inline bool xlnx_dpdma_desc_is_contiguous(DPDMADescriptor *desc)
+{
+ return (desc->control & DSCR_CTRL_DESCRIPTOR_MODE) == 0;
+}
+
+static inline bool xlnx_dpdma_desc_update_enabled(DPDMADescriptor *desc)
+{
+ return (desc->control & DSCR_CTRL_EN_DSCR_UPDATE) != 0;
+}
+
+static inline void xlnx_dpdma_desc_set_done(DPDMADescriptor *desc)
+{
+ desc->timestamp_msb |= STATUS_DONE;
+}
+
+static inline bool xlnx_dpdma_desc_is_already_done(DPDMADescriptor *desc)
+{
+ return (desc->timestamp_msb & STATUS_DONE) != 0;
+}
+
+static inline bool xlnx_dpdma_desc_ignore_done_bit(DPDMADescriptor *desc)
+{
+ return (desc->control & DSCR_CTRL_IGNORE_DONE) != 0;
+}
+
+static const VMStateDescription vmstate_xlnx_dpdma = {
+ .name = TYPE_XLNX_DPDMA,
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(registers, XlnxDPDMAState,
+ XLNX_DPDMA_REG_ARRAY_SIZE),
+ VMSTATE_BOOL_ARRAY(operation_finished, XlnxDPDMAState, 6),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void xlnx_dpdma_update_irq(XlnxDPDMAState *s)
+{
+ bool flags;
+
+ flags = ((s->registers[DPDMA_ISR] & (~s->registers[DPDMA_IMR]))
+ || (s->registers[DPDMA_EISR] & (~s->registers[DPDMA_EIMR])));
+ qemu_set_irq(s->irq, flags);
+}
+
+static uint64_t xlnx_dpdma_descriptor_start_address(XlnxDPDMAState *s,
+ uint8_t channel)
+{
+ return (s->registers[DPDMA_DSCR_STRT_ADDRE_CH(channel)] << 16)
+ + s->registers[DPDMA_DSCR_STRT_ADDR_CH(channel)];
+}
+
+static uint64_t xlnx_dpdma_descriptor_next_address(XlnxDPDMAState *s,
+ uint8_t channel)
+{
+ return ((uint64_t)s->registers[DPDMA_DSCR_NEXT_ADDRE_CH(channel)] << 32)
+ + s->registers[DPDMA_DSCR_NEXT_ADDR_CH(channel)];
+}
+
+static inline void xlnx_dpdma_set_desc_next_address(XlnxDPDMAState *s,
+ uint8_t channel,
+ uint64_t addr)
+{
+ s->registers[DPDMA_DSCR_NEXT_ADDRE_CH(channel)] = extract64(addr, 32, 32);
+ s->registers[DPDMA_DSCR_NEXT_ADDR_CH(channel)] = extract64(addr, 0, 32);
+}
+
+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 0000000..ae571a0
--- /dev/null
+++ b/include/hw/dma/xlnx_dpdma.h
@@ -0,0 +1,85 @@
+/*
+ * xlnx_dpdma.h
+ *
+ * Copyright (C) 2015 : GreenSocs Ltd
+ * http://www.greensocs.com/ , email: info@greensocs.com
+ *
+ * Developed by :
+ * Frederic Konrad <fred.konrad@greensocs.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef XLNX_DPDMA_H
+#define XLNX_DPDMA_H
+
+#include "hw/sysbus.h"
+#include "ui/console.h"
+#include "sysemu/dma.h"
+
+#define XLNX_DPDMA_REG_ARRAY_SIZE (0x1000 >> 2)
+
+struct XlnxDPDMAState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+ MemoryRegion iomem;
+ uint32_t registers[XLNX_DPDMA_REG_ARRAY_SIZE];
+ uint8_t *data[6];
+ bool operation_finished[6];
+ qemu_irq irq;
+};
+
+typedef struct XlnxDPDMAState XlnxDPDMAState;
+
+#define TYPE_XLNX_DPDMA "xlnx.dpdma"
+#define XLNX_DPDMA(obj) OBJECT_CHECK(XlnxDPDMAState, (obj), TYPE_XLNX_DPDMA)
+
+/*
+ * xlnx_dpdma_start_operation: Start the operation on the specified channel. The
+ * DPDMA gets the current descriptor and retrieves
+ * data to the buffer specified by
+ * dpdma_set_host_data_location().
+ *
+ * Returns The number of bytes transfered by the DPDMA or 0 if an error occured.
+ *
+ * @s The DPDMA state.
+ * @channel The channel to start.
+ */
+size_t xlnx_dpdma_start_operation(XlnxDPDMAState *s, uint8_t channel,
+ bool one_desc);
+
+/*
+ * xlnx_dpdma_set_host_data_location: Set the location in the host memory where
+ * to store the data out from the dma
+ * channel.
+ *
+ * @s The DPDMA state.
+ * @channel The channel associated to the pointer.
+ * @p The buffer where to store the data.
+ */
+/* XXX: add a maximum size arg and send an interrupt in case of overflow. */
+void xlnx_dpdma_set_host_data_location(XlnxDPDMAState *s, uint8_t channel,
+ void *p);
+
+/*
+ * xlnx_dpdma_trigger_vsync_irq: Trigger a VSYNC IRQ when the display is
+ * updated.
+ *
+ * @s The DPDMA state.
+ */
+void xlnx_dpdma_trigger_vsync_irq(XlnxDPDMAState *s);
+
+#endif /* !XLNX_DPDMA_H */
--
1.8.3.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [Qemu-devel] [PATCH V8 8/9] introduce xlnx-dp
2016-06-06 14:21 [Qemu-devel] [PATCH V8 0/9] Xilinx DisplayPort fred.konrad
` (6 preceding siblings ...)
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 7/9] introduce xlnx-dpdma fred.konrad
@ 2016-06-06 14:21 ` fred.konrad
2016-06-06 19:02 ` Alistair Francis
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 9/9] arm: xlnx-zynqmp: Add xlnx-dp and xlnx-dpdma fred.konrad
8 siblings, 1 reply; 14+ messages in thread
From: fred.konrad @ 2016-06-06 14:21 UTC (permalink / raw)
To: qemu-devel
Cc: peter.maydell, crosthwaitepeter, alistair.francis,
edgar.iglesias, hyunk, guillaume.delbergue, mark.burton,
fred.konrad
From: KONRAD Frederic <fred.konrad@greensocs.com>
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 <fred.konrad@greensocs.com>
Tested-By: Hyun Kwon <hyun.kwon@xilinx.com>
---
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
diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs
index ddf3275..063889b 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 0000000..552955f
--- /dev/null
+++ b/hw/display/xlnx_dp.c
@@ -0,0 +1,1336 @@
+/*
+ * xlnx_dp.c
+ *
+ * Copyright (C) 2015 : GreenSocs Ltd
+ * http://www.greensocs.com/ , email: info@greensocs.com
+ *
+ * Developed by :
+ * Frederic Konrad <fred.konrad@greensocs.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option)any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/display/xlnx_dp.h"
+
+#ifndef DEBUG_DP
+#define DEBUG_DP 0
+#endif
+
+#define DPRINTF(fmt, ...) do { \
+ if (DEBUG_DP) { \
+ qemu_log("xlnx_dp: " fmt , ## __VA_ARGS__); \
+ } \
+} while (0);
+
+/*
+ * Register offset for DP.
+ */
+#define DP_LINK_BW_SET (0x0000 >> 2)
+#define DP_LANE_COUNT_SET (0x0004 >> 2)
+#define DP_ENHANCED_FRAME_EN (0x0008 >> 2)
+#define DP_TRAINING_PATTERN_SET (0x000C >> 2)
+#define DP_LINK_QUAL_PATTERN_SET (0x0010 >> 2)
+#define DP_SCRAMBLING_DISABLE (0x0014 >> 2)
+#define DP_DOWNSPREAD_CTRL (0x0018 >> 2)
+#define DP_SOFTWARE_RESET (0x001C >> 2)
+#define DP_TRANSMITTER_ENABLE (0x0080 >> 2)
+#define DP_MAIN_STREAM_ENABLE (0x0084 >> 2)
+#define DP_FORCE_SCRAMBLER_RESET (0x00C0 >> 2)
+#define DP_VERSION_REGISTER (0x00F8 >> 2)
+#define DP_CORE_ID (0x00FC >> 2)
+
+#define DP_AUX_COMMAND_REGISTER (0x0100 >> 2)
+#define AUX_ADDR_ONLY_MASK (0x1000)
+#define AUX_COMMAND_MASK (0x0F00)
+#define AUX_COMMAND_SHIFT (8)
+#define AUX_COMMAND_NBYTES (0x000F)
+
+#define DP_AUX_WRITE_FIFO (0x0104 >> 2)
+#define DP_AUX_ADDRESS (0x0108 >> 2)
+#define DP_AUX_CLOCK_DIVIDER (0x010C >> 2)
+#define DP_TX_USER_FIFO_OVERFLOW (0x0110 >> 2)
+#define DP_INTERRUPT_SIGNAL_STATE (0x0130 >> 2)
+#define DP_AUX_REPLY_DATA (0x0134 >> 2)
+#define DP_AUX_REPLY_CODE (0x0138 >> 2)
+#define DP_AUX_REPLY_COUNT (0x013C >> 2)
+#define DP_REPLY_DATA_COUNT (0x0148 >> 2)
+#define DP_REPLY_STATUS (0x014C >> 2)
+#define DP_HPD_DURATION (0x0150 >> 2)
+#define DP_MAIN_STREAM_HTOTAL (0x0180 >> 2)
+#define DP_MAIN_STREAM_VTOTAL (0x0184 >> 2)
+#define DP_MAIN_STREAM_POLARITY (0x0188 >> 2)
+#define DP_MAIN_STREAM_HSWIDTH (0x018C >> 2)
+#define DP_MAIN_STREAM_VSWIDTH (0x0190 >> 2)
+#define DP_MAIN_STREAM_HRES (0x0194 >> 2)
+#define DP_MAIN_STREAM_VRES (0x0198 >> 2)
+#define DP_MAIN_STREAM_HSTART (0x019C >> 2)
+#define DP_MAIN_STREAM_VSTART (0x01A0 >> 2)
+#define DP_MAIN_STREAM_MISC0 (0x01A4 >> 2)
+#define DP_MAIN_STREAM_MISC1 (0x01A8 >> 2)
+#define DP_MAIN_STREAM_M_VID (0x01AC >> 2)
+#define DP_MSA_TRANSFER_UNIT_SIZE (0x01B0 >> 2)
+#define DP_MAIN_STREAM_N_VID (0x01B4 >> 2)
+#define DP_USER_DATA_COUNT_PER_LANE (0x01BC >> 2)
+#define DP_MIN_BYTES_PER_TU (0x01C4 >> 2)
+#define DP_FRAC_BYTES_PER_TU (0x01C8 >> 2)
+#define DP_INIT_WAIT (0x01CC >> 2)
+#define DP_PHY_RESET (0x0200 >> 2)
+#define DP_PHY_VOLTAGE_DIFF_LANE_0 (0x0220 >> 2)
+#define DP_PHY_VOLTAGE_DIFF_LANE_1 (0x0224 >> 2)
+#define DP_TRANSMIT_PRBS7 (0x0230 >> 2)
+#define DP_PHY_CLOCK_SELECT (0x0234 >> 2)
+#define DP_TX_PHY_POWER_DOWN (0x0238 >> 2)
+#define DP_PHY_PRECURSOR_LANE_0 (0x023C >> 2)
+#define DP_PHY_PRECURSOR_LANE_1 (0x0240 >> 2)
+#define DP_PHY_POSTCURSOR_LANE_0 (0x024C >> 2)
+#define DP_PHY_POSTCURSOR_LANE_1 (0x0250 >> 2)
+#define DP_PHY_STATUS (0x0280 >> 2)
+
+#define DP_TX_AUDIO_CONTROL (0x0300 >> 2)
+#define DP_TX_AUD_CTRL (1)
+
+#define DP_TX_AUDIO_CHANNELS (0x0304 >> 2)
+#define DP_TX_AUDIO_INFO_DATA(n) ((0x0308 + 4 * n) >> 2)
+#define DP_TX_M_AUD (0x0328 >> 2)
+#define DP_TX_N_AUD (0x032C >> 2)
+#define DP_TX_AUDIO_EXT_DATA(n) ((0x0330 + 4 * n) >> 2)
+#define DP_INT_STATUS (0x03A0 >> 2)
+#define DP_INT_MASK (0x03A4 >> 2)
+#define DP_INT_EN (0x03A8 >> 2)
+#define DP_INT_DS (0x03AC >> 2)
+
+/*
+ * Registers offset for Audio Video Buffer configuration.
+ */
+#define V_BLEND_OFFSET (0xA000)
+#define V_BLEND_BG_CLR_0 (0x0000 >> 2)
+#define V_BLEND_BG_CLR_1 (0x0004 >> 2)
+#define V_BLEND_BG_CLR_2 (0x0008 >> 2)
+#define V_BLEND_SET_GLOBAL_ALPHA_REG (0x000C >> 2)
+#define V_BLEND_OUTPUT_VID_FORMAT (0x0014 >> 2)
+#define V_BLEND_LAYER0_CONTROL (0x0018 >> 2)
+#define V_BLEND_LAYER1_CONTROL (0x001C >> 2)
+
+#define V_BLEND_RGB2YCBCR_COEFF(n) ((0x0020 + 4 * n) >> 2)
+#define V_BLEND_IN1CSC_COEFF(n) ((0x0044 + 4 * n) >> 2)
+
+#define V_BLEND_LUMA_IN1CSC_OFFSET (0x0068 >> 2)
+#define V_BLEND_CR_IN1CSC_OFFSET (0x006C >> 2)
+#define V_BLEND_CB_IN1CSC_OFFSET (0x0070 >> 2)
+#define V_BLEND_LUMA_OUTCSC_OFFSET (0x0074 >> 2)
+#define V_BLEND_CR_OUTCSC_OFFSET (0x0078 >> 2)
+#define V_BLEND_CB_OUTCSC_OFFSET (0x007C >> 2)
+
+#define V_BLEND_IN2CSC_COEFF(n) ((0x0080 + 4 * n) >> 2)
+
+#define V_BLEND_LUMA_IN2CSC_OFFSET (0x00A4 >> 2)
+#define V_BLEND_CR_IN2CSC_OFFSET (0x00A8 >> 2)
+#define V_BLEND_CB_IN2CSC_OFFSET (0x00AC >> 2)
+#define V_BLEND_CHROMA_KEY_ENABLE (0x01D0 >> 2)
+#define V_BLEND_CHROMA_KEY_COMP1 (0x01D4 >> 2)
+#define V_BLEND_CHROMA_KEY_COMP2 (0x01D8 >> 2)
+#define V_BLEND_CHROMA_KEY_COMP3 (0x01DC >> 2)
+
+/*
+ * Registers offset for Audio Video Buffer configuration.
+ */
+#define AV_BUF_MANAGER_OFFSET (0xB000)
+#define AV_BUF_FORMAT (0x0000 >> 2)
+#define AV_BUF_NON_LIVE_LATENCY (0x0008 >> 2)
+#define AV_CHBUF0 (0x0010 >> 2)
+#define AV_CHBUF1 (0x0014 >> 2)
+#define AV_CHBUF2 (0x0018 >> 2)
+#define AV_CHBUF3 (0x001C >> 2)
+#define AV_CHBUF4 (0x0020 >> 2)
+#define AV_CHBUF5 (0x0024 >> 2)
+#define AV_BUF_STC_CONTROL (0x002C >> 2)
+#define AV_BUF_STC_INIT_VALUE0 (0x0030 >> 2)
+#define AV_BUF_STC_INIT_VALUE1 (0x0034 >> 2)
+#define AV_BUF_STC_ADJ (0x0038 >> 2)
+#define AV_BUF_STC_VIDEO_VSYNC_TS_REG0 (0x003C >> 2)
+#define AV_BUF_STC_VIDEO_VSYNC_TS_REG1 (0x0040 >> 2)
+#define AV_BUF_STC_EXT_VSYNC_TS_REG0 (0x0044 >> 2)
+#define AV_BUF_STC_EXT_VSYNC_TS_REG1 (0x0048 >> 2)
+#define AV_BUF_STC_CUSTOM_EVENT_TS_REG0 (0x004C >> 2)
+#define AV_BUF_STC_CUSTOM_EVENT_TS_REG1 (0x0050 >> 2)
+#define AV_BUF_STC_CUSTOM_EVENT2_TS_REG0 (0x0054 >> 2)
+#define AV_BUF_STC_CUSTOM_EVENT2_TS_REG1 (0x0058 >> 2)
+#define AV_BUF_STC_SNAPSHOT0 (0x0060 >> 2)
+#define AV_BUF_STC_SNAPSHOT1 (0x0064 >> 2)
+#define AV_BUF_OUTPUT_AUDIO_VIDEO_SELECT (0x0070 >> 2)
+#define AV_BUF_HCOUNT_VCOUNT_INT0 (0x0074 >> 2)
+#define AV_BUF_HCOUNT_VCOUNT_INT1 (0x0078 >> 2)
+#define AV_BUF_DITHER_CONFIG (0x007C >> 2)
+#define AV_BUF_DITHER_CONFIG_MAX (0x008C >> 2)
+#define AV_BUF_DITHER_CONFIG_MIN (0x0090 >> 2)
+#define AV_BUF_PATTERN_GEN_SELECT (0x0100 >> 2)
+#define AV_BUF_AUD_VID_CLK_SOURCE (0x0120 >> 2)
+#define AV_BUF_SRST_REG (0x0124 >> 2)
+#define AV_BUF_AUDIO_RDY_INTERVAL (0x0128 >> 2)
+#define AV_BUF_AUDIO_CH_CONFIG (0x012C >> 2)
+
+#define AV_BUF_GRAPHICS_COMP_SCALE_FACTOR(n)((0x0200 + 4 * n) >> 2)
+
+#define AV_BUF_VIDEO_COMP_SCALE_FACTOR(n) ((0x020C + 4 * n) >> 2)
+
+#define AV_BUF_LIVE_VIDEO_COMP_SF(n) ((0x0218 + 4 * n) >> 2)
+
+#define AV_BUF_LIVE_VID_CONFIG (0x0224 >> 2)
+
+#define AV_BUF_LIVE_GFX_COMP_SF(n) ((0x0228 + 4 * n) >> 2)
+
+#define AV_BUF_LIVE_GFX_CONFIG (0x0234 >> 2)
+
+#define AUDIO_MIXER_REGISTER_OFFSET (0xC000)
+#define AUDIO_MIXER_VOLUME_CONTROL (0x0000 >> 2)
+#define AUDIO_MIXER_META_DATA (0x0004 >> 2)
+#define AUD_CH_STATUS_REG(n) ((0x0008 + 4 * n) >> 2)
+#define AUD_CH_A_DATA_REG(n) ((0x0020 + 4 * n) >> 2)
+#define AUD_CH_B_DATA_REG(n) ((0x0038 + 4 * n) >> 2)
+
+#define DP_AUDIO_DMA_CHANNEL(n) (4 + n)
+#define DP_GRAPHIC_DMA_CHANNEL (3)
+#define DP_VIDEO_DMA_CHANNEL (0)
+
+enum DPGraphicFmt {
+ DP_GRAPHIC_RGBA8888 = 0 << 8,
+ DP_GRAPHIC_ABGR8888 = 1 << 8,
+ DP_GRAPHIC_RGB888 = 2 << 8,
+ DP_GRAPHIC_BGR888 = 3 << 8,
+ DP_GRAPHIC_RGBA5551 = 4 << 8,
+ DP_GRAPHIC_RGBA4444 = 5 << 8,
+ DP_GRAPHIC_RGB565 = 6 << 8,
+ DP_GRAPHIC_8BPP = 7 << 8,
+ DP_GRAPHIC_4BPP = 8 << 8,
+ DP_GRAPHIC_2BPP = 9 << 8,
+ DP_GRAPHIC_1BPP = 10 << 8,
+ DP_GRAPHIC_MASK = 0xF << 8
+};
+
+enum DPVideoFmt {
+ DP_NL_VID_CB_Y0_CR_Y1 = 0,
+ DP_NL_VID_CR_Y0_CB_Y1 = 1,
+ DP_NL_VID_Y0_CR_Y1_CB = 2,
+ DP_NL_VID_Y0_CB_Y1_CR = 3,
+ DP_NL_VID_YV16 = 4,
+ DP_NL_VID_YV24 = 5,
+ DP_NL_VID_YV16CL = 6,
+ DP_NL_VID_MONO = 7,
+ DP_NL_VID_YV16CL2 = 8,
+ DP_NL_VID_YUV444 = 9,
+ DP_NL_VID_RGB888 = 10,
+ DP_NL_VID_RGBA8880 = 11,
+ DP_NL_VID_RGB888_10BPC = 12,
+ DP_NL_VID_YUV444_10BPC = 13,
+ DP_NL_VID_YV16CL2_10BPC = 14,
+ DP_NL_VID_YV16CL_10BPC = 15,
+ DP_NL_VID_YV16_10BPC = 16,
+ DP_NL_VID_YV24_10BPC = 17,
+ DP_NL_VID_Y_ONLY_10BPC = 18,
+ DP_NL_VID_YV16_420 = 19,
+ DP_NL_VID_YV16CL_420 = 20,
+ DP_NL_VID_YV16CL2_420 = 21,
+ DP_NL_VID_YV16_420_10BPC = 22,
+ DP_NL_VID_YV16CL_420_10BPC = 23,
+ DP_NL_VID_YV16CL2_420_10BPC = 24,
+ DP_NL_VID_FMT_MASK = 0x1F
+};
+
+typedef enum DPGraphicFmt DPGraphicFmt;
+typedef enum DPVideoFmt DPVideoFmt;
+
+static const VMStateDescription vmstate_dp = {
+ .name = TYPE_XLNX_DP,
+ .version_id = 1,
+ .fields = (VMStateField[]){
+ VMSTATE_UINT32_ARRAY(core_registers, XlnxDPState,
+ DP_CORE_REG_ARRAY_SIZE),
+ VMSTATE_UINT32_ARRAY(avbufm_registers, XlnxDPState,
+ DP_AVBUF_REG_ARRAY_SIZE),
+ VMSTATE_UINT32_ARRAY(vblend_registers, XlnxDPState,
+ DP_VBLEND_REG_ARRAY_SIZE),
+ VMSTATE_UINT32_ARRAY(audio_registers, XlnxDPState,
+ DP_AUDIO_REG_ARRAY_SIZE),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void xlnx_dp_update_irq(XlnxDPState *s);
+
+static uint64_t xlnx_dp_audio_read(void *opaque, hwaddr offset, unsigned size)
+{
+ XlnxDPState *s = XLNX_DP(opaque);
+
+ offset = offset >> 2;
+ return s->audio_registers[offset];
+}
+
+static void xlnx_dp_audio_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ XlnxDPState *s = XLNX_DP(opaque);
+
+ offset = offset >> 2;
+
+ switch (offset) {
+ case AUDIO_MIXER_META_DATA:
+ s->audio_registers[offset] = value & 0x00000001;
+ break;
+ default:
+ s->audio_registers[offset] = value;
+ break;
+ }
+}
+
+static const MemoryRegionOps audio_ops = {
+ .read = xlnx_dp_audio_read,
+ .write = xlnx_dp_audio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static inline uint32_t xlnx_dp_audio_get_volume(XlnxDPState *s,
+ uint8_t channel)
+{
+ switch (channel) {
+ case 0:
+ return extract32(s->audio_registers[AUDIO_MIXER_VOLUME_CONTROL], 0, 16);
+ case 1:
+ return extract32(s->audio_registers[AUDIO_MIXER_VOLUME_CONTROL], 16,
+ 16);
+ default:
+ return 0;
+ }
+}
+
+static inline void xlnx_dp_audio_activate(XlnxDPState *s)
+{
+ bool activated = ((s->core_registers[DP_TX_AUDIO_CONTROL]
+ & DP_TX_AUD_CTRL) != 0);
+ AUD_set_active_out(s->amixer_output_stream, activated);
+ xlnx_dpdma_set_host_data_location(s->dpdma, DP_AUDIO_DMA_CHANNEL(0),
+ &s->audio_buffer_0);
+ xlnx_dpdma_set_host_data_location(s->dpdma, DP_AUDIO_DMA_CHANNEL(1),
+ &s->audio_buffer_1);
+}
+
+static inline void xlnx_dp_audio_mix_buffer(XlnxDPState *s)
+{
+ /*
+ * Audio packets are signed and have this shape:
+ * | 16 | 16 | 16 | 16 | 16 | 16 | 16 | 16 |
+ * | R3 | L3 | R2 | L2 | R1 | L1 | R0 | L0 |
+ *
+ * Output audio is 16bits saturated.
+ */
+ int i;
+
+ if ((s->audio_data_available[0]) && (xlnx_dp_audio_get_volume(s, 0))) {
+ for (i = 0; i < s->audio_data_available[0] / 2; i++) {
+ s->temp_buffer[i] = (int64_t)(s->audio_buffer_0[i])
+ * xlnx_dp_audio_get_volume(s, 0) / 8192;
+ }
+ s->byte_left = s->audio_data_available[0];
+ } else {
+ memset(s->temp_buffer, 0, s->audio_data_available[1] / 2);
+ }
+
+ if ((s->audio_data_available[1]) && (xlnx_dp_audio_get_volume(s, 1))) {
+ if ((s->audio_data_available[0] == 0)
+ || (s->audio_data_available[1] == s->audio_data_available[0])) {
+ for (i = 0; i < s->audio_data_available[1] / 2; i++) {
+ s->temp_buffer[i] += (int64_t)(s->audio_buffer_1[i])
+ * xlnx_dp_audio_get_volume(s, 1) / 8192;
+ }
+ s->byte_left = s->audio_data_available[1];
+ }
+ }
+
+ for (i = 0; i < s->byte_left / 2; i++) {
+ s->out_buffer[i] = MAX(-32767, MIN(s->temp_buffer[i], 32767));
+ }
+
+ s->data_ptr = 0;
+}
+
+static void xlnx_dp_audio_callback(void *opaque, int avail)
+{
+ /*
+ * Get some data from the DPDMA and compute these datas.
+ * Then wait for QEMU's audio subsystem to call this callback.
+ */
+ XlnxDPState *s = XLNX_DP(opaque);
+ size_t written = 0;
+
+ /* If there are already some data don't get more data. */
+ if (s->byte_left == 0) {
+ s->audio_data_available[0] = xlnx_dpdma_start_operation(s->dpdma, 4,
+ true);
+ s->audio_data_available[1] = xlnx_dpdma_start_operation(s->dpdma, 5,
+ true);
+ xlnx_dp_audio_mix_buffer(s);
+ }
+
+ /* Send the buffer through the audio. */
+ if (s->byte_left <= MAX_QEMU_BUFFER_SIZE) {
+ if (s->byte_left != 0) {
+ written = AUD_write(s->amixer_output_stream,
+ &s->out_buffer[s->data_ptr], s->byte_left);
+ } else {
+ /*
+ * There is nothing to play.. We don't have any data! Fill the
+ * buffer with zero's and send it.
+ */
+ written = 0;
+ memset(s->out_buffer, 0, 1024);
+ AUD_write(s->amixer_output_stream, s->out_buffer, 1024);
+ }
+ } else {
+ written = AUD_write(s->amixer_output_stream,
+ &s->out_buffer[s->data_ptr], MAX_QEMU_BUFFER_SIZE);
+ }
+ s->byte_left -= written;
+ s->data_ptr += written;
+}
+
+/*
+ * AUX channel related function.
+ */
+static void xlnx_dp_aux_clear_rx_fifo(XlnxDPState *s)
+{
+ fifo8_reset(&s->rx_fifo);
+}
+
+static void xlnx_dp_aux_push_rx_fifo(XlnxDPState *s, uint8_t *buf, size_t len)
+{
+ DPRINTF("Push %u data in rx_fifo\n", (unsigned)len);
+ fifo8_push_all(&s->rx_fifo, buf, len);
+}
+
+static uint8_t xlnx_dp_aux_pop_rx_fifo(XlnxDPState *s)
+{
+ uint8_t ret;
+
+ if (fifo8_is_empty(&s->rx_fifo)) {
+ DPRINTF("rx_fifo underflow..\n");
+ abort();
+ }
+ ret = fifo8_pop(&s->rx_fifo);
+ DPRINTF("pop 0x%" PRIX8 " from rx_fifo.\n", ret);
+ return ret;
+}
+
+static void xlnx_dp_aux_clear_tx_fifo(XlnxDPState *s)
+{
+ fifo8_reset(&s->tx_fifo);
+}
+
+static void xlnx_dp_aux_push_tx_fifo(XlnxDPState *s, uint8_t val, size_t len)
+{
+ DPRINTF("Push %u data in tx_fifo\n", (unsigned)len);
+ fifo8_push_all(&s->tx_fifo, &val, len);
+}
+
+static uint8_t xlnx_dp_aux_pop_tx_fifo(XlnxDPState *s)
+{
+ uint8_t ret;
+
+ if (fifo8_is_empty(&s->tx_fifo)) {
+ DPRINTF("tx_fifo underflow..\n");
+ abort();
+ }
+ ret = fifo8_pop(&s->tx_fifo);
+ DPRINTF("pop 0x%2.2X from tx_fifo.\n", ret);
+ return ret;
+}
+
+static uint32_t xlnx_dp_aux_get_address(XlnxDPState *s)
+{
+ return s->core_registers[DP_AUX_ADDRESS];
+}
+
+/*
+ * Get command from the register.
+ */
+static void xlnx_dp_aux_set_command(XlnxDPState *s, uint32_t value)
+{
+ bool address_only = (value & AUX_ADDR_ONLY_MASK) != 0;
+ AUXCommand cmd = (value & AUX_COMMAND_MASK) >> AUX_COMMAND_SHIFT;
+ uint8_t nbytes = (value & AUX_COMMAND_NBYTES) + 1;
+ uint8_t buf[16];
+ int i;
+
+ /*
+ * When an address_only command is executed nothing happen to the fifo, so
+ * just make nbytes = 0.
+ */
+ if (address_only) {
+ nbytes = 0;
+ }
+
+ switch (cmd) {
+ case READ_AUX:
+ case READ_I2C:
+ case READ_I2C_MOT:
+ s->core_registers[DP_AUX_REPLY_CODE] = aux_request(s->aux_bus, cmd,
+ xlnx_dp_aux_get_address(s),
+ nbytes, buf);
+ s->core_registers[DP_REPLY_DATA_COUNT] = nbytes;
+
+ if (s->core_registers[DP_AUX_REPLY_CODE] == AUX_I2C_ACK) {
+ xlnx_dp_aux_push_rx_fifo(s, buf, nbytes);
+ }
+ break;
+ case WRITE_AUX:
+ case WRITE_I2C:
+ case WRITE_I2C_MOT:
+ for (i = 0; i < nbytes; i++) {
+ buf[i] = xlnx_dp_aux_pop_tx_fifo(s);
+ }
+ s->core_registers[DP_AUX_REPLY_CODE] = aux_request(s->aux_bus, cmd,
+ xlnx_dp_aux_get_address(s),
+ nbytes, buf);
+ xlnx_dp_aux_clear_tx_fifo(s);
+ break;
+ case WRITE_I2C_STATUS:
+ qemu_log_mask(LOG_UNIMP, "xlnx_dp: Write i2c status not implemented\n");
+ break;
+ default:
+ abort();
+ }
+
+ s->core_registers[DP_INTERRUPT_SIGNAL_STATE] |= 0x04;
+}
+
+static void xlnx_dp_set_dpdma(Object *obj, const char *name, Object *val,
+ Error **errp)
+{
+ XlnxDPState *s = XLNX_DP(obj);
+ if (s->console) {
+ DisplaySurface *surface = qemu_console_surface(s->console);
+ XlnxDPDMAState *dma = XLNX_DPDMA(val);
+ xlnx_dpdma_set_host_data_location(dma, DP_GRAPHIC_DMA_CHANNEL,
+ surface_data(surface));
+ }
+}
+
+static inline uint8_t xlnx_dp_global_alpha_value(XlnxDPState *s)
+{
+ return (s->vblend_registers[V_BLEND_SET_GLOBAL_ALPHA_REG] & 0x1FE) >> 1;
+}
+
+static inline bool xlnx_dp_global_alpha_enabled(XlnxDPState *s)
+{
+ /*
+ * If the alpha is totally opaque (255) we consider the alpha is disabled to
+ * reduce CPU consumption.
+ */
+ return ((xlnx_dp_global_alpha_value(s) != 0xFF) &&
+ ((s->vblend_registers[V_BLEND_SET_GLOBAL_ALPHA_REG] & 0x01) != 0));
+}
+
+static void xlnx_dp_recreate_surface(XlnxDPState *s)
+{
+ /*
+ * Two possibilities, if blending is enabled the console displays
+ * bout_plane, if not g_plane is displayed.
+ */
+ uint16_t width = s->core_registers[DP_MAIN_STREAM_HRES];
+ uint16_t height = s->core_registers[DP_MAIN_STREAM_VRES];
+ DisplaySurface *current_console_surface = qemu_console_surface(s->console);
+
+ if ((width != 0) && (height != 0)) {
+ /*
+ * As dpy_gfx_replace_surface calls qemu_free_displaysurface on the
+ * surface we need to be carefull and don't free the surface associated
+ * to the console or double free will happen.
+ */
+ if (s->bout_plane.surface != current_console_surface) {
+ qemu_free_displaysurface(s->bout_plane.surface);
+ }
+ if (s->v_plane.surface != current_console_surface) {
+ qemu_free_displaysurface(s->v_plane.surface);
+ }
+ if (s->g_plane.surface != current_console_surface) {
+ qemu_free_displaysurface(s->g_plane.surface);
+ }
+
+ s->g_plane.surface
+ = qemu_create_displaysurface_from(width, height,
+ s->g_plane.format, 0, NULL);
+ s->v_plane.surface
+ = qemu_create_displaysurface_from(width, height,
+ s->v_plane.format, 0, NULL);
+ if (xlnx_dp_global_alpha_enabled(s)) {
+ s->bout_plane.surface =
+ qemu_create_displaysurface_from(width,
+ height,
+ s->g_plane.format,
+ 0, NULL);
+ dpy_gfx_replace_surface(s->console, s->bout_plane.surface);
+ } else {
+ s->bout_plane.surface = NULL;
+ dpy_gfx_replace_surface(s->console, s->g_plane.surface);
+ }
+
+ xlnx_dpdma_set_host_data_location(s->dpdma, DP_GRAPHIC_DMA_CHANNEL,
+ surface_data(s->g_plane.surface));
+ xlnx_dpdma_set_host_data_location(s->dpdma, DP_VIDEO_DMA_CHANNEL,
+ surface_data(s->v_plane.surface));
+ }
+}
+
+/*
+ * Change the graphic format of the surface.
+ */
+static void xlnx_dp_change_graphic_fmt(XlnxDPState *s)
+{
+ switch (s->avbufm_registers[AV_BUF_FORMAT] & DP_GRAPHIC_MASK) {
+ case DP_GRAPHIC_RGBA8888:
+ s->g_plane.format = PIXMAN_r8g8b8a8;
+ break;
+ case DP_GRAPHIC_ABGR8888:
+ s->g_plane.format = PIXMAN_a8b8g8r8;
+ break;
+ case DP_GRAPHIC_RGB565:
+ s->g_plane.format = PIXMAN_r5g6b5;
+ break;
+ case DP_GRAPHIC_RGB888:
+ s->g_plane.format = PIXMAN_r8g8b8;
+ break;
+ case DP_GRAPHIC_BGR888:
+ s->g_plane.format = PIXMAN_b8g8r8;
+ break;
+ default:
+ DPRINTF("error: unsupported graphic format %u.\n",
+ s->avbufm_registers[AV_BUF_FORMAT] & DP_GRAPHIC_MASK);
+ abort();
+ }
+
+ switch (s->avbufm_registers[AV_BUF_FORMAT] & DP_NL_VID_FMT_MASK) {
+ case 0:
+ s->v_plane.format = PIXMAN_x8b8g8r8;
+ break;
+ case DP_NL_VID_RGBA8880:
+ s->v_plane.format = PIXMAN_x8b8g8r8;
+ break;
+ default:
+ DPRINTF("error: unsupported video format %u.\n",
+ s->avbufm_registers[AV_BUF_FORMAT] & DP_NL_VID_FMT_MASK);
+ abort();
+ }
+
+ xlnx_dp_recreate_surface(s);
+}
+
+static void xlnx_dp_update_irq(XlnxDPState *s)
+{
+ uint32_t flags;
+
+ flags = s->core_registers[DP_INT_STATUS] & ~s->core_registers[DP_INT_MASK];
+ DPRINTF("update IRQ value = %" PRIx32 "\n", flags);
+ qemu_set_irq(s->irq, flags != 0);
+}
+
+static uint64_t xlnx_dp_read(void *opaque, hwaddr offset, unsigned size)
+{
+ XlnxDPState *s = XLNX_DP(opaque);
+ uint64_t ret = 0;
+
+ offset = offset >> 2;
+
+ switch (offset) {
+ case DP_TX_USER_FIFO_OVERFLOW:
+ /* This register is cleared after a read */
+ ret = s->core_registers[DP_TX_USER_FIFO_OVERFLOW];
+ s->core_registers[DP_TX_USER_FIFO_OVERFLOW] = 0;
+ break;
+ case DP_AUX_REPLY_DATA:
+ ret = xlnx_dp_aux_pop_rx_fifo(s);
+ break;
+ case DP_INTERRUPT_SIGNAL_STATE:
+ /*
+ * XXX: Not sure it is the right thing to do actually.
+ * The register is not written by the device driver so it's stuck
+ * to 0x04.
+ */
+ ret = s->core_registers[DP_INTERRUPT_SIGNAL_STATE];
+ s->core_registers[DP_INTERRUPT_SIGNAL_STATE] &= ~0x04;
+ break;
+ case DP_AUX_WRITE_FIFO:
+ case DP_TX_AUDIO_INFO_DATA(0):
+ case DP_TX_AUDIO_INFO_DATA(1):
+ case DP_TX_AUDIO_INFO_DATA(2):
+ case DP_TX_AUDIO_INFO_DATA(3):
+ case DP_TX_AUDIO_INFO_DATA(4):
+ case DP_TX_AUDIO_INFO_DATA(5):
+ case DP_TX_AUDIO_INFO_DATA(6):
+ case DP_TX_AUDIO_INFO_DATA(7):
+ case DP_TX_AUDIO_EXT_DATA(0):
+ case DP_TX_AUDIO_EXT_DATA(1):
+ case DP_TX_AUDIO_EXT_DATA(2):
+ case DP_TX_AUDIO_EXT_DATA(3):
+ case DP_TX_AUDIO_EXT_DATA(4):
+ case DP_TX_AUDIO_EXT_DATA(5):
+ case DP_TX_AUDIO_EXT_DATA(6):
+ case DP_TX_AUDIO_EXT_DATA(7):
+ case DP_TX_AUDIO_EXT_DATA(8):
+ /* write only registers */
+ ret = 0;
+ break;
+ default:
+ assert(offset <= (0x3AC >> 2));
+ ret = s->core_registers[offset];
+ break;
+ }
+
+ DPRINTF("core read @%" PRIx64 " = 0x%8.8lX\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.8lX\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 0000000..d3a03f1
--- /dev/null
+++ b/include/hw/display/xlnx_dp.h
@@ -0,0 +1,109 @@
+/*
+ * xlnx_dp.h
+ *
+ * Copyright (C) 2015 : GreenSocs Ltd
+ * http://www.greensocs.com/ , email: info@greensocs.com
+ *
+ * Developed by :
+ * Frederic Konrad <fred.konrad@greensocs.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "hw/sysbus.h"
+#include "ui/console.h"
+#include "hw/misc/aux.h"
+#include "hw/i2c/i2c.h"
+#include "hw/display/dpcd.h"
+#include "hw/i2c/i2c-ddc.h"
+#include "qemu/fifo8.h"
+#include "hw/dma/xlnx_dpdma.h"
+#include "audio/audio.h"
+
+#ifndef XLNX_DP_H
+#define XLNX_DP_H
+
+#define AUD_CHBUF_MAX_DEPTH 32768
+#define MAX_QEMU_BUFFER_SIZE 4096
+
+#define DP_CORE_REG_ARRAY_SIZE (0x3AF >> 2)
+#define DP_AVBUF_REG_ARRAY_SIZE (0x238 >> 2)
+#define DP_VBLEND_REG_ARRAY_SIZE (0x1DF >> 2)
+#define DP_AUDIO_REG_ARRAY_SIZE (0x50 >> 2)
+
+struct PixmanPlane {
+ pixman_format_code_t format;
+ DisplaySurface *surface;
+};
+
+typedef struct XlnxDPState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+
+ /* < public >*/
+ MemoryRegion container;
+
+ uint32_t core_registers[DP_CORE_REG_ARRAY_SIZE];
+ MemoryRegion core_iomem;
+
+ uint32_t avbufm_registers[DP_AVBUF_REG_ARRAY_SIZE];
+ MemoryRegion avbufm_iomem;
+
+ uint32_t vblend_registers[DP_VBLEND_REG_ARRAY_SIZE];
+ MemoryRegion vblend_iomem;
+
+ uint32_t audio_registers[DP_AUDIO_REG_ARRAY_SIZE];
+ MemoryRegion audio_iomem;
+
+ QemuConsole *console;
+
+ /*
+ * This is the planes used to display in console. When the blending is
+ * enabled bout_plane is displayed in console else it's g_plane.
+ */
+ struct PixmanPlane g_plane;
+ struct PixmanPlane v_plane;
+ struct PixmanPlane bout_plane;
+
+ QEMUSoundCard aud_card;
+ SWVoiceOut *amixer_output_stream;
+ int16_t audio_buffer_0[AUD_CHBUF_MAX_DEPTH];
+ int16_t audio_buffer_1[AUD_CHBUF_MAX_DEPTH];
+ size_t audio_data_available[2];
+ int64_t temp_buffer[AUD_CHBUF_MAX_DEPTH];
+ int16_t out_buffer[AUD_CHBUF_MAX_DEPTH];
+ size_t byte_left; /* byte available in out_buffer. */
+ size_t data_ptr; /* next byte to be sent to QEMU. */
+
+ /* Associated DPDMA controller. */
+ XlnxDPDMAState *dpdma;
+
+ qemu_irq irq;
+
+ AUXBus *aux_bus;
+ Fifo8 rx_fifo;
+ Fifo8 tx_fifo;
+
+ /*
+ * XXX: This should be in an other module.
+ */
+ DPCDState *dpcd;
+ I2CDDCState *edid;
+} XlnxDPState;
+
+#define TYPE_XLNX_DP "xlnx.v-dp"
+#define XLNX_DP(obj) OBJECT_CHECK(XlnxDPState, (obj), TYPE_XLNX_DP)
+
+#endif /* !XLNX_DP_H */
--
1.8.3.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [Qemu-devel] [PATCH V8 8/9] introduce xlnx-dp
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 8/9] introduce xlnx-dp fred.konrad
@ 2016-06-06 19:02 ` Alistair Francis
0 siblings, 0 replies; 14+ messages in thread
From: Alistair Francis @ 2016-06-06 19:02 UTC (permalink / raw)
To: KONRAD Frédéric
Cc: qemu-devel@nongnu.org Developers, Edgar Iglesias, Peter Maydell,
hyunk, Mark Burton, Alistair Francis, Peter Crosthwaite,
Guillaume Delbergue
On Mon, Jun 6, 2016 at 7:21 AM, <fred.konrad@greensocs.com> wrote:
> From: KONRAD Frederic <fred.konrad@greensocs.com>
>
> 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 <fred.konrad@greensocs.com>
> Tested-By: Hyun Kwon <hyun.kwon@xilinx.com>
Reviewed-by: Alistair Francis <alistair.francis@xilinx.com>
Thanks,
Alistair
> ---
> 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
>
> diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs
> index ddf3275..063889b 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 0000000..552955f
> --- /dev/null
> +++ b/hw/display/xlnx_dp.c
> @@ -0,0 +1,1336 @@
> +/*
> + * xlnx_dp.c
> + *
> + * Copyright (C) 2015 : GreenSocs Ltd
> + * http://www.greensocs.com/ , email: info@greensocs.com
> + *
> + * Developed by :
> + * Frederic Konrad <fred.konrad@greensocs.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation, either version 2 of the License, or
> + * (at your option)any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "hw/display/xlnx_dp.h"
> +
> +#ifndef DEBUG_DP
> +#define DEBUG_DP 0
> +#endif
> +
> +#define DPRINTF(fmt, ...) do { \
> + if (DEBUG_DP) { \
> + qemu_log("xlnx_dp: " fmt , ## __VA_ARGS__); \
> + } \
> +} while (0);
> +
> +/*
> + * Register offset for DP.
> + */
> +#define DP_LINK_BW_SET (0x0000 >> 2)
> +#define DP_LANE_COUNT_SET (0x0004 >> 2)
> +#define DP_ENHANCED_FRAME_EN (0x0008 >> 2)
> +#define DP_TRAINING_PATTERN_SET (0x000C >> 2)
> +#define DP_LINK_QUAL_PATTERN_SET (0x0010 >> 2)
> +#define DP_SCRAMBLING_DISABLE (0x0014 >> 2)
> +#define DP_DOWNSPREAD_CTRL (0x0018 >> 2)
> +#define DP_SOFTWARE_RESET (0x001C >> 2)
> +#define DP_TRANSMITTER_ENABLE (0x0080 >> 2)
> +#define DP_MAIN_STREAM_ENABLE (0x0084 >> 2)
> +#define DP_FORCE_SCRAMBLER_RESET (0x00C0 >> 2)
> +#define DP_VERSION_REGISTER (0x00F8 >> 2)
> +#define DP_CORE_ID (0x00FC >> 2)
> +
> +#define DP_AUX_COMMAND_REGISTER (0x0100 >> 2)
> +#define AUX_ADDR_ONLY_MASK (0x1000)
> +#define AUX_COMMAND_MASK (0x0F00)
> +#define AUX_COMMAND_SHIFT (8)
> +#define AUX_COMMAND_NBYTES (0x000F)
> +
> +#define DP_AUX_WRITE_FIFO (0x0104 >> 2)
> +#define DP_AUX_ADDRESS (0x0108 >> 2)
> +#define DP_AUX_CLOCK_DIVIDER (0x010C >> 2)
> +#define DP_TX_USER_FIFO_OVERFLOW (0x0110 >> 2)
> +#define DP_INTERRUPT_SIGNAL_STATE (0x0130 >> 2)
> +#define DP_AUX_REPLY_DATA (0x0134 >> 2)
> +#define DP_AUX_REPLY_CODE (0x0138 >> 2)
> +#define DP_AUX_REPLY_COUNT (0x013C >> 2)
> +#define DP_REPLY_DATA_COUNT (0x0148 >> 2)
> +#define DP_REPLY_STATUS (0x014C >> 2)
> +#define DP_HPD_DURATION (0x0150 >> 2)
> +#define DP_MAIN_STREAM_HTOTAL (0x0180 >> 2)
> +#define DP_MAIN_STREAM_VTOTAL (0x0184 >> 2)
> +#define DP_MAIN_STREAM_POLARITY (0x0188 >> 2)
> +#define DP_MAIN_STREAM_HSWIDTH (0x018C >> 2)
> +#define DP_MAIN_STREAM_VSWIDTH (0x0190 >> 2)
> +#define DP_MAIN_STREAM_HRES (0x0194 >> 2)
> +#define DP_MAIN_STREAM_VRES (0x0198 >> 2)
> +#define DP_MAIN_STREAM_HSTART (0x019C >> 2)
> +#define DP_MAIN_STREAM_VSTART (0x01A0 >> 2)
> +#define DP_MAIN_STREAM_MISC0 (0x01A4 >> 2)
> +#define DP_MAIN_STREAM_MISC1 (0x01A8 >> 2)
> +#define DP_MAIN_STREAM_M_VID (0x01AC >> 2)
> +#define DP_MSA_TRANSFER_UNIT_SIZE (0x01B0 >> 2)
> +#define DP_MAIN_STREAM_N_VID (0x01B4 >> 2)
> +#define DP_USER_DATA_COUNT_PER_LANE (0x01BC >> 2)
> +#define DP_MIN_BYTES_PER_TU (0x01C4 >> 2)
> +#define DP_FRAC_BYTES_PER_TU (0x01C8 >> 2)
> +#define DP_INIT_WAIT (0x01CC >> 2)
> +#define DP_PHY_RESET (0x0200 >> 2)
> +#define DP_PHY_VOLTAGE_DIFF_LANE_0 (0x0220 >> 2)
> +#define DP_PHY_VOLTAGE_DIFF_LANE_1 (0x0224 >> 2)
> +#define DP_TRANSMIT_PRBS7 (0x0230 >> 2)
> +#define DP_PHY_CLOCK_SELECT (0x0234 >> 2)
> +#define DP_TX_PHY_POWER_DOWN (0x0238 >> 2)
> +#define DP_PHY_PRECURSOR_LANE_0 (0x023C >> 2)
> +#define DP_PHY_PRECURSOR_LANE_1 (0x0240 >> 2)
> +#define DP_PHY_POSTCURSOR_LANE_0 (0x024C >> 2)
> +#define DP_PHY_POSTCURSOR_LANE_1 (0x0250 >> 2)
> +#define DP_PHY_STATUS (0x0280 >> 2)
> +
> +#define DP_TX_AUDIO_CONTROL (0x0300 >> 2)
> +#define DP_TX_AUD_CTRL (1)
> +
> +#define DP_TX_AUDIO_CHANNELS (0x0304 >> 2)
> +#define DP_TX_AUDIO_INFO_DATA(n) ((0x0308 + 4 * n) >> 2)
> +#define DP_TX_M_AUD (0x0328 >> 2)
> +#define DP_TX_N_AUD (0x032C >> 2)
> +#define DP_TX_AUDIO_EXT_DATA(n) ((0x0330 + 4 * n) >> 2)
> +#define DP_INT_STATUS (0x03A0 >> 2)
> +#define DP_INT_MASK (0x03A4 >> 2)
> +#define DP_INT_EN (0x03A8 >> 2)
> +#define DP_INT_DS (0x03AC >> 2)
> +
> +/*
> + * Registers offset for Audio Video Buffer configuration.
> + */
> +#define V_BLEND_OFFSET (0xA000)
> +#define V_BLEND_BG_CLR_0 (0x0000 >> 2)
> +#define V_BLEND_BG_CLR_1 (0x0004 >> 2)
> +#define V_BLEND_BG_CLR_2 (0x0008 >> 2)
> +#define V_BLEND_SET_GLOBAL_ALPHA_REG (0x000C >> 2)
> +#define V_BLEND_OUTPUT_VID_FORMAT (0x0014 >> 2)
> +#define V_BLEND_LAYER0_CONTROL (0x0018 >> 2)
> +#define V_BLEND_LAYER1_CONTROL (0x001C >> 2)
> +
> +#define V_BLEND_RGB2YCBCR_COEFF(n) ((0x0020 + 4 * n) >> 2)
> +#define V_BLEND_IN1CSC_COEFF(n) ((0x0044 + 4 * n) >> 2)
> +
> +#define V_BLEND_LUMA_IN1CSC_OFFSET (0x0068 >> 2)
> +#define V_BLEND_CR_IN1CSC_OFFSET (0x006C >> 2)
> +#define V_BLEND_CB_IN1CSC_OFFSET (0x0070 >> 2)
> +#define V_BLEND_LUMA_OUTCSC_OFFSET (0x0074 >> 2)
> +#define V_BLEND_CR_OUTCSC_OFFSET (0x0078 >> 2)
> +#define V_BLEND_CB_OUTCSC_OFFSET (0x007C >> 2)
> +
> +#define V_BLEND_IN2CSC_COEFF(n) ((0x0080 + 4 * n) >> 2)
> +
> +#define V_BLEND_LUMA_IN2CSC_OFFSET (0x00A4 >> 2)
> +#define V_BLEND_CR_IN2CSC_OFFSET (0x00A8 >> 2)
> +#define V_BLEND_CB_IN2CSC_OFFSET (0x00AC >> 2)
> +#define V_BLEND_CHROMA_KEY_ENABLE (0x01D0 >> 2)
> +#define V_BLEND_CHROMA_KEY_COMP1 (0x01D4 >> 2)
> +#define V_BLEND_CHROMA_KEY_COMP2 (0x01D8 >> 2)
> +#define V_BLEND_CHROMA_KEY_COMP3 (0x01DC >> 2)
> +
> +/*
> + * Registers offset for Audio Video Buffer configuration.
> + */
> +#define AV_BUF_MANAGER_OFFSET (0xB000)
> +#define AV_BUF_FORMAT (0x0000 >> 2)
> +#define AV_BUF_NON_LIVE_LATENCY (0x0008 >> 2)
> +#define AV_CHBUF0 (0x0010 >> 2)
> +#define AV_CHBUF1 (0x0014 >> 2)
> +#define AV_CHBUF2 (0x0018 >> 2)
> +#define AV_CHBUF3 (0x001C >> 2)
> +#define AV_CHBUF4 (0x0020 >> 2)
> +#define AV_CHBUF5 (0x0024 >> 2)
> +#define AV_BUF_STC_CONTROL (0x002C >> 2)
> +#define AV_BUF_STC_INIT_VALUE0 (0x0030 >> 2)
> +#define AV_BUF_STC_INIT_VALUE1 (0x0034 >> 2)
> +#define AV_BUF_STC_ADJ (0x0038 >> 2)
> +#define AV_BUF_STC_VIDEO_VSYNC_TS_REG0 (0x003C >> 2)
> +#define AV_BUF_STC_VIDEO_VSYNC_TS_REG1 (0x0040 >> 2)
> +#define AV_BUF_STC_EXT_VSYNC_TS_REG0 (0x0044 >> 2)
> +#define AV_BUF_STC_EXT_VSYNC_TS_REG1 (0x0048 >> 2)
> +#define AV_BUF_STC_CUSTOM_EVENT_TS_REG0 (0x004C >> 2)
> +#define AV_BUF_STC_CUSTOM_EVENT_TS_REG1 (0x0050 >> 2)
> +#define AV_BUF_STC_CUSTOM_EVENT2_TS_REG0 (0x0054 >> 2)
> +#define AV_BUF_STC_CUSTOM_EVENT2_TS_REG1 (0x0058 >> 2)
> +#define AV_BUF_STC_SNAPSHOT0 (0x0060 >> 2)
> +#define AV_BUF_STC_SNAPSHOT1 (0x0064 >> 2)
> +#define AV_BUF_OUTPUT_AUDIO_VIDEO_SELECT (0x0070 >> 2)
> +#define AV_BUF_HCOUNT_VCOUNT_INT0 (0x0074 >> 2)
> +#define AV_BUF_HCOUNT_VCOUNT_INT1 (0x0078 >> 2)
> +#define AV_BUF_DITHER_CONFIG (0x007C >> 2)
> +#define AV_BUF_DITHER_CONFIG_MAX (0x008C >> 2)
> +#define AV_BUF_DITHER_CONFIG_MIN (0x0090 >> 2)
> +#define AV_BUF_PATTERN_GEN_SELECT (0x0100 >> 2)
> +#define AV_BUF_AUD_VID_CLK_SOURCE (0x0120 >> 2)
> +#define AV_BUF_SRST_REG (0x0124 >> 2)
> +#define AV_BUF_AUDIO_RDY_INTERVAL (0x0128 >> 2)
> +#define AV_BUF_AUDIO_CH_CONFIG (0x012C >> 2)
> +
> +#define AV_BUF_GRAPHICS_COMP_SCALE_FACTOR(n)((0x0200 + 4 * n) >> 2)
> +
> +#define AV_BUF_VIDEO_COMP_SCALE_FACTOR(n) ((0x020C + 4 * n) >> 2)
> +
> +#define AV_BUF_LIVE_VIDEO_COMP_SF(n) ((0x0218 + 4 * n) >> 2)
> +
> +#define AV_BUF_LIVE_VID_CONFIG (0x0224 >> 2)
> +
> +#define AV_BUF_LIVE_GFX_COMP_SF(n) ((0x0228 + 4 * n) >> 2)
> +
> +#define AV_BUF_LIVE_GFX_CONFIG (0x0234 >> 2)
> +
> +#define AUDIO_MIXER_REGISTER_OFFSET (0xC000)
> +#define AUDIO_MIXER_VOLUME_CONTROL (0x0000 >> 2)
> +#define AUDIO_MIXER_META_DATA (0x0004 >> 2)
> +#define AUD_CH_STATUS_REG(n) ((0x0008 + 4 * n) >> 2)
> +#define AUD_CH_A_DATA_REG(n) ((0x0020 + 4 * n) >> 2)
> +#define AUD_CH_B_DATA_REG(n) ((0x0038 + 4 * n) >> 2)
> +
> +#define DP_AUDIO_DMA_CHANNEL(n) (4 + n)
> +#define DP_GRAPHIC_DMA_CHANNEL (3)
> +#define DP_VIDEO_DMA_CHANNEL (0)
> +
> +enum DPGraphicFmt {
> + DP_GRAPHIC_RGBA8888 = 0 << 8,
> + DP_GRAPHIC_ABGR8888 = 1 << 8,
> + DP_GRAPHIC_RGB888 = 2 << 8,
> + DP_GRAPHIC_BGR888 = 3 << 8,
> + DP_GRAPHIC_RGBA5551 = 4 << 8,
> + DP_GRAPHIC_RGBA4444 = 5 << 8,
> + DP_GRAPHIC_RGB565 = 6 << 8,
> + DP_GRAPHIC_8BPP = 7 << 8,
> + DP_GRAPHIC_4BPP = 8 << 8,
> + DP_GRAPHIC_2BPP = 9 << 8,
> + DP_GRAPHIC_1BPP = 10 << 8,
> + DP_GRAPHIC_MASK = 0xF << 8
> +};
> +
> +enum DPVideoFmt {
> + DP_NL_VID_CB_Y0_CR_Y1 = 0,
> + DP_NL_VID_CR_Y0_CB_Y1 = 1,
> + DP_NL_VID_Y0_CR_Y1_CB = 2,
> + DP_NL_VID_Y0_CB_Y1_CR = 3,
> + DP_NL_VID_YV16 = 4,
> + DP_NL_VID_YV24 = 5,
> + DP_NL_VID_YV16CL = 6,
> + DP_NL_VID_MONO = 7,
> + DP_NL_VID_YV16CL2 = 8,
> + DP_NL_VID_YUV444 = 9,
> + DP_NL_VID_RGB888 = 10,
> + DP_NL_VID_RGBA8880 = 11,
> + DP_NL_VID_RGB888_10BPC = 12,
> + DP_NL_VID_YUV444_10BPC = 13,
> + DP_NL_VID_YV16CL2_10BPC = 14,
> + DP_NL_VID_YV16CL_10BPC = 15,
> + DP_NL_VID_YV16_10BPC = 16,
> + DP_NL_VID_YV24_10BPC = 17,
> + DP_NL_VID_Y_ONLY_10BPC = 18,
> + DP_NL_VID_YV16_420 = 19,
> + DP_NL_VID_YV16CL_420 = 20,
> + DP_NL_VID_YV16CL2_420 = 21,
> + DP_NL_VID_YV16_420_10BPC = 22,
> + DP_NL_VID_YV16CL_420_10BPC = 23,
> + DP_NL_VID_YV16CL2_420_10BPC = 24,
> + DP_NL_VID_FMT_MASK = 0x1F
> +};
> +
> +typedef enum DPGraphicFmt DPGraphicFmt;
> +typedef enum DPVideoFmt DPVideoFmt;
> +
> +static const VMStateDescription vmstate_dp = {
> + .name = TYPE_XLNX_DP,
> + .version_id = 1,
> + .fields = (VMStateField[]){
> + VMSTATE_UINT32_ARRAY(core_registers, XlnxDPState,
> + DP_CORE_REG_ARRAY_SIZE),
> + VMSTATE_UINT32_ARRAY(avbufm_registers, XlnxDPState,
> + DP_AVBUF_REG_ARRAY_SIZE),
> + VMSTATE_UINT32_ARRAY(vblend_registers, XlnxDPState,
> + DP_VBLEND_REG_ARRAY_SIZE),
> + VMSTATE_UINT32_ARRAY(audio_registers, XlnxDPState,
> + DP_AUDIO_REG_ARRAY_SIZE),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static void xlnx_dp_update_irq(XlnxDPState *s);
> +
> +static uint64_t xlnx_dp_audio_read(void *opaque, hwaddr offset, unsigned size)
> +{
> + XlnxDPState *s = XLNX_DP(opaque);
> +
> + offset = offset >> 2;
> + return s->audio_registers[offset];
> +}
> +
> +static void xlnx_dp_audio_write(void *opaque, hwaddr offset, uint64_t value,
> + unsigned size)
> +{
> + XlnxDPState *s = XLNX_DP(opaque);
> +
> + offset = offset >> 2;
> +
> + switch (offset) {
> + case AUDIO_MIXER_META_DATA:
> + s->audio_registers[offset] = value & 0x00000001;
> + break;
> + default:
> + s->audio_registers[offset] = value;
> + break;
> + }
> +}
> +
> +static const MemoryRegionOps audio_ops = {
> + .read = xlnx_dp_audio_read,
> + .write = xlnx_dp_audio_write,
> + .endianness = DEVICE_NATIVE_ENDIAN,
> +};
> +
> +static inline uint32_t xlnx_dp_audio_get_volume(XlnxDPState *s,
> + uint8_t channel)
> +{
> + switch (channel) {
> + case 0:
> + return extract32(s->audio_registers[AUDIO_MIXER_VOLUME_CONTROL], 0, 16);
> + case 1:
> + return extract32(s->audio_registers[AUDIO_MIXER_VOLUME_CONTROL], 16,
> + 16);
> + default:
> + return 0;
> + }
> +}
> +
> +static inline void xlnx_dp_audio_activate(XlnxDPState *s)
> +{
> + bool activated = ((s->core_registers[DP_TX_AUDIO_CONTROL]
> + & DP_TX_AUD_CTRL) != 0);
> + AUD_set_active_out(s->amixer_output_stream, activated);
> + xlnx_dpdma_set_host_data_location(s->dpdma, DP_AUDIO_DMA_CHANNEL(0),
> + &s->audio_buffer_0);
> + xlnx_dpdma_set_host_data_location(s->dpdma, DP_AUDIO_DMA_CHANNEL(1),
> + &s->audio_buffer_1);
> +}
> +
> +static inline void xlnx_dp_audio_mix_buffer(XlnxDPState *s)
> +{
> + /*
> + * Audio packets are signed and have this shape:
> + * | 16 | 16 | 16 | 16 | 16 | 16 | 16 | 16 |
> + * | R3 | L3 | R2 | L2 | R1 | L1 | R0 | L0 |
> + *
> + * Output audio is 16bits saturated.
> + */
> + int i;
> +
> + if ((s->audio_data_available[0]) && (xlnx_dp_audio_get_volume(s, 0))) {
> + for (i = 0; i < s->audio_data_available[0] / 2; i++) {
> + s->temp_buffer[i] = (int64_t)(s->audio_buffer_0[i])
> + * xlnx_dp_audio_get_volume(s, 0) / 8192;
> + }
> + s->byte_left = s->audio_data_available[0];
> + } else {
> + memset(s->temp_buffer, 0, s->audio_data_available[1] / 2);
> + }
> +
> + if ((s->audio_data_available[1]) && (xlnx_dp_audio_get_volume(s, 1))) {
> + if ((s->audio_data_available[0] == 0)
> + || (s->audio_data_available[1] == s->audio_data_available[0])) {
> + for (i = 0; i < s->audio_data_available[1] / 2; i++) {
> + s->temp_buffer[i] += (int64_t)(s->audio_buffer_1[i])
> + * xlnx_dp_audio_get_volume(s, 1) / 8192;
> + }
> + s->byte_left = s->audio_data_available[1];
> + }
> + }
> +
> + for (i = 0; i < s->byte_left / 2; i++) {
> + s->out_buffer[i] = MAX(-32767, MIN(s->temp_buffer[i], 32767));
> + }
> +
> + s->data_ptr = 0;
> +}
> +
> +static void xlnx_dp_audio_callback(void *opaque, int avail)
> +{
> + /*
> + * Get some data from the DPDMA and compute these datas.
> + * Then wait for QEMU's audio subsystem to call this callback.
> + */
> + XlnxDPState *s = XLNX_DP(opaque);
> + size_t written = 0;
> +
> + /* If there are already some data don't get more data. */
> + if (s->byte_left == 0) {
> + s->audio_data_available[0] = xlnx_dpdma_start_operation(s->dpdma, 4,
> + true);
> + s->audio_data_available[1] = xlnx_dpdma_start_operation(s->dpdma, 5,
> + true);
> + xlnx_dp_audio_mix_buffer(s);
> + }
> +
> + /* Send the buffer through the audio. */
> + if (s->byte_left <= MAX_QEMU_BUFFER_SIZE) {
> + if (s->byte_left != 0) {
> + written = AUD_write(s->amixer_output_stream,
> + &s->out_buffer[s->data_ptr], s->byte_left);
> + } else {
> + /*
> + * There is nothing to play.. We don't have any data! Fill the
> + * buffer with zero's and send it.
> + */
> + written = 0;
> + memset(s->out_buffer, 0, 1024);
> + AUD_write(s->amixer_output_stream, s->out_buffer, 1024);
> + }
> + } else {
> + written = AUD_write(s->amixer_output_stream,
> + &s->out_buffer[s->data_ptr], MAX_QEMU_BUFFER_SIZE);
> + }
> + s->byte_left -= written;
> + s->data_ptr += written;
> +}
> +
> +/*
> + * AUX channel related function.
> + */
> +static void xlnx_dp_aux_clear_rx_fifo(XlnxDPState *s)
> +{
> + fifo8_reset(&s->rx_fifo);
> +}
> +
> +static void xlnx_dp_aux_push_rx_fifo(XlnxDPState *s, uint8_t *buf, size_t len)
> +{
> + DPRINTF("Push %u data in rx_fifo\n", (unsigned)len);
> + fifo8_push_all(&s->rx_fifo, buf, len);
> +}
> +
> +static uint8_t xlnx_dp_aux_pop_rx_fifo(XlnxDPState *s)
> +{
> + uint8_t ret;
> +
> + if (fifo8_is_empty(&s->rx_fifo)) {
> + DPRINTF("rx_fifo underflow..\n");
> + abort();
> + }
> + ret = fifo8_pop(&s->rx_fifo);
> + DPRINTF("pop 0x%" PRIX8 " from rx_fifo.\n", ret);
> + return ret;
> +}
> +
> +static void xlnx_dp_aux_clear_tx_fifo(XlnxDPState *s)
> +{
> + fifo8_reset(&s->tx_fifo);
> +}
> +
> +static void xlnx_dp_aux_push_tx_fifo(XlnxDPState *s, uint8_t val, size_t len)
> +{
> + DPRINTF("Push %u data in tx_fifo\n", (unsigned)len);
> + fifo8_push_all(&s->tx_fifo, &val, len);
> +}
> +
> +static uint8_t xlnx_dp_aux_pop_tx_fifo(XlnxDPState *s)
> +{
> + uint8_t ret;
> +
> + if (fifo8_is_empty(&s->tx_fifo)) {
> + DPRINTF("tx_fifo underflow..\n");
> + abort();
> + }
> + ret = fifo8_pop(&s->tx_fifo);
> + DPRINTF("pop 0x%2.2X from tx_fifo.\n", ret);
> + return ret;
> +}
> +
> +static uint32_t xlnx_dp_aux_get_address(XlnxDPState *s)
> +{
> + return s->core_registers[DP_AUX_ADDRESS];
> +}
> +
> +/*
> + * Get command from the register.
> + */
> +static void xlnx_dp_aux_set_command(XlnxDPState *s, uint32_t value)
> +{
> + bool address_only = (value & AUX_ADDR_ONLY_MASK) != 0;
> + AUXCommand cmd = (value & AUX_COMMAND_MASK) >> AUX_COMMAND_SHIFT;
> + uint8_t nbytes = (value & AUX_COMMAND_NBYTES) + 1;
> + uint8_t buf[16];
> + int i;
> +
> + /*
> + * When an address_only command is executed nothing happen to the fifo, so
> + * just make nbytes = 0.
> + */
> + if (address_only) {
> + nbytes = 0;
> + }
> +
> + switch (cmd) {
> + case READ_AUX:
> + case READ_I2C:
> + case READ_I2C_MOT:
> + s->core_registers[DP_AUX_REPLY_CODE] = aux_request(s->aux_bus, cmd,
> + xlnx_dp_aux_get_address(s),
> + nbytes, buf);
> + s->core_registers[DP_REPLY_DATA_COUNT] = nbytes;
> +
> + if (s->core_registers[DP_AUX_REPLY_CODE] == AUX_I2C_ACK) {
> + xlnx_dp_aux_push_rx_fifo(s, buf, nbytes);
> + }
> + break;
> + case WRITE_AUX:
> + case WRITE_I2C:
> + case WRITE_I2C_MOT:
> + for (i = 0; i < nbytes; i++) {
> + buf[i] = xlnx_dp_aux_pop_tx_fifo(s);
> + }
> + s->core_registers[DP_AUX_REPLY_CODE] = aux_request(s->aux_bus, cmd,
> + xlnx_dp_aux_get_address(s),
> + nbytes, buf);
> + xlnx_dp_aux_clear_tx_fifo(s);
> + break;
> + case WRITE_I2C_STATUS:
> + qemu_log_mask(LOG_UNIMP, "xlnx_dp: Write i2c status not implemented\n");
> + break;
> + default:
> + abort();
> + }
> +
> + s->core_registers[DP_INTERRUPT_SIGNAL_STATE] |= 0x04;
> +}
> +
> +static void xlnx_dp_set_dpdma(Object *obj, const char *name, Object *val,
> + Error **errp)
> +{
> + XlnxDPState *s = XLNX_DP(obj);
> + if (s->console) {
> + DisplaySurface *surface = qemu_console_surface(s->console);
> + XlnxDPDMAState *dma = XLNX_DPDMA(val);
> + xlnx_dpdma_set_host_data_location(dma, DP_GRAPHIC_DMA_CHANNEL,
> + surface_data(surface));
> + }
> +}
> +
> +static inline uint8_t xlnx_dp_global_alpha_value(XlnxDPState *s)
> +{
> + return (s->vblend_registers[V_BLEND_SET_GLOBAL_ALPHA_REG] & 0x1FE) >> 1;
> +}
> +
> +static inline bool xlnx_dp_global_alpha_enabled(XlnxDPState *s)
> +{
> + /*
> + * If the alpha is totally opaque (255) we consider the alpha is disabled to
> + * reduce CPU consumption.
> + */
> + return ((xlnx_dp_global_alpha_value(s) != 0xFF) &&
> + ((s->vblend_registers[V_BLEND_SET_GLOBAL_ALPHA_REG] & 0x01) != 0));
> +}
> +
> +static void xlnx_dp_recreate_surface(XlnxDPState *s)
> +{
> + /*
> + * Two possibilities, if blending is enabled the console displays
> + * bout_plane, if not g_plane is displayed.
> + */
> + uint16_t width = s->core_registers[DP_MAIN_STREAM_HRES];
> + uint16_t height = s->core_registers[DP_MAIN_STREAM_VRES];
> + DisplaySurface *current_console_surface = qemu_console_surface(s->console);
> +
> + if ((width != 0) && (height != 0)) {
> + /*
> + * As dpy_gfx_replace_surface calls qemu_free_displaysurface on the
> + * surface we need to be carefull and don't free the surface associated
> + * to the console or double free will happen.
> + */
> + if (s->bout_plane.surface != current_console_surface) {
> + qemu_free_displaysurface(s->bout_plane.surface);
> + }
> + if (s->v_plane.surface != current_console_surface) {
> + qemu_free_displaysurface(s->v_plane.surface);
> + }
> + if (s->g_plane.surface != current_console_surface) {
> + qemu_free_displaysurface(s->g_plane.surface);
> + }
> +
> + s->g_plane.surface
> + = qemu_create_displaysurface_from(width, height,
> + s->g_plane.format, 0, NULL);
> + s->v_plane.surface
> + = qemu_create_displaysurface_from(width, height,
> + s->v_plane.format, 0, NULL);
> + if (xlnx_dp_global_alpha_enabled(s)) {
> + s->bout_plane.surface =
> + qemu_create_displaysurface_from(width,
> + height,
> + s->g_plane.format,
> + 0, NULL);
> + dpy_gfx_replace_surface(s->console, s->bout_plane.surface);
> + } else {
> + s->bout_plane.surface = NULL;
> + dpy_gfx_replace_surface(s->console, s->g_plane.surface);
> + }
> +
> + xlnx_dpdma_set_host_data_location(s->dpdma, DP_GRAPHIC_DMA_CHANNEL,
> + surface_data(s->g_plane.surface));
> + xlnx_dpdma_set_host_data_location(s->dpdma, DP_VIDEO_DMA_CHANNEL,
> + surface_data(s->v_plane.surface));
> + }
> +}
> +
> +/*
> + * Change the graphic format of the surface.
> + */
> +static void xlnx_dp_change_graphic_fmt(XlnxDPState *s)
> +{
> + switch (s->avbufm_registers[AV_BUF_FORMAT] & DP_GRAPHIC_MASK) {
> + case DP_GRAPHIC_RGBA8888:
> + s->g_plane.format = PIXMAN_r8g8b8a8;
> + break;
> + case DP_GRAPHIC_ABGR8888:
> + s->g_plane.format = PIXMAN_a8b8g8r8;
> + break;
> + case DP_GRAPHIC_RGB565:
> + s->g_plane.format = PIXMAN_r5g6b5;
> + break;
> + case DP_GRAPHIC_RGB888:
> + s->g_plane.format = PIXMAN_r8g8b8;
> + break;
> + case DP_GRAPHIC_BGR888:
> + s->g_plane.format = PIXMAN_b8g8r8;
> + break;
> + default:
> + DPRINTF("error: unsupported graphic format %u.\n",
> + s->avbufm_registers[AV_BUF_FORMAT] & DP_GRAPHIC_MASK);
> + abort();
> + }
> +
> + switch (s->avbufm_registers[AV_BUF_FORMAT] & DP_NL_VID_FMT_MASK) {
> + case 0:
> + s->v_plane.format = PIXMAN_x8b8g8r8;
> + break;
> + case DP_NL_VID_RGBA8880:
> + s->v_plane.format = PIXMAN_x8b8g8r8;
> + break;
> + default:
> + DPRINTF("error: unsupported video format %u.\n",
> + s->avbufm_registers[AV_BUF_FORMAT] & DP_NL_VID_FMT_MASK);
> + abort();
> + }
> +
> + xlnx_dp_recreate_surface(s);
> +}
> +
> +static void xlnx_dp_update_irq(XlnxDPState *s)
> +{
> + uint32_t flags;
> +
> + flags = s->core_registers[DP_INT_STATUS] & ~s->core_registers[DP_INT_MASK];
> + DPRINTF("update IRQ value = %" PRIx32 "\n", flags);
> + qemu_set_irq(s->irq, flags != 0);
> +}
> +
> +static uint64_t xlnx_dp_read(void *opaque, hwaddr offset, unsigned size)
> +{
> + XlnxDPState *s = XLNX_DP(opaque);
> + uint64_t ret = 0;
> +
> + offset = offset >> 2;
> +
> + switch (offset) {
> + case DP_TX_USER_FIFO_OVERFLOW:
> + /* This register is cleared after a read */
> + ret = s->core_registers[DP_TX_USER_FIFO_OVERFLOW];
> + s->core_registers[DP_TX_USER_FIFO_OVERFLOW] = 0;
> + break;
> + case DP_AUX_REPLY_DATA:
> + ret = xlnx_dp_aux_pop_rx_fifo(s);
> + break;
> + case DP_INTERRUPT_SIGNAL_STATE:
> + /*
> + * XXX: Not sure it is the right thing to do actually.
> + * The register is not written by the device driver so it's stuck
> + * to 0x04.
> + */
> + ret = s->core_registers[DP_INTERRUPT_SIGNAL_STATE];
> + s->core_registers[DP_INTERRUPT_SIGNAL_STATE] &= ~0x04;
> + break;
> + case DP_AUX_WRITE_FIFO:
> + case DP_TX_AUDIO_INFO_DATA(0):
> + case DP_TX_AUDIO_INFO_DATA(1):
> + case DP_TX_AUDIO_INFO_DATA(2):
> + case DP_TX_AUDIO_INFO_DATA(3):
> + case DP_TX_AUDIO_INFO_DATA(4):
> + case DP_TX_AUDIO_INFO_DATA(5):
> + case DP_TX_AUDIO_INFO_DATA(6):
> + case DP_TX_AUDIO_INFO_DATA(7):
> + case DP_TX_AUDIO_EXT_DATA(0):
> + case DP_TX_AUDIO_EXT_DATA(1):
> + case DP_TX_AUDIO_EXT_DATA(2):
> + case DP_TX_AUDIO_EXT_DATA(3):
> + case DP_TX_AUDIO_EXT_DATA(4):
> + case DP_TX_AUDIO_EXT_DATA(5):
> + case DP_TX_AUDIO_EXT_DATA(6):
> + case DP_TX_AUDIO_EXT_DATA(7):
> + case DP_TX_AUDIO_EXT_DATA(8):
> + /* write only registers */
> + ret = 0;
> + break;
> + default:
> + assert(offset <= (0x3AC >> 2));
> + ret = s->core_registers[offset];
> + break;
> + }
> +
> + DPRINTF("core read @%" PRIx64 " = 0x%8.8lX\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.8lX\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 0000000..d3a03f1
> --- /dev/null
> +++ b/include/hw/display/xlnx_dp.h
> @@ -0,0 +1,109 @@
> +/*
> + * xlnx_dp.h
> + *
> + * Copyright (C) 2015 : GreenSocs Ltd
> + * http://www.greensocs.com/ , email: info@greensocs.com
> + *
> + * Developed by :
> + * Frederic Konrad <fred.konrad@greensocs.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation, either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "hw/sysbus.h"
> +#include "ui/console.h"
> +#include "hw/misc/aux.h"
> +#include "hw/i2c/i2c.h"
> +#include "hw/display/dpcd.h"
> +#include "hw/i2c/i2c-ddc.h"
> +#include "qemu/fifo8.h"
> +#include "hw/dma/xlnx_dpdma.h"
> +#include "audio/audio.h"
> +
> +#ifndef XLNX_DP_H
> +#define XLNX_DP_H
> +
> +#define AUD_CHBUF_MAX_DEPTH 32768
> +#define MAX_QEMU_BUFFER_SIZE 4096
> +
> +#define DP_CORE_REG_ARRAY_SIZE (0x3AF >> 2)
> +#define DP_AVBUF_REG_ARRAY_SIZE (0x238 >> 2)
> +#define DP_VBLEND_REG_ARRAY_SIZE (0x1DF >> 2)
> +#define DP_AUDIO_REG_ARRAY_SIZE (0x50 >> 2)
> +
> +struct PixmanPlane {
> + pixman_format_code_t format;
> + DisplaySurface *surface;
> +};
> +
> +typedef struct XlnxDPState {
> + /*< private >*/
> + SysBusDevice parent_obj;
> +
> + /* < public >*/
> + MemoryRegion container;
> +
> + uint32_t core_registers[DP_CORE_REG_ARRAY_SIZE];
> + MemoryRegion core_iomem;
> +
> + uint32_t avbufm_registers[DP_AVBUF_REG_ARRAY_SIZE];
> + MemoryRegion avbufm_iomem;
> +
> + uint32_t vblend_registers[DP_VBLEND_REG_ARRAY_SIZE];
> + MemoryRegion vblend_iomem;
> +
> + uint32_t audio_registers[DP_AUDIO_REG_ARRAY_SIZE];
> + MemoryRegion audio_iomem;
> +
> + QemuConsole *console;
> +
> + /*
> + * This is the planes used to display in console. When the blending is
> + * enabled bout_plane is displayed in console else it's g_plane.
> + */
> + struct PixmanPlane g_plane;
> + struct PixmanPlane v_plane;
> + struct PixmanPlane bout_plane;
> +
> + QEMUSoundCard aud_card;
> + SWVoiceOut *amixer_output_stream;
> + int16_t audio_buffer_0[AUD_CHBUF_MAX_DEPTH];
> + int16_t audio_buffer_1[AUD_CHBUF_MAX_DEPTH];
> + size_t audio_data_available[2];
> + int64_t temp_buffer[AUD_CHBUF_MAX_DEPTH];
> + int16_t out_buffer[AUD_CHBUF_MAX_DEPTH];
> + size_t byte_left; /* byte available in out_buffer. */
> + size_t data_ptr; /* next byte to be sent to QEMU. */
> +
> + /* Associated DPDMA controller. */
> + XlnxDPDMAState *dpdma;
> +
> + qemu_irq irq;
> +
> + AUXBus *aux_bus;
> + Fifo8 rx_fifo;
> + Fifo8 tx_fifo;
> +
> + /*
> + * XXX: This should be in an other module.
> + */
> + DPCDState *dpcd;
> + I2CDDCState *edid;
> +} XlnxDPState;
> +
> +#define TYPE_XLNX_DP "xlnx.v-dp"
> +#define XLNX_DP(obj) OBJECT_CHECK(XlnxDPState, (obj), TYPE_XLNX_DP)
> +
> +#endif /* !XLNX_DP_H */
> --
> 1.8.3.1
>
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* [Qemu-devel] [PATCH V8 9/9] arm: xlnx-zynqmp: Add xlnx-dp and xlnx-dpdma
2016-06-06 14:21 [Qemu-devel] [PATCH V8 0/9] Xilinx DisplayPort fred.konrad
` (7 preceding siblings ...)
2016-06-06 14:21 ` [Qemu-devel] [PATCH V8 8/9] introduce xlnx-dp fred.konrad
@ 2016-06-06 14:21 ` fred.konrad
8 siblings, 0 replies; 14+ messages in thread
From: fred.konrad @ 2016-06-06 14:21 UTC (permalink / raw)
To: qemu-devel
Cc: peter.maydell, crosthwaitepeter, alistair.francis,
edgar.iglesias, hyunk, guillaume.delbergue, mark.burton,
fred.konrad
From: KONRAD Frederic <fred.konrad@greensocs.com>
This adds the DP and the DPDMA to the Zynq MP platform.
Signed-off-by: KONRAD Frederic <fred.konrad@greensocs.com>
Reviewed-by: Peter Crosthwaite <peter.crosthwaite@xilinx.com>
Reviewed-by: Alistair Francis <alistair.francis@xilinx.com>
Tested-By: Hyun Kwon <hyun.kwon@xilinx.com>
---
hw/arm/xlnx-zynqmp.c | 32 +++++++++++++++++++++++++++++++-
include/hw/arm/xlnx-zynqmp.h | 4 ++++
2 files changed, 35 insertions(+), 1 deletion(-)
diff --git a/hw/arm/xlnx-zynqmp.c b/hw/arm/xlnx-zynqmp.c
index 4d504da..5069df2 100644
--- a/hw/arm/xlnx-zynqmp.c
+++ b/hw/arm/xlnx-zynqmp.c
@@ -36,6 +36,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,
};
@@ -135,6 +141,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)
@@ -364,8 +376,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 2332596..3067aaa 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;
--
1.8.3.1
^ permalink raw reply related [flat|nested] 14+ messages in thread