All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/7] Enable Cubieboard A10 boot SPL from SD card
@ 2022-12-26 22:02 Strahinja Jankovic
  2022-12-26 22:02 ` [PATCH v3 1/7] hw/misc: Allwinner-A10 Clock Controller Module Emulation Strahinja Jankovic
                   ` (7 more replies)
  0 siblings, 8 replies; 12+ messages in thread
From: Strahinja Jankovic @ 2022-12-26 22:02 UTC (permalink / raw)
  To: Peter Maydell
  Cc: Beniamino Galvani, Niek Linnenbank, philmd, qemu-arm, qemu-devel,
	Strahinja Jankovic

This patch series adds missing Allwinner A10 modules needed for
successful SPL boot:
- Clock controller module
- DRAM controller
- I2C0 controller (added also for Allwinner H3 since it is the same)
- AXP-209 connected to I2C0 bus

It also updates Allwinner A10 emulation so SPL is copied from attached
SD card if `-kernel` parameter is not passed when starting QEMU
(approach adapted from Allwinner H3 implementation).

Boot from SD card has been tested with Cubieboard Armbian SD card image and custom
Yocto image built for Cubieboard.
Example usage for Armbian image:
qemu-system-arm -M cubieboard -nographic -sd ~/Armbian_22.11.0-trunk_Cubieboard_kinetic_edge_6.0.7.img

v3:
* new avocado test for SD card boot for Cubieboard with OpenWrt image 
* renamed file and functions for AXP209 so there is not allwinner prefix
* replaced cast with I2C_BUS in AXP209 instantiation for Cubieboard
* squashed commit with Cubieboard and OrangePi-PC documentation update
with actual code changes
* added myself as Designated Reviewer to the Allwinner-A10 section in
MAINTAINERS file and added hw/misc/axp209.c to the same section.

v2:
* replaced DB_PRINTF with tracing functions
* removed .init function in AXP209 since .reset covers functionality
* moved defines to allwinner_i2c.c from header file
* updated docs with information about TWI
* minor code style fixes


Strahinja Jankovic (7):
  hw/misc: Allwinner-A10 Clock Controller Module Emulation
  hw/misc: Allwinner A10 DRAM Controller Emulation
  {hw/i2c,docs/system/arm}: Allwinner TWI/I2C Emulation
  hw/misc: AXP209 PMU Emulation
  hw/arm: Add AXP209 to Cubieboard
  hw/arm: Allwinner A10 enable SPL load from MMC
  tests/avocado: Add SD boot test to Cubieboard

 MAINTAINERS                           |   2 +
 docs/system/arm/cubieboard.rst        |   1 +
 docs/system/arm/orangepi.rst          |   1 +
 hw/arm/Kconfig                        |   5 +
 hw/arm/allwinner-a10.c                |  40 +++
 hw/arm/allwinner-h3.c                 |  11 +-
 hw/arm/cubieboard.c                   |  11 +
 hw/i2c/Kconfig                        |   4 +
 hw/i2c/allwinner-i2c.c                | 459 ++++++++++++++++++++++++++
 hw/i2c/meson.build                    |   1 +
 hw/i2c/trace-events                   |   5 +
 hw/misc/Kconfig                       |  10 +
 hw/misc/allwinner-a10-ccm.c           | 224 +++++++++++++
 hw/misc/allwinner-a10-dramc.c         | 179 ++++++++++
 hw/misc/axp209.c                      | 238 +++++++++++++
 hw/misc/meson.build                   |   3 +
 hw/misc/trace-events                  |   5 +
 include/hw/arm/allwinner-a10.h        |  27 ++
 include/hw/arm/allwinner-h3.h         |   3 +
 include/hw/i2c/allwinner-i2c.h        |  55 +++
 include/hw/misc/allwinner-a10-ccm.h   |  67 ++++
 include/hw/misc/allwinner-a10-dramc.h |  68 ++++
 tests/avocado/boot_linux_console.py   |  47 +++
 23 files changed, 1465 insertions(+), 1 deletion(-)
 create mode 100644 hw/i2c/allwinner-i2c.c
 create mode 100644 hw/misc/allwinner-a10-ccm.c
 create mode 100644 hw/misc/allwinner-a10-dramc.c
 create mode 100644 hw/misc/axp209.c
 create mode 100644 include/hw/i2c/allwinner-i2c.h
 create mode 100644 include/hw/misc/allwinner-a10-ccm.h
 create mode 100644 include/hw/misc/allwinner-a10-dramc.h

-- 
2.30.2



^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v3 1/7] hw/misc: Allwinner-A10 Clock Controller Module Emulation
  2022-12-26 22:02 [PATCH v3 0/7] Enable Cubieboard A10 boot SPL from SD card Strahinja Jankovic
@ 2022-12-26 22:02 ` Strahinja Jankovic
  2022-12-26 22:02 ` [PATCH v3 2/7] hw/misc: Allwinner A10 DRAM Controller Emulation Strahinja Jankovic
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 12+ messages in thread
From: Strahinja Jankovic @ 2022-12-26 22:02 UTC (permalink / raw)
  To: Peter Maydell
  Cc: Beniamino Galvani, Niek Linnenbank, philmd, qemu-arm, qemu-devel,
	Strahinja Jankovic

During SPL boot several Clock Controller Module (CCM) registers are
read, most important are PLL and Tuning, as well as divisor registers.

This patch adds these registers and initializes reset values from user's
guide.

Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>

Reviewed-by: Niek Linnenbank <nieklinnenbank@gmail.com>
---
 hw/arm/Kconfig                      |   1 +
 hw/arm/allwinner-a10.c              |   7 +
 hw/misc/Kconfig                     |   3 +
 hw/misc/allwinner-a10-ccm.c         | 224 ++++++++++++++++++++++++++++
 hw/misc/meson.build                 |   1 +
 include/hw/arm/allwinner-a10.h      |   2 +
 include/hw/misc/allwinner-a10-ccm.h |  67 +++++++++
 7 files changed, 305 insertions(+)
 create mode 100644 hw/misc/allwinner-a10-ccm.c
 create mode 100644 include/hw/misc/allwinner-a10-ccm.h

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 17fcde8e1c..14f52b41af 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -319,6 +319,7 @@ config ALLWINNER_A10
     select AHCI
     select ALLWINNER_A10_PIT
     select ALLWINNER_A10_PIC
+    select ALLWINNER_A10_CCM
     select ALLWINNER_EMAC
     select SERIAL
     select UNIMP
diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c
index 79082289ea..86baeeeca2 100644
--- a/hw/arm/allwinner-a10.c
+++ b/hw/arm/allwinner-a10.c
@@ -26,6 +26,7 @@
 #include "hw/usb/hcd-ohci.h"
 
 #define AW_A10_MMC0_BASE        0x01c0f000
+#define AW_A10_CCM_BASE         0x01c20000
 #define AW_A10_PIC_REG_BASE     0x01c20400
 #define AW_A10_PIT_REG_BASE     0x01c20c00
 #define AW_A10_UART0_REG_BASE   0x01c28000
@@ -46,6 +47,8 @@ static void aw_a10_init(Object *obj)
 
     object_initialize_child(obj, "timer", &s->timer, TYPE_AW_A10_PIT);
 
+    object_initialize_child(obj, "ccm", &s->ccm, TYPE_AW_A10_CCM);
+
     object_initialize_child(obj, "emac", &s->emac, TYPE_AW_EMAC);
 
     object_initialize_child(obj, "sata", &s->sata, TYPE_ALLWINNER_AHCI);
@@ -103,6 +106,10 @@ static void aw_a10_realize(DeviceState *dev, Error **errp)
     memory_region_add_subregion(get_system_memory(), 0x00000000, &s->sram_a);
     create_unimplemented_device("a10-sram-ctrl", 0x01c00000, 4 * KiB);
 
