* [RFC Patch 1/5] hw/display: Allwinner A10 HDMI controller emulation
2023-09-05 20:14 [RFC Patch 0/5] Allwinner A10 input/output peripherals Strahinja Jankovic
@ 2023-09-05 20:14 ` Strahinja Jankovic
2023-09-06 4:50 ` Philippe Mathieu-Daudé
2023-09-05 20:14 ` [RFC Patch 2/5] hw/display: Allwinner basic MALI GPU emulation Strahinja Jankovic
` (3 subsequent siblings)
4 siblings, 1 reply; 11+ messages in thread
From: Strahinja Jankovic @ 2023-09-05 20:14 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-arm, Beniamino Galvani, Peter Maydell, Strahinja Jankovic
This patch adds basic Allwinner A10 HDMI controller support.
Emulated HDMI component will always show that a display is connected and
provide default EDID info.
Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
---
hw/arm/allwinner-a10.c | 7 +
hw/display/allwinner-a10-hdmi.c | 214 ++++++++++++++++++++++++
hw/display/meson.build | 2 +
hw/display/trace-events | 4 +
include/hw/arm/allwinner-a10.h | 2 +
include/hw/display/allwinner-a10-hdmi.h | 69 ++++++++
6 files changed, 298 insertions(+)
create mode 100644 hw/display/allwinner-a10-hdmi.c
create mode 100644 include/hw/display/allwinner-a10-hdmi.h
diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c
index b0ea3f7f66..2351d1a69b 100644
--- a/hw/arm/allwinner-a10.c
+++ b/hw/arm/allwinner-a10.c
@@ -41,6 +41,7 @@
#define AW_A10_WDT_BASE 0x01c20c90
#define AW_A10_RTC_BASE 0x01c20d00
#define AW_A10_I2C0_BASE 0x01c2ac00
+#define AW_A10_HDMI_BASE 0x01c16000
void allwinner_a10_bootrom_setup(AwA10State *s, BlockBackend *blk)
{
@@ -95,6 +96,8 @@ static void aw_a10_init(Object *obj)
object_initialize_child(obj, "rtc", &s->rtc, TYPE_AW_RTC_SUN4I);
object_initialize_child(obj, "wdt", &s->wdt, TYPE_AW_WDT_SUN4I);
+
+ object_initialize_child(obj, "hdmi", &s->hdmi, TYPE_AW_A10_HDMI);
}
static void aw_a10_realize(DeviceState *dev, Error **errp)
@@ -210,6 +213,10 @@ static void aw_a10_realize(DeviceState *dev, Error **errp)
/* WDT */
sysbus_realize(SYS_BUS_DEVICE(&s->wdt), &error_fatal);
sysbus_mmio_map_overlap(SYS_BUS_DEVICE(&s->wdt), 0, AW_A10_WDT_BASE, 1);
+
+ /* HDMI */
+ sysbus_realize(SYS_BUS_DEVICE(&s->hdmi), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->hdmi), 0, AW_A10_HDMI_BASE);
}
static void aw_a10_class_init(ObjectClass *oc, void *data)
diff --git a/hw/display/allwinner-a10-hdmi.c b/hw/display/allwinner-a10-hdmi.c
new file mode 100644
index 0000000000..0f046e3cc7
--- /dev/null
+++ b/hw/display/allwinner-a10-hdmi.c
@@ -0,0 +1,214 @@
+/*
+ * Allwinner A10 HDMI Module emulation
+ *
+ * Copyright (C) 2023 Strahinja Jankovic <strahinja.p.jankovic@gmail.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/units.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "hw/qdev-properties.h"
+#include "qemu/module.h"
+#include "hw/display/allwinner-a10-hdmi.h"
+#include "trace.h"
+
+/* HDMI register offsets */
+enum {
+ REG_HPD = 0x000C, /* HDMI Hotplug detect */
+ REG_DDC_CTRL = 0x0500, /* DDC Control */
+ REG_DDC_SLAVE_ADDRESS = 0x0504, /* DDC Slave address */
+ REG_DDC_INT_STATUS = 0x050C, /* DDC Interrupt status */
+ REG_DDC_FIFO_CTRL = 0x0510, /* DDC FIFO Control */
+ REG_DDC_FIFO_ACCESS = 0x0518, /* DDC FIFO access */
+ REG_DDC_COMMAND = 0x0520, /* DDC Command */
+};
+
+/* HPD register fields */
+#define FIELD_HPD_HOTPLUG_DET_HIGH (1 << 0)
+
+/* DDC_CTRL register fields */
+#define FIELD_DDC_CTRL_SW_RST (1 << 0)
+#define FIELD_DDC_CTRL_ACCESS_CMD_START (1 << 30)
+
+/* FIFO_CTRL register fields */
+#define FIELD_FIFO_CTRL_ADDRESS_CLEAR (1 << 31)
+
+/* DDC_SLAVE_ADDRESS register fields */
+#define FIELD_DDC_SLAVE_ADDRESS_SEGMENT_SHIFT (24)
+#define FIELD_DDC_SLAVE_ADDRESS_OFFSET_SHIFT (8)
+
+/* DDC_INT_STATUS register fields */
+#define FIELD_DDC_INT_STATUS_TRANSFER_COMPLETE (1 << 0)
+
+/* DDC access command */
+enum {
+ DDC_COMMAND_E_DDC_READ = 6,
+};
+
+
+
+#define REG_INDEX(offset) (offset / sizeof(uint32_t))
+
+static uint64_t allwinner_a10_hdmi_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ AwA10HdmiState *s = AW_A10_HDMI(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+ uint32_t val = s->regs[idx];
+
+ switch (offset) {
+ case REG_HPD:
+ val = FIELD_HPD_HOTPLUG_DET_HIGH;
+ break;
+ case REG_DDC_FIFO_ACCESS:
+ val = s->edid_blob[s->edid_reg % sizeof(s->edid_blob)];
+ s->edid_reg++;
+ break;
+ case 0x544 ... AW_A10_HDMI_IOSIZE:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ default:
+ break;
+ }
+
+ trace_allwinner_a10_hdmi_read(offset, val);
+
+ return val;
+}
+
+static void allwinner_a10_hdmi_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwA10HdmiState *s = AW_A10_HDMI(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ switch (offset) {
+ case REG_DDC_CTRL:
+ if (val & FIELD_DDC_CTRL_SW_RST) {
+ val &= ~FIELD_DDC_CTRL_SW_RST;
+ }
+ if (val & FIELD_DDC_CTRL_ACCESS_CMD_START) {
+ val &= ~FIELD_DDC_CTRL_ACCESS_CMD_START;
+ if (s->regs[REG_INDEX(REG_DDC_COMMAND)] == DDC_COMMAND_E_DDC_READ) {
+ uint32_t regval = s->regs[REG_INDEX(REG_DDC_SLAVE_ADDRESS)];
+ uint8_t segment = 0xFFu &
+ (regval >> FIELD_DDC_SLAVE_ADDRESS_SEGMENT_SHIFT);
+ uint8_t offset = 0xFFu &
+ (regval >> FIELD_DDC_SLAVE_ADDRESS_OFFSET_SHIFT);
+ if (segment == 0) {
+ s->edid_reg = offset;
+ }
+ }
+ }
+ break;
+ case REG_DDC_INT_STATUS:
+ /* Clear interrupts */
+ val = s->regs[REG_INDEX(REG_DDC_INT_STATUS)] & ~(val & 0xFFu);
+ /* Set transfer complete */
+ val |= FIELD_DDC_INT_STATUS_TRANSFER_COMPLETE;
+ break;
+ case REG_DDC_FIFO_CTRL:
+ if (val & FIELD_FIFO_CTRL_ADDRESS_CLEAR) {
+ val &= ~FIELD_FIFO_CTRL_ADDRESS_CLEAR;
+ }
+ break;
+ case 0x544 ... AW_A10_HDMI_IOSIZE:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ break;
+ default:
+ break;
+ }
+
+ trace_allwinner_a10_hdmi_write(offset, (uint32_t)val);
+
+ s->regs[idx] = (uint32_t) val;
+}
+
+static const MemoryRegionOps allwinner_a10_hdmi_ops = {
+ .read = allwinner_a10_hdmi_read,
+ .write = allwinner_a10_hdmi_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 1,
+};
+
+static void allwinner_a10_hdmi_reset_enter(Object *obj, ResetType type)
+{
+ AwA10HdmiState *s = AW_A10_HDMI(obj);
+
+ s->edid_reg = 0;
+}
+
+static void allwinner_a10_hdmi_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ AwA10HdmiState *s = AW_A10_HDMI(obj);
+
+ /* Memory mapping */
+ memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_a10_hdmi_ops, s,
+ TYPE_AW_A10_HDMI, AW_A10_HDMI_IOSIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ qemu_edid_generate(s->edid_blob, sizeof(s->edid_blob), &s->edid_info);
+}
+
+static const VMStateDescription allwinner_a10_hdmi_vmstate = {
+ .name = "allwinner-a10-hdmi",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, AwA10HdmiState, AW_A10_HDMI_REGS_NUM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property allwinner_a10_hdmi_properties[] = {
+ DEFINE_EDID_PROPERTIES(AwA10HdmiState, edid_info),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void allwinner_a10_hdmi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ device_class_set_props(dc, allwinner_a10_hdmi_properties);
+
+ rc->phases.enter = allwinner_a10_hdmi_reset_enter;
+ dc->vmsd = &allwinner_a10_hdmi_vmstate;
+}
+
+static const TypeInfo allwinner_a10_hdmi_info = {
+ .name = TYPE_AW_A10_HDMI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = allwinner_a10_hdmi_init,
+ .instance_size = sizeof(AwA10HdmiState),
+ .class_init = allwinner_a10_hdmi_class_init,
+};
+
+static void allwinner_a10_hdmi_register(void)
+{
+ type_register_static(&allwinner_a10_hdmi_info);
+}
+
+type_init(allwinner_a10_hdmi_register)
diff --git a/hw/display/meson.build b/hw/display/meson.build
index 413ba4ab24..0a36c3ed85 100644
--- a/hw/display/meson.build
+++ b/hw/display/meson.build
@@ -38,6 +38,8 @@ system_ss.add(when: 'CONFIG_NEXTCUBE', if_true: files('next-fb.c'))
system_ss.add(when: 'CONFIG_VGA', if_true: files('vga.c'))
+system_ss.add(when: 'CONFIG_ALLWINNER_A10', if_true: files('allwinner-a10-hdmi.c')
+
if (config_all_devices.has_key('CONFIG_VGA_CIRRUS') or
config_all_devices.has_key('CONFIG_VGA_PCI') or
config_all_devices.has_key('CONFIG_VMWARE_VGA') or
diff --git a/hw/display/trace-events b/hw/display/trace-events
index 2336a0ca15..8d0d33ce4d 100644
--- a/hw/display/trace-events
+++ b/hw/display/trace-events
@@ -177,3 +177,7 @@ macfb_ctrl_write(uint64_t addr, uint64_t value, unsigned int size) "addr 0x%"PRI
macfb_sense_read(uint32_t value) "video sense: 0x%"PRIx32
macfb_sense_write(uint32_t value) "video sense: 0x%"PRIx32
macfb_update_mode(uint32_t width, uint32_t height, uint8_t depth) "setting mode to width %"PRId32 " height %"PRId32 " size %d"
+
+# allwinner-a10-hdmi.c
+allwinner_a10_hdmi_read(uint64_t offset, uint64_t data) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64
+allwinner_a10_hdmi_write(uint64_t offset, uint64_t data) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64
diff --git a/include/hw/arm/allwinner-a10.h b/include/hw/arm/allwinner-a10.h
index cd1465c613..db8cbeecfa 100644
--- a/include/hw/arm/allwinner-a10.h
+++ b/include/hw/arm/allwinner-a10.h
@@ -12,6 +12,7 @@
#include "hw/rtc/allwinner-rtc.h"
#include "hw/misc/allwinner-a10-ccm.h"
#include "hw/misc/allwinner-a10-dramc.h"
+#include "hw/display/allwinner-a10-hdmi.h"
#include "hw/i2c/allwinner-i2c.h"
#include "hw/watchdog/allwinner-wdt.h"
#include "sysemu/block-backend.h"
@@ -43,6 +44,7 @@ struct AwA10State {
AWI2CState i2c0;
AwRtcState rtc;
AwWdtState wdt;
+ AwA10HdmiState hdmi;
MemoryRegion sram_a;
EHCISysBusState ehci[AW_A10_NUM_USB];
OHCISysBusState ohci[AW_A10_NUM_USB];
diff --git a/include/hw/display/allwinner-a10-hdmi.h b/include/hw/display/allwinner-a10-hdmi.h
new file mode 100644
index 0000000000..1065dca2f7
--- /dev/null
+++ b/include/hw/display/allwinner-a10-hdmi.h
@@ -0,0 +1,69 @@
+/*
+ * Allwinner A10 HDMI Module emulation
+ *
+ * Copyright (C) 2023 Strahinja Jankovic <strahinja.p.jankovic@gmail.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 HW_DISPLAY_ALLWINNER_A10_HDMI_H
+#define HW_DISPLAY_ALLWINNER_A10_HDMI_H
+
+#include "hw/display/edid.h"
+#include "qom/object.h"
+#include "hw/sysbus.h"
+
+/**
+ * @name Constants
+ * @{
+ */
+
+/** Size of register I/O address space used by HDMI device */
+#define AW_A10_HDMI_IOSIZE (0x1000)
+
+/** Total number of known registers */
+#define AW_A10_HDMI_REGS_NUM (AW_A10_HDMI_IOSIZE / sizeof(uint32_t))
+
+/** @} */
+
+/**
+ * @name Object model
+ * @{
+ */
+
+#define TYPE_AW_A10_HDMI "allwinner-a10-hdmi"
+OBJECT_DECLARE_SIMPLE_TYPE(AwA10HdmiState, AW_A10_HDMI)
+
+/** @} */
+
+/**
+ * Allwinner A10 HDMI object instance state.
+ */
+struct AwA10HdmiState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ /** Maps I/O registers in physical memory */
+ MemoryRegion iomem;
+
+ uint8_t edid_reg;
+ qemu_edid_info edid_info;
+ uint8_t edid_blob[128];
+
+ /** Array of hardware registers */
+ uint32_t regs[AW_A10_HDMI_REGS_NUM];
+};
+
+#endif /* HW_DISPLAY_ALLWINNER_A10_HDMI_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [RFC Patch 1/5] hw/display: Allwinner A10 HDMI controller emulation
2023-09-05 20:14 ` [RFC Patch 1/5] hw/display: Allwinner A10 HDMI controller emulation Strahinja Jankovic
@ 2023-09-06 4:50 ` Philippe Mathieu-Daudé
0 siblings, 0 replies; 11+ messages in thread
From: Philippe Mathieu-Daudé @ 2023-09-06 4:50 UTC (permalink / raw)
To: Strahinja Jankovic, qemu-devel
Cc: qemu-arm, Beniamino Galvani, Peter Maydell, Strahinja Jankovic
Hi Strahinja,
On 5/9/23 22:14, Strahinja Jankovic wrote:
> This patch adds basic Allwinner A10 HDMI controller support.
> Emulated HDMI component will always show that a display is connected and
> provide default EDID info.
>
> Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
> ---
> hw/arm/allwinner-a10.c | 7 +
> hw/display/allwinner-a10-hdmi.c | 214 ++++++++++++++++++++++++
> hw/display/meson.build | 2 +
> hw/display/trace-events | 4 +
> include/hw/arm/allwinner-a10.h | 2 +
> include/hw/display/allwinner-a10-hdmi.h | 69 ++++++++
> 6 files changed, 298 insertions(+)
> create mode 100644 hw/display/allwinner-a10-hdmi.c
> create mode 100644 include/hw/display/allwinner-a10-hdmi.h
> diff --git a/hw/display/allwinner-a10-hdmi.c b/hw/display/allwinner-a10-hdmi.c
> new file mode 100644
> index 0000000000..0f046e3cc7
> --- /dev/null
> +++ b/hw/display/allwinner-a10-hdmi.c
> +#define REG_INDEX(offset) (offset / sizeof(uint32_t))
> +
> +static uint64_t allwinner_a10_hdmi_read(void *opaque, hwaddr offset,
> + unsigned size)
> +{
> + AwA10HdmiState *s = AW_A10_HDMI(opaque);
> + const uint32_t idx = REG_INDEX(offset);
> + uint32_t val = s->regs[idx];
> +
> + switch (offset) {
> + case REG_HPD:
> + val = FIELD_HPD_HOTPLUG_DET_HIGH;
> + break;
> +}
> +
> +static void allwinner_a10_hdmi_write(void *opaque, hwaddr offset,
> + uint64_t val, unsigned size)
> +{
> + AwA10HdmiState *s = AW_A10_HDMI(opaque);
> + const uint32_t idx = REG_INDEX(offset);
> +
> + switch (offset) {
> + case REG_DDC_CTRL:
> + if (val & FIELD_DDC_CTRL_SW_RST) {
> + val &= ~FIELD_DDC_CTRL_SW_RST;
> + }
> + s->regs[idx] = (uint32_t) val;
> +}
> +
> +static const MemoryRegionOps allwinner_a10_hdmi_ops = {
> + .read = allwinner_a10_hdmi_read,
> + .write = allwinner_a10_hdmi_write,
> + .endianness = DEVICE_NATIVE_ENDIAN,
> + .valid = {
> + .min_access_size = 1,
> + .max_access_size = 4,
> + },
> + .impl.min_access_size = 1,
Per REG_INDEX() you have .impl.min/max = 4.
Otherwise your patch LGTM :)
> +};
^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC Patch 2/5] hw/display: Allwinner basic MALI GPU emulation
2023-09-05 20:14 [RFC Patch 0/5] Allwinner A10 input/output peripherals Strahinja Jankovic
2023-09-05 20:14 ` [RFC Patch 1/5] hw/display: Allwinner A10 HDMI controller emulation Strahinja Jankovic
@ 2023-09-05 20:14 ` Strahinja Jankovic
2023-09-05 20:14 ` [RFC Patch 3/5] hw/display: Allwinner A10 Display Engine Backend emulation Strahinja Jankovic
` (2 subsequent siblings)
4 siblings, 0 replies; 11+ messages in thread
From: Strahinja Jankovic @ 2023-09-05 20:14 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-arm, Beniamino Galvani, Peter Maydell, Strahinja Jankovic
This patch adds minimal MALI GPU emulation needed so emulated system
thinks GPU is working.
Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
---
hw/arm/allwinner-a10.c | 7 +
hw/display/allwinner-gpu.c | 212 +++++++++++++++++++++++++++++
hw/display/meson.build | 3 +-
hw/display/trace-events | 4 +
include/hw/arm/allwinner-a10.h | 2 +
include/hw/display/allwinner-gpu.h | 64 +++++++++
6 files changed, 291 insertions(+), 1 deletion(-)
create mode 100644 hw/display/allwinner-gpu.c
create mode 100644 include/hw/display/allwinner-gpu.h
diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c
index 2351d1a69b..75cd879d24 100644
--- a/hw/arm/allwinner-a10.c
+++ b/hw/arm/allwinner-a10.c
@@ -42,6 +42,7 @@
#define AW_A10_RTC_BASE 0x01c20d00
#define AW_A10_I2C0_BASE 0x01c2ac00
#define AW_A10_HDMI_BASE 0x01c16000
+#define AW_A10_GPU_BASE 0x01c40000
void allwinner_a10_bootrom_setup(AwA10State *s, BlockBackend *blk)
{
@@ -98,6 +99,8 @@ static void aw_a10_init(Object *obj)
object_initialize_child(obj, "wdt", &s->wdt, TYPE_AW_WDT_SUN4I);
object_initialize_child(obj, "hdmi", &s->hdmi, TYPE_AW_A10_HDMI);
+
+ object_initialize_child(obj, "mali400", &s->gpu, TYPE_AW_GPU);
}
static void aw_a10_realize(DeviceState *dev, Error **errp)
@@ -217,6 +220,10 @@ static void aw_a10_realize(DeviceState *dev, Error **errp)
/* HDMI */
sysbus_realize(SYS_BUS_DEVICE(&s->hdmi), &error_fatal);
sysbus_mmio_map(SYS_BUS_DEVICE(&s->hdmi), 0, AW_A10_HDMI_BASE);
+
+ /* MALI GPU */
+ sysbus_realize(SYS_BUS_DEVICE(&s->gpu), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpu), 0, AW_A10_GPU_BASE);
}
static void aw_a10_class_init(ObjectClass *oc, void *data)
diff --git a/hw/display/allwinner-gpu.c b/hw/display/allwinner-gpu.c
new file mode 100644
index 0000000000..735976d206
--- /dev/null
+++ b/hw/display/allwinner-gpu.c
@@ -0,0 +1,212 @@
+/*
+ * Allwinner GPU Module emulation
+ *
+ * Copyright (C) 2023 Strahinja Jankovic <strahinja.p.jankovic@gmail.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/units.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/display/allwinner-gpu.h"
+#include "trace.h"
+
+/* GPU register offsets - only the important ones. */
+enum {
+ REG_MALI_GP_CMD = 0x0020,
+ REG_MALI_GP_INT_RAWSTAT = 0x0024,
+ REG_MALI_GP_VERSION = 0x006C,
+ REG_MALI_GP_MMU_DTE = 0x3000,
+ REG_MALI_GP_MMU_STATUS = 0x3004,
+ REG_MALI_GP_MMU_COMMAND = 0x3008,
+ REG_MALI_PP0_MMU_DTE = 0x4000,
+ REG_MALI_PP0_MMU_STATUS = 0x4004,
+ REG_MALI_PP0_MMU_COMMAND = 0x4008,
+ REG_MALI_PP0_VERSION = 0x9000,
+ REG_MALI_PP0_CTRL = 0x900C,
+ REG_MALI_PP0_INT_RAWSTAT = 0x9020,
+};
+
+#define REG_INDEX(offset) (offset / sizeof(uint32_t))
+
+#define MALI_GP_VERSION_READ_VAL (0x0B07u << 16)
+#define MALI_PP0_VERSION_READ_VAL (0xCD07u << 16)
+#define MALI_MMU_DTE_MASK (0x0FFF)
+
+/* MALI_GP_CMD register fields */
+#define MALI_GP_CMD_SOFT_RESET (1 << 10)
+
+/* MALI_GP_INT_RAWSTAT register fields */
+#define MALI_GP_INT_RAWSTAT_RESET_COMPLETED (1 << 19)
+
+/* MALI_MMU_COMMAND values */
+enum {
+ MALI_MMU_COMMAND_ENABLE_PAGING = 0,
+ MALI_MMU_COMMAND_HARD_RESET = 6,
+};
+
+/* MALI_MMU_STATUS register fields */
+#define MALI_MMU_STATUS_PAGING_ENABLED (1 << 0)
+
+/* MALI_PP_CTRL register fields */
+#define MALI_PP_CTRL_SOFT_RESET (1 << 7)
+
+/* MALI_PP_INT_RAWSTAT register fields */
+#define MALI_PP_INT_RAWSTAT_RESET_COMPLETED (1 << 12)
+
+static uint64_t allwinner_gpu_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ const AwGpuState *s = AW_GPU(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+ uint32_t val = s->regs[idx];
+
+ switch (offset) {
+ case REG_MALI_GP_VERSION:
+ val = MALI_GP_VERSION_READ_VAL;
+ break;
+ case REG_MALI_GP_MMU_DTE:
+ case REG_MALI_PP0_MMU_DTE:
+ val &= ~MALI_MMU_DTE_MASK;
+ break;
+ case REG_MALI_PP0_VERSION:
+ val = MALI_PP0_VERSION_READ_VAL;
+ break;
+ case 0xF0B8 ... AW_GPU_IOSIZE:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ default:
+ break;
+ }
+
+ trace_allwinner_gpu_read(offset, val);
+
+ return val;
+}
+
+static void allwinner_gpu_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwGpuState *s = AW_GPU(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ trace_allwinner_gpu_write(offset, (uint32_t)val);
+
+ switch (offset) {
+ case REG_MALI_GP_CMD:
+ if (val == MALI_GP_CMD_SOFT_RESET) {
+ s->regs[REG_INDEX(REG_MALI_GP_INT_RAWSTAT)] |=
+ MALI_GP_INT_RAWSTAT_RESET_COMPLETED;
+ }
+ break;
+ case REG_MALI_GP_MMU_COMMAND:
+ if (val == MALI_MMU_COMMAND_ENABLE_PAGING) {
+ s->regs[REG_INDEX(REG_MALI_GP_MMU_STATUS)] |=
+ MALI_MMU_STATUS_PAGING_ENABLED;
+ } else if (val == MALI_MMU_COMMAND_HARD_RESET) {
+ s->regs[REG_INDEX(REG_MALI_GP_MMU_DTE)] = 0;
+ }
+ break;
+ case REG_MALI_PP0_MMU_COMMAND:
+ if (val == MALI_MMU_COMMAND_ENABLE_PAGING) {
+ s->regs[REG_INDEX(REG_MALI_PP0_MMU_STATUS)] |=
+ MALI_MMU_STATUS_PAGING_ENABLED;
+ } else if (val == MALI_MMU_COMMAND_HARD_RESET) {
+ s->regs[REG_INDEX(REG_MALI_PP0_MMU_DTE)] = 0;
+ }
+ break;
+ case REG_MALI_PP0_CTRL:
+ if (val == MALI_PP_CTRL_SOFT_RESET) {
+ s->regs[REG_INDEX(REG_MALI_PP0_INT_RAWSTAT)] =
+ MALI_PP_INT_RAWSTAT_RESET_COMPLETED;
+ }
+ break;
+ case 0xF0B8 ... AW_GPU_IOSIZE:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ break;
+ default:
+ break;
+ }
+
+ s->regs[idx] = (uint32_t) val;
+}
+
+static const MemoryRegionOps allwinner_gpu_ops = {
+ .read = allwinner_gpu_read,
+ .write = allwinner_gpu_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+static void allwinner_gpu_reset_enter(Object *obj, ResetType type)
+{
+ AwGpuState *s = AW_GPU(obj);
+
+ memset(&s->regs[0], 0, AW_GPU_IOSIZE);
+}
+
+static void allwinner_gpu_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ AwGpuState *s = AW_GPU(obj);
+
+ /* Memory mapping */
+ memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_gpu_ops, s,
+ TYPE_AW_GPU, AW_GPU_IOSIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription allwinner_gpu_vmstate = {
+ .name = "allwinner-gpu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, AwGpuState, AW_GPU_REGS_NUM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void allwinner_gpu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ rc->phases.enter = allwinner_gpu_reset_enter;
+ dc->vmsd = &allwinner_gpu_vmstate;
+}
+
+static const TypeInfo allwinner_gpu_info = {
+ .name = TYPE_AW_GPU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = allwinner_gpu_init,
+ .instance_size = sizeof(AwGpuState),
+ .class_init = allwinner_gpu_class_init,
+};
+
+static void allwinner_gpu_register(void)
+{
+ type_register_static(&allwinner_gpu_info);
+}
+
+type_init(allwinner_gpu_register)
diff --git a/hw/display/meson.build b/hw/display/meson.build
index 0a36c3ed85..a5eb01fe2b 100644
--- a/hw/display/meson.build
+++ b/hw/display/meson.build
@@ -38,7 +38,8 @@ system_ss.add(when: 'CONFIG_NEXTCUBE', if_true: files('next-fb.c'))
system_ss.add(when: 'CONFIG_VGA', if_true: files('vga.c'))
-system_ss.add(when: 'CONFIG_ALLWINNER_A10', if_true: files('allwinner-a10-hdmi.c')
+system_ss.add(when: 'CONFIG_ALLWINNER_A10', if_true: files('allwinner-a10-hdmi.c',
+ 'allwinner-gpu.c'))
if (config_all_devices.has_key('CONFIG_VGA_CIRRUS') or
config_all_devices.has_key('CONFIG_VGA_PCI') or
diff --git a/hw/display/trace-events b/hw/display/trace-events
index 8d0d33ce4d..d1c0f05e52 100644
--- a/hw/display/trace-events
+++ b/hw/display/trace-events
@@ -181,3 +181,7 @@ macfb_update_mode(uint32_t width, uint32_t height, uint8_t depth) "setting mode
# allwinner-a10-hdmi.c
allwinner_a10_hdmi_read(uint64_t offset, uint64_t data) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64
allwinner_a10_hdmi_write(uint64_t offset, uint64_t data) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64
+
+# allwinner-gpu.c
+allwinner_gpu_read(uint64_t offset, uint64_t data) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64
+allwinner_gpu_write(uint64_t offset, uint64_t data) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64
diff --git a/include/hw/arm/allwinner-a10.h b/include/hw/arm/allwinner-a10.h
index db8cbeecfa..8109656421 100644
--- a/include/hw/arm/allwinner-a10.h
+++ b/include/hw/arm/allwinner-a10.h
@@ -13,6 +13,7 @@
#include "hw/misc/allwinner-a10-ccm.h"
#include "hw/misc/allwinner-a10-dramc.h"
#include "hw/display/allwinner-a10-hdmi.h"
+#include "hw/display/allwinner-gpu.h"
#include "hw/i2c/allwinner-i2c.h"
#include "hw/watchdog/allwinner-wdt.h"
#include "sysemu/block-backend.h"
@@ -44,6 +45,7 @@ struct AwA10State {
AWI2CState i2c0;
AwRtcState rtc;
AwWdtState wdt;
+ AwGpuState gpu;
AwA10HdmiState hdmi;
MemoryRegion sram_a;
EHCISysBusState ehci[AW_A10_NUM_USB];
diff --git a/include/hw/display/allwinner-gpu.h b/include/hw/display/allwinner-gpu.h
new file mode 100644
index 0000000000..6800a58dde
--- /dev/null
+++ b/include/hw/display/allwinner-gpu.h
@@ -0,0 +1,64 @@
+/*
+ * Allwinner A10 GPU Module emulation
+ *
+ * Copyright (C) 2023 Strahinja Jankovic <strahinja.p.jankovic@gmail.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 HW_DISPLAY_ALLWINNER_GPU_H
+#define HW_DISPLAY_ALLWINNER_GPU_H
+
+#include "qom/object.h"
+#include "hw/sysbus.h"
+
+/**
+ * @name Constants
+ * @{
+ */
+
+/** Size of register I/O address space used by GPU device */
+#define AW_GPU_IOSIZE (0x10000)
+
+/** Total number of known registers */
+#define AW_GPU_REGS_NUM (AW_GPU_IOSIZE / sizeof(uint32_t))
+
+/** @} */
+
+/**
+ * @name Object model
+ * @{
+ */
+
+#define TYPE_AW_GPU "allwinner-gpu"
+OBJECT_DECLARE_SIMPLE_TYPE(AwGpuState, AW_GPU)
+
+/** @} */
+
+/**
+ * Allwinner GPU object instance state.
+ */
+struct AwGpuState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ /** Maps I/O registers in physical memory */
+ MemoryRegion iomem;
+
+ /** Array of hardware registers */
+ uint32_t regs[AW_GPU_REGS_NUM];
+};
+
+#endif /* HW_DISPLAY_ALLWINNER_GPU_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [RFC Patch 3/5] hw/display: Allwinner A10 Display Engine Backend emulation
2023-09-05 20:14 [RFC Patch 0/5] Allwinner A10 input/output peripherals Strahinja Jankovic
2023-09-05 20:14 ` [RFC Patch 1/5] hw/display: Allwinner A10 HDMI controller emulation Strahinja Jankovic
2023-09-05 20:14 ` [RFC Patch 2/5] hw/display: Allwinner basic MALI GPU emulation Strahinja Jankovic
@ 2023-09-05 20:14 ` Strahinja Jankovic
2023-09-05 20:14 ` [RFC Patch 4/5] hw/display: Allwinner A10 LCDC emulation Strahinja Jankovic
2023-09-05 20:14 ` [RFC Patch 5/5] hw/input: Add Allwinner-A10 PS2 emulation Strahinja Jankovic
4 siblings, 0 replies; 11+ messages in thread
From: Strahinja Jankovic @ 2023-09-05 20:14 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-arm, Beniamino Galvani, Peter Maydell, Strahinja Jankovic
This patch adds Display Engine Backend 0 (DEBE0) support.
This peripheral will hold runtime configuration for the display size and
framebuffer offset which will be used by other components.
Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
---
hw/arm/allwinner-a10.c | 9 +
hw/display/allwinner-a10-debe.c | 229 ++++++++++++++++++++++++
hw/display/meson.build | 3 +-
hw/display/trace-events | 4 +
include/hw/arm/allwinner-a10.h | 2 +
include/hw/display/allwinner-a10-debe.h | 71 ++++++++
6 files changed, 317 insertions(+), 1 deletion(-)
create mode 100644 hw/display/allwinner-a10-debe.c
create mode 100644 include/hw/display/allwinner-a10-debe.h
diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c
index 75cd879d24..624e95af46 100644
--- a/hw/arm/allwinner-a10.c
+++ b/hw/arm/allwinner-a10.c
@@ -43,6 +43,7 @@
#define AW_A10_I2C0_BASE 0x01c2ac00
#define AW_A10_HDMI_BASE 0x01c16000
#define AW_A10_GPU_BASE 0x01c40000
+#define AW_A10_DE_BE0_BASE 0x01e60000
void allwinner_a10_bootrom_setup(AwA10State *s, BlockBackend *blk)
{
@@ -100,6 +101,8 @@ static void aw_a10_init(Object *obj)
object_initialize_child(obj, "hdmi", &s->hdmi, TYPE_AW_A10_HDMI);
+ object_initialize_child(obj, "de_be0", &s->de_be0, TYPE_AW_A10_DEBE);
+
object_initialize_child(obj, "mali400", &s->gpu, TYPE_AW_GPU);
}
@@ -221,6 +224,12 @@ static void aw_a10_realize(DeviceState *dev, Error **errp)
sysbus_realize(SYS_BUS_DEVICE(&s->hdmi), &error_fatal);
sysbus_mmio_map(SYS_BUS_DEVICE(&s->hdmi), 0, AW_A10_HDMI_BASE);
+ /* Display Engine Backend */
+ object_property_set_uint(OBJECT(&s->de_be0), "ram-base",
+ AW_A10_SDRAM_BASE, &error_fatal);
+ sysbus_realize(SYS_BUS_DEVICE(&s->de_be0), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->de_be0), 0, AW_A10_DE_BE0_BASE);
+
/* MALI GPU */
sysbus_realize(SYS_BUS_DEVICE(&s->gpu), &error_fatal);
sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpu), 0, AW_A10_GPU_BASE);
diff --git a/hw/display/allwinner-a10-debe.c b/hw/display/allwinner-a10-debe.c
new file mode 100644
index 0000000000..3760728eab
--- /dev/null
+++ b/hw/display/allwinner-a10-debe.c
@@ -0,0 +1,229 @@
+/*
+ * Allwinner A10 Display Engine Backend emulation
+ *
+ * Copyright (C) 2023 Strahinja Jankovic <strahinja.p.jankovic@gmail.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/units.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/qdev-properties.h"
+#include "hw/display/allwinner-a10-debe.h"
+#include "trace.h"
+
+/* DEBE register offsets - only important ones */
+enum {
+ REG_DEBE_MODCTL = 0x0800, /* DE mode control */
+ REG_DEBE_DISSIZE = 0x0808, /* DE display size */
+ REG_DEBE_LAY0FB_L32ADD = 0x0850, /* DE Layer 0 lower 32-bit address */
+ REG_DEBE_REGBUFFCTL = 0x0870, /* DE buffer control register */
+ REG_DEBE_ATTCTL_REG1_L0 = 0x08A0, /* DE Layer 0 attribute ctrl reg 1 */
+};
+
+/* DEBE_DISSIZE fields */
+#define FIELD_DEBE_DISSIZE_DIS_HEIGHT (16)
+#define FIELD_DEBE_DISSIZE_DIS_WIDTH (0)
+#define DEBE_DISSIZE_DIS_MASK (0xFFFFu)
+
+/* DEBE_REGBUFFCTL fields */
+#define FIELD_DEBE_REGBUFFCTL_REGLOADCTL (1)
+#define FIELD_DEBE_REGBUFFCTL_REGAUTOLOAD_DIS (2)
+
+/* DEBE_ATTCTL_REG1_L0 fields */
+#define FIELD_DEBE_ATTCTL_REG1_L0_LAY_FBFMT (8)
+#define DEBE_ATTCTL_REG1_L0_LAY_FBFMT_MASK (0xFu)
+enum {
+ ATTCTL_REG1_LAY_FBFMT_MONO_1BPP = 0,
+ ATTCTL_REG1_LAY_FBFMT_MONO_2BPP,
+ ATTCTL_REG1_LAY_FBFMT_MONO_4BPP,
+ ATTCTL_REG1_LAY_FBFMT_MONO_8BPP,
+ ATTCTL_REG1_LAY_FBFMT_COLOR_16BPP_655,
+ ATTCTL_REG1_LAY_FBFMT_COLOR_16BPP_565,
+ ATTCTL_REG1_LAY_FBFMT_COLOR_16BPP_556,
+ ATTCTL_REG1_LAY_FBFMT_COLOR_16BPP_1555,
+ ATTCTL_REG1_LAY_FBFMT_COLOR_16BPP_5551,
+ ATTCTL_REG1_LAY_FBFMT_COLOR_32BPP_P888,
+ ATTCTL_REG1_LAY_FBFMT_COLOR_32BPP_8888,
+ ATTCTL_REG1_LAY_FBFMT_COLOR_24BPP_888,
+ ATTCTL_REG1_LAY_FBFMT_COLOR_16BPP_4444,
+};
+
+static uint8_t debe_lay_fbfmt_bpp[] = {
+ 1,
+ 2,
+ 4,
+ 8,
+ 16,
+ 16,
+ 16,
+ 16,
+ 16,
+ 32,
+ 32,
+ 24,
+ 16
+};
+
+#define REG_INDEX(offset) (offset / sizeof(uint32_t))
+
+static uint64_t allwinner_a10_debe_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ const AwA10DEBEState *s = AW_A10_DEBE(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+ uint32_t val = 0;
+
+ switch (offset) {
+ case REG_DEBE_DISSIZE:
+ case REG_DEBE_LAY0FB_L32ADD:
+ case REG_DEBE_REGBUFFCTL:
+ break;
+ case 0x5800 ... AW_A10_DEBE_IOSIZE:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ default:
+ break;
+ }
+
+ val = s->regs[idx];
+
+ trace_allwinner_a10_debe_read(offset, val);
+
+ return val;
+}
+
+static void allwinner_a10_debe_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwA10DEBEState *s = AW_A10_DEBE(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ trace_allwinner_a10_debe_write(offset, (uint32_t)val);
+
+ switch (offset) {
+ case REG_DEBE_DISSIZE:
+ /* Store display width and height */
+ s->height = 1 +
+ ((val >> FIELD_DEBE_DISSIZE_DIS_HEIGHT) & DEBE_DISSIZE_DIS_MASK);
+ s->width = 1 +
+ ((val >> FIELD_DEBE_DISSIZE_DIS_WIDTH) & DEBE_DISSIZE_DIS_MASK);
+ s->invalidate = true;
+ break;
+ case REG_DEBE_LAY0FB_L32ADD:
+ /* Store framebuffer offset */
+ s->framebuffer_offset = s->ram_base + (val >> 3);
+ if (val != 0) {
+ s->ready = true;
+ }
+ break;
+ case REG_DEBE_REGBUFFCTL:
+ if (val ==
+ (FIELD_DEBE_REGBUFFCTL_REGLOADCTL |
+ FIELD_DEBE_REGBUFFCTL_REGAUTOLOAD_DIS)) {
+ /* Clear to indicate that register loading is done. */
+ val &= ~FIELD_DEBE_REGBUFFCTL_REGLOADCTL;
+ }
+ break;
+ case REG_DEBE_ATTCTL_REG1_L0:
+ {
+ uint8_t bpp = (val >> FIELD_DEBE_ATTCTL_REG1_L0_LAY_FBFMT) &
+ DEBE_ATTCTL_REG1_L0_LAY_FBFMT_MASK;
+ s->bpp = debe_lay_fbfmt_bpp[bpp];
+ }
+ break;
+ case 0x5800 ... AW_A10_DEBE_IOSIZE:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ break;
+ default:
+ break;
+ }
+
+ s->regs[idx] = (uint32_t) val;
+}
+
+static const MemoryRegionOps allwinner_a10_debe_ops = {
+ .read = allwinner_a10_debe_read,
+ .write = allwinner_a10_debe_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+static void allwinner_a10_debe_reset_enter(Object *obj, ResetType type)
+{
+ AwA10DEBEState *s = AW_A10_DEBE(obj);
+
+ memset(&s->regs[0], 0, AW_A10_DEBE_IOSIZE);
+}
+
+static void allwinner_a10_debe_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ AwA10DEBEState *s = AW_A10_DEBE(obj);
+
+ /* Memory mapping */
+ memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_a10_debe_ops, s,
+ TYPE_AW_A10_DEBE, AW_A10_DEBE_IOSIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription allwinner_a10_debe_vmstate = {
+ .name = "allwinner-a10-debe",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, AwA10DEBEState, AW_A10_DEBE_REGS_NUM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property allwinner_a10_debe_properties[] = {
+ DEFINE_PROP_UINT64("ram-base", AwA10DEBEState, ram_base, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void allwinner_a10_debe_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ rc->phases.enter = allwinner_a10_debe_reset_enter;
+ dc->vmsd = &allwinner_a10_debe_vmstate;
+ device_class_set_props(dc, allwinner_a10_debe_properties);
+}
+
+static const TypeInfo allwinner_a10_debe_info = {
+ .name = TYPE_AW_A10_DEBE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = allwinner_a10_debe_init,
+ .instance_size = sizeof(AwA10DEBEState),
+ .class_init = allwinner_a10_debe_class_init,
+};
+
+static void allwinner_a10_debe_register(void)
+{
+ type_register_static(&allwinner_a10_debe_info);
+}
+
+type_init(allwinner_a10_debe_register)
diff --git a/hw/display/meson.build b/hw/display/meson.build
index a5eb01fe2b..a3ef580b1c 100644
--- a/hw/display/meson.build
+++ b/hw/display/meson.build
@@ -38,7 +38,8 @@ system_ss.add(when: 'CONFIG_NEXTCUBE', if_true: files('next-fb.c'))
system_ss.add(when: 'CONFIG_VGA', if_true: files('vga.c'))
-system_ss.add(when: 'CONFIG_ALLWINNER_A10', if_true: files('allwinner-a10-hdmi.c',
+system_ss.add(when: 'CONFIG_ALLWINNER_A10', if_true: files('allwinner-a10-debe.c',
+ 'allwinner-a10-hdmi.c',
'allwinner-gpu.c'))
if (config_all_devices.has_key('CONFIG_VGA_CIRRUS') or
diff --git a/hw/display/trace-events b/hw/display/trace-events
index d1c0f05e52..132b66fc81 100644
--- a/hw/display/trace-events
+++ b/hw/display/trace-events
@@ -178,6 +178,10 @@ macfb_sense_read(uint32_t value) "video sense: 0x%"PRIx32
macfb_sense_write(uint32_t value) "video sense: 0x%"PRIx32
macfb_update_mode(uint32_t width, uint32_t height, uint8_t depth) "setting mode to width %"PRId32 " height %"PRId32 " size %d"
+# allwinner-a10-debe.c
+allwinner_a10_debe_read(uint64_t offset, uint64_t data) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64
+allwinner_a10_debe_write(uint64_t offset, uint64_t data) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64
+
# allwinner-a10-hdmi.c
allwinner_a10_hdmi_read(uint64_t offset, uint64_t data) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64
allwinner_a10_hdmi_write(uint64_t offset, uint64_t data) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64
diff --git a/include/hw/arm/allwinner-a10.h b/include/hw/arm/allwinner-a10.h
index 8109656421..2de7e402b2 100644
--- a/include/hw/arm/allwinner-a10.h
+++ b/include/hw/arm/allwinner-a10.h
@@ -12,6 +12,7 @@
#include "hw/rtc/allwinner-rtc.h"
#include "hw/misc/allwinner-a10-ccm.h"
#include "hw/misc/allwinner-a10-dramc.h"
+#include "hw/display/allwinner-a10-debe.h"
#include "hw/display/allwinner-a10-hdmi.h"
#include "hw/display/allwinner-gpu.h"
#include "hw/i2c/allwinner-i2c.h"
@@ -45,6 +46,7 @@ struct AwA10State {
AWI2CState i2c0;
AwRtcState rtc;
AwWdtState wdt;
+ AwA10DEBEState de_be0;
AwGpuState gpu;
AwA10HdmiState hdmi;
MemoryRegion sram_a;
diff --git a/include/hw/display/allwinner-a10-debe.h b/include/hw/display/allwinner-a10-debe.h
new file mode 100644
index 0000000000..30727516bc
--- /dev/null
+++ b/include/hw/display/allwinner-a10-debe.h
@@ -0,0 +1,71 @@
+/*
+ * Allwinner A10 Display engine Backend emulation
+ *
+ * Copyright (C) 2023 Strahinja Jankovic <strahinja.p.jankovic@gmail.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 HW_DISPLAY_ALLWINNER_A10_DEBE_H
+#define HW_DISPLAY_ALLWINNER_A10_DEBE_H
+
+#include "qom/object.h"
+#include "hw/sysbus.h"
+
+/**
+ * @name Constants
+ * @{
+ */
+
+/** Size of register I/O address space used by DEBE device */
+#define AW_A10_DEBE_IOSIZE (0x20000)
+
+/** Total number of known registers for DEBE */
+#define AW_A10_DEBE_REGS_NUM (AW_A10_DEBE_IOSIZE / sizeof(uint32_t))
+
+/** @} */
+
+/**
+ * @name Object model
+ * @{
+ */
+
+#define TYPE_AW_A10_DEBE "allwinner-a10-debe"
+OBJECT_DECLARE_SIMPLE_TYPE(AwA10DEBEState, AW_A10_DEBE)
+
+/** @} */
+
+/**
+ * Allwinner A10 DEBE object instance state.
+ */
+struct AwA10DEBEState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+ uint32_t width;
+ uint32_t height;
+ hwaddr framebuffer_offset;
+ hwaddr ram_base;
+ uint8_t bpp;
+ bool ready;
+ bool invalidate;
+
+ /** Maps I/O registers in physical memory */
+ MemoryRegion iomem;
+
+ /** Array of hardware registers */
+ uint32_t regs[AW_A10_DEBE_REGS_NUM];
+};
+
+#endif /* HW_DISPLAY_ALLWINNER_A10_DEBE_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [RFC Patch 4/5] hw/display: Allwinner A10 LCDC emulation
2023-09-05 20:14 [RFC Patch 0/5] Allwinner A10 input/output peripherals Strahinja Jankovic
` (2 preceding siblings ...)
2023-09-05 20:14 ` [RFC Patch 3/5] hw/display: Allwinner A10 Display Engine Backend emulation Strahinja Jankovic
@ 2023-09-05 20:14 ` Strahinja Jankovic
2023-09-06 5:03 ` Philippe Mathieu-Daudé
2023-09-05 20:14 ` [RFC Patch 5/5] hw/input: Add Allwinner-A10 PS2 emulation Strahinja Jankovic
4 siblings, 1 reply; 11+ messages in thread
From: Strahinja Jankovic @ 2023-09-05 20:14 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-arm, Beniamino Galvani, Peter Maydell, Strahinja Jankovic
This patch adds support for Allwinner A10 LCD controller.
Current emulation supports only RGB32 colorspace and interacts with
DEBE0 to obtain framebuffer address and screen size.
Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
---
hw/arm/allwinner-a10.c | 10 +
hw/display/allwinner-a10-lcdc.c | 275 ++++++++++++++++++++++++
hw/display/meson.build | 1 +
hw/display/trace-events | 5 +
include/hw/arm/allwinner-a10.h | 2 +
include/hw/display/allwinner-a10-lcdc.h | 77 +++++++
6 files changed, 370 insertions(+)
create mode 100644 hw/display/allwinner-a10-lcdc.c
create mode 100644 include/hw/display/allwinner-a10-lcdc.h
diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c
index 624e95af46..f93bc5266d 100644
--- a/hw/arm/allwinner-a10.c
+++ b/hw/arm/allwinner-a10.c
@@ -41,6 +41,7 @@
#define AW_A10_WDT_BASE 0x01c20c90
#define AW_A10_RTC_BASE 0x01c20d00
#define AW_A10_I2C0_BASE 0x01c2ac00
+#define AW_A10_LCDC0_BASE 0x01c0c000
#define AW_A10_HDMI_BASE 0x01c16000
#define AW_A10_GPU_BASE 0x01c40000
#define AW_A10_DE_BE0_BASE 0x01e60000
@@ -101,6 +102,8 @@ static void aw_a10_init(Object *obj)
object_initialize_child(obj, "hdmi", &s->hdmi, TYPE_AW_A10_HDMI);
+ object_initialize_child(obj, "lcd0", &s->lcd0, TYPE_AW_A10_LCDC);
+
object_initialize_child(obj, "de_be0", &s->de_be0, TYPE_AW_A10_DEBE);
object_initialize_child(obj, "mali400", &s->gpu, TYPE_AW_GPU);
@@ -230,6 +233,13 @@ static void aw_a10_realize(DeviceState *dev, Error **errp)
sysbus_realize(SYS_BUS_DEVICE(&s->de_be0), &error_fatal);
sysbus_mmio_map(SYS_BUS_DEVICE(&s->de_be0), 0, AW_A10_DE_BE0_BASE);
+ /* LCD Controller */
+ object_property_set_link(OBJECT(&s->lcd0), "debe",
+ OBJECT(&s->de_be0), &error_fatal);
+ sysbus_realize(SYS_BUS_DEVICE(&s->lcd0), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->lcd0), 0, AW_A10_LCDC0_BASE);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->lcd0), 0, qdev_get_gpio_in(dev, 44));
+
/* MALI GPU */
sysbus_realize(SYS_BUS_DEVICE(&s->gpu), &error_fatal);
sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpu), 0, AW_A10_GPU_BASE);
diff --git a/hw/display/allwinner-a10-lcdc.c b/hw/display/allwinner-a10-lcdc.c
new file mode 100644
index 0000000000..8367ac32be
--- /dev/null
+++ b/hw/display/allwinner-a10-lcdc.c
@@ -0,0 +1,275 @@
+/*
+ * Allwinner A10 LCD Control Module emulation
+ *
+ * Copyright (C) 2023 Strahinja Jankovic <strahinja.p.jankovic@gmail.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/units.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "hw/qdev-properties.h"
+#include "qemu/module.h"
+#include "hw/display/allwinner-a10-lcdc.h"
+#include "hw/irq.h"
+#include "ui/pixel_ops.h"
+#include "trace.h"
+#include "sysemu/dma.h"
+#include "framebuffer.h"
+
+/* LCDC register offsets */
+enum {
+ REG_TCON_GCTL = 0x0000, /* TCON Global control register */
+ REG_TCON_GINT0 = 0x0004, /* TCON Global interrupt register 0 */
+};
+
+/* TCON_GCTL register fields */
+#define REG_TCON_GCTL_EN (1 << 31)
+
+/* TCON_GINT0 register fields */
+#define REG_TCON_GINT0_VB_INT_EN (1 << 31)
+#define REG_TCON_GINT0_VB_INT_FLAG (1 << 14)
+
+#define REG_INDEX(offset) (offset / sizeof(uint32_t))
+
+static void allwinner_a10_lcdc_tick(void *opaque)
+{
+ AwA10LcdcState *s = AW_A10_LCDC(opaque);
+
+ if (s->regs[REG_INDEX(REG_TCON_GINT0)] & REG_TCON_GINT0_VB_INT_EN) {
+ s->regs[REG_INDEX(REG_TCON_GINT0)] |= REG_TCON_GINT0_VB_INT_FLAG;
+ qemu_irq_raise(s->irq);
+ }
+}
+
+static uint64_t allwinner_a10_lcdc_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ AwA10LcdcState *s = AW_A10_LCDC(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+ uint32_t val = s->regs[idx];
+
+ switch (offset) {
+ case 0x800 ... AW_A10_LCDC_IOSIZE:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ default:
+ break;
+ }
+
+ trace_allwinner_a10_lcdc_read(offset, val);
+
+ return val;
+}
+
+static void allwinner_a10_lcdc_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwA10LcdcState *s = AW_A10_LCDC(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ switch (offset) {
+ case REG_TCON_GCTL:
+ s->is_enabled = !!REG_TCON_GCTL_EN;
+ break;
+ case REG_TCON_GINT0:
+ if (0 == (val & REG_TCON_GINT0_VB_INT_FLAG)) {
+ qemu_irq_lower(s->irq);
+ }
+ break;
+ case 0x800 ... AW_A10_LCDC_IOSIZE:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ break;
+ default:
+ break;
+ }
+
+ trace_allwinner_a10_lcdc_write(offset, (uint32_t)val);
+
+ s->regs[idx] = (uint32_t) val;
+}
+
+static const MemoryRegionOps allwinner_a10_lcdc_ops = {
+ .read = allwinner_a10_lcdc_read,
+ .write = allwinner_a10_lcdc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 1,
+};
+
+#define COPY_PIXEL(to, from) do { *(uint32_t *)to = from; to += 4; } while (0)
+
+static void draw_line(void *opaque, uint8_t *d, const uint8_t *src,
+ int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+ b = data & 0xff;
+ g = (data >> 8) & 0xff;
+ r = (data >> 16) & 0xff;
+ COPY_PIXEL(d, rgb_to_pixel32(r, g, b));
+ width--;
+ src += 4;
+ }
+}
+
+static void allwinner_a10_lcdc_invalidate_display(void *opaque)
+{
+ AwA10LcdcState *s = AW_A10_LCDC(opaque);
+ qemu_console_resize(s->con, s->debe->width, s->debe->height);
+ s->invalidate = 1;
+}
+
+static void allwinner_a10_lcdc_update_display(void *opaque)
+{
+ AwA10LcdcState *s = AW_A10_LCDC(opaque);
+ DisplaySurface *surface;
+ int step, width, height, linesize, first = 0, last;
+
+ if (!s->is_enabled || !s->debe->ready) {
+ return;
+ }
+
+ width = s->debe->width;
+ height = s->debe->height;
+ step = width * (s->debe->bpp >> 3);
+
+ if (s->debe->invalidate) {
+ allwinner_a10_lcdc_invalidate_display(opaque);
+ s->debe->invalidate = false;
+ }
+
+ surface = qemu_console_surface(s->con);
+ linesize = surface_stride(surface);
+
+ if (s->invalidate) {
+ framebuffer_update_memory_section(&s->fbsection,
+ sysbus_address_space(SYS_BUS_DEVICE(s)),
+ s->debe->framebuffer_offset,
+ height, step);
+ }
+
+ framebuffer_update_display(surface, &s->fbsection,
+ width, height,
+ step, linesize, 0,
+ s->invalidate,
+ draw_line, NULL,
+ &first, &last);
+
+ trace_allwinner_a10_draw(first, last, s->invalidate);
+
+ if (first >= 0) {
+ dpy_gfx_update(s->con, 0, first, width, last - first + 1);
+ }
+ s->invalidate = 0;
+
+}
+
+static const GraphicHwOps allwinner_a10_lcdc_gfx_ops = {
+ .invalidate = allwinner_a10_lcdc_invalidate_display,
+ .gfx_update = allwinner_a10_lcdc_update_display,
+};
+
+static void allwinner_a10_lcdc_reset_enter(Object *obj, ResetType type)
+{
+ AwA10LcdcState *s = AW_A10_LCDC(obj);
+ s->invalidate = 1;
+}
+
+static void allwinner_a10_lcdc_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ AwA10LcdcState *s = AW_A10_LCDC(obj);
+
+ /* Memory mapping */
+ memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_a10_lcdc_ops, s,
+ TYPE_AW_A10_LCDC, AW_A10_LCDC_IOSIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+ s->invalidate = 1;
+ s->is_enabled = 0;
+}
+
+static void allwinner_a10_lcdc_realize(DeviceState *dev, Error **errp)
+{
+ AwA10LcdcState *s = AW_A10_LCDC(dev);
+
+ s->timer = ptimer_init(allwinner_a10_lcdc_tick, s,
+ PTIMER_POLICY_NO_IMMEDIATE_TRIGGER |
+ PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
+ PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
+
+ ptimer_transaction_begin(s->timer);
+ /* Set to 60Hz */
+ ptimer_set_freq(s->timer, 60);
+ ptimer_set_limit(s->timer, 0x1, 1);
+ ptimer_run(s->timer, 0);
+ ptimer_transaction_commit(s->timer);
+
+ s->invalidate = 1;
+ s->con = graphic_console_init(NULL, 0, &allwinner_a10_lcdc_gfx_ops, s);
+ qemu_console_resize(s->con, s->debe->width, s->debe->height);
+}
+
+static const VMStateDescription allwinner_a10_lcdc_vmstate = {
+ .name = "allwinner-a10_lcdc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, AwA10LcdcState, AW_A10_LCDC_REGS_NUM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property allwinner_a10_lcdc_properties[] = {
+ DEFINE_PROP_LINK("debe", AwA10LcdcState, debe,
+ TYPE_AW_A10_DEBE, AwA10DEBEState *),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void allwinner_a10_lcdc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ rc->phases.enter = allwinner_a10_lcdc_reset_enter;
+ dc->vmsd = &allwinner_a10_lcdc_vmstate;
+ dc->realize = allwinner_a10_lcdc_realize;
+ device_class_set_props(dc, allwinner_a10_lcdc_properties);
+}
+
+static const TypeInfo allwinner_a10_lcdc_info = {
+ .name = TYPE_AW_A10_LCDC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = allwinner_a10_lcdc_init,
+ .instance_size = sizeof(AwA10LcdcState),
+ .class_init = allwinner_a10_lcdc_class_init,
+};
+
+static void allwinner_a10_lcdc_register(void)
+{
+ type_register_static(&allwinner_a10_lcdc_info);
+}
+
+type_init(allwinner_a10_lcdc_register)
diff --git a/hw/display/meson.build b/hw/display/meson.build
index a3ef580b1c..e233026fdd 100644
--- a/hw/display/meson.build
+++ b/hw/display/meson.build
@@ -40,6 +40,7 @@ system_ss.add(when: 'CONFIG_VGA', if_true: files('vga.c'))
system_ss.add(when: 'CONFIG_ALLWINNER_A10', if_true: files('allwinner-a10-debe.c',
'allwinner-a10-hdmi.c',
+ 'allwinner-a10-lcdc.c',
'allwinner-gpu.c'))
if (config_all_devices.has_key('CONFIG_VGA_CIRRUS') or
diff --git a/hw/display/trace-events b/hw/display/trace-events
index 132b66fc81..4b962d6eda 100644
--- a/hw/display/trace-events
+++ b/hw/display/trace-events
@@ -186,6 +186,11 @@ allwinner_a10_debe_write(uint64_t offset, uint64_t data) "Write: offset 0x%" PRI
allwinner_a10_hdmi_read(uint64_t offset, uint64_t data) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64
allwinner_a10_hdmi_write(uint64_t offset, uint64_t data) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64
+# allwinner-a10-lcdc.c
+allwinner_a10_lcdc_read(uint64_t offset, uint64_t data) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64
+allwinner_a10_lcdc_write(uint64_t offset, uint64_t data) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64
+allwinner_a10_draw(uint32_t first, uint32_t last, uint32_t invalidate) "Draw: 0x%x, 0x%x, 0x%x"
+
# allwinner-gpu.c
allwinner_gpu_read(uint64_t offset, uint64_t data) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64
allwinner_gpu_write(uint64_t offset, uint64_t data) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64
diff --git a/include/hw/arm/allwinner-a10.h b/include/hw/arm/allwinner-a10.h
index 2de7e402b2..c99ca6c1c4 100644
--- a/include/hw/arm/allwinner-a10.h
+++ b/include/hw/arm/allwinner-a10.h
@@ -14,6 +14,7 @@
#include "hw/misc/allwinner-a10-dramc.h"
#include "hw/display/allwinner-a10-debe.h"
#include "hw/display/allwinner-a10-hdmi.h"
+#include "hw/display/allwinner-a10-lcdc.h"
#include "hw/display/allwinner-gpu.h"
#include "hw/i2c/allwinner-i2c.h"
#include "hw/watchdog/allwinner-wdt.h"
@@ -49,6 +50,7 @@ struct AwA10State {
AwA10DEBEState de_be0;
AwGpuState gpu;
AwA10HdmiState hdmi;
+ AwA10LcdcState lcd0;
MemoryRegion sram_a;
EHCISysBusState ehci[AW_A10_NUM_USB];
OHCISysBusState ohci[AW_A10_NUM_USB];
diff --git a/include/hw/display/allwinner-a10-lcdc.h b/include/hw/display/allwinner-a10-lcdc.h
new file mode 100644
index 0000000000..82f6d397fb
--- /dev/null
+++ b/include/hw/display/allwinner-a10-lcdc.h
@@ -0,0 +1,77 @@
+/*
+ * Allwinner A10 LCD Control Module emulation
+ *
+ * Copyright (C) 2023 Strahinja Jankovic <strahinja.p.jankovic@gmail.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 HW_DISPLAY_ALLWINNER_A10_LCDC_H
+#define HW_DISPLAY_ALLWINNER_A10_LCDC_H
+
+#include "qom/object.h"
+#include "hw/ptimer.h"
+#include "hw/sysbus.h"
+#include "ui/console.h"
+#include "hw/display/allwinner-a10-debe.h"
+
+/**
+ * @name Constants
+ * @{
+ */
+
+/** Size of register I/O address space used by LCDC device */
+#define AW_A10_LCDC_IOSIZE (0x1000)
+
+/** Total number of known registers */
+#define AW_A10_LCDC_REGS_NUM (AW_A10_LCDC_IOSIZE / sizeof(uint32_t))
+
+/** @} */
+
+/**
+ * @name Object model
+ * @{
+ */
+
+#define TYPE_AW_A10_LCDC "allwinner-a10-lcdc"
+OBJECT_DECLARE_SIMPLE_TYPE(AwA10LcdcState, AW_A10_LCDC)
+
+/** @} */
+
+/**
+ * Allwinner A10 LCDC object instance state.
+ */
+struct AwA10LcdcState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+ qemu_irq irq;
+ struct ptimer_state *timer;
+ QemuConsole *con;
+
+ MemoryRegionSection fbsection;
+
+ int invalidate;
+ bool is_enabled;
+
+ AwA10DEBEState *debe;
+
+ /** Maps I/O registers in physical memory */
+ MemoryRegion iomem;
+
+ /** Array of hardware registers */
+ uint32_t regs[AW_A10_LCDC_REGS_NUM];
+};
+
+#endif /* HW_DISPLAY_ALLWINNER_A10_LCDC_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [RFC Patch 4/5] hw/display: Allwinner A10 LCDC emulation
2023-09-05 20:14 ` [RFC Patch 4/5] hw/display: Allwinner A10 LCDC emulation Strahinja Jankovic
@ 2023-09-06 5:03 ` Philippe Mathieu-Daudé
2023-09-08 18:36 ` Strahinja Jankovic
0 siblings, 1 reply; 11+ messages in thread
From: Philippe Mathieu-Daudé @ 2023-09-06 5:03 UTC (permalink / raw)
To: Strahinja Jankovic, qemu-devel
Cc: qemu-arm, Beniamino Galvani, Peter Maydell, Strahinja Jankovic,
Gerd Hoffmann, Marc-André Lureau
+Gerd & Marc-André for the ui/fb parts.
On 5/9/23 22:14, Strahinja Jankovic wrote:
> This patch adds support for Allwinner A10 LCD controller.
> Current emulation supports only RGB32 colorspace and interacts with
> DEBE0 to obtain framebuffer address and screen size.
>
> Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
> ---
> hw/arm/allwinner-a10.c | 10 +
> hw/display/allwinner-a10-lcdc.c | 275 ++++++++++++++++++++++++
> hw/display/meson.build | 1 +
> hw/display/trace-events | 5 +
> include/hw/arm/allwinner-a10.h | 2 +
> include/hw/display/allwinner-a10-lcdc.h | 77 +++++++
> 6 files changed, 370 insertions(+)
> create mode 100644 hw/display/allwinner-a10-lcdc.c
> create mode 100644 include/hw/display/allwinner-a10-lcdc.h
>
> diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c
> index 624e95af46..f93bc5266d 100644
> --- a/hw/arm/allwinner-a10.c
> +++ b/hw/arm/allwinner-a10.c
> @@ -41,6 +41,7 @@
> #define AW_A10_WDT_BASE 0x01c20c90
> #define AW_A10_RTC_BASE 0x01c20d00
> #define AW_A10_I2C0_BASE 0x01c2ac00
> +#define AW_A10_LCDC0_BASE 0x01c0c000
> #define AW_A10_HDMI_BASE 0x01c16000
> #define AW_A10_GPU_BASE 0x01c40000
> #define AW_A10_DE_BE0_BASE 0x01e60000
> @@ -101,6 +102,8 @@ static void aw_a10_init(Object *obj)
>
> object_initialize_child(obj, "hdmi", &s->hdmi, TYPE_AW_A10_HDMI);
>
> + object_initialize_child(obj, "lcd0", &s->lcd0, TYPE_AW_A10_LCDC);
> +
> object_initialize_child(obj, "de_be0", &s->de_be0, TYPE_AW_A10_DEBE);
>
> object_initialize_child(obj, "mali400", &s->gpu, TYPE_AW_GPU);
> @@ -230,6 +233,13 @@ static void aw_a10_realize(DeviceState *dev, Error **errp)
> sysbus_realize(SYS_BUS_DEVICE(&s->de_be0), &error_fatal);
> sysbus_mmio_map(SYS_BUS_DEVICE(&s->de_be0), 0, AW_A10_DE_BE0_BASE);
>
> + /* LCD Controller */
> + object_property_set_link(OBJECT(&s->lcd0), "debe",
> + OBJECT(&s->de_be0), &error_fatal);
IIUC you have LCDC polling DEBE for size update then invalidate,
shouldn't be the opposite, LCDC linked to DEBE and DEBE call the
LCDC invalidate handler on resize?
> + sysbus_realize(SYS_BUS_DEVICE(&s->lcd0), &error_fatal);
> + sysbus_mmio_map(SYS_BUS_DEVICE(&s->lcd0), 0, AW_A10_LCDC0_BASE);
> + sysbus_connect_irq(SYS_BUS_DEVICE(&s->lcd0), 0, qdev_get_gpio_in(dev, 44));
> +
> /* MALI GPU */
> sysbus_realize(SYS_BUS_DEVICE(&s->gpu), &error_fatal);
> sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpu), 0, AW_A10_GPU_BASE);
> diff --git a/hw/display/allwinner-a10-lcdc.c b/hw/display/allwinner-a10-lcdc.c
> new file mode 100644
> index 0000000000..8367ac32be
> --- /dev/null
> +++ b/hw/display/allwinner-a10-lcdc.c
> @@ -0,0 +1,275 @@
> +/*
> + * Allwinner A10 LCD Control Module emulation
> + *
> + * Copyright (C) 2023 Strahinja Jankovic <strahinja.p.jankovic@gmail.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/units.h"
> +#include "hw/sysbus.h"
> +#include "migration/vmstate.h"
> +#include "qemu/log.h"
> +#include "hw/qdev-properties.h"
> +#include "qemu/module.h"
> +#include "hw/display/allwinner-a10-lcdc.h"
> +#include "hw/irq.h"
> +#include "ui/pixel_ops.h"
> +#include "trace.h"
> +#include "sysemu/dma.h"
> +#include "framebuffer.h"
> +
> +/* LCDC register offsets */
> +enum {
> + REG_TCON_GCTL = 0x0000, /* TCON Global control register */
> + REG_TCON_GINT0 = 0x0004, /* TCON Global interrupt register 0 */
> +};
> +
> +/* TCON_GCTL register fields */
> +#define REG_TCON_GCTL_EN (1 << 31)
> +
> +/* TCON_GINT0 register fields */
> +#define REG_TCON_GINT0_VB_INT_EN (1 << 31)
> +#define REG_TCON_GINT0_VB_INT_FLAG (1 << 14)
> +
> +#define REG_INDEX(offset) (offset / sizeof(uint32_t))
> +
> +static void allwinner_a10_lcdc_tick(void *opaque)
> +{
> + AwA10LcdcState *s = AW_A10_LCDC(opaque);
> +
> + if (s->regs[REG_INDEX(REG_TCON_GINT0)] & REG_TCON_GINT0_VB_INT_EN) {
> + s->regs[REG_INDEX(REG_TCON_GINT0)] |= REG_TCON_GINT0_VB_INT_FLAG;
> + qemu_irq_raise(s->irq);
> + }
> +}
> +
> +static uint64_t allwinner_a10_lcdc_read(void *opaque, hwaddr offset,
> + unsigned size)
> +{
> + AwA10LcdcState *s = AW_A10_LCDC(opaque);
> + const uint32_t idx = REG_INDEX(offset);
> + uint32_t val = s->regs[idx];
> +
> + switch (offset) {
> + case 0x800 ... AW_A10_LCDC_IOSIZE:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
> + __func__, (uint32_t)offset);
> + return 0;
> + default:
> + break;
> + }
> +
> + trace_allwinner_a10_lcdc_read(offset, val);
> +
> + return val;
> +}
> +
> +static void allwinner_a10_lcdc_write(void *opaque, hwaddr offset,
> + uint64_t val, unsigned size)
> +{
> + AwA10LcdcState *s = AW_A10_LCDC(opaque);
> + const uint32_t idx = REG_INDEX(offset);
> +
> + switch (offset) {
> + case REG_TCON_GCTL:
> + s->is_enabled = !!REG_TCON_GCTL_EN;
> + break;
> + case REG_TCON_GINT0:
> + if (0 == (val & REG_TCON_GINT0_VB_INT_FLAG)) {
> + qemu_irq_lower(s->irq);
> + }
> + break;
> + case 0x800 ... AW_A10_LCDC_IOSIZE:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
> + __func__, (uint32_t)offset);
> + break;
> + default:
> + break;
> + }
> +
> + trace_allwinner_a10_lcdc_write(offset, (uint32_t)val);
> +
> + s->regs[idx] = (uint32_t) val;
> +}
> +
> +static const MemoryRegionOps allwinner_a10_lcdc_ops = {
> + .read = allwinner_a10_lcdc_read,
> + .write = allwinner_a10_lcdc_write,
> + .endianness = DEVICE_NATIVE_ENDIAN,
> + .valid = {
> + .min_access_size = 1,
> + .max_access_size = 4,
> + },
> + .impl.min_access_size = 1,
> +};
> +
> +#define COPY_PIXEL(to, from) do { *(uint32_t *)to = from; to += 4; } while (0)
> +
> +static void draw_line(void *opaque, uint8_t *d, const uint8_t *src,
> + int width, int deststep)
> +{
> + uint32_t data;
> + unsigned int r, g, b;
> + while (width > 0) {
> + data = *(uint32_t *)src;
> + b = data & 0xff;
> + g = (data >> 8) & 0xff;
> + r = (data >> 16) & 0xff;
> + COPY_PIXEL(d, rgb_to_pixel32(r, g, b));
> + width--;
> + src += 4;
> + }
> +}
> +
> +static void allwinner_a10_lcdc_invalidate_display(void *opaque)
> +{
> + AwA10LcdcState *s = AW_A10_LCDC(opaque);
> + qemu_console_resize(s->con, s->debe->width, s->debe->height);
> + s->invalidate = 1;
> +}
> +
> +static void allwinner_a10_lcdc_update_display(void *opaque)
> +{
> + AwA10LcdcState *s = AW_A10_LCDC(opaque);
> + DisplaySurface *surface;
> + int step, width, height, linesize, first = 0, last;
> +
> + if (!s->is_enabled || !s->debe->ready) {
> + return;
> + }
> +
> + width = s->debe->width;
> + height = s->debe->height;
> + step = width * (s->debe->bpp >> 3);
> +
> + if (s->debe->invalidate) {
> + allwinner_a10_lcdc_invalidate_display(opaque);
> + s->debe->invalidate = false;
> + }
> +
> + surface = qemu_console_surface(s->con);
> + linesize = surface_stride(surface);
> +
> + if (s->invalidate) {
> + framebuffer_update_memory_section(&s->fbsection,
> + sysbus_address_space(SYS_BUS_DEVICE(s)),
> + s->debe->framebuffer_offset,
> + height, step);
> + }
> +
> + framebuffer_update_display(surface, &s->fbsection,
> + width, height,
> + step, linesize, 0,
> + s->invalidate,
> + draw_line, NULL,
> + &first, &last);
> +
> + trace_allwinner_a10_draw(first, last, s->invalidate);
> +
> + if (first >= 0) {
> + dpy_gfx_update(s->con, 0, first, width, last - first + 1);
> + }
> + s->invalidate = 0;
> +
> +}
> +
> +static const GraphicHwOps allwinner_a10_lcdc_gfx_ops = {
> + .invalidate = allwinner_a10_lcdc_invalidate_display,
> + .gfx_update = allwinner_a10_lcdc_update_display,
> +};
> +
> +static void allwinner_a10_lcdc_reset_enter(Object *obj, ResetType type)
> +{
> + AwA10LcdcState *s = AW_A10_LCDC(obj);
> + s->invalidate = 1;
> +}
> +
> +static void allwinner_a10_lcdc_init(Object *obj)
> +{
> + SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
> + AwA10LcdcState *s = AW_A10_LCDC(obj);
> +
> + /* Memory mapping */
> + memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_a10_lcdc_ops, s,
> + TYPE_AW_A10_LCDC, AW_A10_LCDC_IOSIZE);
> + sysbus_init_mmio(sbd, &s->iomem);
> + sysbus_init_irq(sbd, &s->irq);
> + s->invalidate = 1;
> + s->is_enabled = 0;
> +}
> +
> +static void allwinner_a10_lcdc_realize(DeviceState *dev, Error **errp)
> +{
> + AwA10LcdcState *s = AW_A10_LCDC(dev);
> +
> + s->timer = ptimer_init(allwinner_a10_lcdc_tick, s,
> + PTIMER_POLICY_NO_IMMEDIATE_TRIGGER |
> + PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
> + PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
> +
> + ptimer_transaction_begin(s->timer);
> + /* Set to 60Hz */
> + ptimer_set_freq(s->timer, 60);
> + ptimer_set_limit(s->timer, 0x1, 1);
> + ptimer_run(s->timer, 0);
> + ptimer_transaction_commit(s->timer);
> +
> + s->invalidate = 1;
> + s->con = graphic_console_init(NULL, 0, &allwinner_a10_lcdc_gfx_ops, s);
> + qemu_console_resize(s->con, s->debe->width, s->debe->height);
> +}
> +
> +static const VMStateDescription allwinner_a10_lcdc_vmstate = {
> + .name = "allwinner-a10_lcdc",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .fields = (VMStateField[]) {
> + VMSTATE_UINT32_ARRAY(regs, AwA10LcdcState, AW_A10_LCDC_REGS_NUM),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static Property allwinner_a10_lcdc_properties[] = {
> + DEFINE_PROP_LINK("debe", AwA10LcdcState, debe,
> + TYPE_AW_A10_DEBE, AwA10DEBEState *),
> + DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void allwinner_a10_lcdc_class_init(ObjectClass *klass, void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + ResettableClass *rc = RESETTABLE_CLASS(klass);
> +
> + rc->phases.enter = allwinner_a10_lcdc_reset_enter;
> + dc->vmsd = &allwinner_a10_lcdc_vmstate;
> + dc->realize = allwinner_a10_lcdc_realize;
> + device_class_set_props(dc, allwinner_a10_lcdc_properties);
> +}
> +
> +static const TypeInfo allwinner_a10_lcdc_info = {
> + .name = TYPE_AW_A10_LCDC,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_init = allwinner_a10_lcdc_init,
> + .instance_size = sizeof(AwA10LcdcState),
> + .class_init = allwinner_a10_lcdc_class_init,
> +};
> +
> +static void allwinner_a10_lcdc_register(void)
> +{
> + type_register_static(&allwinner_a10_lcdc_info);
> +}
> +
> +type_init(allwinner_a10_lcdc_register)
> diff --git a/hw/display/meson.build b/hw/display/meson.build
> index a3ef580b1c..e233026fdd 100644
> --- a/hw/display/meson.build
> +++ b/hw/display/meson.build
> @@ -40,6 +40,7 @@ system_ss.add(when: 'CONFIG_VGA', if_true: files('vga.c'))
>
> system_ss.add(when: 'CONFIG_ALLWINNER_A10', if_true: files('allwinner-a10-debe.c',
> 'allwinner-a10-hdmi.c',
> + 'allwinner-a10-lcdc.c',
> 'allwinner-gpu.c'))
>
> if (config_all_devices.has_key('CONFIG_VGA_CIRRUS') or
> diff --git a/hw/display/trace-events b/hw/display/trace-events
> index 132b66fc81..4b962d6eda 100644
> --- a/hw/display/trace-events
> +++ b/hw/display/trace-events
> @@ -186,6 +186,11 @@ allwinner_a10_debe_write(uint64_t offset, uint64_t data) "Write: offset 0x%" PRI
> allwinner_a10_hdmi_read(uint64_t offset, uint64_t data) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64
> allwinner_a10_hdmi_write(uint64_t offset, uint64_t data) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64
>
> +# allwinner-a10-lcdc.c
> +allwinner_a10_lcdc_read(uint64_t offset, uint64_t data) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64
> +allwinner_a10_lcdc_write(uint64_t offset, uint64_t data) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64
> +allwinner_a10_draw(uint32_t first, uint32_t last, uint32_t invalidate) "Draw: 0x%x, 0x%x, 0x%x"
> +
> # allwinner-gpu.c
> allwinner_gpu_read(uint64_t offset, uint64_t data) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64
> allwinner_gpu_write(uint64_t offset, uint64_t data) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64
> diff --git a/include/hw/arm/allwinner-a10.h b/include/hw/arm/allwinner-a10.h
> index 2de7e402b2..c99ca6c1c4 100644
> --- a/include/hw/arm/allwinner-a10.h
> +++ b/include/hw/arm/allwinner-a10.h
> @@ -14,6 +14,7 @@
> #include "hw/misc/allwinner-a10-dramc.h"
> #include "hw/display/allwinner-a10-debe.h"
> #include "hw/display/allwinner-a10-hdmi.h"
> +#include "hw/display/allwinner-a10-lcdc.h"
> #include "hw/display/allwinner-gpu.h"
> #include "hw/i2c/allwinner-i2c.h"
> #include "hw/watchdog/allwinner-wdt.h"
> @@ -49,6 +50,7 @@ struct AwA10State {
> AwA10DEBEState de_be0;
> AwGpuState gpu;
> AwA10HdmiState hdmi;
> + AwA10LcdcState lcd0;
> MemoryRegion sram_a;
> EHCISysBusState ehci[AW_A10_NUM_USB];
> OHCISysBusState ohci[AW_A10_NUM_USB];
> diff --git a/include/hw/display/allwinner-a10-lcdc.h b/include/hw/display/allwinner-a10-lcdc.h
> new file mode 100644
> index 0000000000..82f6d397fb
> --- /dev/null
> +++ b/include/hw/display/allwinner-a10-lcdc.h
> @@ -0,0 +1,77 @@
> +/*
> + * Allwinner A10 LCD Control Module emulation
> + *
> + * Copyright (C) 2023 Strahinja Jankovic <strahinja.p.jankovic@gmail.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 HW_DISPLAY_ALLWINNER_A10_LCDC_H
> +#define HW_DISPLAY_ALLWINNER_A10_LCDC_H
> +
> +#include "qom/object.h"
> +#include "hw/ptimer.h"
> +#include "hw/sysbus.h"
> +#include "ui/console.h"
> +#include "hw/display/allwinner-a10-debe.h"
> +
> +/**
> + * @name Constants
> + * @{
> + */
> +
> +/** Size of register I/O address space used by LCDC device */
> +#define AW_A10_LCDC_IOSIZE (0x1000)
> +
> +/** Total number of known registers */
> +#define AW_A10_LCDC_REGS_NUM (AW_A10_LCDC_IOSIZE / sizeof(uint32_t))
> +
> +/** @} */
> +
> +/**
> + * @name Object model
> + * @{
> + */
> +
> +#define TYPE_AW_A10_LCDC "allwinner-a10-lcdc"
> +OBJECT_DECLARE_SIMPLE_TYPE(AwA10LcdcState, AW_A10_LCDC)
> +
> +/** @} */
> +
> +/**
> + * Allwinner A10 LCDC object instance state.
> + */
> +struct AwA10LcdcState {
> + /*< private >*/
> + SysBusDevice parent_obj;
> + /*< public >*/
> + qemu_irq irq;
> + struct ptimer_state *timer;
> + QemuConsole *con;
> +
> + MemoryRegionSection fbsection;
> +
> + int invalidate;
> + bool is_enabled;
> +
> + AwA10DEBEState *debe;
> +
> + /** Maps I/O registers in physical memory */
> + MemoryRegion iomem;
> +
> + /** Array of hardware registers */
> + uint32_t regs[AW_A10_LCDC_REGS_NUM];
> +};
> +
> +#endif /* HW_DISPLAY_ALLWINNER_A10_LCDC_H */
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [RFC Patch 4/5] hw/display: Allwinner A10 LCDC emulation
2023-09-06 5:03 ` Philippe Mathieu-Daudé
@ 2023-09-08 18:36 ` Strahinja Jankovic
0 siblings, 0 replies; 11+ messages in thread
From: Strahinja Jankovic @ 2023-09-08 18:36 UTC (permalink / raw)
To: Philippe Mathieu-Daudé
Cc: qemu-devel, qemu-arm, Beniamino Galvani, Peter Maydell,
Gerd Hoffmann, Marc-André Lureau
[-- Attachment #1: Type: text/plain, Size: 18733 bytes --]
Hi Philippe,
Thank you for your comment.
On Wed, Sep 6, 2023 at 7:03 AM Philippe Mathieu-Daudé <philmd@linaro.org>
wrote:
> +Gerd & Marc-André for the ui/fb parts.
>
> On 5/9/23 22:14, Strahinja Jankovic wrote:
> > This patch adds support for Allwinner A10 LCD controller.
> > Current emulation supports only RGB32 colorspace and interacts with
> > DEBE0 to obtain framebuffer address and screen size.
> >
> > Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
> > ---
> > hw/arm/allwinner-a10.c | 10 +
> > hw/display/allwinner-a10-lcdc.c | 275 ++++++++++++++++++++++++
> > hw/display/meson.build | 1 +
> > hw/display/trace-events | 5 +
> > include/hw/arm/allwinner-a10.h | 2 +
> > include/hw/display/allwinner-a10-lcdc.h | 77 +++++++
> > 6 files changed, 370 insertions(+)
> > create mode 100644 hw/display/allwinner-a10-lcdc.c
> > create mode 100644 include/hw/display/allwinner-a10-lcdc.h
> >
> > diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c
> > index 624e95af46..f93bc5266d 100644
> > --- a/hw/arm/allwinner-a10.c
> > +++ b/hw/arm/allwinner-a10.c
> > @@ -41,6 +41,7 @@
> > #define AW_A10_WDT_BASE 0x01c20c90
> > #define AW_A10_RTC_BASE 0x01c20d00
> > #define AW_A10_I2C0_BASE 0x01c2ac00
> > +#define AW_A10_LCDC0_BASE 0x01c0c000
> > #define AW_A10_HDMI_BASE 0x01c16000
> > #define AW_A10_GPU_BASE 0x01c40000
> > #define AW_A10_DE_BE0_BASE 0x01e60000
> > @@ -101,6 +102,8 @@ static void aw_a10_init(Object *obj)
> >
> > object_initialize_child(obj, "hdmi", &s->hdmi, TYPE_AW_A10_HDMI);
> >
> > + object_initialize_child(obj, "lcd0", &s->lcd0, TYPE_AW_A10_LCDC);
> > +
> > object_initialize_child(obj, "de_be0", &s->de_be0,
> TYPE_AW_A10_DEBE);
> >
> > object_initialize_child(obj, "mali400", &s->gpu, TYPE_AW_GPU);
> > @@ -230,6 +233,13 @@ static void aw_a10_realize(DeviceState *dev, Error
> **errp)
> > sysbus_realize(SYS_BUS_DEVICE(&s->de_be0), &error_fatal);
> > sysbus_mmio_map(SYS_BUS_DEVICE(&s->de_be0), 0, AW_A10_DE_BE0_BASE);
> >
> > + /* LCD Controller */
> > + object_property_set_link(OBJECT(&s->lcd0), "debe",
> > + OBJECT(&s->de_be0), &error_fatal);
>
> IIUC you have LCDC polling DEBE for size update then invalidate,
> shouldn't be the opposite, LCDC linked to DEBE and DEBE call the
> LCDC invalidate handler on resize?
>
I think I understand what you meant. In this case, it was easier to do it
this way, since this way only LCDC needs to store a pointer to DEBE, there
is no need for storing the pointer the other way around as well.
Also the update function should be called frequently, so that should not
affect the performance.
If there is another way to implement this behavior, I am open to changing
this implementation.
Best regards,
Strahinja
> > + sysbus_realize(SYS_BUS_DEVICE(&s->lcd0), &error_fatal);
> > + sysbus_mmio_map(SYS_BUS_DEVICE(&s->lcd0), 0, AW_A10_LCDC0_BASE);
> > + sysbus_connect_irq(SYS_BUS_DEVICE(&s->lcd0), 0,
> qdev_get_gpio_in(dev, 44));
> > +
> > /* MALI GPU */
> > sysbus_realize(SYS_BUS_DEVICE(&s->gpu), &error_fatal);
> > sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpu), 0, AW_A10_GPU_BASE);
> > diff --git a/hw/display/allwinner-a10-lcdc.c
> b/hw/display/allwinner-a10-lcdc.c
> > new file mode 100644
> > index 0000000000..8367ac32be
> > --- /dev/null
> > +++ b/hw/display/allwinner-a10-lcdc.c
> > @@ -0,0 +1,275 @@
> > +/*
> > + * Allwinner A10 LCD Control Module emulation
> > + *
> > + * Copyright (C) 2023 Strahinja Jankovic <
> strahinja.p.jankovic@gmail.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/units.h"
> > +#include "hw/sysbus.h"
> > +#include "migration/vmstate.h"
> > +#include "qemu/log.h"
> > +#include "hw/qdev-properties.h"
> > +#include "qemu/module.h"
> > +#include "hw/display/allwinner-a10-lcdc.h"
> > +#include "hw/irq.h"
> > +#include "ui/pixel_ops.h"
> > +#include "trace.h"
> > +#include "sysemu/dma.h"
> > +#include "framebuffer.h"
> > +
> > +/* LCDC register offsets */
> > +enum {
> > + REG_TCON_GCTL = 0x0000, /* TCON Global control register */
> > + REG_TCON_GINT0 = 0x0004, /* TCON Global interrupt register 0 */
> > +};
> > +
> > +/* TCON_GCTL register fields */
> > +#define REG_TCON_GCTL_EN (1 << 31)
> > +
> > +/* TCON_GINT0 register fields */
> > +#define REG_TCON_GINT0_VB_INT_EN (1 << 31)
> > +#define REG_TCON_GINT0_VB_INT_FLAG (1 << 14)
> > +
> > +#define REG_INDEX(offset) (offset / sizeof(uint32_t))
> > +
> > +static void allwinner_a10_lcdc_tick(void *opaque)
> > +{
> > + AwA10LcdcState *s = AW_A10_LCDC(opaque);
> > +
> > + if (s->regs[REG_INDEX(REG_TCON_GINT0)] & REG_TCON_GINT0_VB_INT_EN) {
> > + s->regs[REG_INDEX(REG_TCON_GINT0)] |=
> REG_TCON_GINT0_VB_INT_FLAG;
> > + qemu_irq_raise(s->irq);
> > + }
> > +}
> > +
> > +static uint64_t allwinner_a10_lcdc_read(void *opaque, hwaddr offset,
> > + unsigned size)
> > +{
> > + AwA10LcdcState *s = AW_A10_LCDC(opaque);
> > + const uint32_t idx = REG_INDEX(offset);
> > + uint32_t val = s->regs[idx];
> > +
> > + switch (offset) {
> > + case 0x800 ... AW_A10_LCDC_IOSIZE:
> > + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset
> 0x%04x\n",
> > + __func__, (uint32_t)offset);
> > + return 0;
> > + default:
> > + break;
> > + }
> > +
> > + trace_allwinner_a10_lcdc_read(offset, val);
> > +
> > + return val;
> > +}
> > +
> > +static void allwinner_a10_lcdc_write(void *opaque, hwaddr offset,
> > + uint64_t val, unsigned size)
> > +{
> > + AwA10LcdcState *s = AW_A10_LCDC(opaque);
> > + const uint32_t idx = REG_INDEX(offset);
> > +
> > + switch (offset) {
> > + case REG_TCON_GCTL:
> > + s->is_enabled = !!REG_TCON_GCTL_EN;
> > + break;
> > + case REG_TCON_GINT0:
> > + if (0 == (val & REG_TCON_GINT0_VB_INT_FLAG)) {
> > + qemu_irq_lower(s->irq);
> > + }
> > + break;
> > + case 0x800 ... AW_A10_LCDC_IOSIZE:
> > + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset
> 0x%04x\n",
> > + __func__, (uint32_t)offset);
> > + break;
> > + default:
> > + break;
> > + }
> > +
> > + trace_allwinner_a10_lcdc_write(offset, (uint32_t)val);
> > +
> > + s->regs[idx] = (uint32_t) val;
> > +}
> > +
> > +static const MemoryRegionOps allwinner_a10_lcdc_ops = {
> > + .read = allwinner_a10_lcdc_read,
> > + .write = allwinner_a10_lcdc_write,
> > + .endianness = DEVICE_NATIVE_ENDIAN,
> > + .valid = {
> > + .min_access_size = 1,
> > + .max_access_size = 4,
> > + },
> > + .impl.min_access_size = 1,
> > +};
> > +
> > +#define COPY_PIXEL(to, from) do { *(uint32_t *)to = from; to += 4; }
> while (0)
> > +
> > +static void draw_line(void *opaque, uint8_t *d, const uint8_t *src,
> > + int width, int deststep)
> > +{
> > + uint32_t data;
> > + unsigned int r, g, b;
> > + while (width > 0) {
> > + data = *(uint32_t *)src;
> > + b = data & 0xff;
> > + g = (data >> 8) & 0xff;
> > + r = (data >> 16) & 0xff;
> > + COPY_PIXEL(d, rgb_to_pixel32(r, g, b));
> > + width--;
> > + src += 4;
> > + }
> > +}
> > +
> > +static void allwinner_a10_lcdc_invalidate_display(void *opaque)
> > +{
> > + AwA10LcdcState *s = AW_A10_LCDC(opaque);
> > + qemu_console_resize(s->con, s->debe->width, s->debe->height);
> > + s->invalidate = 1;
> > +}
> > +
> > +static void allwinner_a10_lcdc_update_display(void *opaque)
> > +{
> > + AwA10LcdcState *s = AW_A10_LCDC(opaque);
> > + DisplaySurface *surface;
> > + int step, width, height, linesize, first = 0, last;
> > +
> > + if (!s->is_enabled || !s->debe->ready) {
> > + return;
> > + }
> > +
> > + width = s->debe->width;
> > + height = s->debe->height;
> > + step = width * (s->debe->bpp >> 3);
> > +
> > + if (s->debe->invalidate) {
> > + allwinner_a10_lcdc_invalidate_display(opaque);
> > + s->debe->invalidate = false;
> > + }
> > +
> > + surface = qemu_console_surface(s->con);
> > + linesize = surface_stride(surface);
> > +
> > + if (s->invalidate) {
> > + framebuffer_update_memory_section(&s->fbsection,
> > +
> sysbus_address_space(SYS_BUS_DEVICE(s)),
> > + s->debe->framebuffer_offset,
> > + height, step);
> > + }
> > +
> > + framebuffer_update_display(surface, &s->fbsection,
> > + width, height,
> > + step, linesize, 0,
> > + s->invalidate,
> > + draw_line, NULL,
> > + &first, &last);
> > +
> > + trace_allwinner_a10_draw(first, last, s->invalidate);
> > +
> > + if (first >= 0) {
> > + dpy_gfx_update(s->con, 0, first, width, last - first + 1);
> > + }
> > + s->invalidate = 0;
> > +
> > +}
> > +
> > +static const GraphicHwOps allwinner_a10_lcdc_gfx_ops = {
> > + .invalidate = allwinner_a10_lcdc_invalidate_display,
> > + .gfx_update = allwinner_a10_lcdc_update_display,
> > +};
> > +
> > +static void allwinner_a10_lcdc_reset_enter(Object *obj, ResetType type)
> > +{
> > + AwA10LcdcState *s = AW_A10_LCDC(obj);
> > + s->invalidate = 1;
> > +}
> > +
> > +static void allwinner_a10_lcdc_init(Object *obj)
> > +{
> > + SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
> > + AwA10LcdcState *s = AW_A10_LCDC(obj);
> > +
> > + /* Memory mapping */
> > + memory_region_init_io(&s->iomem, OBJECT(s),
> &allwinner_a10_lcdc_ops, s,
> > + TYPE_AW_A10_LCDC, AW_A10_LCDC_IOSIZE);
> > + sysbus_init_mmio(sbd, &s->iomem);
> > + sysbus_init_irq(sbd, &s->irq);
> > + s->invalidate = 1;
> > + s->is_enabled = 0;
> > +}
> > +
> > +static void allwinner_a10_lcdc_realize(DeviceState *dev, Error **errp)
> > +{
> > + AwA10LcdcState *s = AW_A10_LCDC(dev);
> > +
> > + s->timer = ptimer_init(allwinner_a10_lcdc_tick, s,
> > + PTIMER_POLICY_NO_IMMEDIATE_TRIGGER |
> > + PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
> > + PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
> > +
> > + ptimer_transaction_begin(s->timer);
> > + /* Set to 60Hz */
> > + ptimer_set_freq(s->timer, 60);
> > + ptimer_set_limit(s->timer, 0x1, 1);
> > + ptimer_run(s->timer, 0);
> > + ptimer_transaction_commit(s->timer);
> > +
> > + s->invalidate = 1;
> > + s->con = graphic_console_init(NULL, 0, &allwinner_a10_lcdc_gfx_ops,
> s);
> > + qemu_console_resize(s->con, s->debe->width, s->debe->height);
> > +}
> > +
> > +static const VMStateDescription allwinner_a10_lcdc_vmstate = {
> > + .name = "allwinner-a10_lcdc",
> > + .version_id = 1,
> > + .minimum_version_id = 1,
> > + .fields = (VMStateField[]) {
> > + VMSTATE_UINT32_ARRAY(regs, AwA10LcdcState,
> AW_A10_LCDC_REGS_NUM),
> > + VMSTATE_END_OF_LIST()
> > + }
> > +};
> > +
> > +static Property allwinner_a10_lcdc_properties[] = {
> > + DEFINE_PROP_LINK("debe", AwA10LcdcState, debe,
> > + TYPE_AW_A10_DEBE, AwA10DEBEState *),
> > + DEFINE_PROP_END_OF_LIST(),
> > +};
> > +
> > +static void allwinner_a10_lcdc_class_init(ObjectClass *klass, void
> *data)
> > +{
> > + DeviceClass *dc = DEVICE_CLASS(klass);
> > + ResettableClass *rc = RESETTABLE_CLASS(klass);
> > +
> > + rc->phases.enter = allwinner_a10_lcdc_reset_enter;
> > + dc->vmsd = &allwinner_a10_lcdc_vmstate;
> > + dc->realize = allwinner_a10_lcdc_realize;
> > + device_class_set_props(dc, allwinner_a10_lcdc_properties);
> > +}
> > +
> > +static const TypeInfo allwinner_a10_lcdc_info = {
> > + .name = TYPE_AW_A10_LCDC,
> > + .parent = TYPE_SYS_BUS_DEVICE,
> > + .instance_init = allwinner_a10_lcdc_init,
> > + .instance_size = sizeof(AwA10LcdcState),
> > + .class_init = allwinner_a10_lcdc_class_init,
> > +};
> > +
> > +static void allwinner_a10_lcdc_register(void)
> > +{
> > + type_register_static(&allwinner_a10_lcdc_info);
> > +}
> > +
> > +type_init(allwinner_a10_lcdc_register)
> > diff --git a/hw/display/meson.build b/hw/display/meson.build
> > index a3ef580b1c..e233026fdd 100644
> > --- a/hw/display/meson.build
> > +++ b/hw/display/meson.build
> > @@ -40,6 +40,7 @@ system_ss.add(when: 'CONFIG_VGA', if_true:
> files('vga.c'))
> >
> > system_ss.add(when: 'CONFIG_ALLWINNER_A10', if_true:
> files('allwinner-a10-debe.c',
> >
> 'allwinner-a10-hdmi.c',
> > +
> 'allwinner-a10-lcdc.c',
> >
> 'allwinner-gpu.c'))
> >
> > if (config_all_devices.has_key('CONFIG_VGA_CIRRUS') or
> > diff --git a/hw/display/trace-events b/hw/display/trace-events
> > index 132b66fc81..4b962d6eda 100644
> > --- a/hw/display/trace-events
> > +++ b/hw/display/trace-events
> > @@ -186,6 +186,11 @@ allwinner_a10_debe_write(uint64_t offset, uint64_t
> data) "Write: offset 0x%" PRI
> > allwinner_a10_hdmi_read(uint64_t offset, uint64_t data) "Read: offset
> 0x%" PRIx64 " data 0x%" PRIx64
> > allwinner_a10_hdmi_write(uint64_t offset, uint64_t data) "Write:
> offset 0x%" PRIx64 " data 0x%" PRIx64
> >
> > +# allwinner-a10-lcdc.c
> > +allwinner_a10_lcdc_read(uint64_t offset, uint64_t data) "Read: offset
> 0x%" PRIx64 " data 0x%" PRIx64
> > +allwinner_a10_lcdc_write(uint64_t offset, uint64_t data) "Write: offset
> 0x%" PRIx64 " data 0x%" PRIx64
> > +allwinner_a10_draw(uint32_t first, uint32_t last, uint32_t invalidate)
> "Draw: 0x%x, 0x%x, 0x%x"
> > +
> > # allwinner-gpu.c
> > allwinner_gpu_read(uint64_t offset, uint64_t data) "Read: offset 0x%"
> PRIx64 " data 0x%" PRIx64
> > allwinner_gpu_write(uint64_t offset, uint64_t data) "Write: offset
> 0x%" PRIx64 " data 0x%" PRIx64
> > diff --git a/include/hw/arm/allwinner-a10.h
> b/include/hw/arm/allwinner-a10.h
> > index 2de7e402b2..c99ca6c1c4 100644
> > --- a/include/hw/arm/allwinner-a10.h
> > +++ b/include/hw/arm/allwinner-a10.h
> > @@ -14,6 +14,7 @@
> > #include "hw/misc/allwinner-a10-dramc.h"
> > #include "hw/display/allwinner-a10-debe.h"
> > #include "hw/display/allwinner-a10-hdmi.h"
> > +#include "hw/display/allwinner-a10-lcdc.h"
> > #include "hw/display/allwinner-gpu.h"
> > #include "hw/i2c/allwinner-i2c.h"
> > #include "hw/watchdog/allwinner-wdt.h"
> > @@ -49,6 +50,7 @@ struct AwA10State {
> > AwA10DEBEState de_be0;
> > AwGpuState gpu;
> > AwA10HdmiState hdmi;
> > + AwA10LcdcState lcd0;
> > MemoryRegion sram_a;
> > EHCISysBusState ehci[AW_A10_NUM_USB];
> > OHCISysBusState ohci[AW_A10_NUM_USB];
> > diff --git a/include/hw/display/allwinner-a10-lcdc.h
> b/include/hw/display/allwinner-a10-lcdc.h
> > new file mode 100644
> > index 0000000000..82f6d397fb
> > --- /dev/null
> > +++ b/include/hw/display/allwinner-a10-lcdc.h
> > @@ -0,0 +1,77 @@
> > +/*
> > + * Allwinner A10 LCD Control Module emulation
> > + *
> > + * Copyright (C) 2023 Strahinja Jankovic <
> strahinja.p.jankovic@gmail.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 HW_DISPLAY_ALLWINNER_A10_LCDC_H
> > +#define HW_DISPLAY_ALLWINNER_A10_LCDC_H
> > +
> > +#include "qom/object.h"
> > +#include "hw/ptimer.h"
> > +#include "hw/sysbus.h"
> > +#include "ui/console.h"
> > +#include "hw/display/allwinner-a10-debe.h"
> > +
> > +/**
> > + * @name Constants
> > + * @{
> > + */
> > +
> > +/** Size of register I/O address space used by LCDC device */
> > +#define AW_A10_LCDC_IOSIZE (0x1000)
> > +
> > +/** Total number of known registers */
> > +#define AW_A10_LCDC_REGS_NUM (AW_A10_LCDC_IOSIZE /
> sizeof(uint32_t))
> > +
> > +/** @} */
> > +
> > +/**
> > + * @name Object model
> > + * @{
> > + */
> > +
> > +#define TYPE_AW_A10_LCDC "allwinner-a10-lcdc"
> > +OBJECT_DECLARE_SIMPLE_TYPE(AwA10LcdcState, AW_A10_LCDC)
> > +
> > +/** @} */
> > +
> > +/**
> > + * Allwinner A10 LCDC object instance state.
> > + */
> > +struct AwA10LcdcState {
> > + /*< private >*/
> > + SysBusDevice parent_obj;
> > + /*< public >*/
> > + qemu_irq irq;
> > + struct ptimer_state *timer;
> > + QemuConsole *con;
> > +
> > + MemoryRegionSection fbsection;
> > +
> > + int invalidate;
> > + bool is_enabled;
> > +
> > + AwA10DEBEState *debe;
> > +
> > + /** Maps I/O registers in physical memory */
> > + MemoryRegion iomem;
> > +
> > + /** Array of hardware registers */
> > + uint32_t regs[AW_A10_LCDC_REGS_NUM];
> > +};
> > +
> > +#endif /* HW_DISPLAY_ALLWINNER_A10_LCDC_H */
>
>
[-- Attachment #2: Type: text/html, Size: 24104 bytes --]
^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC Patch 5/5] hw/input: Add Allwinner-A10 PS2 emulation
2023-09-05 20:14 [RFC Patch 0/5] Allwinner A10 input/output peripherals Strahinja Jankovic
` (3 preceding siblings ...)
2023-09-05 20:14 ` [RFC Patch 4/5] hw/display: Allwinner A10 LCDC emulation Strahinja Jankovic
@ 2023-09-05 20:14 ` Strahinja Jankovic
2023-09-15 14:23 ` Peter Maydell
4 siblings, 1 reply; 11+ messages in thread
From: Strahinja Jankovic @ 2023-09-05 20:14 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-arm, Beniamino Galvani, Peter Maydell, Strahinja Jankovic
Add emulation for PS2-0 and PS2-1 for keyboard/mouse.
Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
---
hw/arm/allwinner-a10.c | 18 ++
hw/input/allwinner-a10-ps2.c | 345 +++++++++++++++++++++++++++
hw/input/meson.build | 2 +
include/hw/arm/allwinner-a10.h | 3 +
include/hw/input/allwinner-a10-ps2.h | 96 ++++++++
5 files changed, 464 insertions(+)
create mode 100644 hw/input/allwinner-a10-ps2.c
create mode 100644 include/hw/input/allwinner-a10-ps2.h
diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c
index f93bc5266d..3d25dbb4e3 100644
--- a/hw/arm/allwinner-a10.c
+++ b/hw/arm/allwinner-a10.c
@@ -40,6 +40,8 @@
#define AW_A10_SATA_BASE 0x01c18000
#define AW_A10_WDT_BASE 0x01c20c90
#define AW_A10_RTC_BASE 0x01c20d00
+#define AW_A10_PS2_0_BASE 0x01c2a000
+#define AW_A10_PS2_1_BASE 0x01c2a400
#define AW_A10_I2C0_BASE 0x01c2ac00
#define AW_A10_LCDC0_BASE 0x01c0c000
#define AW_A10_HDMI_BASE 0x01c16000
@@ -107,6 +109,12 @@ static void aw_a10_init(Object *obj)
object_initialize_child(obj, "de_be0", &s->de_be0, TYPE_AW_A10_DEBE);
object_initialize_child(obj, "mali400", &s->gpu, TYPE_AW_GPU);
+
+ object_initialize_child(obj, "keyboard", &s->kbd,
+ TYPE_AW_A10_PS2_KBD_DEVICE);
+
+ object_initialize_child(obj, "mouse", &s->mouse,
+ TYPE_AW_A10_PS2_MOUSE_DEVICE);
}
static void aw_a10_realize(DeviceState *dev, Error **errp)
@@ -243,6 +251,16 @@ static void aw_a10_realize(DeviceState *dev, Error **errp)
/* MALI GPU */
sysbus_realize(SYS_BUS_DEVICE(&s->gpu), &error_fatal);
sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpu), 0, AW_A10_GPU_BASE);
+
+ /* PS2-0 - keyboard */
+ sysbus_realize(SYS_BUS_DEVICE(&s->kbd), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->kbd), 0, AW_A10_PS2_0_BASE);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->kbd), 0, qdev_get_gpio_in(dev, 62));
+
+ /* PS2-1 - mouse */
+ sysbus_realize(SYS_BUS_DEVICE(&s->mouse), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->mouse), 0, AW_A10_PS2_1_BASE);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->mouse), 0, qdev_get_gpio_in(dev, 63));
}
static void aw_a10_class_init(ObjectClass *oc, void *data)
diff --git a/hw/input/allwinner-a10-ps2.c b/hw/input/allwinner-a10-ps2.c
new file mode 100644
index 0000000000..c4b09c0ea3
--- /dev/null
+++ b/hw/input/allwinner-a10-ps2.c
@@ -0,0 +1,345 @@
+/*
+ * Allwinner A10 PS2 Module emulation
+ *
+ * Copyright (C) 2023 Strahinja Jankovic <strahinja.p.jankovic@gmail.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/units.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/input/allwinner-a10-ps2.h"
+#include "hw/input/ps2.h"
+#include "hw/irq.h"
+
+/* PS2 register offsets */
+enum {
+ REG_GCTL = 0x0000, /* Global Control Reg */
+ REG_DATA = 0x0004, /* Data Reg */
+ REG_LCTL = 0x0008, /* Line Control Reg */
+ REG_LSTS = 0x000C, /* Line Status Reg */
+ REG_FCTL = 0x0010, /* FIFO Control Reg */
+ REG_FSTS = 0x0014, /* FIFO Status Reg */
+ REG_CLKDR = 0x0018, /* Clock Divider Reg */
+};
+
+#define REG_INDEX(offset) (offset / sizeof(uint32_t))
+
+/* PS2 register reset values */
+enum {
+ REG_GCTL_RST = 0x00000002,
+ REG_DATA_RST = 0x00000000,
+ REG_LCTL_RST = 0x00000000,
+ REG_LSTS_RST = 0x00030000,
+ REG_FCTL_RST = 0x00000000,
+ REG_FSTS_RST = 0x00000100,
+ REG_CLKDR_RST = 0x00002F4F,
+};
+
+/* REG_GCTL Fields */
+#define FIELD_REG_GCTL_SOFT_RST (1 << 2)
+#define FIELD_REG_GCTL_INT_EN (1 << 3)
+#define FIELD_REG_GCTL_INT_FLAG (1 << 4)
+
+/* REG_FCTL Fields */
+#define FIELD_REG_FCTL_RXRDY_IEN (1 << 0)
+#define FIELD_REG_FCTL_TXRDY_IEN (1 << 8)
+
+/* REG_FSTS Fields */
+#define FIELD_REG_FSTS_RX_RDY (1 << 0)
+#define FIELD_REG_FSTS_TX_RDY (1 << 8)
+#define FIELD_REG_FSTS_RX_LEVEL1 (1 << 16)
+
+static int allwinner_a10_ps2_fctl_is_irq(AwA10PS2State *s)
+{
+ return (s->regs[REG_INDEX(REG_FCTL)] & FIELD_REG_FCTL_TXRDY_IEN) ||
+ (s->pending &&
+ (s->regs[REG_INDEX(REG_FCTL)] & FIELD_REG_FCTL_RXRDY_IEN));
+}
+
+static void allwinner_a10_ps2_update_irq(AwA10PS2State *s)
+{
+ int level = (s->regs[REG_INDEX(REG_GCTL)] & FIELD_REG_GCTL_INT_EN) &&
+ allwinner_a10_ps2_fctl_is_irq(s);
+
+ qemu_set_irq(s->irq, level);
+}
+
+static void allwinner_a10_ps2_set_irq(void *opaque, int n, int level)
+{
+ AwA10PS2State *s = (AwA10PS2State *)opaque;
+
+ s->pending = level;
+ allwinner_a10_ps2_update_irq(s);
+}
+
+static uint64_t allwinner_a10_ps2_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ AwA10PS2State *s = AW_A10_PS2(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ switch (offset) {
+ case REG_FSTS:
+ {
+ uint32_t stat = FIELD_REG_FSTS_TX_RDY;
+ if (s->pending) {
+ stat |= FIELD_REG_FSTS_RX_LEVEL1 | FIELD_REG_FSTS_RX_RDY;
+ }
+ return stat;
+ }
+ break;
+ case REG_DATA:
+ if (s->pending) {
+ s->last = ps2_read_data(s->ps2dev);
+ }
+ return s->last;
+ case REG_GCTL:
+ {
+ if (allwinner_a10_ps2_fctl_is_irq(s)) {
+ s->regs[idx] |= FIELD_REG_GCTL_INT_FLAG;
+ } else {
+ s->regs[idx] &= FIELD_REG_GCTL_INT_FLAG;
+ }
+ }
+ break;
+ case REG_LCTL:
+ case REG_LSTS:
+ case REG_FCTL:
+ case REG_CLKDR:
+ break;
+ case 0x1C ... AW_A10_PS2_IOSIZE:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented read offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ }
+
+ return s->regs[idx];
+}
+
+static void allwinner_a10_ps2_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwA10PS2State *s = AW_A10_PS2(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ s->regs[idx] = (uint32_t) val;
+
+ switch (offset) {
+ case REG_GCTL:
+ allwinner_a10_ps2_update_irq(s);
+ s->regs[idx] &= ~FIELD_REG_GCTL_SOFT_RST;
+ break;
+ case REG_DATA:
+ /* ??? This should toggle the TX interrupt line. */
+ /* ??? This means kbd/mouse can block each other. */
+ if (s->is_mouse) {
+ ps2_write_mouse(PS2_MOUSE_DEVICE(s->ps2dev), val);
+ } else {
+ ps2_write_keyboard(PS2_KBD_DEVICE(s->ps2dev), val);
+ }
+ break;
+ case REG_LCTL:
+ case REG_LSTS:
+ case REG_FCTL:
+ case REG_FSTS:
+ case REG_CLKDR:
+ break;
+ case 0x1C ... AW_A10_PS2_IOSIZE:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented write offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps allwinner_a10_ps2_ops = {
+ .read = allwinner_a10_ps2_read,
+ .write = allwinner_a10_ps2_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+static const VMStateDescription allwinner_a10_ps2_vmstate = {
+ .name = "allwinner-a10-ps2",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, AwA10PS2State, AW_A10_PS2_REGS_NUM),
+ VMSTATE_INT32(pending, AwA10PS2State),
+ VMSTATE_UINT32(last, AwA10PS2State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void allwinner_a10_ps2_realize(DeviceState *dev, Error **errp)
+{
+ AwA10PS2State *s = AW_A10_PS2(dev);
+
+ qdev_connect_gpio_out(DEVICE(s->ps2dev), PS2_DEVICE_IRQ,
+ qdev_get_gpio_in_named(dev, "ps2-input-irq", 0));
+}
+
+static void allwinner_a10_ps2_kbd_realize(DeviceState *dev, Error **errp)
+{
+ AwA10PS2DeviceClass *pdc = AW_A10_PS2_GET_CLASS(dev);
+ AwA10PS2KbdState *s = AW_A10_PS2_KBD_DEVICE(dev);
+ AwA10PS2State *ps = AW_A10_PS2(dev);
+
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->kbd), errp)) {
+ return;
+ }
+
+ ps->ps2dev = PS2_DEVICE(&s->kbd);
+ pdc->parent_realize(dev, errp);
+}
+
+static void allwinner_a10_ps2_kbd_init(Object *obj)
+{
+ AwA10PS2KbdState *s = AW_A10_PS2_KBD_DEVICE(obj);
+ AwA10PS2State *ps = AW_A10_PS2(obj);
+
+ ps->is_mouse = false;
+ object_initialize_child(obj, "kbd", &s->kbd, TYPE_PS2_KBD_DEVICE);
+}
+
+static void allwinner_a10_ps2_mouse_realize(DeviceState *dev, Error **errp)
+{
+ AwA10PS2DeviceClass *pdc = AW_A10_PS2_GET_CLASS(dev);
+ AwA10PS2MouseState *s = AW_A10_PS2_MOUSE_DEVICE(dev);
+ AwA10PS2State *ps = AW_A10_PS2(dev);
+
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->mouse), errp)) {
+ return;
+ }
+
+ ps->ps2dev = PS2_DEVICE(&s->mouse);
+ pdc->parent_realize(dev, errp);
+}
+
+static void allwinner_a10_ps2_mouse_init(Object *obj)
+{
+ AwA10PS2MouseState *s = AW_A10_PS2_MOUSE_DEVICE(obj);
+ AwA10PS2State *ps = AW_A10_PS2(obj);
+
+ ps->is_mouse = true;
+ object_initialize_child(obj, "mouse", &s->mouse, TYPE_PS2_MOUSE_DEVICE);
+}
+
+static void allwinner_a10_ps2_kbd_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ AwA10PS2DeviceClass *pdc = AW_A10_PS2_CLASS(oc);
+
+ device_class_set_parent_realize(dc, allwinner_a10_ps2_kbd_realize,
+ &pdc->parent_realize);
+}
+
+static const TypeInfo allwinner_a10_ps2_kbd_info = {
+ .name = TYPE_AW_A10_PS2_KBD_DEVICE,
+ .parent = TYPE_AW_A10_PS2,
+ .instance_init = allwinner_a10_ps2_kbd_init,
+ .instance_size = sizeof(AwA10PS2KbdState),
+ .class_init = allwinner_a10_ps2_kbd_class_init,
+};
+
+static void allwinner_a10_ps2_mouse_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ AwA10PS2DeviceClass *pdc = AW_A10_PS2_CLASS(oc);
+
+ device_class_set_parent_realize(dc, allwinner_a10_ps2_mouse_realize,
+ &pdc->parent_realize);
+}
+
+static const TypeInfo allwinner_a10_ps2_mouse_info = {
+ .name = TYPE_AW_A10_PS2_MOUSE_DEVICE,
+ .parent = TYPE_AW_A10_PS2,
+ .instance_init = allwinner_a10_ps2_mouse_init,
+ .instance_size = sizeof(AwA10PS2MouseState),
+ .class_init = allwinner_a10_ps2_mouse_class_init,
+};
+
+static void allwinner_a10_ps2_reset_enter(Object *obj, ResetType type)
+{
+ AwA10PS2State *s = AW_A10_PS2(obj);
+
+ /* Set default values for registers */
+ s->regs[REG_INDEX(REG_GCTL)] = REG_GCTL_RST;
+ s->regs[REG_INDEX(REG_DATA)] = REG_DATA_RST;
+ s->regs[REG_INDEX(REG_LCTL)] = REG_LCTL_RST;
+ s->regs[REG_INDEX(REG_LSTS)] = REG_LSTS_RST;
+ s->regs[REG_INDEX(REG_FCTL)] = REG_FCTL_RST;
+ s->regs[REG_INDEX(REG_FSTS)] = REG_FSTS_RST;
+ s->regs[REG_INDEX(REG_CLKDR)] = REG_CLKDR_RST;
+}
+
+static void allwinner_a10_ps2_init(Object *obj)
+{
+ AwA10PS2State *s = AW_A10_PS2(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ memory_region_init_io(&s->iomem, obj, &allwinner_a10_ps2_ops, s,
+ "allwinner-a10-ps2", AW_A10_PS2_IOSIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+
+ qdev_init_gpio_in_named(DEVICE(obj), allwinner_a10_ps2_set_irq,
+ "ps2-input-irq", 1);
+}
+
+static void allwinner_a10_ps2_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ ResettableClass *rc = RESETTABLE_CLASS(oc);
+
+ rc->phases.enter = allwinner_a10_ps2_reset_enter;
+ dc->realize = allwinner_a10_ps2_realize;
+ dc->vmsd = &allwinner_a10_ps2_vmstate;
+}
+
+static const TypeInfo allwinner_a10_ps2_type_info = {
+ .name = TYPE_AW_A10_PS2,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = allwinner_a10_ps2_init,
+ .instance_size = sizeof(AwA10PS2State),
+ .class_init = allwinner_a10_ps2_class_init,
+ .class_size = sizeof(AwA10PS2DeviceClass),
+ .abstract = true,
+ .class_init = allwinner_a10_ps2_class_init,
+};
+
+static void allwinner_a10_ps2_register_types(void)
+{
+ type_register_static(&allwinner_a10_ps2_type_info);
+ type_register_static(&allwinner_a10_ps2_kbd_info);
+ type_register_static(&allwinner_a10_ps2_mouse_info);
+}
+
+type_init(allwinner_a10_ps2_register_types)
diff --git a/hw/input/meson.build b/hw/input/meson.build
index c0d4482180..b7bf4d9c9a 100644
--- a/hw/input/meson.build
+++ b/hw/input/meson.build
@@ -16,3 +16,5 @@ system_ss.add(when: 'CONFIG_VHOST_USER_INPUT', if_true: files('vhost-user-input.
system_ss.add(when: 'CONFIG_PXA2XX', if_true: files('pxa2xx_keypad.c'))
system_ss.add(when: 'CONFIG_TSC210X', if_true: files('tsc210x.c'))
system_ss.add(when: 'CONFIG_LASIPS2', if_true: files('lasips2.c'))
+
+system_ss.add(when: 'CONFIG_ALLWINNER_A10', if_true: files('allwinner-a10-ps2.c'))
diff --git a/include/hw/arm/allwinner-a10.h b/include/hw/arm/allwinner-a10.h
index c99ca6c1c4..163cc3af2b 100644
--- a/include/hw/arm/allwinner-a10.h
+++ b/include/hw/arm/allwinner-a10.h
@@ -17,6 +17,7 @@
#include "hw/display/allwinner-a10-lcdc.h"
#include "hw/display/allwinner-gpu.h"
#include "hw/i2c/allwinner-i2c.h"
+#include "hw/input/allwinner-a10-ps2.h"
#include "hw/watchdog/allwinner-wdt.h"
#include "sysemu/block-backend.h"
@@ -51,6 +52,8 @@ struct AwA10State {
AwGpuState gpu;
AwA10HdmiState hdmi;
AwA10LcdcState lcd0;
+ AwA10PS2KbdState kbd;
+ AwA10PS2MouseState mouse;
MemoryRegion sram_a;
EHCISysBusState ehci[AW_A10_NUM_USB];
OHCISysBusState ohci[AW_A10_NUM_USB];
diff --git a/include/hw/input/allwinner-a10-ps2.h b/include/hw/input/allwinner-a10-ps2.h
new file mode 100644
index 0000000000..230026bf28
--- /dev/null
+++ b/include/hw/input/allwinner-a10-ps2.h
@@ -0,0 +1,96 @@
+/*
+ * Allwinner A10 PS2 Module emulation
+ *
+ * Copyright (C) 2023 Strahinja Jankovic <strahinja.p.jankovic@gmail.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 HW_INPUT_ALLWINNER_A10_PS2_H
+#define HW_INPUT_ALLWINNER_A10_PS2_H
+
+#include "qom/object.h"
+#include "hw/sysbus.h"
+#include "hw/input/ps2.h"
+
+/**
+ * @name Constants
+ * @{
+ */
+
+/** Size of register I/O address space used by CCM device */
+#define AW_A10_PS2_IOSIZE (0x400)
+
+/** Total number of known registers */
+#define AW_A10_PS2_REGS_NUM (AW_A10_PS2_IOSIZE / sizeof(uint32_t))
+
+/** @} */
+
+struct AwA10PS2DeviceClass {
+ SysBusDeviceClass parent_class;
+
+ DeviceRealize parent_realize;
+};
+
+/**
+ * @name Object model
+ * @{
+ */
+
+#define TYPE_AW_A10_PS2 "allwinner-a10-ps2"
+OBJECT_DECLARE_TYPE(AwA10PS2State, AwA10PS2DeviceClass, AW_A10_PS2)
+
+/** @} */
+
+/**
+ * Allwinner A10 PS2 object instance state.
+ */
+struct AwA10PS2State {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ /** Maps I/O registers in physical memory */
+ MemoryRegion iomem;
+
+ PS2State *ps2dev;
+
+ /** Array of hardware registers */
+ uint32_t regs[AW_A10_PS2_REGS_NUM];
+ int pending;
+ uint32_t last;
+ qemu_irq irq;
+ bool is_mouse;
+};
+
+#define TYPE_AW_A10_PS2_KBD_DEVICE "allwinner-a10-ps2-keyboard"
+OBJECT_DECLARE_SIMPLE_TYPE(AwA10PS2KbdState, AW_A10_PS2_KBD_DEVICE)
+
+struct AwA10PS2KbdState {
+ AwA10PS2State parent_obj;
+
+ PS2KbdState kbd;
+};
+
+#define TYPE_AW_A10_PS2_MOUSE_DEVICE "allwinner-a10-ps2-mouse"
+OBJECT_DECLARE_SIMPLE_TYPE(AwA10PS2MouseState, AW_A10_PS2_MOUSE_DEVICE)
+
+struct AwA10PS2MouseState {
+ AwA10PS2State parent_obj;
+
+ PS2MouseState mouse;
+};
+
+
+#endif /* HW_INPUT_ALLWINNER_A10_PS2_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [RFC Patch 5/5] hw/input: Add Allwinner-A10 PS2 emulation
2023-09-05 20:14 ` [RFC Patch 5/5] hw/input: Add Allwinner-A10 PS2 emulation Strahinja Jankovic
@ 2023-09-15 14:23 ` Peter Maydell
2023-09-16 9:26 ` Strahinja Jankovic
0 siblings, 1 reply; 11+ messages in thread
From: Peter Maydell @ 2023-09-15 14:23 UTC (permalink / raw)
To: Strahinja Jankovic
Cc: qemu-devel, qemu-arm, Beniamino Galvani, Strahinja Jankovic
On Tue, 5 Sept 2023 at 21:14, Strahinja Jankovic
<strahinjapjankovic@gmail.com> wrote:
>
> Add emulation for PS2-0 and PS2-1 for keyboard/mouse.
>
> Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
> +static int allwinner_a10_ps2_fctl_is_irq(AwA10PS2State *s)
> +{
> + return (s->regs[REG_INDEX(REG_FCTL)] & FIELD_REG_FCTL_TXRDY_IEN) ||
> + (s->pending &&
> + (s->regs[REG_INDEX(REG_FCTL)] & FIELD_REG_FCTL_RXRDY_IEN));
It looks a little odd that you need a separate s->pending bool here.
Sometimes hardware does odd things, but the most usual situation
is that the pending status of an interrupt is directly reflected
in a register bit somewhere, and "is the interrupt high" logic
is then just "is the pending bit set and is the enable bit set".
Often the bit positions are deliberately the same in the two
registers and then "is an interrupt set" is something like
if (s->regs[REG_INDEX(REG_FCTL)] & s->regs[REG_INDEX(REG_FSTS)] &
(TXRDY_IEN | RXRDY_IEN))
> +}
> +
> +static void allwinner_a10_ps2_update_irq(AwA10PS2State *s)
> +{
> + int level = (s->regs[REG_INDEX(REG_GCTL)] & FIELD_REG_GCTL_INT_EN) &&
> + allwinner_a10_ps2_fctl_is_irq(s);
> +
> + qemu_set_irq(s->irq, level);
> +}
> +
> +static void allwinner_a10_ps2_set_irq(void *opaque, int n, int level)
> +{
> + AwA10PS2State *s = (AwA10PS2State *)opaque;
> +
> + s->pending = level;
> + allwinner_a10_ps2_update_irq(s);
> +}
> +
> +static uint64_t allwinner_a10_ps2_read(void *opaque, hwaddr offset,
> + unsigned size)
> +{
> + AwA10PS2State *s = AW_A10_PS2(opaque);
> + const uint32_t idx = REG_INDEX(offset);
> +
> + switch (offset) {
> + case REG_FSTS:
> + {
> + uint32_t stat = FIELD_REG_FSTS_TX_RDY;
> + if (s->pending) {
> + stat |= FIELD_REG_FSTS_RX_LEVEL1 | FIELD_REG_FSTS_RX_RDY;
> + }
> + return stat;
The logic here also suggests that the code would be simpler if you
keep the TX_RDY and RX_RDY state directly in this register value,
rather than hardcoding TX_RDY to always-set and keeping RX_RDY
in a separate pending field.
> + }
> + break;
> + case REG_DATA:
> + if (s->pending) {
> + s->last = ps2_read_data(s->ps2dev);
> + }
> + return s->last;
You could keep the last returned data in s->regs[REG_IDX(REG_DATA)]
and avoid having to have an extra s->last field in the state struct.
> + case REG_GCTL:
> + {
> + if (allwinner_a10_ps2_fctl_is_irq(s)) {
> + s->regs[idx] |= FIELD_REG_GCTL_INT_FLAG;
> + } else {
> + s->regs[idx] &= FIELD_REG_GCTL_INT_FLAG;
> + }
> + }
> + break;
> + case REG_LCTL:
> + case REG_LSTS:
> + case REG_FCTL:
> + case REG_CLKDR:
> + break;
> + case 0x1C ... AW_A10_PS2_IOSIZE:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
> + __func__, (uint32_t)offset);
> + return 0;
> + default:
> + qemu_log_mask(LOG_UNIMP, "%s: unimplemented read offset 0x%04x\n",
> + __func__, (uint32_t)offset);
> + return 0;
> + }
> +
> + return s->regs[idx];
> +}
> +
> +static void allwinner_a10_ps2_write(void *opaque, hwaddr offset,
> + uint64_t val, unsigned size)
> +{
> + AwA10PS2State *s = AW_A10_PS2(opaque);
> + const uint32_t idx = REG_INDEX(offset);
> +
> + s->regs[idx] = (uint32_t) val;
> +
> + switch (offset) {
> + case REG_GCTL:
> + allwinner_a10_ps2_update_irq(s);
> + s->regs[idx] &= ~FIELD_REG_GCTL_SOFT_RST;
> + break;
> + case REG_DATA:
> + /* ??? This should toggle the TX interrupt line. */
> + /* ??? This means kbd/mouse can block each other. */
I don't understand this comment. It looks like it was cut-and-pasted
from another device where it was originally written in 2005 (and
I don't understand it there either). We should either understand
what we mean here, or else not have the comment at all...
> + if (s->is_mouse) {
> + ps2_write_mouse(PS2_MOUSE_DEVICE(s->ps2dev), val);
> + } else {
> + ps2_write_keyboard(PS2_KBD_DEVICE(s->ps2dev), val);
> + }
> + break;
> + case REG_LCTL:
> + case REG_LSTS:
> + case REG_FCTL:
> + case REG_FSTS:
> + case REG_CLKDR:
> + break;
> + case 0x1C ... AW_A10_PS2_IOSIZE:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
> + __func__, (uint32_t)offset);
> + break;
> + default:
> + qemu_log_mask(LOG_UNIMP, "%s: unimplemented write offset 0x%04x\n",
> + __func__, (uint32_t)offset);
> + break;
> + }
> +}
thanks
-- PMM
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [RFC Patch 5/5] hw/input: Add Allwinner-A10 PS2 emulation
2023-09-15 14:23 ` Peter Maydell
@ 2023-09-16 9:26 ` Strahinja Jankovic
0 siblings, 0 replies; 11+ messages in thread
From: Strahinja Jankovic @ 2023-09-16 9:26 UTC (permalink / raw)
To: Peter Maydell; +Cc: qemu-devel, qemu-arm, Beniamino Galvani
[-- Attachment #1: Type: text/plain, Size: 5859 bytes --]
Hi Peter,
Thank you for your comments.
I used the PL050 component as a starting point, but I did not clean things
up well after I saw it working. I will clean it up before sending the new
patch version.
On Fri, Sep 15, 2023 at 4:23 PM Peter Maydell <peter.maydell@linaro.org>
wrote:
> On Tue, 5 Sept 2023 at 21:14, Strahinja Jankovic
> <strahinjapjankovic@gmail.com> wrote:
> >
> > Add emulation for PS2-0 and PS2-1 for keyboard/mouse.
> >
> > Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
>
>
>
> > +static int allwinner_a10_ps2_fctl_is_irq(AwA10PS2State *s)
> > +{
> > + return (s->regs[REG_INDEX(REG_FCTL)] & FIELD_REG_FCTL_TXRDY_IEN) ||
> > + (s->pending &&
> > + (s->regs[REG_INDEX(REG_FCTL)] & FIELD_REG_FCTL_RXRDY_IEN));
>
> It looks a little odd that you need a separate s->pending bool here.
> Sometimes hardware does odd things, but the most usual situation
> is that the pending status of an interrupt is directly reflected
> in a register bit somewhere, and "is the interrupt high" logic
> is then just "is the pending bit set and is the enable bit set".
> Often the bit positions are deliberately the same in the two
> registers and then "is an interrupt set" is something like
> if (s->regs[REG_INDEX(REG_FCTL)] & s->regs[REG_INDEX(REG_FSTS)] &
> (TXRDY_IEN | RXRDY_IEN))
>
>
Yes, that makes sense. I will try to improve this.
>
> > +}
> > +
> > +static void allwinner_a10_ps2_update_irq(AwA10PS2State *s)
> > +{
> > + int level = (s->regs[REG_INDEX(REG_GCTL)] & FIELD_REG_GCTL_INT_EN)
> &&
> > + allwinner_a10_ps2_fctl_is_irq(s);
> > +
> > + qemu_set_irq(s->irq, level);
> > +}
> > +
> > +static void allwinner_a10_ps2_set_irq(void *opaque, int n, int level)
> > +{
> > + AwA10PS2State *s = (AwA10PS2State *)opaque;
> > +
> > + s->pending = level;
> > + allwinner_a10_ps2_update_irq(s);
> > +}
> > +
> > +static uint64_t allwinner_a10_ps2_read(void *opaque, hwaddr offset,
> > + unsigned size)
> > +{
> > + AwA10PS2State *s = AW_A10_PS2(opaque);
> > + const uint32_t idx = REG_INDEX(offset);
> > +
> > + switch (offset) {
> > + case REG_FSTS:
> > + {
> > + uint32_t stat = FIELD_REG_FSTS_TX_RDY;
> > + if (s->pending) {
> > + stat |= FIELD_REG_FSTS_RX_LEVEL1 |
> FIELD_REG_FSTS_RX_RDY;
> > + }
> > + return stat;
>
> The logic here also suggests that the code would be simpler if you
> keep the TX_RDY and RX_RDY state directly in this register value,
> rather than hardcoding TX_RDY to always-set and keeping RX_RDY
> in a separate pending field.
>
>
That makes sense, I'll fix it.
> > + }
> > + break;
> > + case REG_DATA:
> > + if (s->pending) {
> > + s->last = ps2_read_data(s->ps2dev);
> > + }
> > + return s->last;
>
> You could keep the last returned data in s->regs[REG_IDX(REG_DATA)]
> and avoid having to have an extra s->last field in the state struct.
>
>
Ok.
> > + case REG_GCTL:
> > + {
> > + if (allwinner_a10_ps2_fctl_is_irq(s)) {
> > + s->regs[idx] |= FIELD_REG_GCTL_INT_FLAG;
> > + } else {
> > + s->regs[idx] &= FIELD_REG_GCTL_INT_FLAG;
> > + }
> > + }
> > + break;
> > + case REG_LCTL:
> > + case REG_LSTS:
> > + case REG_FCTL:
> > + case REG_CLKDR:
> > + break;
> > + case 0x1C ... AW_A10_PS2_IOSIZE:
> > + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset
> 0x%04x\n",
> > + __func__, (uint32_t)offset);
> > + return 0;
> > + default:
> > + qemu_log_mask(LOG_UNIMP, "%s: unimplemented read offset
> 0x%04x\n",
> > + __func__, (uint32_t)offset);
> > + return 0;
> > + }
> > +
> > + return s->regs[idx];
> > +}
> > +
> > +static void allwinner_a10_ps2_write(void *opaque, hwaddr offset,
> > + uint64_t val, unsigned size)
> > +{
> > + AwA10PS2State *s = AW_A10_PS2(opaque);
> > + const uint32_t idx = REG_INDEX(offset);
> > +
> > + s->regs[idx] = (uint32_t) val;
> > +
> > + switch (offset) {
> > + case REG_GCTL:
> > + allwinner_a10_ps2_update_irq(s);
> > + s->regs[idx] &= ~FIELD_REG_GCTL_SOFT_RST;
> > + break;
> > + case REG_DATA:
> > + /* ??? This should toggle the TX interrupt line. */
> > + /* ??? This means kbd/mouse can block each other. */
>
> I don't understand this comment. It looks like it was cut-and-pasted
> from another device where it was originally written in 2005 (and
> I don't understand it there either). We should either understand
> what we mean here, or else not have the comment at all...
>
>
Yes, unfortunately I missed this one before sending it out...
> > + if (s->is_mouse) {
> > + ps2_write_mouse(PS2_MOUSE_DEVICE(s->ps2dev), val);
> > + } else {
> > + ps2_write_keyboard(PS2_KBD_DEVICE(s->ps2dev), val);
> > + }
> > + break;
> > + case REG_LCTL:
> > + case REG_LSTS:
> > + case REG_FCTL:
> > + case REG_FSTS:
> > + case REG_CLKDR:
> > + break;
> > + case 0x1C ... AW_A10_PS2_IOSIZE:
> > + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset
> 0x%04x\n",
> > + __func__, (uint32_t)offset);
> > + break;
> > + default:
> > + qemu_log_mask(LOG_UNIMP, "%s: unimplemented write offset
> 0x%04x\n",
> > + __func__, (uint32_t)offset);
> > + break;
> > + }
> > +}
>
> thanks
> -- PMM
>
Thanks!
Best regards,
Strahinja
[-- Attachment #2: Type: text/html, Size: 8247 bytes --]
^ permalink raw reply [flat|nested] 11+ messages in thread