+    /* Clock Control Module */
+    sysbus_realize(SYS_BUS_DEVICE(&s->ccm), &error_fatal);
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->ccm), 0, AW_A10_CCM_BASE);
+
     /* FIXME use qdev NIC properties instead of nd_table[] */
     if (nd_table[0].used) {
         qemu_check_nic_model(&nd_table[0], TYPE_AW_EMAC);
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index cbabe9f78c..ed07bf4133 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -174,4 +174,7 @@ config VIRT_CTRL
 config LASI
     bool
 
+config ALLWINNER_A10_CCM
+    bool
+
 source macio/Kconfig
diff --git a/hw/misc/allwinner-a10-ccm.c b/hw/misc/allwinner-a10-ccm.c
new file mode 100644
index 0000000000..68146ee340
--- /dev/null
+++ b/hw/misc/allwinner-a10-ccm.c
@@ -0,0 +1,224 @@
+/*
+ * Allwinner A10 Clock Control Module emulation
+ *
+ * Copyright (C) 2022 Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
+ *
+ *  This file is derived from Allwinner H3 CCU,
+ *  by Niek Linnenbank.
+ *
+ * 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/misc/allwinner-a10-ccm.h"
+
+/* CCM register offsets */
+enum {
+    REG_PLL1_CFG             = 0x0000, /* PLL1 Control */
+    REG_PLL1_TUN             = 0x0004, /* PLL1 Tuning */
+    REG_PLL2_CFG             = 0x0008, /* PLL2 Control */
+    REG_PLL2_TUN             = 0x000C, /* PLL2 Tuning */
+    REG_PLL3_CFG             = 0x0010, /* PLL3 Control */
+    REG_PLL4_CFG             = 0x0018, /* PLL4 Control */
+    REG_PLL5_CFG             = 0x0020, /* PLL5 Control */
+    REG_PLL5_TUN             = 0x0024, /* PLL5 Tuning */
+    REG_PLL6_CFG             = 0x0028, /* PLL6 Control */
+    REG_PLL6_TUN             = 0x002C, /* PLL6 Tuning */
+    REG_PLL7_CFG             = 0x0030, /* PLL7 Control */
+    REG_PLL1_TUN2            = 0x0038, /* PLL1 Tuning2 */
+    REG_PLL5_TUN2            = 0x003C, /* PLL5 Tuning2 */
+    REG_PLL8_CFG             = 0x0040, /* PLL8 Control */
+    REG_OSC24M_CFG           = 0x0050, /* OSC24M Control */
+    REG_CPU_AHB_APB0_CFG     = 0x0054, /* CPU, AHB and APB0 Divide Ratio */
+};
+
+#define REG_INDEX(offset)    (offset / sizeof(uint32_t))
+
+/* CCM register reset values */
+enum {
+    REG_PLL1_CFG_RST         = 0x21005000,
+    REG_PLL1_TUN_RST         = 0x0A101000,
+    REG_PLL2_CFG_RST         = 0x08100010,
+    REG_PLL2_TUN_RST         = 0x00000000,
+    REG_PLL3_CFG_RST         = 0x0010D063,
+    REG_PLL4_CFG_RST         = 0x21009911,
+    REG_PLL5_CFG_RST         = 0x11049280,
+    REG_PLL5_TUN_RST         = 0x14888000,
+    REG_PLL6_CFG_RST         = 0x21009911,
+    REG_PLL6_TUN_RST         = 0x00000000,
+    REG_PLL7_CFG_RST         = 0x0010D063,
+    REG_PLL1_TUN2_RST        = 0x00000000,
+    REG_PLL5_TUN2_RST        = 0x00000000,
+    REG_PLL8_CFG_RST         = 0x21009911,
+    REG_OSC24M_CFG_RST       = 0x00138013,
+    REG_CPU_AHB_APB0_CFG_RST = 0x00010010,
+};
+
+static uint64_t allwinner_a10_ccm_read(void *opaque, hwaddr offset,
+                                       unsigned size)
+{
+    const AwA10ClockCtlState *s = AW_A10_CCM(opaque);
+    const uint32_t idx = REG_INDEX(offset);
+
+    switch (offset) {
+    case REG_PLL1_CFG:
+    case REG_PLL1_TUN:
+    case REG_PLL2_CFG:
+    case REG_PLL2_TUN:
+    case REG_PLL3_CFG:
+    case REG_PLL4_CFG:
+    case REG_PLL5_CFG:
+    case REG_PLL5_TUN:
+    case REG_PLL6_CFG:
+    case REG_PLL6_TUN:
+    case REG_PLL7_CFG:
+    case REG_PLL1_TUN2:
+    case REG_PLL5_TUN2:
+    case REG_PLL8_CFG:
+    case REG_OSC24M_CFG:
+    case REG_CPU_AHB_APB0_CFG:
+        break;
+    case 0x158 ... AW_A10_CCM_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_ccm_write(void *opaque, hwaddr offset,
+                                   uint64_t val, unsigned size)
+{
+    AwA10ClockCtlState *s = AW_A10_CCM(opaque);
+    const uint32_t idx = REG_INDEX(offset);
+
+    switch (offset) {
+    case REG_PLL1_CFG:
+    case REG_PLL1_TUN:
+    case REG_PLL2_CFG:
+    case REG_PLL2_TUN:
+    case REG_PLL3_CFG:
+    case REG_PLL4_CFG:
+    case REG_PLL5_CFG:
+    case REG_PLL5_TUN:
+    case REG_PLL6_CFG:
+    case REG_PLL6_TUN:
+    case REG_PLL7_CFG:
+    case REG_PLL1_TUN2:
+    case REG_PLL5_TUN2:
+    case REG_PLL8_CFG:
+    case REG_OSC24M_CFG:
+    case REG_CPU_AHB_APB0_CFG:
+        break;
+    case 0x158 ... AW_A10_CCM_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;
+    }
+
+    s->regs[idx] = (uint32_t) val;
+}
+
+static const MemoryRegionOps allwinner_a10_ccm_ops = {
+    .read = allwinner_a10_ccm_read,
+    .write = allwinner_a10_ccm_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+    .impl.min_access_size = 4,
+};
+
+static void allwinner_a10_ccm_reset_enter(Object *obj, ResetType type)
+{
+    AwA10ClockCtlState *s = AW_A10_CCM(obj);
+
+    /* Set default values for registers */
+    s->regs[REG_INDEX(REG_PLL1_CFG)] = REG_PLL1_CFG_RST;
+    s->regs[REG_INDEX(REG_PLL1_TUN)] = REG_PLL1_TUN_RST;
+    s->regs[REG_INDEX(REG_PLL2_CFG)] = REG_PLL2_CFG_RST;
+    s->regs[REG_INDEX(REG_PLL2_TUN)] = REG_PLL2_TUN_RST;
+    s->regs[REG_INDEX(REG_PLL3_CFG)] = REG_PLL3_CFG_RST;
+    s->regs[REG_INDEX(REG_PLL4_CFG)] = REG_PLL4_CFG_RST;
+    s->regs[REG_INDEX(REG_PLL5_CFG)] = REG_PLL5_CFG_RST;
+    s->regs[REG_INDEX(REG_PLL5_TUN)] = REG_PLL5_TUN_RST;
+    s->regs[REG_INDEX(REG_PLL6_CFG)] = REG_PLL6_CFG_RST;
+    s->regs[REG_INDEX(REG_PLL6_TUN)] = REG_PLL6_TUN_RST;
+    s->regs[REG_INDEX(REG_PLL7_CFG)] = REG_PLL7_CFG_RST;
+    s->regs[REG_INDEX(REG_PLL1_TUN2)] = REG_PLL1_TUN2_RST;
+    s->regs[REG_INDEX(REG_PLL5_TUN2)] = REG_PLL5_TUN2_RST;
+    s->regs[REG_INDEX(REG_PLL8_CFG)] = REG_PLL8_CFG_RST;
+    s->regs[REG_INDEX(REG_OSC24M_CFG)] = REG_OSC24M_CFG_RST;
+    s->regs[REG_INDEX(REG_CPU_AHB_APB0_CFG)] = REG_CPU_AHB_APB0_CFG_RST;
+}
+
+static void allwinner_a10_ccm_init(Object *obj)
+{
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    AwA10ClockCtlState *s = AW_A10_CCM(obj);
+
+    /* Memory mapping */
+    memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_a10_ccm_ops, s,
+                          TYPE_AW_A10_CCM, AW_A10_CCM_IOSIZE);
+    sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription allwinner_a10_ccm_vmstate = {
+    .name = "allwinner-a10-ccm",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, AwA10ClockCtlState, AW_A10_CCM_REGS_NUM),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void allwinner_a10_ccm_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    rc->phases.enter = allwinner_a10_ccm_reset_enter;
+    dc->vmsd = &allwinner_a10_ccm_vmstate;
+}
+
+static const TypeInfo allwinner_a10_ccm_info = {
+    .name          = TYPE_AW_A10_CCM,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_init = allwinner_a10_ccm_init,
+    .instance_size = sizeof(AwA10ClockCtlState),
+    .class_init    = allwinner_a10_ccm_class_init,
+};
+
+static void allwinner_a10_ccm_register(void)
+{
+    type_register_static(&allwinner_a10_ccm_info);
+}
+
+type_init(allwinner_a10_ccm_register)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index ed0598dc9e..c828dbeb26 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -38,6 +38,7 @@ subdir('macio')
 
 softmmu_ss.add(when: 'CONFIG_IVSHMEM_DEVICE', if_true: files('ivshmem.c'))
 
+softmmu_ss.add(when: 'CONFIG_ALLWINNER_A10_CCM', if_true: files('allwinner-a10-ccm.c'))
 softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-ccu.c'))
 specific_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-cpucfg.c'))
 softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-dramc.c'))
diff --git a/include/hw/arm/allwinner-a10.h b/include/hw/arm/allwinner-a10.h
index a76dc7b84d..45d0fc2f7e 100644
--- a/include/hw/arm/allwinner-a10.h
+++ b/include/hw/arm/allwinner-a10.h
@@ -12,6 +12,7 @@
 #include "hw/usb/hcd-ohci.h"
 #include "hw/usb/hcd-ehci.h"
 #include "hw/rtc/allwinner-rtc.h"
+#include "hw/misc/allwinner-a10-ccm.h"
 
 #include "target/arm/cpu.h"
 #include "qom/object.h"
@@ -30,6 +31,7 @@ struct AwA10State {
     /*< public >*/
 
     ARMCPU cpu;
+    AwA10ClockCtlState ccm;
     AwA10PITState timer;
     AwA10PICState intc;
     AwEmacState emac;
diff --git a/include/hw/misc/allwinner-a10-ccm.h b/include/hw/misc/allwinner-a10-ccm.h
new file mode 100644
index 0000000000..7f22532efa
--- /dev/null
+++ b/include/hw/misc/allwinner-a10-ccm.h
@@ -0,0 +1,67 @@
+/*
+ * Allwinner A10 Clock Control Module emulation
+ *
+ * Copyright (C) 2022 Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
+ *
+ *  This file is derived from Allwinner H3 CCU,
+ *  by Niek Linnenbank.
+ *
+ * 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_MISC_ALLWINNER_A10_CCM_H
+#define HW_MISC_ALLWINNER_A10_CCM_H
+
+#include "qom/object.h"
+#include "hw/sysbus.h"
+
+/**
+ * @name Constants
+ * @{
+ */
+
+/** Size of register I/O address space used by CCM device */
+#define AW_A10_CCM_IOSIZE        (0x400)
+
+/** Total number of known registers */
+#define AW_A10_CCM_REGS_NUM      (AW_A10_CCM_IOSIZE / sizeof(uint32_t))
+
+/** @} */
+
+/**
+ * @name Object model
+ * @{
+ */
+
+#define TYPE_AW_A10_CCM    "allwinner-a10-ccm"
+OBJECT_DECLARE_SIMPLE_TYPE(AwA10ClockCtlState, AW_A10_CCM)
+
+/** @} */
+
+/**
+ * Allwinner A10 CCM object instance state.
+ */
+struct AwA10ClockCtlState {
+    /*< private >*/
+    SysBusDevice parent_obj;
+    /*< public >*/
+
+    /** Maps I/O registers in physical memory */
+    MemoryRegion iomem;
+
+    /** Array of hardware registers */
+    uint32_t regs[AW_A10_CCM_REGS_NUM];
+};
+
+#endif /* HW_MISC_ALLWINNER_H3_CCU_H */
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v3 2/7] hw/misc: Allwinner A10 DRAM Controller Emulation
  2022-12-26 22:02 [PATCH v3 0/7] Enable Cubieboard A10 boot SPL from SD card Strahinja Jankovic
  2022-12-26 22:02 ` [PATCH v3 1/7] hw/misc: Allwinner-A10 Clock Controller Module Emulation Strahinja Jankovic
@ 2022-12-26 22:02 ` Strahinja Jankovic
  2022-12-26 22:02 ` [PATCH v3 3/7] {hw/i2c,docs/system/arm}: Allwinner TWI/I2C Emulation Strahinja Jankovic
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 12+ messages in thread
From: Strahinja Jankovic @ 2022-12-26 22:02 UTC (permalink / raw)
  To: Peter Maydell
  Cc: Beniamino Galvani, Niek Linnenbank, philmd, qemu-arm, qemu-devel,
	Strahinja Jankovic

During SPL boot several DRAM Controller registers are used. Most
important registers are those related to DRAM initialization and
calibration, where SPL initiates process and waits until certain bit is
set/cleared.

This patch adds these registers, initializes reset values from user's
guide and updates state of registers as SPL expects it.

Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>

Reviewed-by: Niek Linnenbank <nieklinnenbank@gmail.com>
---
 hw/arm/Kconfig                        |   1 +
 hw/arm/allwinner-a10.c                |   7 +
 hw/misc/Kconfig                       |   3 +
 hw/misc/allwinner-a10-dramc.c         | 179 ++++++++++++++++++++++++++
 hw/misc/meson.build                   |   1 +
 include/hw/arm/allwinner-a10.h        |   2 +
 include/hw/misc/allwinner-a10-dramc.h |  68 ++++++++++
 7 files changed, 261 insertions(+)
 create mode 100644 hw/misc/allwinner-a10-dramc.c
 create mode 100644 include/hw/misc/allwinner-a10-dramc.h

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 14f52b41af..140f142ae5 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -320,6 +320,7 @@ config ALLWINNER_A10
     select ALLWINNER_A10_PIT
     select ALLWINNER_A10_PIC
     select ALLWINNER_A10_CCM
+    select ALLWINNER_A10_DRAMC
     select ALLWINNER_EMAC
     select SERIAL
     select UNIMP
diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c
index 86baeeeca2..a5f7a36ac9 100644
--- a/hw/arm/allwinner-a10.c
+++ b/hw/arm/allwinner-a10.c
@@ -25,6 +25,7 @@
 #include "hw/boards.h"
 #include "hw/usb/hcd-ohci.h"
 
+#define AW_A10_DRAMC_BASE       0x01c01000
 #define AW_A10_MMC0_BASE        0x01c0f000
 #define AW_A10_CCM_BASE         0x01c20000
 #define AW_A10_PIC_REG_BASE     0x01c20400
@@ -49,6 +50,8 @@ static void aw_a10_init(Object *obj)
 
     object_initialize_child(obj, "ccm", &s->ccm, TYPE_AW_A10_CCM);
 
+    object_initialize_child(obj, "dramc", &s->dramc, TYPE_AW_A10_DRAMC);
+
     object_initialize_child(obj, "emac", &s->emac, TYPE_AW_EMAC);
 
     object_initialize_child(obj, "sata", &s->sata, TYPE_ALLWINNER_AHCI);
@@ -110,6 +113,10 @@ static void aw_a10_realize(DeviceState *dev, Error **errp)
     sysbus_realize(SYS_BUS_DEVICE(&s->ccm), &error_fatal);
     sysbus_mmio_map(SYS_BUS_DEVICE(&s->ccm), 0, AW_A10_CCM_BASE);
 
+    /* DRAM Control Module */
+    sysbus_realize(SYS_BUS_DEVICE(&s->dramc), &error_fatal);
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->dramc), 0, AW_A10_DRAMC_BASE);
+
     /* FIXME use qdev NIC properties instead of nd_table[] */
     if (nd_table[0].used) {
         qemu_check_nic_model(&nd_table[0], TYPE_AW_EMAC);
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index ed07bf4133..052fb54310 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -177,4 +177,7 @@ config LASI
 config ALLWINNER_A10_CCM
     bool
 
+config ALLWINNER_A10_DRAMC
+    bool
+
 source macio/Kconfig
diff --git a/hw/misc/allwinner-a10-dramc.c b/hw/misc/allwinner-a10-dramc.c
new file mode 100644
index 0000000000..e118b0c2fd
--- /dev/null
+++ b/hw/misc/allwinner-a10-dramc.c
@@ -0,0 +1,179 @@
+/*
+ * Allwinner A10 DRAM Controller emulation
+ *
+ * Copyright (C) 2022 Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
+ *
+ *  This file is derived from Allwinner H3 DRAMC,
+ *  by Niek Linnenbank.
+ *
+ * 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/misc/allwinner-a10-dramc.h"
+
+/* DRAMC register offsets */
+enum {
+    REG_SDR_CCR = 0x0000,
+    REG_SDR_ZQCR0 = 0x00a8,
+    REG_SDR_ZQSR = 0x00b0
+};
+
+#define REG_INDEX(offset)    (offset / sizeof(uint32_t))
+
+/* DRAMC register flags */
+enum {
+    REG_SDR_CCR_DATA_TRAINING = (1 << 30),
+    REG_SDR_CCR_DRAM_INIT     = (1 << 31),
+};
+enum {
+    REG_SDR_ZQSR_ZCAL         = (1 << 31),
+};
+
+/* DRAMC register reset values */
+enum {
+    REG_SDR_CCR_RESET   = 0x80020000,
+    REG_SDR_ZQCR0_RESET = 0x07b00000,
+    REG_SDR_ZQSR_RESET  = 0x80000000
+};
+
+static uint64_t allwinner_a10_dramc_read(void *opaque, hwaddr offset,
+                                       unsigned size)
+{
+    const AwA10DramControllerState *s = AW_A10_DRAMC(opaque);
+    const uint32_t idx = REG_INDEX(offset);
+
+    switch (offset) {
+    case REG_SDR_CCR:
+    case REG_SDR_ZQCR0:
+    case REG_SDR_ZQSR:
+        break;
+    case 0x2e4 ... AW_A10_DRAMC_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_dramc_write(void *opaque, hwaddr offset,
+                                   uint64_t val, unsigned size)
+{
+    AwA10DramControllerState *s = AW_A10_DRAMC(opaque);
+    const uint32_t idx = REG_INDEX(offset);
+
+    switch (offset) {
+    case REG_SDR_CCR:
+        if (val & REG_SDR_CCR_DRAM_INIT) {
+            /* Clear DRAM_INIT to indicate process is done. */
+            val &= ~REG_SDR_CCR_DRAM_INIT;
+        }
+        if (val & REG_SDR_CCR_DATA_TRAINING) {
+            /* Clear DATA_TRAINING to indicate process is done. */
+            val &= ~REG_SDR_CCR_DATA_TRAINING;
+        }
+        break;
+    case REG_SDR_ZQCR0:
+        /* Set ZCAL in ZQSR to indicate calibration is done. */
+        s->regs[REG_INDEX(REG_SDR_ZQSR)] |= REG_SDR_ZQSR_ZCAL;
+        break;
+    case 0x2e4 ... AW_A10_DRAMC_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;
+    }
+
+    s->regs[idx] = (uint32_t) val;
+}
+
+static const MemoryRegionOps allwinner_a10_dramc_ops = {
+    .read = allwinner_a10_dramc_read,
+    .write = allwinner_a10_dramc_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+    .impl.min_access_size = 4,
+};
+
+static void allwinner_a10_dramc_reset_enter(Object *obj, ResetType type)
+{
+    AwA10DramControllerState *s = AW_A10_DRAMC(obj);
+
+    /* Set default values for registers */
+    s->regs[REG_INDEX(REG_SDR_CCR)] = REG_SDR_CCR_RESET;
+    s->regs[REG_INDEX(REG_SDR_ZQCR0)] = REG_SDR_ZQCR0_RESET;
+    s->regs[REG_INDEX(REG_SDR_ZQSR)] = REG_SDR_ZQSR_RESET;
+}
+
+static void allwinner_a10_dramc_init(Object *obj)
+{
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    AwA10DramControllerState *s = AW_A10_DRAMC(obj);
+
+    /* Memory mapping */
+    memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_a10_dramc_ops, s,
+                          TYPE_AW_A10_DRAMC, AW_A10_DRAMC_IOSIZE);
+    sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription allwinner_a10_dramc_vmstate = {
+    .name = "allwinner-a10-dramc",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, AwA10DramControllerState,
+                             AW_A10_DRAMC_REGS_NUM),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void allwinner_a10_dramc_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    rc->phases.enter = allwinner_a10_dramc_reset_enter;
+    dc->vmsd = &allwinner_a10_dramc_vmstate;
+}
+
+static const TypeInfo allwinner_a10_dramc_info = {
+    .name          = TYPE_AW_A10_DRAMC,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_init = allwinner_a10_dramc_init,
+    .instance_size = sizeof(AwA10DramControllerState),
+    .class_init    = allwinner_a10_dramc_class_init,
+};
+
+static void allwinner_a10_dramc_register(void)
+{
+    type_register_static(&allwinner_a10_dramc_info);
+}
+
+type_init(allwinner_a10_dramc_register)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index c828dbeb26..9eaa0750b5 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -39,6 +39,7 @@ subdir('macio')
 softmmu_ss.add(when: 'CONFIG_IVSHMEM_DEVICE', if_true: files('ivshmem.c'))
 
 softmmu_ss.add(when: 'CONFIG_ALLWINNER_A10_CCM', if_true: files('allwinner-a10-ccm.c'))
+softmmu_ss.add(when: 'CONFIG_ALLWINNER_A10_DRAMC', if_true: files('allwinner-a10-dramc.c'))
 softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-ccu.c'))
 specific_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-cpucfg.c'))
 softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-dramc.c'))
diff --git a/include/hw/arm/allwinner-a10.h b/include/hw/arm/allwinner-a10.h
index 45d0fc2f7e..abe4ff7066 100644
--- a/include/hw/arm/allwinner-a10.h
+++ b/include/hw/arm/allwinner-a10.h
@@ -13,6 +13,7 @@
 #include "hw/usb/hcd-ehci.h"
 #include "hw/rtc/allwinner-rtc.h"
 #include "hw/misc/allwinner-a10-ccm.h"
+#include "hw/misc/allwinner-a10-dramc.h"
 
 #include "target/arm/cpu.h"
 #include "qom/object.h"
@@ -32,6 +33,7 @@ struct AwA10State {
 
     ARMCPU cpu;
     AwA10ClockCtlState ccm;
+    AwA10DramControllerState dramc;
     AwA10PITState timer;
     AwA10PICState intc;
     AwEmacState emac;
diff --git a/include/hw/misc/allwinner-a10-dramc.h b/include/hw/misc/allwinner-a10-dramc.h
new file mode 100644
index 0000000000..b61fbecbe7
--- /dev/null
+++ b/include/hw/misc/allwinner-a10-dramc.h
@@ -0,0 +1,68 @@
+/*
+ * Allwinner A10 DRAM Controller emulation
+ *
+ * Copyright (C) 2022 Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
+ *
+ *  This file is derived from Allwinner H3 DRAMC,
+ *  by Niek Linnenbank.
+ *
+ * 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_MISC_ALLWINNER_A10_DRAMC_H
+#define HW_MISC_ALLWINNER_A10_DRAMC_H
+
+#include "qom/object.h"
+#include "hw/sysbus.h"
+#include "hw/register.h"
+
+/**
+ * @name Constants
+ * @{
+ */
+
+/** Size of register I/O address space used by DRAMC device */
+#define AW_A10_DRAMC_IOSIZE        (0x1000)
+
+/** Total number of known registers */
+#define AW_A10_DRAMC_REGS_NUM      (AW_A10_DRAMC_IOSIZE / sizeof(uint32_t))
+
+/** @} */
+
+/**
+ * @name Object model
+ * @{
+ */
+
+#define TYPE_AW_A10_DRAMC    "allwinner-a10-dramc"
+OBJECT_DECLARE_SIMPLE_TYPE(AwA10DramControllerState, AW_A10_DRAMC)
+
+/** @} */
+
+/**
+ * Allwinner A10 DRAMC object instance state.
+ */
+struct AwA10DramControllerState {
+    /*< private >*/
+    SysBusDevice parent_obj;
+    /*< public >*/
+
+    /** Maps I/O registers in physical memory */
+    MemoryRegion iomem;
+
+    /** Array of hardware registers */
+    uint32_t regs[AW_A10_DRAMC_REGS_NUM];
+};
+
+#endif /* HW_MISC_ALLWINNER_A10_DRAMC_H */
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v3 3/7] {hw/i2c,docs/system/arm}: Allwinner TWI/I2C Emulation
  2022-12-26 22:02 [PATCH v3 0/7] Enable Cubieboard A10 boot SPL from SD card Strahinja Jankovic
  2022-12-26 22:02 ` [PATCH v3 1/7] hw/misc: Allwinner-A10 Clock Controller Module Emulation Strahinja Jankovic
  2022-12-26 22:02 ` [PATCH v3 2/7] hw/misc: Allwinner A10 DRAM Controller Emulation Strahinja Jankovic
@ 2022-12-26 22:02 ` Strahinja Jankovic
  2022-12-28 20:35   ` [PATCH v3 3/7] {hw/i2c, docs/system/arm}: " Niek Linnenbank
  2022-12-26 22:03 ` [PATCH v3 4/7] hw/misc: AXP209 PMU Emulation Strahinja Jankovic
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 12+ messages in thread
From: Strahinja Jankovic @ 2022-12-26 22:02 UTC (permalink / raw)
  To: Peter Maydell
  Cc: Beniamino Galvani, Niek Linnenbank, philmd, qemu-arm, qemu-devel,
	Strahinja Jankovic

This patch implements Allwinner TWI/I2C controller emulation. Only
master-mode functionality is implemented.

The SPL boot for Cubieboard expects AXP209 PMIC on TWI0/I2C0 bus, so this is
first part enabling the TWI/I2C bus operation.

Since both Allwinner A10 and H3 use the same module, it is added for
both boards.

Docs are also updated for Cubieboard and Orangepi-PC board to indicate
I2C availability.

Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
---
 docs/system/arm/cubieboard.rst |   1 +
 docs/system/arm/orangepi.rst   |   1 +
 hw/arm/Kconfig                 |   2 +
 hw/arm/allwinner-a10.c         |   8 +
 hw/arm/allwinner-h3.c          |  11 +-
 hw/i2c/Kconfig                 |   4 +
 hw/i2c/allwinner-i2c.c         | 459 +++++++++++++++++++++++++++++++++
 hw/i2c/meson.build             |   1 +
 hw/i2c/trace-events            |   5 +
 include/hw/arm/allwinner-a10.h |   2 +
 include/hw/arm/allwinner-h3.h  |   3 +
 include/hw/i2c/allwinner-i2c.h |  55 ++++
 12 files changed, 551 insertions(+), 1 deletion(-)
 create mode 100644 hw/i2c/allwinner-i2c.c
 create mode 100644 include/hw/i2c/allwinner-i2c.h

diff --git a/docs/system/arm/cubieboard.rst b/docs/system/arm/cubieboard.rst
index 344ff8cef9..8d485f5435 100644
--- a/docs/system/arm/cubieboard.rst
+++ b/docs/system/arm/cubieboard.rst
@@ -14,3 +14,4 @@ Emulated devices:
 - SDHCI
 - USB controller
 - SATA controller
+- TWI (I2C) controller
diff --git a/docs/system/arm/orangepi.rst b/docs/system/arm/orangepi.rst
index 83c7445197..e5973600a1 100644
--- a/docs/system/arm/orangepi.rst
+++ b/docs/system/arm/orangepi.rst
@@ -25,6 +25,7 @@ The Orange Pi PC machine supports the following devices:
  * Clock Control Unit
  * System Control module
  * Security Identifier device
+ * TWI (I2C)
 
 Limitations
 """""""""""
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 140f142ae5..eefe1fd134 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -322,6 +322,7 @@ config ALLWINNER_A10
     select ALLWINNER_A10_CCM
     select ALLWINNER_A10_DRAMC
     select ALLWINNER_EMAC
+    select ALLWINNER_I2C
     select SERIAL
     select UNIMP
 
@@ -329,6 +330,7 @@ config ALLWINNER_H3
     bool
     select ALLWINNER_A10_PIT
     select ALLWINNER_SUN8I_EMAC
+    select ALLWINNER_I2C
     select SERIAL
     select ARM_TIMER
     select ARM_GIC
diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c
index a5f7a36ac9..17e439777e 100644
--- a/hw/arm/allwinner-a10.c
+++ b/hw/arm/allwinner-a10.c
@@ -36,6 +36,7 @@
 #define AW_A10_OHCI_BASE        0x01c14400
 #define AW_A10_SATA_BASE        0x01c18000
 #define AW_A10_RTC_BASE         0x01c20d00
+#define AW_A10_I2C0_BASE        0x01c2ac00
 
 static void aw_a10_init(Object *obj)
 {
@@ -56,6 +57,8 @@ static void aw_a10_init(Object *obj)
 
     object_initialize_child(obj, "sata", &s->sata, TYPE_ALLWINNER_AHCI);
 
+    object_initialize_child(obj, "i2c0", &s->i2c0, TYPE_AW_I2C);
+
     if (machine_usb(current_machine)) {
         int i;
 
@@ -176,6 +179,11 @@ static void aw_a10_realize(DeviceState *dev, Error **errp)
     /* RTC */
     sysbus_realize(SYS_BUS_DEVICE(&s->rtc), &error_fatal);
     sysbus_mmio_map_overlap(SYS_BUS_DEVICE(&s->rtc), 0, AW_A10_RTC_BASE, 10);
+
+    /* I2C */
+    sysbus_realize(SYS_BUS_DEVICE(&s->i2c0), &error_fatal);
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->i2c0), 0, AW_A10_I2C0_BASE);
+    sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c0), 0, qdev_get_gpio_in(dev, 7));
 }
 
 static void aw_a10_class_init(ObjectClass *oc, void *data)
diff --git a/hw/arm/allwinner-h3.c b/hw/arm/allwinner-h3.c
index 308ed15552..bfce3c8d92 100644
--- a/hw/arm/allwinner-h3.c
+++ b/hw/arm/allwinner-h3.c
@@ -53,6 +53,7 @@ const hwaddr allwinner_h3_memmap[] = {
     [AW_H3_DEV_UART1]      = 0x01c28400,
     [AW_H3_DEV_UART2]      = 0x01c28800,
     [AW_H3_DEV_UART3]      = 0x01c28c00,
+    [AW_H3_DEV_TWI0]       = 0x01c2ac00,
     [AW_H3_DEV_EMAC]       = 0x01c30000,
     [AW_H3_DEV_DRAMCOM]    = 0x01c62000,
     [AW_H3_DEV_DRAMCTL]    = 0x01c63000,
@@ -106,7 +107,6 @@ struct AwH3Unimplemented {
     { "uart1",     0x01c28400, 1 * KiB },
     { "uart2",     0x01c28800, 1 * KiB },
     { "uart3",     0x01c28c00, 1 * KiB },
-    { "twi0",      0x01c2ac00, 1 * KiB },
     { "twi1",      0x01c2b000, 1 * KiB },
     { "twi2",      0x01c2b400, 1 * KiB },
     { "scr",       0x01c2c400, 1 * KiB },
@@ -150,6 +150,7 @@ enum {
     AW_H3_GIC_SPI_UART1     =  1,
     AW_H3_GIC_SPI_UART2     =  2,
     AW_H3_GIC_SPI_UART3     =  3,
+    AW_H3_GIC_SPI_TWI0      =  6,
     AW_H3_GIC_SPI_TIMER0    = 18,
     AW_H3_GIC_SPI_TIMER1    = 19,
     AW_H3_GIC_SPI_MMC0      = 60,
@@ -225,6 +226,8 @@ static void allwinner_h3_init(Object *obj)
                               "ram-size");
 
     object_initialize_child(obj, "rtc", &s->rtc, TYPE_AW_RTC_SUN6I);
+
+    object_initialize_child(obj, "twi0", &s->i2c0, TYPE_AW_I2C);
 }
 
 static void allwinner_h3_realize(DeviceState *dev, Error **errp)
@@ -423,6 +426,12 @@ static void allwinner_h3_realize(DeviceState *dev, Error **errp)
     sysbus_realize(SYS_BUS_DEVICE(&s->rtc), &error_fatal);
     sysbus_mmio_map(SYS_BUS_DEVICE(&s->rtc), 0, s->memmap[AW_H3_DEV_RTC]);
 
+    /* I2C */
+    sysbus_realize(SYS_BUS_DEVICE(&s->i2c0), &error_fatal);
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->i2c0), 0, s->memmap[AW_H3_DEV_TWI0]);
+    sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c0), 0,
+                       qdev_get_gpio_in(DEVICE(&s->gic), AW_H3_GIC_SPI_TWI0));
+
     /* Unimplemented devices */
     for (i = 0; i < ARRAY_SIZE(unimplemented); i++) {
         create_unimplemented_device(unimplemented[i].device_name,
diff --git a/hw/i2c/Kconfig b/hw/i2c/Kconfig
index 9bb8870517..f8ec461be3 100644
--- a/hw/i2c/Kconfig
+++ b/hw/i2c/Kconfig
@@ -34,6 +34,10 @@ config MPC_I2C
     bool
     select I2C
 
+config ALLWINNER_I2C
+    bool
+    select I2C
+
 config PCA954X
     bool
     select I2C
diff --git a/hw/i2c/allwinner-i2c.c b/hw/i2c/allwinner-i2c.c
new file mode 100644
index 0000000000..a435965836
--- /dev/null
+++ b/hw/i2c/allwinner-i2c.c
@@ -0,0 +1,459 @@
+/*
+ *  Allwinner I2C Bus Serial Interface Emulation
+ *
+ *  Copyright (C) 2022 Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
+ *
+ *  This file is derived from IMX I2C controller,
+ *  by Jean-Christophe DUBOIS .
+ *
+ *  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/>.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/allwinner-i2c.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "hw/i2c/i2c.h"
+#include "qemu/log.h"
+#include "trace.h"
+#include "qemu/module.h"
+
+/* Allwinner I2C memory map */
+#define TWI_ADDR_REG            0x00  /* slave address register */
+#define TWI_XADDR_REG           0x04  /* extended slave address register */
+#define TWI_DATA_REG            0x08  /* data register */
+#define TWI_CNTR_REG            0x0c  /* control register */
+#define TWI_STAT_REG            0x10  /* status register */
+#define TWI_CCR_REG             0x14  /* clock control register */
+#define TWI_SRST_REG            0x18  /* software reset register */
+#define TWI_EFR_REG             0x1c  /* enhance feature register */
+#define TWI_LCR_REG             0x20  /* line control register */
+
+/* Used only in slave mode, do not set */
+#define TWI_ADDR_RESET          0
+#define TWI_XADDR_RESET         0
+
+/* Data register */
+#define TWI_DATA_MASK           0xFF
+#define TWI_DATA_RESET          0
+
+/* Control register */
+#define TWI_CNTR_INT_EN         (1 << 7)
+#define TWI_CNTR_BUS_EN         (1 << 6)
+#define TWI_CNTR_M_STA          (1 << 5)
+#define TWI_CNTR_M_STP          (1 << 4)
+#define TWI_CNTR_INT_FLAG       (1 << 3)
+#define TWI_CNTR_A_ACK          (1 << 2)
+#define TWI_CNTR_MASK           0xFC
+#define TWI_CNTR_RESET          0
+
+/* Status register */
+#define TWI_STAT_MASK           0xF8
+#define TWI_STAT_RESET          0xF8
+
+/* Clock register */
+#define TWI_CCR_CLK_M_MASK      0x78
+#define TWI_CCR_CLK_N_MASK      0x07
+#define TWI_CCR_MASK            0x7F
+#define TWI_CCR_RESET           0
+
+/* Soft reset */
+#define TWI_SRST_MASK           0x01
+#define TWI_SRST_RESET          0
+
+/* Enhance feature */
+#define TWI_EFR_MASK            0x03
+#define TWI_EFR_RESET           0
+
+/* Line control */
+#define TWI_LCR_SCL_STATE       (1 << 5)
+#define TWI_LCR_SDA_STATE       (1 << 4)
+#define TWI_LCR_SCL_CTL         (1 << 3)
+#define TWI_LCR_SCL_CTL_EN      (1 << 2)
+#define TWI_LCR_SDA_CTL         (1 << 1)
+#define TWI_LCR_SDA_CTL_EN      (1 << 0)
+#define TWI_LCR_MASK            0x3F
+#define TWI_LCR_RESET           0x3A
+
+/* Status value in STAT register is shifted by 3 bits */
+#define TWI_STAT_SHIFT      3
+#define STAT_FROM_STA(x)    ((x) << TWI_STAT_SHIFT)
+#define STAT_TO_STA(x)      ((x) >> TWI_STAT_SHIFT)
+
+enum {
+    STAT_BUS_ERROR = 0,
+    /* Master mode */
+    STAT_M_STA_TX,
+    STAT_M_RSTA_TX,
+    STAT_M_ADDR_WR_ACK,
+    STAT_M_ADDR_WR_NACK,
+    STAT_M_DATA_TX_ACK,
+    STAT_M_DATA_TX_NACK,
+    STAT_M_ARB_LOST,
+    STAT_M_ADDR_RD_ACK,
+    STAT_M_ADDR_RD_NACK,
+    STAT_M_DATA_RX_ACK,
+    STAT_M_DATA_RX_NACK,
+    /* Slave mode */
+    STAT_S_ADDR_WR_ACK,
+    STAT_S_ARB_LOST_AW_ACK,
+    STAT_S_GCA_ACK,
+    STAT_S_ARB_LOST_GCA_ACK,
+    STAT_S_DATA_RX_SA_ACK,
+    STAT_S_DATA_RX_SA_NACK,
+    STAT_S_DATA_RX_GCA_ACK,
+    STAT_S_DATA_RX_GCA_NACK,
+    STAT_S_STP_RSTA,
+    STAT_S_ADDR_RD_ACK,
+    STAT_S_ARB_LOST_AR_ACK,
+    STAT_S_DATA_TX_ACK,
+    STAT_S_DATA_TX_NACK,
+    STAT_S_LB_TX_ACK,
+    /* Master mode, 10-bit */
+    STAT_M_2ND_ADDR_WR_ACK,
+    STAT_M_2ND_ADDR_WR_NACK,
+    /* Idle */
+    STAT_IDLE = 0x1f
+} TWI_STAT_STA;
+
+static const char *allwinner_i2c_get_regname(unsigned offset)
+{
+    switch (offset) {
+    case TWI_ADDR_REG:
+        return "ADDR";
+    case TWI_XADDR_REG:
+        return "XADDR";
+    case TWI_DATA_REG:
+        return "DATA";
+    case TWI_CNTR_REG:
+        return "CNTR";
+    case TWI_STAT_REG:
+        return "STAT";
+    case TWI_CCR_REG:
+        return "CCR";
+    case TWI_SRST_REG:
+        return "SRST";
+    case TWI_EFR_REG:
+        return "EFR";
+    case TWI_LCR_REG:
+        return "LCR";
+    default:
+        return "[?]";
+    }
+}
+
+static inline bool allwinner_i2c_is_reset(AWI2CState *s)
+{
+    return s->srst & TWI_SRST_MASK;
+}
+
+static inline bool allwinner_i2c_bus_is_enabled(AWI2CState *s)
+{
+    return s->cntr & TWI_CNTR_BUS_EN;
+}
+
+static inline bool allwinner_i2c_interrupt_is_enabled(AWI2CState *s)
+{
+    return s->cntr & TWI_CNTR_INT_EN;
+}
+
+static void allwinner_i2c_reset_hold(Object *obj)
+{
+    AWI2CState *s = AW_I2C(obj);
+
+    if (STAT_TO_STA(s->stat) != STAT_IDLE) {
+        i2c_end_transfer(s->bus);
+    }
+
+    s->addr  = TWI_ADDR_RESET;
+    s->xaddr = TWI_XADDR_RESET;
+    s->data  = TWI_DATA_RESET;
+    s->cntr  = TWI_CNTR_RESET;
+    s->stat  = TWI_STAT_RESET;
+    s->ccr   = TWI_CCR_RESET;
+    s->srst  = TWI_SRST_RESET;
+    s->efr   = TWI_EFR_RESET;
+    s->lcr   = TWI_LCR_RESET;
+}
+
+static inline void allwinner_i2c_raise_interrupt(AWI2CState *s)
+{
+    /*
+     * Raise an interrupt if the device is not reset and it is configured
+     * to generate some interrupts.
+     */
+    if (!allwinner_i2c_is_reset(s) && allwinner_i2c_bus_is_enabled(s)) {
+        if (STAT_TO_STA(s->stat) != STAT_IDLE) {
+            s->cntr |= TWI_CNTR_INT_FLAG;
+            if (allwinner_i2c_interrupt_is_enabled(s)) {
+                qemu_irq_raise(s->irq);
+            }
+        }
+    }
+}
+
+static uint64_t allwinner_i2c_read(void *opaque, hwaddr offset,
+                                   unsigned size)
+{
+    uint16_t value;
+    AWI2CState *s = AW_I2C(opaque);
+
+    switch (offset) {
+    case TWI_ADDR_REG:
+        value = s->addr;
+        break;
+    case TWI_XADDR_REG:
+        value = s->xaddr;
+        break;
+    case TWI_DATA_REG:
+        if ((STAT_TO_STA(s->stat) == STAT_M_ADDR_RD_ACK) ||
+            (STAT_TO_STA(s->stat) == STAT_M_DATA_RX_ACK) ||
+            (STAT_TO_STA(s->stat) == STAT_M_DATA_RX_NACK)) {
+            /* Get the next byte */
+            s->data = i2c_recv(s->bus);
+
+            if (s->cntr & TWI_CNTR_A_ACK) {
+                s->stat = STAT_FROM_STA(STAT_M_DATA_RX_ACK);
+            } else {
+                s->stat = STAT_FROM_STA(STAT_M_DATA_RX_NACK);
+            }
+            allwinner_i2c_raise_interrupt(s);
+        }
+        value = s->data;
+        break;
+    case TWI_CNTR_REG:
+        value = s->cntr;
+        break;
+    case TWI_STAT_REG:
+        value = s->stat;
+        /*
+         * If polling when reading then change state to indicate data
+         * is available
+         */
+        if (STAT_TO_STA(s->stat) == STAT_M_ADDR_RD_ACK) {
+            if (s->cntr & TWI_CNTR_A_ACK) {
+                s->stat = STAT_FROM_STA(STAT_M_DATA_RX_ACK);
+            } else {
+                s->stat = STAT_FROM_STA(STAT_M_DATA_RX_NACK);
+            }
+            allwinner_i2c_raise_interrupt(s);
+        }
+        break;
+    case TWI_CCR_REG:
+        value = s->ccr;
+        break;
+    case TWI_SRST_REG:
+        value = s->srst;
+        break;
+    case TWI_EFR_REG:
+        value = s->efr;
+        break;
+    case TWI_LCR_REG:
+        value = s->lcr;
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad address at offset 0x%"
+                      HWADDR_PRIx "\n", TYPE_AW_I2C, __func__, offset);
+        value = 0;
+        break;
+    }
+
+    trace_allwinner_i2c_read(allwinner_i2c_get_regname(offset), offset, value);
+
+    return (uint64_t)value;
+}
+
+static void allwinner_i2c_write(void *opaque, hwaddr offset,
+                                uint64_t value, unsigned size)
+{
+    AWI2CState *s = AW_I2C(opaque);
+
+    value &= 0xff;
+
+    trace_allwinner_i2c_write(allwinner_i2c_get_regname(offset), offset, value);
+
+    switch (offset) {
+    case TWI_ADDR_REG:
+        s->addr = (uint8_t)value;
+        break;
+    case TWI_XADDR_REG:
+        s->xaddr = (uint8_t)value;
+        break;
+    case TWI_DATA_REG:
+        /* If the device is in reset or not enabled, nothing to do */
+        if (allwinner_i2c_is_reset(s) || (!allwinner_i2c_bus_is_enabled(s))) {
+            break;
+        }
+
+        s->data = value & TWI_DATA_MASK;
+
+        switch (STAT_TO_STA(s->stat)) {
+        case STAT_M_STA_TX:
+        case STAT_M_RSTA_TX:
+            /* Send address */
+            if (i2c_start_transfer(s->bus, extract32(s->data, 1, 7),
+                                extract32(s->data, 0, 1))) {
+                /* If non zero is returned, the address is not valid */
+                s->stat = STAT_FROM_STA(STAT_M_ADDR_WR_NACK);
+            } else {
+                /* Determine if read of write */
+                if (extract32(s->data, 0, 1)) {
+                    s->stat = STAT_FROM_STA(STAT_M_ADDR_RD_ACK);
+                } else {
+                    s->stat = STAT_FROM_STA(STAT_M_ADDR_WR_ACK);
+                }
+                allwinner_i2c_raise_interrupt(s);
+            }
+            break;
+        case STAT_M_ADDR_WR_ACK:
+        case STAT_M_DATA_TX_ACK:
+            if (i2c_send(s->bus, s->data)) {
+                /* If the target return non zero then end the transfer */
+                s->stat = STAT_FROM_STA(STAT_M_DATA_TX_NACK);
+                i2c_end_transfer(s->bus);
+            } else {
+                s->stat = STAT_FROM_STA(STAT_M_DATA_TX_ACK);
+                allwinner_i2c_raise_interrupt(s);
+            }
+            break;
+        default:
+            break;
+        }
+        break;
+    case TWI_CNTR_REG:
+        if (!allwinner_i2c_is_reset(s)) {
+            /* Do something only if not in software reset */
+            s->cntr = value & TWI_CNTR_MASK;
+
+            /* Check if start condition should be sent */
+            if (s->cntr & TWI_CNTR_M_STA) {
+                /* Update status */
+                if (STAT_TO_STA(s->stat) == STAT_IDLE) {
+                    /* Send start condition */
+                    s->stat = STAT_FROM_STA(STAT_M_STA_TX);
+                } else {
+                    /* Send repeated start condition */
+                    s->stat = STAT_FROM_STA(STAT_M_RSTA_TX);
+                }
+                /* Clear start condition */
+                s->cntr &= ~TWI_CNTR_M_STA;
+            }
+            if (s->cntr & TWI_CNTR_M_STP) {
+                /* Update status */
+                i2c_end_transfer(s->bus);
+                s->stat = STAT_FROM_STA(STAT_IDLE);
+                s->cntr &= ~TWI_CNTR_M_STP;
+            }
+            if ((s->cntr & TWI_CNTR_INT_FLAG) == 0) {
+                /* Interrupt flag cleared */
+                qemu_irq_lower(s->irq);
+            }
+            if ((s->cntr & TWI_CNTR_A_ACK) == 0) {
+                if (STAT_TO_STA(s->stat) == STAT_M_DATA_RX_ACK) {
+                    s->stat = STAT_FROM_STA(STAT_M_DATA_RX_NACK);
+                }
+            } else {
+                if (STAT_TO_STA(s->stat) == STAT_M_DATA_RX_NACK) {
+                    s->stat = STAT_FROM_STA(STAT_M_DATA_RX_ACK);
+                }
+            }
+            allwinner_i2c_raise_interrupt(s);
+
+        }
+        break;
+    case TWI_CCR_REG:
+        s->ccr = value & TWI_CCR_MASK;
+        break;
+    case TWI_SRST_REG:
+        if (((value & TWI_SRST_MASK) == 0) && (s->srst & TWI_SRST_MASK)) {
+            /* Perform reset */
+            allwinner_i2c_reset_hold(OBJECT(s));
+        }
+        s->srst = value & TWI_SRST_MASK;
+        break;
+    case TWI_EFR_REG:
+        s->efr = value & TWI_EFR_MASK;
+        break;
+    case TWI_LCR_REG:
+        s->lcr = value & TWI_LCR_MASK;
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad address at offset 0x%"
+                      HWADDR_PRIx "\n", TYPE_AW_I2C, __func__, offset);
+        break;
+    }
+}
+
+static const MemoryRegionOps allwinner_i2c_ops = {
+    .read = allwinner_i2c_read,
+    .write = allwinner_i2c_write,
+    .valid.min_access_size = 1,
+    .valid.max_access_size = 4,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription allwinner_i2c_vmstate = {
+    .name = TYPE_AW_I2C,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT8(addr, AWI2CState),
+        VMSTATE_UINT8(xaddr, AWI2CState),
+        VMSTATE_UINT8(data, AWI2CState),
+        VMSTATE_UINT8(cntr, AWI2CState),
+        VMSTATE_UINT8(ccr, AWI2CState),
+        VMSTATE_UINT8(srst, AWI2CState),
+        VMSTATE_UINT8(efr, AWI2CState),
+        VMSTATE_UINT8(lcr, AWI2CState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void allwinner_i2c_realize(DeviceState *dev, Error **errp)
+{
+    AWI2CState *s = AW_I2C(dev);
+
+    memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_i2c_ops, s,
+                          TYPE_AW_I2C, AW_I2C_MEM_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
+    s->bus = i2c_init_bus(dev, "i2c");
+}
+
+static void allwinner_i2c_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    rc->phases.hold = allwinner_i2c_reset_hold;
+    dc->vmsd = &allwinner_i2c_vmstate;
+    dc->realize = allwinner_i2c_realize;
+    dc->desc = "Allwinner I2C Controller";
+}
+
+static const TypeInfo allwinner_i2c_type_info = {
+    .name = TYPE_AW_I2C,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(AWI2CState),
+    .class_init = allwinner_i2c_class_init,
+};
+
+static void allwinner_i2c_register_types(void)
+{
+    type_register_static(&allwinner_i2c_type_info);
+}
+
+type_init(allwinner_i2c_register_types)
diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build
index d3df273251..7de7f2f540 100644
--- a/hw/i2c/meson.build
+++ b/hw/i2c/meson.build
@@ -8,6 +8,7 @@ i2c_ss.add(when: 'CONFIG_BITBANG_I2C', if_true: files('bitbang_i2c.c'))
 i2c_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_i2c.c'))
 i2c_ss.add(when: 'CONFIG_IMX_I2C', if_true: files('imx_i2c.c'))
 i2c_ss.add(when: 'CONFIG_MPC_I2C', if_true: files('mpc_i2c.c'))
+i2c_ss.add(when: 'CONFIG_ALLWINNER_I2C', if_true: files('allwinner-i2c.c'))
 i2c_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('microbit_i2c.c'))
 i2c_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_smbus.c'))
 i2c_ss.add(when: 'CONFIG_SMBUS_EEPROM', if_true: files('smbus_eeprom.c'))
diff --git a/hw/i2c/trace-events b/hw/i2c/trace-events
index af181d43ee..52dbd53a23 100644
--- a/hw/i2c/trace-events
+++ b/hw/i2c/trace-events
@@ -8,6 +8,11 @@ i2c_send_async(uint8_t address, uint8_t data) "send_async(addr:0x%02x) data:0x%0
 i2c_recv(uint8_t address, uint8_t data) "recv(addr:0x%02x) data:0x%02x"
 i2c_ack(void) ""
 
+# allwinner_i2c.c
+
+allwinner_i2c_read(const char* reg_name, uint64_t offset, uint64_t value) "read %s [0x%" PRIx64 "]: -> 0x%" PRIx64
+allwinner_i2c_write(const char* reg_name, uint64_t offset, uint64_t value) "write %s [0x%" PRIx64 "]: <- 0x%" PRIx64
+
 # aspeed_i2c.c
 
 aspeed_i2c_bus_cmd(uint32_t cmd, const char *cmd_flags, uint32_t count, uint32_t intr_status) "handling cmd=0x%x %s count=%d intr=0x%x"
diff --git a/include/hw/arm/allwinner-a10.h b/include/hw/arm/allwinner-a10.h
index abe4ff7066..763935fca9 100644
--- a/include/hw/arm/allwinner-a10.h
+++ b/include/hw/arm/allwinner-a10.h
@@ -14,6 +14,7 @@
 #include "hw/rtc/allwinner-rtc.h"
 #include "hw/misc/allwinner-a10-ccm.h"
 #include "hw/misc/allwinner-a10-dramc.h"
+#include "hw/i2c/allwinner-i2c.h"
 
 #include "target/arm/cpu.h"
 #include "qom/object.h"
@@ -39,6 +40,7 @@ struct AwA10State {
     AwEmacState emac;
     AllwinnerAHCIState sata;
     AwSdHostState mmc0;
+    AWI2CState i2c0;
     AwRtcState rtc;
     MemoryRegion sram_a;
     EHCISysBusState ehci[AW_A10_NUM_USB];
diff --git a/include/hw/arm/allwinner-h3.h b/include/hw/arm/allwinner-h3.h
index 63025fb27c..1d7ce20589 100644
--- a/include/hw/arm/allwinner-h3.h
+++ b/include/hw/arm/allwinner-h3.h
@@ -47,6 +47,7 @@
 #include "hw/sd/allwinner-sdhost.h"
 #include "hw/net/allwinner-sun8i-emac.h"
 #include "hw/rtc/allwinner-rtc.h"
+#include "hw/i2c/allwinner-i2c.h"
 #include "target/arm/cpu.h"
 #include "sysemu/block-backend.h"
 
@@ -82,6 +83,7 @@ enum {
     AW_H3_DEV_UART2,
     AW_H3_DEV_UART3,
     AW_H3_DEV_EMAC,
+    AW_H3_DEV_TWI0,
     AW_H3_DEV_DRAMCOM,
     AW_H3_DEV_DRAMCTL,
     AW_H3_DEV_DRAMPHY,
@@ -130,6 +132,7 @@ struct AwH3State {
     AwH3SysCtrlState sysctrl;
     AwSidState sid;
     AwSdHostState mmc0;
+    AWI2CState i2c0;
     AwSun8iEmacState emac;
     AwRtcState rtc;
     GICState gic;
diff --git a/include/hw/i2c/allwinner-i2c.h b/include/hw/i2c/allwinner-i2c.h
new file mode 100644
index 0000000000..4f378b86ba
--- /dev/null
+++ b/include/hw/i2c/allwinner-i2c.h
@@ -0,0 +1,55 @@
+/*
+ *  Allwinner I2C Bus Serial Interface registers definition
+ *
+ *  Copyright (C) 2022 Strahinja Jankovic. <strahinja.p.jankovic@gmail.com>
+ *
+ *  This file is derived from IMX I2C controller,
+ *  by Jean-Christophe DUBOIS .
+ *
+ *  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 ALLWINNER_I2C_H
+#define ALLWINNER_I2C_H
+
+#include "hw/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_AW_I2C "allwinner.i2c"
+OBJECT_DECLARE_SIMPLE_TYPE(AWI2CState, AW_I2C)
+
+#define AW_I2C_MEM_SIZE         0x24
+
+struct AWI2CState {
+    /*< private >*/
+    SysBusDevice parent_obj;
+
+    /*< public >*/
+    MemoryRegion iomem;
+    I2CBus *bus;
+    qemu_irq irq;
+
+    uint8_t addr;
+    uint8_t xaddr;
+    uint8_t data;
+    uint8_t cntr;
+    uint8_t stat;
+    uint8_t ccr;
+    uint8_t srst;
+    uint8_t efr;
+    uint8_t lcr;
+};
+
+#endif /* ALLWINNER_I2C_H */
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v3 4/7] hw/misc: AXP209 PMU Emulation
  2022-12-26 22:02 [PATCH v3 0/7] Enable Cubieboard A10 boot SPL from SD card Strahinja Jankovic
                   ` (2 preceding siblings ...)
  2022-12-26 22:02 ` [PATCH v3 3/7] {hw/i2c,docs/system/arm}: Allwinner TWI/I2C Emulation Strahinja Jankovic
@ 2022-12-26 22:03 ` Strahinja Jankovic
  2023-01-02 20:06   ` Strahinja Jankovic
  2022-12-26 22:03 ` [PATCH v3 5/7] hw/arm: Add AXP209 to Cubieboard Strahinja Jankovic
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 12+ messages in thread
From: Strahinja Jankovic @ 2022-12-26 22:03 UTC (permalink / raw)
  To: Peter Maydell
  Cc: Beniamino Galvani, Niek Linnenbank, philmd, qemu-arm, qemu-devel,
	Strahinja Jankovic

This patch adds minimal support for AXP-209 PMU.
Most important is chip ID since U-Boot SPL expects version 0x1. Besides
the chip ID register, reset values for two more registers used by A10
U-Boot SPL are covered.

Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
---
 MAINTAINERS          |   2 +
 hw/misc/Kconfig      |   4 +
 hw/misc/axp209.c     | 238 +++++++++++++++++++++++++++++++++++++++++++
 hw/misc/meson.build  |   1 +
 hw/misc/trace-events |   5 +
 5 files changed, 250 insertions(+)
 create mode 100644 hw/misc/axp209.c

diff --git a/MAINTAINERS b/MAINTAINERS
index b270eb8e5b..354da68249 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -576,12 +576,14 @@ ARM Machines
 Allwinner-a10
 M: Beniamino Galvani <b.galvani@gmail.com>
 M: Peter Maydell <peter.maydell@linaro.org>
+R: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
 L: qemu-arm@nongnu.org
 S: Odd Fixes
 F: hw/*/allwinner*
 F: include/hw/*/allwinner*
 F: hw/arm/cubieboard.c
 F: docs/system/arm/cubieboard.rst
+F: hw/misc/axp209.c
 
 Allwinner-h3
 M: Niek Linnenbank <nieklinnenbank@gmail.com>
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 052fb54310..eaeddca277 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -180,4 +180,8 @@ config ALLWINNER_A10_CCM
 config ALLWINNER_A10_DRAMC
     bool
 
+config AXP209_PMU
+    bool
+    depends on I2C
+
 source macio/Kconfig
diff --git a/hw/misc/axp209.c b/hw/misc/axp209.c
new file mode 100644
index 0000000000..2908ed99a6
--- /dev/null
+++ b/hw/misc/axp209.c
@@ -0,0 +1,238 @@
+/*
+ * AXP-209 PMU Emulation
+ *
+ * Copyright (C) 2022 Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "trace.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+
+#define TYPE_AXP209_PMU "axp209_pmu"
+
+#define AXP209(obj) \
+    OBJECT_CHECK(AXP209I2CState, (obj), TYPE_AXP209_PMU)
+
+/* registers */
+enum {
+    REG_POWER_STATUS = 0x0u,
+    REG_OPERATING_MODE,
+    REG_OTG_VBUS_STATUS,
+    REG_CHIP_VERSION,
+    REG_DATA_CACHE_0,
+    REG_DATA_CACHE_1,
+    REG_DATA_CACHE_2,
+    REG_DATA_CACHE_3,
+    REG_DATA_CACHE_4,
+    REG_DATA_CACHE_5,
+    REG_DATA_CACHE_6,
+    REG_DATA_CACHE_7,
+    REG_DATA_CACHE_8,
+    REG_DATA_CACHE_9,
+    REG_DATA_CACHE_A,
+    REG_DATA_CACHE_B,
+    REG_POWER_OUTPUT_CTRL = 0x12u,
+    REG_DC_DC2_OUT_V_CTRL = 0x23u,
+    REG_DC_DC2_DVS_CTRL = 0x25u,
+    REG_DC_DC3_OUT_V_CTRL = 0x27u,
+    REG_LDO2_4_OUT_V_CTRL,
+    REG_LDO3_OUT_V_CTRL,
+    REG_VBUS_CH_MGMT = 0x30u,
+    REG_SHUTDOWN_V_CTRL,
+    REG_SHUTDOWN_CTRL,
+    REG_CHARGE_CTRL_1,
+    REG_CHARGE_CTRL_2,
+    REG_SPARE_CHARGE_CTRL,
+    REG_PEK_KEY_CTRL,
+    REG_DC_DC_FREQ_SET,
+    REG_CHR_TEMP_TH_SET,
+    REG_CHR_HIGH_TEMP_TH_CTRL,
+    REG_IPSOUT_WARN_L1,
+    REG_IPSOUT_WARN_L2,
+    REG_DISCHR_TEMP_TH_SET,
+    REG_DISCHR_HIGH_TEMP_TH_CTRL,
+    REG_IRQ_BANK_1_CTRL = 0x40u,
+    REG_IRQ_BANK_2_CTRL,
+    REG_IRQ_BANK_3_CTRL,
+    REG_IRQ_BANK_4_CTRL,
+    REG_IRQ_BANK_5_CTRL,
+    REG_IRQ_BANK_1_STAT = 0x48u,
+    REG_IRQ_BANK_2_STAT,
+    REG_IRQ_BANK_3_STAT,
+    REG_IRQ_BANK_4_STAT,
+    REG_IRQ_BANK_5_STAT,
+    REG_ADC_ACIN_V_H = 0x56u,
+    REG_ADC_ACIN_V_L,
+    REG_ADC_ACIN_CURR_H,
+    REG_ADC_ACIN_CURR_L,
+    REG_ADC_VBUS_V_H,
+    REG_ADC_VBUS_V_L,
+    REG_ADC_VBUS_CURR_H,
+    REG_ADC_VBUS_CURR_L,
+    REG_ADC_INT_TEMP_H,
+    REG_ADC_INT_TEMP_L,
+    REG_ADC_TEMP_SENS_V_H = 0x62u,
+    REG_ADC_TEMP_SENS_V_L,
+    REG_ADC_BAT_V_H = 0x78u,
+    REG_ADC_BAT_V_L,
+    REG_ADC_BAT_DISCHR_CURR_H,
+    REG_ADC_BAT_DISCHR_CURR_L,
+    REG_ADC_BAT_CHR_CURR_H,
+    REG_ADC_BAT_CHR_CURR_L,
+    REG_ADC_IPSOUT_V_H,
+    REG_ADC_IPSOUT_V_L,
+    REG_DC_DC_MOD_SEL = 0x80u,
+    REG_ADC_EN_1,
+    REG_ADC_EN_2,
+    REG_ADC_SR_CTRL,
+    REG_ADC_IN_RANGE,
+    REG_GPIO1_ADC_IRQ_RISING_TH,
+    REG_GPIO1_ADC_IRQ_FALLING_TH,
+    REG_TIMER_CTRL = 0x8au,
+    REG_VBUS_CTRL_MON_SRP,
+    REG_OVER_TEMP_SHUTDOWN = 0x8fu,
+    REG_GPIO0_FEAT_SET,
+    REG_GPIO_OUT_HIGH_SET,
+    REG_GPIO1_FEAT_SET,
+    REG_GPIO2_FEAT_SET,
+    REG_GPIO_SIG_STATE_SET_MON,
+    REG_GPIO3_SET,
+    REG_COULOMB_CNTR_CTRL = 0xb8u,
+    REG_POWER_MEAS_RES,
+    NR_REGS
+};
+
+#define AXP209_CHIP_VERSION_ID             (0x01)
+#define AXP209_DC_DC2_OUT_V_CTRL_RESET     (0x16)
+#define AXP209_IRQ_BANK_1_CTRL_RESET       (0xd8)
+
+/* A simple I2C slave which returns values of ID or CNT register. */
+typedef struct AXP209I2CState {
+    /*< private >*/
+    I2CSlave i2c;
+    /*< public >*/
+    uint8_t regs[NR_REGS];  /* peripheral registers */
+    uint8_t ptr;            /* current register index */
+    uint8_t count;          /* counter used for tx/rx */
+} AXP209I2CState;
+
+/* Reset all counters and load ID register */
+static void axp209_reset_enter(Object *obj, ResetType type)
+{
+    AXP209I2CState *s = AXP209(obj);
+
+    memset(s->regs, 0, NR_REGS);
+    s->ptr = 0;
+    s->count = 0;
+    s->regs[REG_CHIP_VERSION] = AXP209_CHIP_VERSION_ID;
+    s->regs[REG_DC_DC2_OUT_V_CTRL] = AXP209_DC_DC2_OUT_V_CTRL_RESET;
+    s->regs[REG_IRQ_BANK_1_CTRL] = AXP209_IRQ_BANK_1_CTRL_RESET;
+}
+
+/* Handle events from master. */
+static int axp209_event(I2CSlave *i2c, enum i2c_event event)
+{
+    AXP209I2CState *s = AXP209(i2c);
+
+    s->count = 0;
+
+    return 0;
+}
+
+/* Called when master requests read */
+static uint8_t axp209_rx(I2CSlave *i2c)
+{
+    AXP209I2CState *s = AXP209(i2c);
+    uint8_t ret = 0xff;
+
+    if (s->ptr < NR_REGS) {
+        ret = s->regs[s->ptr++];
+    }
+
+    trace_axp209_rx(s->ptr - 1, ret);
+
+    return ret;
+}
+
+/*
+ * Called when master sends write.
+ * Update ptr with byte 0, then perform write with second byte.
+ */
+static int axp209_tx(I2CSlave *i2c, uint8_t data)
+{
+    AXP209I2CState *s = AXP209(i2c);
+
+    if (s->count == 0) {
+        /* Store register address */
+        s->ptr = data;
+        s->count++;
+        trace_axp209_select(data);
+    } else {
+        trace_axp209_tx(s->ptr, data);
+        if (s->ptr == REG_DC_DC2_OUT_V_CTRL) {
+            s->regs[s->ptr++] = data;
+        }
+    }
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_axp209 = {
+    .name = TYPE_AXP209_PMU,
+    .version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT8_ARRAY(regs, AXP209I2CState, NR_REGS),
+        VMSTATE_UINT8(count, AXP209I2CState),
+        VMSTATE_UINT8(ptr, AXP209I2CState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void axp209_class_init(ObjectClass *oc, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(oc);
+    I2CSlaveClass *isc = I2C_SLAVE_CLASS(oc);
+    ResettableClass *rc = RESETTABLE_CLASS(oc);
+
+    rc->phases.enter = axp209_reset_enter;
+    dc->vmsd = &vmstate_axp209;
+    isc->event = axp209_event;
+    isc->recv = axp209_rx;
+    isc->send = axp209_tx;
+}
+
+static const TypeInfo axp209_info = {
+    .name = TYPE_AXP209_PMU,
+    .parent = TYPE_I2C_SLAVE,
+    .instance_size = sizeof(AXP209I2CState),
+    .class_init = axp209_class_init
+};
+
+static void axp209_register_devices(void)
+{
+    type_register_static(&axp209_info);
+}
+
+type_init(axp209_register_devices);
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 9eaa0750b5..448e14b531 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -45,6 +45,7 @@ specific_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-cpucfg.c'
 softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-dramc.c'))
 softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-sysctrl.c'))
 softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-sid.c'))
+softmmu_ss.add(when: 'CONFIG_AXP209_PMU', if_true: files('axp209.c'))
 softmmu_ss.add(when: 'CONFIG_REALVIEW', if_true: files('arm_sysctl.c'))
 softmmu_ss.add(when: 'CONFIG_NSERIES', if_true: files('cbus.c'))
 softmmu_ss.add(when: 'CONFIG_ECCMEMCTL', if_true: files('eccmemctl.c'))
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index c18bc0605e..c47876a902 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -23,6 +23,11 @@ allwinner_sid_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%"
 avr_power_read(uint8_t value) "power_reduc read value:%u"
 avr_power_write(uint8_t value) "power_reduc write value:%u"
 
+# axp209.c
+axp209_rx(uint8_t reg, uint8_t data) "Read reg 0x%" PRIx8 " : 0x%" PRIx8
+axp209_select(uint8_t reg) "Accessing reg 0x%" PRIx8
+axp209_tx(uint8_t reg, uint8_t data) "Write reg 0x%" PRIx8 " : 0x%" PRIx8
+
 # eccmemctl.c
 ecc_mem_writel_mer(uint32_t val) "Write memory enable 0x%08x"
 ecc_mem_writel_mdr(uint32_t val) "Write memory delay 0x%08x"
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v3 5/7] hw/arm: Add AXP209 to Cubieboard
  2022-12-26 22:02 [PATCH v3 0/7] Enable Cubieboard A10 boot SPL from SD card Strahinja Jankovic
                   ` (3 preceding siblings ...)
  2022-12-26 22:03 ` [PATCH v3 4/7] hw/misc: AXP209 PMU Emulation Strahinja Jankovic
@ 2022-12-26 22:03 ` Strahinja Jankovic
  2022-12-26 22:03 ` [PATCH v3 6/7] hw/arm: Allwinner A10 enable SPL load from MMC Strahinja Jankovic
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 12+ messages in thread
From: Strahinja Jankovic @ 2022-12-26 22:03 UTC (permalink / raw)
  To: Peter Maydell
  Cc: Beniamino Galvani, Niek Linnenbank, philmd, qemu-arm, qemu-devel,
	Strahinja Jankovic

SPL Boot for Cubieboard expects AXP209 connected to I2C0 bus.

Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>

Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
---
 hw/arm/Kconfig      | 1 +
 hw/arm/cubieboard.c | 6 ++++++
 2 files changed, 7 insertions(+)

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index eefe1fd134..da6741f112 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -323,6 +323,7 @@ config ALLWINNER_A10
     select ALLWINNER_A10_DRAMC
     select ALLWINNER_EMAC
     select ALLWINNER_I2C
+    select AXP209_PMU
     select SERIAL
     select UNIMP
 
diff --git a/hw/arm/cubieboard.c b/hw/arm/cubieboard.c
index 5e3372a3c7..dca257620d 100644
--- a/hw/arm/cubieboard.c
+++ b/hw/arm/cubieboard.c
@@ -20,6 +20,7 @@
 #include "hw/boards.h"
 #include "hw/qdev-properties.h"
 #include "hw/arm/allwinner-a10.h"
+#include "hw/i2c/i2c.h"
 
 static struct arm_boot_info cubieboard_binfo = {
     .loader_start = AW_A10_SDRAM_BASE,
@@ -34,6 +35,7 @@ static void cubieboard_init(MachineState *machine)
     BlockBackend *blk;
     BusState *bus;
     DeviceState *carddev;
+    I2CBus *i2c;
 
     /* BIOS is not supported by this board */
     if (machine->firmware) {
@@ -80,6 +82,10 @@ static void cubieboard_init(MachineState *machine)
         exit(1);
     }
 
+    /* Connect AXP 209 */
+    i2c = I2C_BUS(qdev_get_child_bus(DEVICE(&a10->i2c0), "i2c"));
+    i2c_slave_create_simple(i2c, "axp209_pmu", 0x34);
+
     /* Retrieve SD bus */
     di = drive_get(IF_SD, 0, 0);
     blk = di ? blk_by_legacy_dinfo(di) : NULL;
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v3 6/7] hw/arm: Allwinner A10 enable SPL load from MMC
  2022-12-26 22:02 [PATCH v3 0/7] Enable Cubieboard A10 boot SPL from SD card Strahinja Jankovic
                   ` (4 preceding siblings ...)
  2022-12-26 22:03 ` [PATCH v3 5/7] hw/arm: Add AXP209 to Cubieboard Strahinja Jankovic
@ 2022-12-26 22:03 ` Strahinja Jankovic
  2022-12-26 22:03 ` [PATCH v3 7/7] tests/avocado: Add SD boot test to Cubieboard Strahinja Jankovic
  2023-01-06 16:07 ` [PATCH v3 0/7] Enable Cubieboard A10 boot SPL from SD card Peter Maydell
  7 siblings, 0 replies; 12+ messages in thread
From: Strahinja Jankovic @ 2022-12-26 22:03 UTC (permalink / raw)
  To: Peter Maydell
  Cc: Beniamino Galvani, Niek Linnenbank, philmd, qemu-arm, qemu-devel,
	Strahinja Jankovic

This patch enables copying of SPL from MMC if `-kernel` parameter is not
passed when starting QEMU. SPL is copied to SRAM_A.

The approach is reused from Allwinner H3 implementation.

Tested with Armbian and custom Yocto image.

Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>

Reviewed-by: Niek Linnenbank <nieklinnenbank@gmail.com>
---
 hw/arm/allwinner-a10.c         | 18 ++++++++++++++++++
 hw/arm/cubieboard.c            |  5 +++++
 include/hw/arm/allwinner-a10.h | 21 +++++++++++++++++++++
 3 files changed, 44 insertions(+)

diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c
index 17e439777e..dc1966ff7a 100644
--- a/hw/arm/allwinner-a10.c
+++ b/hw/arm/allwinner-a10.c
@@ -24,7 +24,9 @@
 #include "sysemu/sysemu.h"
 #include "hw/boards.h"
 #include "hw/usb/hcd-ohci.h"
+#include "hw/loader.h"
 
+#define AW_A10_SRAM_A_BASE      0x00000000
 #define AW_A10_DRAMC_BASE       0x01c01000
 #define AW_A10_MMC0_BASE        0x01c0f000
 #define AW_A10_CCM_BASE         0x01c20000
@@ -38,6 +40,22 @@
 #define AW_A10_RTC_BASE         0x01c20d00
 #define AW_A10_I2C0_BASE        0x01c2ac00
 
+void allwinner_a10_bootrom_setup(AwA10State *s, BlockBackend *blk)
+{
+    const int64_t rom_size = 32 * KiB;
+    g_autofree uint8_t *buffer = g_new0(uint8_t, rom_size);
+
+    if (blk_pread(blk, 8 * KiB, rom_size, buffer, 0) < 0) {
+        error_setg(&error_fatal, "%s: failed to read BlockBackend data",
+                   __func__);
+        return;
+    }
+
+    rom_add_blob("allwinner-a10.bootrom", buffer, rom_size,
+                  rom_size, AW_A10_SRAM_A_BASE,
+                  NULL, NULL, NULL, NULL, false);
+}
+
 static void aw_a10_init(Object *obj)
 {
     AwA10State *s = AW_A10(obj);
diff --git a/hw/arm/cubieboard.c b/hw/arm/cubieboard.c
index dca257620d..71a7df1508 100644
--- a/hw/arm/cubieboard.c
+++ b/hw/arm/cubieboard.c
@@ -99,6 +99,11 @@ static void cubieboard_init(MachineState *machine)
     memory_region_add_subregion(get_system_memory(), AW_A10_SDRAM_BASE,
                                 machine->ram);
 
+    /* Load target kernel or start using BootROM */
+    if (!machine->kernel_filename && blk && blk_is_available(blk)) {
+        /* Use Boot ROM to copy data from SD card to SRAM */
+        allwinner_a10_bootrom_setup(a10, blk);
+    }
     /* TODO create and connect IDE devices for ide_drive_get() */
 
     cubieboard_binfo.ram_size = machine->ram_size;
diff --git a/include/hw/arm/allwinner-a10.h b/include/hw/arm/allwinner-a10.h
index 763935fca9..e2e27e616a 100644
--- a/include/hw/arm/allwinner-a10.h
+++ b/include/hw/arm/allwinner-a10.h
@@ -15,6 +15,7 @@
 #include "hw/misc/allwinner-a10-ccm.h"
 #include "hw/misc/allwinner-a10-dramc.h"
 #include "hw/i2c/allwinner-i2c.h"
+#include "sysemu/block-backend.h"
 
 #include "target/arm/cpu.h"
 #include "qom/object.h"
@@ -47,4 +48,24 @@ struct AwA10State {
     OHCISysBusState ohci[AW_A10_NUM_USB];
 };
 
+/**
+ * Emulate Boot ROM firmware setup functionality.
+ *
+ * A real Allwinner A10 SoC contains a Boot ROM
+ * which is the first code that runs right after
+ * the SoC is powered on. The Boot ROM is responsible
+ * for loading user code (e.g. a bootloader) from any
+ * of the supported external devices and writing the
+ * downloaded code to internal SRAM. After loading the SoC
+ * begins executing the code written to SRAM.
+ *
+ * This function emulates the Boot ROM by copying 32 KiB
+ * of data at offset 8 KiB from the given block device and writes it to
+ * the start of the first internal SRAM memory.
+ *
+ * @s: Allwinner A10 state object pointer
+ * @blk: Block backend device object pointer
+ */
+void allwinner_a10_bootrom_setup(AwA10State *s, BlockBackend *blk);
+
 #endif
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v3 7/7] tests/avocado: Add SD boot test to Cubieboard
  2022-12-26 22:02 [PATCH v3 0/7] Enable Cubieboard A10 boot SPL from SD card Strahinja Jankovic
                   ` (5 preceding siblings ...)
  2022-12-26 22:03 ` [PATCH v3 6/7] hw/arm: Allwinner A10 enable SPL load from MMC Strahinja Jankovic
@ 2022-12-26 22:03 ` Strahinja Jankovic
  2022-12-28 20:42   ` Niek Linnenbank
  2023-01-06 16:07 ` [PATCH v3 0/7] Enable Cubieboard A10 boot SPL from SD card Peter Maydell
  7 siblings, 1 reply; 12+ messages in thread
From: Strahinja Jankovic @ 2022-12-26 22:03 UTC (permalink / raw)
  To: Peter Maydell
  Cc: Beniamino Galvani, Niek Linnenbank, philmd, qemu-arm, qemu-devel,
	Strahinja Jankovic

Cubieboard now can boot directly from SD card, without the need to pass
`-kernel` parameter. Update Avocado tests to cover this functionality.

Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
---
 tests/avocado/boot_linux_console.py | 47 +++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)

diff --git a/tests/avocado/boot_linux_console.py b/tests/avocado/boot_linux_console.py
index ec07c64291..8c1d981586 100644
--- a/tests/avocado/boot_linux_console.py
+++ b/tests/avocado/boot_linux_console.py
@@ -620,6 +620,53 @@ def test_arm_cubieboard_sata(self):
                                                 'sda')
         # cubieboard's reboot is not functioning; omit reboot test.
 
+    @skipUnless(os.getenv('AVOCADO_ALLOW_LARGE_STORAGE'), 'storage limited')
+    def test_arm_cubieboard_openwrt_22_03_2(self):
+        """
+        :avocado: tags=arch:arm
+        :avocado: tags=machine:cubieboard
+        :avocado: tags=device:sd
+        """
+
+        # This test download a 7.5 MiB compressed image and expand it
+        # to 126 MiB.
+        image_url = ('https://downloads.openwrt.org/releases/22.03.2/targets/'
+                     'sunxi/cortexa8/openwrt-22.03.2-sunxi-cortexa8-'
+                     'cubietech_a10-cubieboard-ext4-sdcard.img.gz')
+        image_hash = ('94b5ecbfbc0b3b56276e5146b899eafa'
+                      '2ac5dc2d08733d6705af9f144f39f554')
+        image_path_gz = self.fetch_asset(image_url, asset_hash=image_hash,
+                                         algorithm='sha256')
+        image_path = archive.extract(image_path_gz, self.workdir)
+        image_pow2ceil_expand(image_path)
+
+        self.vm.set_console()
+        self.vm.add_args('-drive', 'file=' + image_path + ',if=sd,format=raw',
+                         '-nic', 'user',
+                         '-no-reboot')
+        self.vm.launch()
+
+        kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE +
+                               'usbcore.nousb '
+                               'noreboot')
+
+        self.wait_for_console_pattern('U-Boot SPL')
+
+        interrupt_interactive_console_until_pattern(
+                self, 'Hit any key to stop autoboot:', '=>')
+        exec_command_and_wait_for_pattern(self, "setenv extraargs '" +
+                                                kernel_command_line + "'", '=>')
+        exec_command_and_wait_for_pattern(self, 'boot', 'Starting kernel ...');
+
+        self.wait_for_console_pattern(
+            'Please press Enter to activate this console.')
+
+        exec_command_and_wait_for_pattern(self, ' ', 'root@')
+
+        exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo',
+                                                'Allwinner sun4i/sun5i')
+        # cubieboard's reboot is not functioning; omit reboot test.
+
     @skipUnless(os.getenv('AVOCADO_TIMEOUT_EXPECTED'), 'Test might timeout')
     def test_arm_quanta_gsj(self):
         """
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH v3 3/7] {hw/i2c, docs/system/arm}: Allwinner TWI/I2C Emulation
  2022-12-26 22:02 ` [PATCH v3 3/7] {hw/i2c,docs/system/arm}: Allwinner TWI/I2C Emulation Strahinja Jankovic
@ 2022-12-28 20:35   ` Niek Linnenbank
  0 siblings, 0 replies; 12+ messages in thread
From: Niek Linnenbank @ 2022-12-28 20:35 UTC (permalink / raw)
  To: Strahinja Jankovic
  Cc: Peter Maydell, Beniamino Galvani, philmd, qemu-arm, qemu-devel,
	Strahinja Jankovic

[-- Attachment #1: Type: text/plain, Size: 26808 bytes --]

On Mon, Dec 26, 2022 at 11:03 PM Strahinja Jankovic <
strahinjapjankovic@gmail.com> wrote:

> This patch implements Allwinner TWI/I2C controller emulation. Only
> master-mode functionality is implemented.
>
> The SPL boot for Cubieboard expects AXP209 PMIC on TWI0/I2C0 bus, so this
> is
> first part enabling the TWI/I2C bus operation.
>
> Since both Allwinner A10 and H3 use the same module, it is added for
> both boards.
>
> Docs are also updated for Cubieboard and Orangepi-PC board to indicate
> I2C availability.
>
> Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
>
Reviewed-by: Niek Linnenbank <nieklinnenbank@gmail.com>


> ---
>  docs/system/arm/cubieboard.rst |   1 +
>  docs/system/arm/orangepi.rst   |   1 +
>  hw/arm/Kconfig                 |   2 +
>  hw/arm/allwinner-a10.c         |   8 +
>  hw/arm/allwinner-h3.c          |  11 +-
>  hw/i2c/Kconfig                 |   4 +
>  hw/i2c/allwinner-i2c.c         | 459 +++++++++++++++++++++++++++++++++
>  hw/i2c/meson.build             |   1 +
>  hw/i2c/trace-events            |   5 +
>  include/hw/arm/allwinner-a10.h |   2 +
>  include/hw/arm/allwinner-h3.h  |   3 +
>  include/hw/i2c/allwinner-i2c.h |  55 ++++
>  12 files changed, 551 insertions(+), 1 deletion(-)
>  create mode 100644 hw/i2c/allwinner-i2c.c
>  create mode 100644 include/hw/i2c/allwinner-i2c.h
>
> diff --git a/docs/system/arm/cubieboard.rst
> b/docs/system/arm/cubieboard.rst
> index 344ff8cef9..8d485f5435 100644
> --- a/docs/system/arm/cubieboard.rst
> +++ b/docs/system/arm/cubieboard.rst
> @@ -14,3 +14,4 @@ Emulated devices:
>  - SDHCI
>  - USB controller
>  - SATA controller
> +- TWI (I2C) controller
> diff --git a/docs/system/arm/orangepi.rst b/docs/system/arm/orangepi.rst
> index 83c7445197..e5973600a1 100644
> --- a/docs/system/arm/orangepi.rst
> +++ b/docs/system/arm/orangepi.rst
> @@ -25,6 +25,7 @@ The Orange Pi PC machine supports the following devices:
>   * Clock Control Unit
>   * System Control module
>   * Security Identifier device
> + * TWI (I2C)
>
>  Limitations
>  """""""""""
> diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
> index 140f142ae5..eefe1fd134 100644
> --- a/hw/arm/Kconfig
> +++ b/hw/arm/Kconfig
> @@ -322,6 +322,7 @@ config ALLWINNER_A10
>      select ALLWINNER_A10_CCM
>      select ALLWINNER_A10_DRAMC
>      select ALLWINNER_EMAC
> +    select ALLWINNER_I2C
>      select SERIAL
>      select UNIMP
>
> @@ -329,6 +330,7 @@ config ALLWINNER_H3
>      bool
>      select ALLWINNER_A10_PIT
>      select ALLWINNER_SUN8I_EMAC
> +    select ALLWINNER_I2C
>      select SERIAL
>      select ARM_TIMER
>      select ARM_GIC
> diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c
> index a5f7a36ac9..17e439777e 100644
> --- a/hw/arm/allwinner-a10.c
> +++ b/hw/arm/allwinner-a10.c
> @@ -36,6 +36,7 @@
>  #define AW_A10_OHCI_BASE        0x01c14400
>  #define AW_A10_SATA_BASE        0x01c18000
>  #define AW_A10_RTC_BASE         0x01c20d00
> +#define AW_A10_I2C0_BASE        0x01c2ac00
>
>  static void aw_a10_init(Object *obj)
>  {
> @@ -56,6 +57,8 @@ static void aw_a10_init(Object *obj)
>
>      object_initialize_child(obj, "sata", &s->sata, TYPE_ALLWINNER_AHCI);
>
> +    object_initialize_child(obj, "i2c0", &s->i2c0, TYPE_AW_I2C);
> +
>      if (machine_usb(current_machine)) {
>          int i;
>
> @@ -176,6 +179,11 @@ static void aw_a10_realize(DeviceState *dev, Error
> **errp)
>      /* RTC */
>      sysbus_realize(SYS_BUS_DEVICE(&s->rtc), &error_fatal);
>      sysbus_mmio_map_overlap(SYS_BUS_DEVICE(&s->rtc), 0, AW_A10_RTC_BASE,
> 10);
> +
> +    /* I2C */
> +    sysbus_realize(SYS_BUS_DEVICE(&s->i2c0), &error_fatal);
> +    sysbus_mmio_map(SYS_BUS_DEVICE(&s->i2c0), 0, AW_A10_I2C0_BASE);
> +    sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c0), 0, qdev_get_gpio_in(dev,
> 7));
>  }
>
>  static void aw_a10_class_init(ObjectClass *oc, void *data)
> diff --git a/hw/arm/allwinner-h3.c b/hw/arm/allwinner-h3.c
> index 308ed15552..bfce3c8d92 100644
> --- a/hw/arm/allwinner-h3.c
> +++ b/hw/arm/allwinner-h3.c
> @@ -53,6 +53,7 @@ const hwaddr allwinner_h3_memmap[] = {
>      [AW_H3_DEV_UART1]      = 0x01c28400,
>      [AW_H3_DEV_UART2]      = 0x01c28800,
>      [AW_H3_DEV_UART3]      = 0x01c28c00,
> +    [AW_H3_DEV_TWI0]       = 0x01c2ac00,
>      [AW_H3_DEV_EMAC]       = 0x01c30000,
>      [AW_H3_DEV_DRAMCOM]    = 0x01c62000,
>      [AW_H3_DEV_DRAMCTL]    = 0x01c63000,
> @@ -106,7 +107,6 @@ struct AwH3Unimplemented {
>      { "uart1",     0x01c28400, 1 * KiB },
>      { "uart2",     0x01c28800, 1 * KiB },
>      { "uart3",     0x01c28c00, 1 * KiB },
> -    { "twi0",      0x01c2ac00, 1 * KiB },
>      { "twi1",      0x01c2b000, 1 * KiB },
>      { "twi2",      0x01c2b400, 1 * KiB },
>      { "scr",       0x01c2c400, 1 * KiB },
> @@ -150,6 +150,7 @@ enum {
>      AW_H3_GIC_SPI_UART1     =  1,
>      AW_H3_GIC_SPI_UART2     =  2,
>      AW_H3_GIC_SPI_UART3     =  3,
> +    AW_H3_GIC_SPI_TWI0      =  6,
>      AW_H3_GIC_SPI_TIMER0    = 18,
>      AW_H3_GIC_SPI_TIMER1    = 19,
>      AW_H3_GIC_SPI_MMC0      = 60,
> @@ -225,6 +226,8 @@ static void allwinner_h3_init(Object *obj)
>                                "ram-size");
>
>      object_initialize_child(obj, "rtc", &s->rtc, TYPE_AW_RTC_SUN6I);
> +
> +    object_initialize_child(obj, "twi0", &s->i2c0, TYPE_AW_I2C);
>  }
>
>  static void allwinner_h3_realize(DeviceState *dev, Error **errp)
> @@ -423,6 +426,12 @@ static void allwinner_h3_realize(DeviceState *dev,
> Error **errp)
>      sysbus_realize(SYS_BUS_DEVICE(&s->rtc), &error_fatal);
>      sysbus_mmio_map(SYS_BUS_DEVICE(&s->rtc), 0, s->memmap[AW_H3_DEV_RTC]);
>
> +    /* I2C */
> +    sysbus_realize(SYS_BUS_DEVICE(&s->i2c0), &error_fatal);
> +    sysbus_mmio_map(SYS_BUS_DEVICE(&s->i2c0), 0,
> s->memmap[AW_H3_DEV_TWI0]);
> +    sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c0), 0,
> +                       qdev_get_gpio_in(DEVICE(&s->gic),
> AW_H3_GIC_SPI_TWI0));
> +
>      /* Unimplemented devices */
>      for (i = 0; i < ARRAY_SIZE(unimplemented); i++) {
>          create_unimplemented_device(unimplemented[i].device_name,
> diff --git a/hw/i2c/Kconfig b/hw/i2c/Kconfig
> index 9bb8870517..f8ec461be3 100644
> --- a/hw/i2c/Kconfig
> +++ b/hw/i2c/Kconfig
> @@ -34,6 +34,10 @@ config MPC_I2C
>      bool
>      select I2C
>
> +config ALLWINNER_I2C
> +    bool
> +    select I2C
> +
>  config PCA954X
>      bool
>      select I2C
> diff --git a/hw/i2c/allwinner-i2c.c b/hw/i2c/allwinner-i2c.c
> new file mode 100644
> index 0000000000..a435965836
> --- /dev/null
> +++ b/hw/i2c/allwinner-i2c.c
> @@ -0,0 +1,459 @@
> +/*
> + *  Allwinner I2C Bus Serial Interface Emulation
> + *
> + *  Copyright (C) 2022 Strahinja Jankovic <strahinja.p.jankovic@gmail.com
> >
> + *
> + *  This file is derived from IMX I2C controller,
> + *  by Jean-Christophe DUBOIS .
> + *
> + *  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/>.
> + *
> + * SPDX-License-Identifier: MIT
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/i2c/allwinner-i2c.h"
> +#include "hw/irq.h"
> +#include "migration/vmstate.h"
> +#include "hw/i2c/i2c.h"
> +#include "qemu/log.h"
> +#include "trace.h"
> +#include "qemu/module.h"
> +
> +/* Allwinner I2C memory map */
> +#define TWI_ADDR_REG            0x00  /* slave address register */
> +#define TWI_XADDR_REG           0x04  /* extended slave address register
> */
> +#define TWI_DATA_REG            0x08  /* data register */
> +#define TWI_CNTR_REG            0x0c  /* control register */
> +#define TWI_STAT_REG            0x10  /* status register */
> +#define TWI_CCR_REG             0x14  /* clock control register */
> +#define TWI_SRST_REG            0x18  /* software reset register */
> +#define TWI_EFR_REG             0x1c  /* enhance feature register */
> +#define TWI_LCR_REG             0x20  /* line control register */
> +
> +/* Used only in slave mode, do not set */
> +#define TWI_ADDR_RESET          0
> +#define TWI_XADDR_RESET         0
> +
> +/* Data register */
> +#define TWI_DATA_MASK           0xFF
> +#define TWI_DATA_RESET          0
> +
> +/* Control register */
> +#define TWI_CNTR_INT_EN         (1 << 7)
> +#define TWI_CNTR_BUS_EN         (1 << 6)
> +#define TWI_CNTR_M_STA          (1 << 5)
> +#define TWI_CNTR_M_STP          (1 << 4)
> +#define TWI_CNTR_INT_FLAG       (1 << 3)
> +#define TWI_CNTR_A_ACK          (1 << 2)
> +#define TWI_CNTR_MASK           0xFC
> +#define TWI_CNTR_RESET          0
> +
> +/* Status register */
> +#define TWI_STAT_MASK           0xF8
> +#define TWI_STAT_RESET          0xF8
> +
> +/* Clock register */
> +#define TWI_CCR_CLK_M_MASK      0x78
> +#define TWI_CCR_CLK_N_MASK      0x07
> +#define TWI_CCR_MASK            0x7F
> +#define TWI_CCR_RESET           0
> +
> +/* Soft reset */
> +#define TWI_SRST_MASK           0x01
> +#define TWI_SRST_RESET          0
> +
> +/* Enhance feature */
> +#define TWI_EFR_MASK            0x03
> +#define TWI_EFR_RESET           0
> +
> +/* Line control */
> +#define TWI_LCR_SCL_STATE       (1 << 5)
> +#define TWI_LCR_SDA_STATE       (1 << 4)
> +#define TWI_LCR_SCL_CTL         (1 << 3)
> +#define TWI_LCR_SCL_CTL_EN      (1 << 2)
> +#define TWI_LCR_SDA_CTL         (1 << 1)
> +#define TWI_LCR_SDA_CTL_EN      (1 << 0)
> +#define TWI_LCR_MASK            0x3F
> +#define TWI_LCR_RESET           0x3A
> +
> +/* Status value in STAT register is shifted by 3 bits */
> +#define TWI_STAT_SHIFT      3
> +#define STAT_FROM_STA(x)    ((x) << TWI_STAT_SHIFT)
> +#define STAT_TO_STA(x)      ((x) >> TWI_STAT_SHIFT)
> +
> +enum {
> +    STAT_BUS_ERROR = 0,
> +    /* Master mode */
> +    STAT_M_STA_TX,
> +    STAT_M_RSTA_TX,
> +    STAT_M_ADDR_WR_ACK,
> +    STAT_M_ADDR_WR_NACK,
> +    STAT_M_DATA_TX_ACK,
> +    STAT_M_DATA_TX_NACK,
> +    STAT_M_ARB_LOST,
> +    STAT_M_ADDR_RD_ACK,
> +    STAT_M_ADDR_RD_NACK,
> +    STAT_M_DATA_RX_ACK,
> +    STAT_M_DATA_RX_NACK,
> +    /* Slave mode */
> +    STAT_S_ADDR_WR_ACK,
> +    STAT_S_ARB_LOST_AW_ACK,
> +    STAT_S_GCA_ACK,
> +    STAT_S_ARB_LOST_GCA_ACK,
> +    STAT_S_DATA_RX_SA_ACK,
> +    STAT_S_DATA_RX_SA_NACK,
> +    STAT_S_DATA_RX_GCA_ACK,
> +    STAT_S_DATA_RX_GCA_NACK,
> +    STAT_S_STP_RSTA,
> +    STAT_S_ADDR_RD_ACK,
> +    STAT_S_ARB_LOST_AR_ACK,
> +    STAT_S_DATA_TX_ACK,
> +    STAT_S_DATA_TX_NACK,
> +    STAT_S_LB_TX_ACK,
> +    /* Master mode, 10-bit */
> +    STAT_M_2ND_ADDR_WR_ACK,
> +    STAT_M_2ND_ADDR_WR_NACK,
> +    /* Idle */
> +    STAT_IDLE = 0x1f
> +} TWI_STAT_STA;
> +
> +static const char *allwinner_i2c_get_regname(unsigned offset)
> +{
> +    switch (offset) {
> +    case TWI_ADDR_REG:
> +        return "ADDR";
> +    case TWI_XADDR_REG:
> +        return "XADDR";
> +    case TWI_DATA_REG:
> +        return "DATA";
> +    case TWI_CNTR_REG:
> +        return "CNTR";
> +    case TWI_STAT_REG:
> +        return "STAT";
> +    case TWI_CCR_REG:
> +        return "CCR";
> +    case TWI_SRST_REG:
> +        return "SRST";
> +    case TWI_EFR_REG:
> +        return "EFR";
> +    case TWI_LCR_REG:
> +        return "LCR";
> +    default:
> +        return "[?]";
> +    }
> +}
> +
> +static inline bool allwinner_i2c_is_reset(AWI2CState *s)
> +{
> +    return s->srst & TWI_SRST_MASK;
> +}
> +
> +static inline bool allwinner_i2c_bus_is_enabled(AWI2CState *s)
> +{
> +    return s->cntr & TWI_CNTR_BUS_EN;
> +}
> +
> +static inline bool allwinner_i2c_interrupt_is_enabled(AWI2CState *s)
> +{
> +    return s->cntr & TWI_CNTR_INT_EN;
> +}
> +
> +static void allwinner_i2c_reset_hold(Object *obj)
> +{
> +    AWI2CState *s = AW_I2C(obj);
> +
> +    if (STAT_TO_STA(s->stat) != STAT_IDLE) {
> +        i2c_end_transfer(s->bus);
> +    }
> +
> +    s->addr  = TWI_ADDR_RESET;
> +    s->xaddr = TWI_XADDR_RESET;
> +    s->data  = TWI_DATA_RESET;
> +    s->cntr  = TWI_CNTR_RESET;
> +    s->stat  = TWI_STAT_RESET;
> +    s->ccr   = TWI_CCR_RESET;
> +    s->srst  = TWI_SRST_RESET;
> +    s->efr   = TWI_EFR_RESET;
> +    s->lcr   = TWI_LCR_RESET;
> +}
> +
> +static inline void allwinner_i2c_raise_interrupt(AWI2CState *s)
> +{
> +    /*
> +     * Raise an interrupt if the device is not reset and it is configured
> +     * to generate some interrupts.
> +     */
> +    if (!allwinner_i2c_is_reset(s) && allwinner_i2c_bus_is_enabled(s)) {
> +        if (STAT_TO_STA(s->stat) != STAT_IDLE) {
> +            s->cntr |= TWI_CNTR_INT_FLAG;
> +            if (allwinner_i2c_interrupt_is_enabled(s)) {
> +                qemu_irq_raise(s->irq);
> +            }
> +        }
> +    }
> +}
> +
> +static uint64_t allwinner_i2c_read(void *opaque, hwaddr offset,
> +                                   unsigned size)
> +{
> +    uint16_t value;
> +    AWI2CState *s = AW_I2C(opaque);
> +
> +    switch (offset) {
> +    case TWI_ADDR_REG:
> +        value = s->addr;
> +        break;
> +    case TWI_XADDR_REG:
> +        value = s->xaddr;
> +        break;
> +    case TWI_DATA_REG:
> +        if ((STAT_TO_STA(s->stat) == STAT_M_ADDR_RD_ACK) ||
> +            (STAT_TO_STA(s->stat) == STAT_M_DATA_RX_ACK) ||
> +            (STAT_TO_STA(s->stat) == STAT_M_DATA_RX_NACK)) {
> +            /* Get the next byte */
> +            s->data = i2c_recv(s->bus);
> +
> +            if (s->cntr & TWI_CNTR_A_ACK) {
> +                s->stat = STAT_FROM_STA(STAT_M_DATA_RX_ACK);
> +            } else {
> +                s->stat = STAT_FROM_STA(STAT_M_DATA_RX_NACK);
> +            }
> +            allwinner_i2c_raise_interrupt(s);
> +        }
> +        value = s->data;
> +        break;
> +    case TWI_CNTR_REG:
> +        value = s->cntr;
> +        break;
> +    case TWI_STAT_REG:
> +        value = s->stat;
> +        /*
> +         * If polling when reading then change state to indicate data
> +         * is available
> +         */
> +        if (STAT_TO_STA(s->stat) == STAT_M_ADDR_RD_ACK) {
> +            if (s->cntr & TWI_CNTR_A_ACK) {
> +                s->stat = STAT_FROM_STA(STAT_M_DATA_RX_ACK);
> +            } else {
> +                s->stat = STAT_FROM_STA(STAT_M_DATA_RX_NACK);
> +            }
> +            allwinner_i2c_raise_interrupt(s);
> +        }
> +        break;
> +    case TWI_CCR_REG:
> +        value = s->ccr;
> +        break;
> +    case TWI_SRST_REG:
> +        value = s->srst;
> +        break;
> +    case TWI_EFR_REG:
> +        value = s->efr;
> +        break;
> +    case TWI_LCR_REG:
> +        value = s->lcr;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad address at offset 0x%"
> +                      HWADDR_PRIx "\n", TYPE_AW_I2C, __func__, offset);
> +        value = 0;
> +        break;
> +    }
> +
> +    trace_allwinner_i2c_read(allwinner_i2c_get_regname(offset), offset,
> value);
> +
> +    return (uint64_t)value;
> +}
> +
> +static void allwinner_i2c_write(void *opaque, hwaddr offset,
> +                                uint64_t value, unsigned size)
> +{
> +    AWI2CState *s = AW_I2C(opaque);
> +
> +    value &= 0xff;
> +
> +    trace_allwinner_i2c_write(allwinner_i2c_get_regname(offset), offset,
> value);
> +
> +    switch (offset) {
> +    case TWI_ADDR_REG:
> +        s->addr = (uint8_t)value;
> +        break;
> +    case TWI_XADDR_REG:
> +        s->xaddr = (uint8_t)value;
> +        break;
> +    case TWI_DATA_REG:
> +        /* If the device is in reset or not enabled, nothing to do */
> +        if (allwinner_i2c_is_reset(s) ||
> (!allwinner_i2c_bus_is_enabled(s))) {
> +            break;
> +        }
> +
> +        s->data = value & TWI_DATA_MASK;
> +
> +        switch (STAT_TO_STA(s->stat)) {
> +        case STAT_M_STA_TX:
> +        case STAT_M_RSTA_TX:
> +            /* Send address */
> +            if (i2c_start_transfer(s->bus, extract32(s->data, 1, 7),
> +                                extract32(s->data, 0, 1))) {
> +                /* If non zero is returned, the address is not valid */
> +                s->stat = STAT_FROM_STA(STAT_M_ADDR_WR_NACK);
> +            } else {
> +                /* Determine if read of write */
> +                if (extract32(s->data, 0, 1)) {
> +                    s->stat = STAT_FROM_STA(STAT_M_ADDR_RD_ACK);
> +                } else {
> +                    s->stat = STAT_FROM_STA(STAT_M_ADDR_WR_ACK);
> +                }
> +                allwinner_i2c_raise_interrupt(s);
> +            }
> +            break;
> +        case STAT_M_ADDR_WR_ACK:
> +        case STAT_M_DATA_TX_ACK:
> +            if (i2c_send(s->bus, s->data)) {
> +                /* If the target return non zero then end the transfer */
> +                s->stat = STAT_FROM_STA(STAT_M_DATA_TX_NACK);
> +                i2c_end_transfer(s->bus);
> +            } else {
> +                s->stat = STAT_FROM_STA(STAT_M_DATA_TX_ACK);
> +                allwinner_i2c_raise_interrupt(s);
> +            }
> +            break;
> +        default:
> +            break;
> +        }
> +        break;
> +    case TWI_CNTR_REG:
> +        if (!allwinner_i2c_is_reset(s)) {
> +            /* Do something only if not in software reset */
> +            s->cntr = value & TWI_CNTR_MASK;
> +
> +            /* Check if start condition should be sent */
> +            if (s->cntr & TWI_CNTR_M_STA) {
> +                /* Update status */
> +                if (STAT_TO_STA(s->stat) == STAT_IDLE) {
> +                    /* Send start condition */
> +                    s->stat = STAT_FROM_STA(STAT_M_STA_TX);
> +                } else {
> +                    /* Send repeated start condition */
> +                    s->stat = STAT_FROM_STA(STAT_M_RSTA_TX);
> +                }
> +                /* Clear start condition */
> +                s->cntr &= ~TWI_CNTR_M_STA;
> +            }
> +            if (s->cntr & TWI_CNTR_M_STP) {
> +                /* Update status */
> +                i2c_end_transfer(s->bus);
> +                s->stat = STAT_FROM_STA(STAT_IDLE);
> +                s->cntr &= ~TWI_CNTR_M_STP;
> +            }
> +            if ((s->cntr & TWI_CNTR_INT_FLAG) == 0) {
> +                /* Interrupt flag cleared */
> +                qemu_irq_lower(s->irq);
> +            }
> +            if ((s->cntr & TWI_CNTR_A_ACK) == 0) {
> +                if (STAT_TO_STA(s->stat) == STAT_M_DATA_RX_ACK) {
> +                    s->stat = STAT_FROM_STA(STAT_M_DATA_RX_NACK);
> +                }
> +            } else {
> +                if (STAT_TO_STA(s->stat) == STAT_M_DATA_RX_NACK) {
> +                    s->stat = STAT_FROM_STA(STAT_M_DATA_RX_ACK);
> +                }
> +            }
> +            allwinner_i2c_raise_interrupt(s);
> +
> +        }
> +        break;
> +    case TWI_CCR_REG:
> +        s->ccr = value & TWI_CCR_MASK;
> +        break;
> +    case TWI_SRST_REG:
> +        if (((value & TWI_SRST_MASK) == 0) && (s->srst & TWI_SRST_MASK)) {
> +            /* Perform reset */
> +            allwinner_i2c_reset_hold(OBJECT(s));
> +        }
> +        s->srst = value & TWI_SRST_MASK;
> +        break;
> +    case TWI_EFR_REG:
> +        s->efr = value & TWI_EFR_MASK;
> +        break;
> +    case TWI_LCR_REG:
> +        s->lcr = value & TWI_LCR_MASK;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad address at offset 0x%"
> +                      HWADDR_PRIx "\n", TYPE_AW_I2C, __func__, offset);
> +        break;
> +    }
> +}
> +
> +static const MemoryRegionOps allwinner_i2c_ops = {
> +    .read = allwinner_i2c_read,
> +    .write = allwinner_i2c_write,
> +    .valid.min_access_size = 1,
> +    .valid.max_access_size = 4,
> +    .endianness = DEVICE_NATIVE_ENDIAN,
> +};
> +
> +static const VMStateDescription allwinner_i2c_vmstate = {
> +    .name = TYPE_AW_I2C,
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT8(addr, AWI2CState),
> +        VMSTATE_UINT8(xaddr, AWI2CState),
> +        VMSTATE_UINT8(data, AWI2CState),
> +        VMSTATE_UINT8(cntr, AWI2CState),
> +        VMSTATE_UINT8(ccr, AWI2CState),
> +        VMSTATE_UINT8(srst, AWI2CState),
> +        VMSTATE_UINT8(efr, AWI2CState),
> +        VMSTATE_UINT8(lcr, AWI2CState),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static void allwinner_i2c_realize(DeviceState *dev, Error **errp)
> +{
> +    AWI2CState *s = AW_I2C(dev);
> +
> +    memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_i2c_ops, s,
> +                          TYPE_AW_I2C, AW_I2C_MEM_SIZE);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
> +    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
> +    s->bus = i2c_init_bus(dev, "i2c");
> +}
> +
> +static void allwinner_i2c_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    ResettableClass *rc = RESETTABLE_CLASS(klass);
> +
> +    rc->phases.hold = allwinner_i2c_reset_hold;
> +    dc->vmsd = &allwinner_i2c_vmstate;
> +    dc->realize = allwinner_i2c_realize;
> +    dc->desc = "Allwinner I2C Controller";
> +}
> +
> +static const TypeInfo allwinner_i2c_type_info = {
> +    .name = TYPE_AW_I2C,
> +    .parent = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(AWI2CState),
> +    .class_init = allwinner_i2c_class_init,
> +};
> +
> +static void allwinner_i2c_register_types(void)
> +{
> +    type_register_static(&allwinner_i2c_type_info);
> +}
> +
> +type_init(allwinner_i2c_register_types)
> diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build
> index d3df273251..7de7f2f540 100644
> --- a/hw/i2c/meson.build
> +++ b/hw/i2c/meson.build
> @@ -8,6 +8,7 @@ i2c_ss.add(when: 'CONFIG_BITBANG_I2C', if_true:
> files('bitbang_i2c.c'))
>  i2c_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_i2c.c'))
>  i2c_ss.add(when: 'CONFIG_IMX_I2C', if_true: files('imx_i2c.c'))
>  i2c_ss.add(when: 'CONFIG_MPC_I2C', if_true: files('mpc_i2c.c'))
> +i2c_ss.add(when: 'CONFIG_ALLWINNER_I2C', if_true:
> files('allwinner-i2c.c'))
>  i2c_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('microbit_i2c.c'))
>  i2c_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_smbus.c'))
>  i2c_ss.add(when: 'CONFIG_SMBUS_EEPROM', if_true: files('smbus_eeprom.c'))
> diff --git a/hw/i2c/trace-events b/hw/i2c/trace-events
> index af181d43ee..52dbd53a23 100644
> --- a/hw/i2c/trace-events
> +++ b/hw/i2c/trace-events
> @@ -8,6 +8,11 @@ i2c_send_async(uint8_t address, uint8_t data)
> "send_async(addr:0x%02x) data:0x%0
>  i2c_recv(uint8_t address, uint8_t data) "recv(addr:0x%02x) data:0x%02x"
>  i2c_ack(void) ""
>
> +# allwinner_i2c.c
> +
> +allwinner_i2c_read(const char* reg_name, uint64_t offset, uint64_t value)
> "read %s [0x%" PRIx64 "]: -> 0x%" PRIx64
> +allwinner_i2c_write(const char* reg_name, uint64_t offset, uint64_t
> value) "write %s [0x%" PRIx64 "]: <- 0x%" PRIx64
> +
>  # aspeed_i2c.c
>
>  aspeed_i2c_bus_cmd(uint32_t cmd, const char *cmd_flags, uint32_t count,
> uint32_t intr_status) "handling cmd=0x%x %s count=%d intr=0x%x"
> diff --git a/include/hw/arm/allwinner-a10.h
> b/include/hw/arm/allwinner-a10.h
> index abe4ff7066..763935fca9 100644
> --- a/include/hw/arm/allwinner-a10.h
> +++ b/include/hw/arm/allwinner-a10.h
> @@ -14,6 +14,7 @@
>  #include "hw/rtc/allwinner-rtc.h"
>  #include "hw/misc/allwinner-a10-ccm.h"
>  #include "hw/misc/allwinner-a10-dramc.h"
> +#include "hw/i2c/allwinner-i2c.h"
>
>  #include "target/arm/cpu.h"
>  #include "qom/object.h"
> @@ -39,6 +40,7 @@ struct AwA10State {
>      AwEmacState emac;
>      AllwinnerAHCIState sata;
>      AwSdHostState mmc0;
> +    AWI2CState i2c0;
>      AwRtcState rtc;
>      MemoryRegion sram_a;
>      EHCISysBusState ehci[AW_A10_NUM_USB];
> diff --git a/include/hw/arm/allwinner-h3.h b/include/hw/arm/allwinner-h3.h
> index 63025fb27c..1d7ce20589 100644
> --- a/include/hw/arm/allwinner-h3.h
> +++ b/include/hw/arm/allwinner-h3.h
> @@ -47,6 +47,7 @@
>  #include "hw/sd/allwinner-sdhost.h"
>  #include "hw/net/allwinner-sun8i-emac.h"
>  #include "hw/rtc/allwinner-rtc.h"
> +#include "hw/i2c/allwinner-i2c.h"
>  #include "target/arm/cpu.h"
>  #include "sysemu/block-backend.h"
>
> @@ -82,6 +83,7 @@ enum {
>      AW_H3_DEV_UART2,
>      AW_H3_DEV_UART3,
>      AW_H3_DEV_EMAC,
> +    AW_H3_DEV_TWI0,
>      AW_H3_DEV_DRAMCOM,
>      AW_H3_DEV_DRAMCTL,
>      AW_H3_DEV_DRAMPHY,
> @@ -130,6 +132,7 @@ struct AwH3State {
>      AwH3SysCtrlState sysctrl;
>      AwSidState sid;
>      AwSdHostState mmc0;
> +    AWI2CState i2c0;
>      AwSun8iEmacState emac;
>      AwRtcState rtc;
>      GICState gic;
> diff --git a/include/hw/i2c/allwinner-i2c.h
> b/include/hw/i2c/allwinner-i2c.h
> new file mode 100644
> index 0000000000..4f378b86ba
> --- /dev/null
> +++ b/include/hw/i2c/allwinner-i2c.h
> @@ -0,0 +1,55 @@
> +/*
> + *  Allwinner I2C Bus Serial Interface registers definition
> + *
> + *  Copyright (C) 2022 Strahinja Jankovic. <
> strahinja.p.jankovic@gmail.com>
> + *
> + *  This file is derived from IMX I2C controller,
> + *  by Jean-Christophe DUBOIS .
> + *
> + *  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 ALLWINNER_I2C_H
> +#define ALLWINNER_I2C_H
> +
> +#include "hw/sysbus.h"
> +#include "qom/object.h"
> +
> +#define TYPE_AW_I2C "allwinner.i2c"
> +OBJECT_DECLARE_SIMPLE_TYPE(AWI2CState, AW_I2C)
> +
> +#define AW_I2C_MEM_SIZE         0x24
> +
> +struct AWI2CState {
> +    /*< private >*/
> +    SysBusDevice parent_obj;
> +
> +    /*< public >*/
> +    MemoryRegion iomem;
> +    I2CBus *bus;
> +    qemu_irq irq;
> +
> +    uint8_t addr;
> +    uint8_t xaddr;
> +    uint8_t data;
> +    uint8_t cntr;
> +    uint8_t stat;
> +    uint8_t ccr;
> +    uint8_t srst;
> +    uint8_t efr;
> +    uint8_t lcr;
> +};
> +
> +#endif /* ALLWINNER_I2C_H */
> --
> 2.30.2
>
>

-- 
Niek Linnenbank

[-- Attachment #2: Type: text/html, Size: 33495 bytes --]

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v3 7/7] tests/avocado: Add SD boot test to Cubieboard
  2022-12-26 22:03 ` [PATCH v3 7/7] tests/avocado: Add SD boot test to Cubieboard Strahinja Jankovic
@ 2022-12-28 20:42   ` Niek Linnenbank
  0 siblings, 0 replies; 12+ messages in thread
From: Niek Linnenbank @ 2022-12-28 20:42 UTC (permalink / raw)
  To: Strahinja Jankovic
  Cc: Peter Maydell, Beniamino Galvani, philmd, qemu-arm, qemu-devel,
	Strahinja Jankovic

[-- Attachment #1: Type: text/plain, Size: 3995 bytes --]

On Mon, Dec 26, 2022 at 11:03 PM Strahinja Jankovic <
strahinjapjankovic@gmail.com> wrote:

> Cubieboard now can boot directly from SD card, without the need to pass
> `-kernel` parameter. Update Avocado tests to cover this functionality.
>
> Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
>

Test looks fine to me, and is also working well with:

$ AVOCADO_ALLOW_LARGE_STORAGE=yes ./build/tests/venv/bin/avocado
--show=app,console run -t machine:cubieboard
tests/avocado/boot_linux_console.py
...
 (3/3)
tests/avocado/boot_linux_console.py:BootLinuxConsole.test_arm_cubieboard_openwrt_22_03_2:
 console: U-Boot SPL 2020.04-OpenWrt-r19803-9a599fee93 (Oct 14 2022 -
22:44:41 +0000)
console: DRAM: 1024 MiB
console: CPU: 1008000000Hz, AXI/AHB/APB: 3/2/2
...
console: Hardware : Allwinner sun4i/sun5i Families
PASS (13.98 s)
RESULTS    : PASS 3 | ERROR 0 | FAIL 0 | SKIP 0 | WARN 0 | INTERRUPT 0 |
CANCEL 0
JOB TIME   : 36.82 s

So for me:

Reviewed-by: Niek Linnenbank <nieklinnenbank@gmail.com>
Tested-by: Niek Linnenbank <nieklinnenbank@gmail.com>

> ---
>  tests/avocado/boot_linux_console.py | 47 +++++++++++++++++++++++++++++
>  1 file changed, 47 insertions(+)
>
> diff --git a/tests/avocado/boot_linux_console.py
> b/tests/avocado/boot_linux_console.py
> index ec07c64291..8c1d981586 100644
> --- a/tests/avocado/boot_linux_console.py
> +++ b/tests/avocado/boot_linux_console.py
> @@ -620,6 +620,53 @@ def test_arm_cubieboard_sata(self):
>                                                  'sda')
>          # cubieboard's reboot is not functioning; omit reboot test.
>
> +    @skipUnless(os.getenv('AVOCADO_ALLOW_LARGE_STORAGE'), 'storage
> limited')
> +    def test_arm_cubieboard_openwrt_22_03_2(self):
> +        """
> +        :avocado: tags=arch:arm
> +        :avocado: tags=machine:cubieboard
> +        :avocado: tags=device:sd
> +        """
> +
> +        # This test download a 7.5 MiB compressed image and expand it
> +        # to 126 MiB.
> +        image_url = ('
> https://downloads.openwrt.org/releases/22.03.2/targets/'
> +                     'sunxi/cortexa8/openwrt-22.03.2-sunxi-cortexa8-'
> +                     'cubietech_a10-cubieboard-ext4-sdcard.img.gz')
> +        image_hash = ('94b5ecbfbc0b3b56276e5146b899eafa'
> +                      '2ac5dc2d08733d6705af9f144f39f554')
> +        image_path_gz = self.fetch_asset(image_url, asset_hash=image_hash,
> +                                         algorithm='sha256')
> +        image_path = archive.extract(image_path_gz, self.workdir)
> +        image_pow2ceil_expand(image_path)
> +
> +        self.vm.set_console()
> +        self.vm.add_args('-drive', 'file=' + image_path +
> ',if=sd,format=raw',
> +                         '-nic', 'user',
> +                         '-no-reboot')
> +        self.vm.launch()
> +
> +        kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE +
> +                               'usbcore.nousb '
> +                               'noreboot')
> +
> +        self.wait_for_console_pattern('U-Boot SPL')
> +
> +        interrupt_interactive_console_until_pattern(
> +                self, 'Hit any key to stop autoboot:', '=>')
> +        exec_command_and_wait_for_pattern(self, "setenv extraargs '" +
> +                                                kernel_command_line +
> "'", '=>')
> +        exec_command_and_wait_for_pattern(self, 'boot', 'Starting kernel
> ...');
> +
> +        self.wait_for_console_pattern(
> +            'Please press Enter to activate this console.')
> +
> +        exec_command_and_wait_for_pattern(self, ' ', 'root@')
> +
> +        exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo',
> +                                                'Allwinner sun4i/sun5i')
> +        # cubieboard's reboot is not functioning; omit reboot test.
> +
>      @skipUnless(os.getenv('AVOCADO_TIMEOUT_EXPECTED'), 'Test might
> timeout')
>      def test_arm_quanta_gsj(self):
>          """
> --
> 2.30.2
>
>

-- 
Niek Linnenbank

[-- Attachment #2: Type: text/html, Size: 5845 bytes --]

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v3 4/7] hw/misc: AXP209 PMU Emulation
  2022-12-26 22:03 ` [PATCH v3 4/7] hw/misc: AXP209 PMU Emulation Strahinja Jankovic
@ 2023-01-02 20:06   ` Strahinja Jankovic
  0 siblings, 0 replies; 12+ messages in thread
From: Strahinja Jankovic @ 2023-01-02 20:06 UTC (permalink / raw)
  To: Peter Maydell
  Cc: Beniamino Galvani, Niek Linnenbank, philmd, qemu-arm, qemu-devel

Ping

This is the only remaining patch from the series waiting for review. Thanks!

Best regards,
Strahinja

On Mon, Dec 26, 2022 at 11:03 PM Strahinja Jankovic
<strahinjapjankovic@gmail.com> wrote:
>
> This patch adds minimal support for AXP-209 PMU.
> Most important is chip ID since U-Boot SPL expects version 0x1. Besides
> the chip ID register, reset values for two more registers used by A10
> U-Boot SPL are covered.
>
> Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
> ---
>  MAINTAINERS          |   2 +
>  hw/misc/Kconfig      |   4 +
>  hw/misc/axp209.c     | 238 +++++++++++++++++++++++++++++++++++++++++++
>  hw/misc/meson.build  |   1 +
>  hw/misc/trace-events |   5 +
>  5 files changed, 250 insertions(+)
>  create mode 100644 hw/misc/axp209.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b270eb8e5b..354da68249 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -576,12 +576,14 @@ ARM Machines
>  Allwinner-a10
>  M: Beniamino Galvani <b.galvani@gmail.com>
>  M: Peter Maydell <peter.maydell@linaro.org>
> +R: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
>  L: qemu-arm@nongnu.org
>  S: Odd Fixes
>  F: hw/*/allwinner*
>  F: include/hw/*/allwinner*
>  F: hw/arm/cubieboard.c
>  F: docs/system/arm/cubieboard.rst
> +F: hw/misc/axp209.c
>
>  Allwinner-h3
>  M: Niek Linnenbank <nieklinnenbank@gmail.com>
> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
> index 052fb54310..eaeddca277 100644
> --- a/hw/misc/Kconfig
> +++ b/hw/misc/Kconfig
> @@ -180,4 +180,8 @@ config ALLWINNER_A10_CCM
>  config ALLWINNER_A10_DRAMC
>      bool
>
> +config AXP209_PMU
> +    bool
> +    depends on I2C
> +
>  source macio/Kconfig
> diff --git a/hw/misc/axp209.c b/hw/misc/axp209.c
> new file mode 100644
> index 0000000000..2908ed99a6
> --- /dev/null
> +++ b/hw/misc/axp209.c
> @@ -0,0 +1,238 @@
> +/*
> + * AXP-209 PMU Emulation
> + *
> + * Copyright (C) 2022 Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the "Software"),
> + * to deal in the Software without restriction, including without limitation
> + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
> + * and/or sell copies of the Software, and to permit persons to whom the
> + * Software is furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
> + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
> + * DEALINGS IN THE SOFTWARE.
> + *
> + * SPDX-License-Identifier: MIT
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "trace.h"
> +#include "hw/i2c/i2c.h"
> +#include "migration/vmstate.h"
> +
> +#define TYPE_AXP209_PMU "axp209_pmu"
> +
> +#define AXP209(obj) \
> +    OBJECT_CHECK(AXP209I2CState, (obj), TYPE_AXP209_PMU)
> +
> +/* registers */
> +enum {
> +    REG_POWER_STATUS = 0x0u,
> +    REG_OPERATING_MODE,
> +    REG_OTG_VBUS_STATUS,
> +    REG_CHIP_VERSION,
> +    REG_DATA_CACHE_0,
> +    REG_DATA_CACHE_1,
> +    REG_DATA_CACHE_2,
> +    REG_DATA_CACHE_3,
> +    REG_DATA_CACHE_4,
> +    REG_DATA_CACHE_5,
> +    REG_DATA_CACHE_6,
> +    REG_DATA_CACHE_7,
> +    REG_DATA_CACHE_8,
> +    REG_DATA_CACHE_9,
> +    REG_DATA_CACHE_A,
> +    REG_DATA_CACHE_B,
> +    REG_POWER_OUTPUT_CTRL = 0x12u,
> +    REG_DC_DC2_OUT_V_CTRL = 0x23u,
> +    REG_DC_DC2_DVS_CTRL = 0x25u,
> +    REG_DC_DC3_OUT_V_CTRL = 0x27u,
> +    REG_LDO2_4_OUT_V_CTRL,
> +    REG_LDO3_OUT_V_CTRL,
> +    REG_VBUS_CH_MGMT = 0x30u,
> +    REG_SHUTDOWN_V_CTRL,
> +    REG_SHUTDOWN_CTRL,
> +    REG_CHARGE_CTRL_1,
> +    REG_CHARGE_CTRL_2,
> +    REG_SPARE_CHARGE_CTRL,
> +    REG_PEK_KEY_CTRL,
> +    REG_DC_DC_FREQ_SET,
> +    REG_CHR_TEMP_TH_SET,
> +    REG_CHR_HIGH_TEMP_TH_CTRL,
> +    REG_IPSOUT_WARN_L1,
> +    REG_IPSOUT_WARN_L2,
> +    REG_DISCHR_TEMP_TH_SET,
> +    REG_DISCHR_HIGH_TEMP_TH_CTRL,
> +    REG_IRQ_BANK_1_CTRL = 0x40u,
> +    REG_IRQ_BANK_2_CTRL,
> +    REG_IRQ_BANK_3_CTRL,
> +    REG_IRQ_BANK_4_CTRL,
> +    REG_IRQ_BANK_5_CTRL,
> +    REG_IRQ_BANK_1_STAT = 0x48u,
> +    REG_IRQ_BANK_2_STAT,
> +    REG_IRQ_BANK_3_STAT,
> +    REG_IRQ_BANK_4_STAT,
> +    REG_IRQ_BANK_5_STAT,
> +    REG_ADC_ACIN_V_H = 0x56u,
> +    REG_ADC_ACIN_V_L,
> +    REG_ADC_ACIN_CURR_H,
> +    REG_ADC_ACIN_CURR_L,
> +    REG_ADC_VBUS_V_H,
> +    REG_ADC_VBUS_V_L,
> +    REG_ADC_VBUS_CURR_H,
> +    REG_ADC_VBUS_CURR_L,
> +    REG_ADC_INT_TEMP_H,
> +    REG_ADC_INT_TEMP_L,
> +    REG_ADC_TEMP_SENS_V_H = 0x62u,
> +    REG_ADC_TEMP_SENS_V_L,
> +    REG_ADC_BAT_V_H = 0x78u,
> +    REG_ADC_BAT_V_L,
> +    REG_ADC_BAT_DISCHR_CURR_H,
> +    REG_ADC_BAT_DISCHR_CURR_L,
> +    REG_ADC_BAT_CHR_CURR_H,
> +    REG_ADC_BAT_CHR_CURR_L,
> +    REG_ADC_IPSOUT_V_H,
> +    REG_ADC_IPSOUT_V_L,
> +    REG_DC_DC_MOD_SEL = 0x80u,
> +    REG_ADC_EN_1,
> +    REG_ADC_EN_2,
> +    REG_ADC_SR_CTRL,
> +    REG_ADC_IN_RANGE,
> +    REG_GPIO1_ADC_IRQ_RISING_TH,
> +    REG_GPIO1_ADC_IRQ_FALLING_TH,
> +    REG_TIMER_CTRL = 0x8au,
> +    REG_VBUS_CTRL_MON_SRP,
> +    REG_OVER_TEMP_SHUTDOWN = 0x8fu,
> +    REG_GPIO0_FEAT_SET,
> +    REG_GPIO_OUT_HIGH_SET,
> +    REG_GPIO1_FEAT_SET,
> +    REG_GPIO2_FEAT_SET,
> +    REG_GPIO_SIG_STATE_SET_MON,
> +    REG_GPIO3_SET,
> +    REG_COULOMB_CNTR_CTRL = 0xb8u,
> +    REG_POWER_MEAS_RES,
> +    NR_REGS
> +};
> +
> +#define AXP209_CHIP_VERSION_ID             (0x01)
> +#define AXP209_DC_DC2_OUT_V_CTRL_RESET     (0x16)
> +#define AXP209_IRQ_BANK_1_CTRL_RESET       (0xd8)
> +
> +/* A simple I2C slave which returns values of ID or CNT register. */
> +typedef struct AXP209I2CState {
> +    /*< private >*/
> +    I2CSlave i2c;
> +    /*< public >*/
> +    uint8_t regs[NR_REGS];  /* peripheral registers */
> +    uint8_t ptr;            /* current register index */
> +    uint8_t count;          /* counter used for tx/rx */
> +} AXP209I2CState;
> +
> +/* Reset all counters and load ID register */
> +static void axp209_reset_enter(Object *obj, ResetType type)
> +{
> +    AXP209I2CState *s = AXP209(obj);
> +
> +    memset(s->regs, 0, NR_REGS);
> +    s->ptr = 0;
> +    s->count = 0;
> +    s->regs[REG_CHIP_VERSION] = AXP209_CHIP_VERSION_ID;
> +    s->regs[REG_DC_DC2_OUT_V_CTRL] = AXP209_DC_DC2_OUT_V_CTRL_RESET;
> +    s->regs[REG_IRQ_BANK_1_CTRL] = AXP209_IRQ_BANK_1_CTRL_RESET;
> +}
> +
> +/* Handle events from master. */
> +static int axp209_event(I2CSlave *i2c, enum i2c_event event)
> +{
> +    AXP209I2CState *s = AXP209(i2c);
> +
> +    s->count = 0;
> +
> +    return 0;
> +}
> +
> +/* Called when master requests read */
> +static uint8_t axp209_rx(I2CSlave *i2c)
> +{
> +    AXP209I2CState *s = AXP209(i2c);
> +    uint8_t ret = 0xff;
> +
> +    if (s->ptr < NR_REGS) {
> +        ret = s->regs[s->ptr++];
> +    }
> +
> +    trace_axp209_rx(s->ptr - 1, ret);
> +
> +    return ret;
> +}
> +
> +/*
> + * Called when master sends write.
> + * Update ptr with byte 0, then perform write with second byte.
> + */
> +static int axp209_tx(I2CSlave *i2c, uint8_t data)
> +{
> +    AXP209I2CState *s = AXP209(i2c);
> +
> +    if (s->count == 0) {
> +        /* Store register address */
> +        s->ptr = data;
> +        s->count++;
> +        trace_axp209_select(data);
> +    } else {
> +        trace_axp209_tx(s->ptr, data);
> +        if (s->ptr == REG_DC_DC2_OUT_V_CTRL) {
> +            s->regs[s->ptr++] = data;
> +        }
> +    }
> +
> +    return 0;
> +}
> +
> +static const VMStateDescription vmstate_axp209 = {
> +    .name = TYPE_AXP209_PMU,
> +    .version_id = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT8_ARRAY(regs, AXP209I2CState, NR_REGS),
> +        VMSTATE_UINT8(count, AXP209I2CState),
> +        VMSTATE_UINT8(ptr, AXP209I2CState),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static void axp209_class_init(ObjectClass *oc, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(oc);
> +    I2CSlaveClass *isc = I2C_SLAVE_CLASS(oc);
> +    ResettableClass *rc = RESETTABLE_CLASS(oc);
> +
> +    rc->phases.enter = axp209_reset_enter;
> +    dc->vmsd = &vmstate_axp209;
> +    isc->event = axp209_event;
> +    isc->recv = axp209_rx;
> +    isc->send = axp209_tx;
> +}
> +
> +static const TypeInfo axp209_info = {
> +    .name = TYPE_AXP209_PMU,
> +    .parent = TYPE_I2C_SLAVE,
> +    .instance_size = sizeof(AXP209I2CState),
> +    .class_init = axp209_class_init
> +};
> +
> +static void axp209_register_devices(void)
> +{
> +    type_register_static(&axp209_info);
> +}
> +
> +type_init(axp209_register_devices);
> diff --git a/hw/misc/meson.build b/hw/misc/meson.build
> index 9eaa0750b5..448e14b531 100644
> --- a/hw/misc/meson.build
> +++ b/hw/misc/meson.build
> @@ -45,6 +45,7 @@ specific_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-cpucfg.c'
>  softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-dramc.c'))
>  softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-sysctrl.c'))
>  softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-sid.c'))
> +softmmu_ss.add(when: 'CONFIG_AXP209_PMU', if_true: files('axp209.c'))
>  softmmu_ss.add(when: 'CONFIG_REALVIEW', if_true: files('arm_sysctl.c'))
>  softmmu_ss.add(when: 'CONFIG_NSERIES', if_true: files('cbus.c'))
>  softmmu_ss.add(when: 'CONFIG_ECCMEMCTL', if_true: files('eccmemctl.c'))
> diff --git a/hw/misc/trace-events b/hw/misc/trace-events
> index c18bc0605e..c47876a902 100644
> --- a/hw/misc/trace-events
> +++ b/hw/misc/trace-events
> @@ -23,6 +23,11 @@ allwinner_sid_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%"
>  avr_power_read(uint8_t value) "power_reduc read value:%u"
>  avr_power_write(uint8_t value) "power_reduc write value:%u"
>
> +# axp209.c
> +axp209_rx(uint8_t reg, uint8_t data) "Read reg 0x%" PRIx8 " : 0x%" PRIx8
> +axp209_select(uint8_t reg) "Accessing reg 0x%" PRIx8
> +axp209_tx(uint8_t reg, uint8_t data) "Write reg 0x%" PRIx8 " : 0x%" PRIx8
> +
>  # eccmemctl.c
>  ecc_mem_writel_mer(uint32_t val) "Write memory enable 0x%08x"
>  ecc_mem_writel_mdr(uint32_t val) "Write memory delay 0x%08x"
> --
> 2.30.2
>


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v3 0/7] Enable Cubieboard A10 boot SPL from SD card
  2022-12-26 22:02 [PATCH v3 0/7] Enable Cubieboard A10 boot SPL from SD card Strahinja Jankovic
                   ` (6 preceding siblings ...)
  2022-12-26 22:03 ` [PATCH v3 7/7] tests/avocado: Add SD boot test to Cubieboard Strahinja Jankovic
@ 2023-01-06 16:07 ` Peter Maydell
  7 siblings, 0 replies; 12+ messages in thread
From: Peter Maydell @ 2023-01-06 16:07 UTC (permalink / raw)
  To: Strahinja Jankovic
  Cc: Beniamino Galvani, Niek Linnenbank, philmd, qemu-arm, qemu-devel,
	Strahinja Jankovic

On Mon, 26 Dec 2022 at 22:03, Strahinja Jankovic
<strahinjapjankovic@gmail.com> wrote:
>
> This patch series adds missing Allwinner A10 modules needed for
> successful SPL boot:
> - Clock controller module
> - DRAM controller
> - I2C0 controller (added also for Allwinner H3 since it is the same)
> - AXP-209 connected to I2C0 bus
>
> It also updates Allwinner A10 emulation so SPL is copied from attached
> SD card if `-kernel` parameter is not passed when starting QEMU
> (approach adapted from Allwinner H3 implementation).
>
> Boot from SD card has been tested with Cubieboard Armbian SD card image and custom
> Yocto image built for Cubieboard.
> Example usage for Armbian image:
> qemu-system-arm -M cubieboard -nographic -sd ~/Armbian_22.11.0-trunk_Cubieboard_kinetic_edge_6.0.7.img



Applied to target-arm.next, thanks.

-- PMM


^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2023-01-06 16:08 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-12-26 22:02 [PATCH v3 0/7] Enable Cubieboard A10 boot SPL from SD card Strahinja Jankovic
2022-12-26 22:02 ` [PATCH v3 1/7] hw/misc: Allwinner-A10 Clock Controller Module Emulation Strahinja Jankovic
2022-12-26 22:02 ` [PATCH v3 2/7] hw/misc: Allwinner A10 DRAM Controller Emulation Strahinja Jankovic
2022-12-26 22:02 ` [PATCH v3 3/7] {hw/i2c,docs/system/arm}: Allwinner TWI/I2C Emulation Strahinja Jankovic
2022-12-28 20:35   ` [PATCH v3 3/7] {hw/i2c, docs/system/arm}: " Niek Linnenbank
2022-12-26 22:03 ` [PATCH v3 4/7] hw/misc: AXP209 PMU Emulation Strahinja Jankovic
2023-01-02 20:06   ` Strahinja Jankovic
2022-12-26 22:03 ` [PATCH v3 5/7] hw/arm: Add AXP209 to Cubieboard Strahinja Jankovic
2022-12-26 22:03 ` [PATCH v3 6/7] hw/arm: Allwinner A10 enable SPL load from MMC Strahinja Jankovic
2022-12-26 22:03 ` [PATCH v3 7/7] tests/avocado: Add SD boot test to Cubieboard Strahinja Jankovic
2022-12-28 20:42   ` Niek Linnenbank
2023-01-06 16:07 ` [PATCH v3 0/7] Enable Cubieboard A10 boot SPL from SD card Peter Maydell

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.